From 6b49d6a382e30cf7f248a93d448726145c052300 Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Thu, 12 Jan 2023 12:33:15 +0300 Subject: [PATCH 01/46] Add legacy_tester for existing funC contracts (#588) * Add legacy_tester for existing funC contracts * Add storage-contracts and pragma options --- crypto/func/auto-tests/legacy_tester.py | 145 ++ .../bsc-bridge-collector/bridge-config.fc | 13 + .../bsc-bridge-collector/message_utils.fc | 38 + .../bsc-bridge-collector/stdlib.fc | 209 +++ .../bsc-bridge-collector/votes-collector.fc | 118 ++ .../legacy_tests/config/config-code.fc | 643 +++++++++ .../auto-tests/legacy_tests/config/stdlib.fc | 208 +++ .../legacy_tests/dns-collection/dns-utils.fc | 110 ++ .../dns-collection/nft-collection.fc | 144 ++ .../legacy_tests/dns-collection/params.fc | 6 + .../legacy_tests/dns-collection/stdlib.fc | 216 +++ .../legacy_tests/elector/elector-code.fc | 1189 +++++++++++++++++ .../auto-tests/legacy_tests/elector/stdlib.fc | 208 +++ .../eth-bridge-multisig/multisig-code.fc | 382 ++++++ .../eth-bridge-multisig/stdlib.fc | 209 +++ .../gg-marketplace/nft-marketplace-v2.fc | 110 ++ .../legacy_tests/gg-marketplace/stdlib.fc | 215 +++ .../jetton-minter/imports/constants.fc | 13 + .../jetton-minter/imports/jetton-utils.fc | 30 + .../jetton-minter/imports/op-codes.fc | 9 + .../jetton-minter/imports/params.fc | 6 + .../jetton-minter/imports/stdlib.fc | 215 +++ .../jetton-minter/imports/utils.fc | 9 + .../jetton-minter/jetton-minter.fc | 122 ++ .../jetton-wallet/imports/constants.fc | 17 + .../jetton-wallet/imports/jetton-utils.fc | 30 + .../jetton-wallet/imports/op-codes.fc | 9 + .../jetton-wallet/imports/params.fc | 6 + .../jetton-wallet/imports/stdlib.fc | 215 +++ .../jetton-wallet/imports/utils.fc | 9 + .../jetton-wallet/jetton-wallet.fc | 250 ++++ .../nft-collection/nft-collection-editable.fc | 173 +++ .../legacy_tests/nft-collection/op-codes.fc | 24 + .../legacy_tests/nft-collection/params.fc | 10 + .../legacy_tests/nft-collection/stdlib.fc | 215 +++ .../legacy_tests/nominator-pool/pool.fc | 746 +++++++++++ .../legacy_tests/nominator-pool/stdlib.fc | 211 +++ .../legacy_tests/storage/constants.fc | 23 + .../auto-tests/legacy_tests/storage/stdlib.fc | 625 +++++++++ .../legacy_tests/storage/storage-contract.fc | 266 ++++ .../legacy_tests/storage/storage-provider.fc | 228 ++++ .../jetton_JettonDefaultWallet.code.fc | 439 ++++++ .../tact-examples/jetton_SampleJetton.code.fc | 440 ++++++ .../maps_MapTestContract.code.fc | 603 +++++++++ .../legacy_tests/tact-examples/stdlib.fc | 624 +++++++++ .../tact-examples/treasure_Treasure.code.fc | 229 ++++ .../legacy_tests/tele-nft-item/common.fc | 416 ++++++ .../legacy_tests/tele-nft-item/nft-item.fc | 367 +++++ .../legacy_tests/tele-nft-item/stdlib.fc | 208 +++ .../legacy_tests/uni-lock-wallet/stdlib.fc | 211 +++ .../uni-lock-wallet/uni-lockup-wallet.fc | 217 +++ .../legacy_tests/wallet-v4/stdlib.fc | 215 +++ .../legacy_tests/wallet-v4/wallet-v4-code.fc | 199 +++ .../legacy_tests/whales-nominators/LICENSE | 676 ++++++++++ .../whales-nominators/modules/constants.fc | 100 ++ .../whales-nominators/modules/get.fc | 125 ++ .../whales-nominators/modules/model.fc | 297 ++++ .../whales-nominators/modules/op-common.fc | 175 +++ .../modules/op-controller.fc | 414 ++++++ .../modules/op-nominators.fc | 104 ++ .../whales-nominators/modules/op-owner.fc | 60 + .../whales-nominators/modules/store-base.fc | 99 ++ .../modules/store-nominators.fc | 62 + .../modules/store-validator.fc | 28 + .../modules/utils-config-mock.fc | 3 + .../whales-nominators/modules/utils-config.fc | 44 + .../whales-nominators/modules/utils.fc | 185 +++ .../whales-nominators/nominators.fc | 52 + .../legacy_tests/whales-nominators/proxy.fc | 67 + .../legacy_tests/whales-nominators/stdlib.fc | 212 +++ 70 files changed, 14495 insertions(+) create mode 100644 crypto/func/auto-tests/legacy_tester.py create mode 100644 crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/bridge-config.fc create mode 100644 crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/message_utils.fc create mode 100644 crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/stdlib.fc create mode 100644 crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/votes-collector.fc create mode 100644 crypto/func/auto-tests/legacy_tests/config/config-code.fc create mode 100644 crypto/func/auto-tests/legacy_tests/config/stdlib.fc create mode 100644 crypto/func/auto-tests/legacy_tests/dns-collection/dns-utils.fc create mode 100644 crypto/func/auto-tests/legacy_tests/dns-collection/nft-collection.fc create mode 100644 crypto/func/auto-tests/legacy_tests/dns-collection/params.fc create mode 100644 crypto/func/auto-tests/legacy_tests/dns-collection/stdlib.fc create mode 100644 crypto/func/auto-tests/legacy_tests/elector/elector-code.fc create mode 100644 crypto/func/auto-tests/legacy_tests/elector/stdlib.fc create mode 100644 crypto/func/auto-tests/legacy_tests/eth-bridge-multisig/multisig-code.fc create mode 100644 crypto/func/auto-tests/legacy_tests/eth-bridge-multisig/stdlib.fc create mode 100644 crypto/func/auto-tests/legacy_tests/gg-marketplace/nft-marketplace-v2.fc create mode 100644 crypto/func/auto-tests/legacy_tests/gg-marketplace/stdlib.fc create mode 100644 crypto/func/auto-tests/legacy_tests/jetton-minter/imports/constants.fc create mode 100644 crypto/func/auto-tests/legacy_tests/jetton-minter/imports/jetton-utils.fc create mode 100644 crypto/func/auto-tests/legacy_tests/jetton-minter/imports/op-codes.fc create mode 100644 crypto/func/auto-tests/legacy_tests/jetton-minter/imports/params.fc create mode 100644 crypto/func/auto-tests/legacy_tests/jetton-minter/imports/stdlib.fc create mode 100644 crypto/func/auto-tests/legacy_tests/jetton-minter/imports/utils.fc create mode 100644 crypto/func/auto-tests/legacy_tests/jetton-minter/jetton-minter.fc create mode 100644 crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/constants.fc create mode 100644 crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/jetton-utils.fc create mode 100644 crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/op-codes.fc create mode 100644 crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/params.fc create mode 100644 crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/stdlib.fc create mode 100644 crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/utils.fc create mode 100644 crypto/func/auto-tests/legacy_tests/jetton-wallet/jetton-wallet.fc create mode 100644 crypto/func/auto-tests/legacy_tests/nft-collection/nft-collection-editable.fc create mode 100644 crypto/func/auto-tests/legacy_tests/nft-collection/op-codes.fc create mode 100644 crypto/func/auto-tests/legacy_tests/nft-collection/params.fc create mode 100644 crypto/func/auto-tests/legacy_tests/nft-collection/stdlib.fc create mode 100644 crypto/func/auto-tests/legacy_tests/nominator-pool/pool.fc create mode 100644 crypto/func/auto-tests/legacy_tests/nominator-pool/stdlib.fc create mode 100644 crypto/func/auto-tests/legacy_tests/storage/constants.fc create mode 100644 crypto/func/auto-tests/legacy_tests/storage/stdlib.fc create mode 100644 crypto/func/auto-tests/legacy_tests/storage/storage-contract.fc create mode 100644 crypto/func/auto-tests/legacy_tests/storage/storage-provider.fc create mode 100644 crypto/func/auto-tests/legacy_tests/tact-examples/jetton_JettonDefaultWallet.code.fc create mode 100644 crypto/func/auto-tests/legacy_tests/tact-examples/jetton_SampleJetton.code.fc create mode 100644 crypto/func/auto-tests/legacy_tests/tact-examples/maps_MapTestContract.code.fc create mode 100644 crypto/func/auto-tests/legacy_tests/tact-examples/stdlib.fc create mode 100644 crypto/func/auto-tests/legacy_tests/tact-examples/treasure_Treasure.code.fc create mode 100644 crypto/func/auto-tests/legacy_tests/tele-nft-item/common.fc create mode 100644 crypto/func/auto-tests/legacy_tests/tele-nft-item/nft-item.fc create mode 100644 crypto/func/auto-tests/legacy_tests/tele-nft-item/stdlib.fc create mode 100644 crypto/func/auto-tests/legacy_tests/uni-lock-wallet/stdlib.fc create mode 100644 crypto/func/auto-tests/legacy_tests/uni-lock-wallet/uni-lockup-wallet.fc create mode 100644 crypto/func/auto-tests/legacy_tests/wallet-v4/stdlib.fc create mode 100644 crypto/func/auto-tests/legacy_tests/wallet-v4/wallet-v4-code.fc create mode 100644 crypto/func/auto-tests/legacy_tests/whales-nominators/LICENSE create mode 100644 crypto/func/auto-tests/legacy_tests/whales-nominators/modules/constants.fc create mode 100644 crypto/func/auto-tests/legacy_tests/whales-nominators/modules/get.fc create mode 100644 crypto/func/auto-tests/legacy_tests/whales-nominators/modules/model.fc create mode 100644 crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-common.fc create mode 100644 crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-controller.fc create mode 100644 crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-nominators.fc create mode 100644 crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-owner.fc create mode 100644 crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-base.fc create mode 100644 crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-nominators.fc create mode 100644 crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-validator.fc create mode 100644 crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils-config-mock.fc create mode 100644 crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils-config.fc create mode 100644 crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils.fc create mode 100644 crypto/func/auto-tests/legacy_tests/whales-nominators/nominators.fc create mode 100644 crypto/func/auto-tests/legacy_tests/whales-nominators/proxy.fc create mode 100644 crypto/func/auto-tests/legacy_tests/whales-nominators/stdlib.fc diff --git a/crypto/func/auto-tests/legacy_tester.py b/crypto/func/auto-tests/legacy_tester.py new file mode 100644 index 00000000..6db332e5 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tester.py @@ -0,0 +1,145 @@ +import os +import os.path +import subprocess +import sys +import tempfile +import shutil + +add_pragmas = [] #["allow-post-modification", "compute-asm-ltr"]; + +tests = [ + # note, that deployed version of elector,config and multisig differ since it is compilled with func-0.1.0. + # Newer compillators optimize arithmetic and logic expression that can be calculated at the compile time + ["elector/elector-code.fc", 115226404411715505328583639896096915745686314074575650766750648324043316883483], + ["config/config-code.fc", 10913070768607625342121305745084703121685937915388357634624451844356456145601], + ["eth-bridge-multisig/multisig-code.fc", 101509909129354488841890823627011033360100627957439967918234053299675481277954], + + ["bsc-bridge-collector/votes-collector.fc", 62190447221288642706570413295807615918589884489514159926097051017036969900417], + ["uni-lock-wallet/uni-lockup-wallet.fc", 61959738324779104851267145467044677651344601417998258530238254441977103654381], + ["nft-collection/nft-collection-editable.fc", 45561997735512210616567774035540357815786262097548276229169737015839077731274], + ["dns-collection/nft-collection.fc", 107999822699841936063083742021519765435859194241091312445235370766165379261859], + + + # note, that deployed version of tele-nft-item differs since it is compilled with func-0.3.0. + # After introducing of try/catch construction, c2 register is not always the default one. + # Thus it is necessary to save it upon jumps, differences of deployed and below compilled is that + # "c2 SAVE" is added to the beginning of recv_internal. It does not change behavior. + ["tele-nft-item/nft-item.fc", 69777543125381987786450436977742010705076866061362104025338034583422166453344], + + ["storage/storage-contract.fc", 91377830060355733016937375216020277778264560226873154627574229667513068328151], + ["storage/storage-provider.fc", 13618336676213331164384407184540461509022654507176709588621016553953760588122], + ["nominator-pool/pool.fc", 69767057279163099864792356875696330339149706521019810113334238732928422055375], + ["jetton-minter/jetton-minter.fc", 9028309926287301331466371999814928201427184114165428257502393474125007156494], + ["gg-marketplace/nft-marketplace-v2.fc", 92199806964112524639740773542356508485601908152150843819273107618799016205930], + ["jetton-wallet/jetton-wallet.fc", 86251125787443633057458168028617933212663498001665054651523310772884328206542], + ["whales-nominators/nominators.fc", 8941364499854379927692172316865293429893094891593442801401542636695127885153], + + + ["tact-examples/treasure_Treasure.code.fc", 13962538639825790677138656603323869918938565499584297120566680287245364723897], + ["tact-examples/jetton_SampleJetton.code.fc", 94076762218493729104783735200107713211245710256802265203823917715299139499110], + ["tact-examples/jetton_JettonDefaultWallet.code.fc", 29421313492520031238091587108198906058157443241743283101866538036369069620563], + ["tact-examples/maps_MapTestContract.code.fc", 22556550222249123835909180266811414538971143565993192846012583552876721649744], +] + +def getenv(name, default=None): + if name in os.environ: + return os.environ[name] + if default is None: + print("Environment variable", name, "is not set", file=sys.stderr) + exit(1) + return default + +FUNC_EXECUTABLE = getenv("FUNC_EXECUTABLE", "func") +FIFT_EXECUTABLE = getenv("FIFT_EXECUTABLE", "fift") +FIFT_LIBS = getenv("FIFTPATH") +TMP_DIR = tempfile.mkdtemp() + +COMPILED_FIF = os.path.join(TMP_DIR, "compiled.fif") +RUNNER_FIF = os.path.join(TMP_DIR, "runner.fif") + +TESTS_DIR = "legacy_tests" + +class ExecutionError(Exception): + pass + +def pre_process_func(f): + shutil.copyfile(f, f+"_backup") + with open(f, "r") as src: + sources = src.read() + with open(f, "w") as src: + for pragma in add_pragmas: + src.write("#pragma %s;\n"%pragma) + src.write(sources) + +def post_process_func(f): + shutil.move(f+"_backup", f) + +def compile_func(f): + res = None + try: + pre_process_func(f) + if "storage-provider.fc" in f : + # This contract requires building of storage-contract to include it as ref + with open(f, "r") as src: + sources = src.read() + COMPILED_ST_BOC = os.path.join(TMP_DIR, "storage-contract-code.boc") + sources = sources.replace("storage-contract-code.boc", COMPILED_ST_BOC) + with open(f, "w") as src: + src.write(sources) + COMPILED_ST_FIF = os.path.join(TMP_DIR, "storage-contract.fif") + COMPILED_ST_BOC = os.path.join(TMP_DIR, "storage-contract-code.boc") + COMPILED_BUILD_BOC = os.path.join(TMP_DIR, "build-boc.fif") + res = subprocess.run([FUNC_EXECUTABLE, "-o", COMPILED_ST_FIF, "-SPA", f.replace("storage-provider.fc","storage-contract.fc")], capture_output=False, timeout=10) + with open(COMPILED_BUILD_BOC, "w") as scr: + scr.write("\"%s\" include boc>B \"%s\" B>file "%(COMPILED_ST_FIF, COMPILED_ST_BOC)) + res = subprocess.run([FIFT_EXECUTABLE, COMPILED_BUILD_BOC ], capture_output=True, timeout=10) + + + res = subprocess.run([FUNC_EXECUTABLE, "-o", COMPILED_FIF, "-SPA", f], capture_output=True, timeout=10) + except Exception as e: + post_process_func(f) + raise e + else: + post_process_func(f) + if res.returncode != 0: + raise ExecutionError(str(res.stderr, "utf-8")) + +def run_runner(): + res = subprocess.run([FIFT_EXECUTABLE, "-I", FIFT_LIBS, RUNNER_FIF], capture_output=True, timeout=10) + if res.returncode != 0: + raise ExecutionError(str(res.stderr, "utf-8")) + s = str(res.stdout, "utf-8") + s = s.strip() + return int(s) + + +success = 0 +for ti, t in enumerate(tests): + tf, th = t + print(" Running test %d/%d: %s" % (ti + 1, len(tests), tf), file=sys.stderr) + tf = os.path.join(TESTS_DIR, tf) + try: + compile_func(tf) + except ExecutionError as e: + print(file=sys.stderr) + print("Compilation error", file=sys.stderr) + print(e, file=sys.stderr) + exit(2) + + with open(RUNNER_FIF, "w") as f: + print("\"%s\" include hash .s" % COMPILED_FIF , file=f) + + try: + func_out = run_runner() + if func_out != th: + raise ExecutionError("Error : expected '%d', found '%d'" % (th, func_out)) + success += 1 + except ExecutionError as e: + print(e, file=sys.stderr) + #print("Compiled:", file=sys.stderr) + #with open(COMPILED_FIF, "r") as f: + # print(f.read(), file=sys.stderr) + #exit(2) + print(" OK ", file=sys.stderr) + +print("Done: Success %d, Error: %d"%(success, len(tests)-success), file=sys.stderr) diff --git a/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/bridge-config.fc b/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/bridge-config.fc new file mode 100644 index 00000000..b48d3ff0 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/bridge-config.fc @@ -0,0 +1,13 @@ +(int, int, cell) get_bridge_config() impure inline_ref { + cell bridge_config = config_param(72); + if (bridge_config.cell_null?()) { + bridge_config = config_param(-72); + } + throw_if(666, bridge_config.cell_null?()); + slice ds = bridge_config.begin_parse(); + ;; wc always equals to -1 + int bridge_address = ds~load_uint(256); + int oracles_address = ds~load_uint(256); + cell oracles = ds~load_dict(); + return (bridge_address, oracles_address, oracles); +} diff --git a/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/message_utils.fc b/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/message_utils.fc new file mode 100644 index 00000000..184c5fbe --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/message_utils.fc @@ -0,0 +1,38 @@ +() send_receipt_message(addr, ans_tag, query_id, body, grams, mode) impure inline_ref { + ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(addr) + .store_grams(grams) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(ans_tag, 32) + .store_uint(query_id, 64); + if (body >= 0) { + msg~store_uint(body, 256); + } + send_raw_message(msg.end_cell(), mode); +} + +() send_text_receipt_message(addr, grams, mode) impure inline_ref { + ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(addr) + .store_grams(grams) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0, 32) + .store_uint(0x4f4b, 16); ;; "OK" + send_raw_message(msg.end_cell(), mode); +} + +() emit_log_simple (int event_id, slice data) impure inline { + var msg = begin_cell() + .store_uint (12, 4) ;; ext_out_msg_info$11 src:MsgAddressInt () + .store_uint (1, 2) + .store_uint (256, 9) + .store_uint(event_id, 256) + .store_uint(0, 64 + 32 + 2) ;; created_lt, created_at, init:Maybe, body:Either + .store_slice(data) + .end_cell(); + send_raw_message(msg, 0); +} diff --git a/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/stdlib.fc b/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/stdlib.fc new file mode 100644 index 00000000..1d144040 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/stdlib.fc @@ -0,0 +1,209 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + diff --git a/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/votes-collector.fc b/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/votes-collector.fc new file mode 100644 index 00000000..cec4ed21 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/votes-collector.fc @@ -0,0 +1,118 @@ +#include "stdlib.fc"; +#include "bridge-config.fc"; +#include "message_utils.fc"; + +cell load_data() inline_ref { + var ds = get_data().begin_parse(); + return ds~load_dict(); +} + +() save_data(cell external_votings) impure inline_ref { + var st = begin_cell().store_dict(external_votings).end_cell(); + set_data(st); +} + +() vote_on_external_chain(slice s_addr, int query_id, int voting_id, slice signature) impure { + cell external_votings = load_data(); + (_, int oracles_address, cell oracles) = get_bridge_config(); + (int wc, int addr) = parse_std_addr(s_addr); + throw_if(301, wc + 1); + (slice key, int found?) = oracles.udict_get?(256, addr); + throw_unless(304, found?); + + (slice old_voting_data, int voting_found?) = external_votings.udict_get?(256, voting_id); + cell signatures = new_dict(); + if (voting_found?) { + (_, int old_oracles_address, signatures) = (old_voting_data~load_uint(32), + old_voting_data~load_uint(256), + old_voting_data~load_dict()); + if (old_oracles_address != oracles_address) { + signatures = new_dict(); + } + } + int secp_key = key~load_uint(256); + int success? = signatures~udict_add?(256, secp_key, signature); + throw_unless(324, success?); + builder new_voting_data = begin_cell() + .store_uint(now(), 32) + .store_uint(oracles_address, 256) + .store_dict(signatures); + external_votings~udict_set_builder(256, voting_id, new_voting_data); + + save_data(external_votings); + return send_receipt_message(s_addr, 0x10000 + 5, query_id, voting_id, 0, 64); +} + +() remove_outdated_votings(slice s_addr, int query_id, slice external_ids) impure { + cell external_votings = load_data(); + + int bound = now() - 60 * 60 * 24 * 7; + while (~ external_ids.slice_empty?()) { + if (external_ids.slice_data_empty?()) { + external_ids = external_ids.preload_ref().begin_parse(); + } + int voting_id = external_ids~load_uint(256); + (cell external_votings', slice voting, int voting_found?) = external_votings.udict_delete_get?(256, voting_id); + if (voting_found?) { + int last_update = voting~load_uint(32); + if (bound > last_update) { + ;; remove only old votings + external_votings = external_votings'; + } + } + } + + save_data(external_votings); + return send_receipt_message(s_addr, 0x10000 + 6, query_id, 0, 0, 64); ;; thanks +} + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + var cs = in_msg_cell.begin_parse(); + var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + if (flags & 1) { + ;; ignore all bounced messages + return (); + } + slice s_addr = cs~load_msg_addr(); + if (in_msg.slice_empty?()) { + ;; inbound message has empty body + return (); + } + int op = in_msg~load_uint(32); + if (op == 0) { + return (); + } + int query_id = in_msg~load_uint(64); + + if (op == 5) { ;; submit signatures + int voting_id = in_msg~load_uint(256); + slice signature = in_msg~load_bits(520); + return vote_on_external_chain(s_addr, query_id, voting_id, signature); + } + + if (op == 6) { ;; remove old swaps + return remove_outdated_votings(s_addr, query_id, in_msg); + } +} + +(tuple) get_external_voting_data(int voting_id) method_id { + cell external_votings = load_data(); + (slice voting_data, int found?) = external_votings.udict_get?(256, voting_id); + throw_unless(309, found?); + (int time, int old_oracles_address, cell signatures) = (voting_data~load_uint(32), + voting_data~load_uint(256), + voting_data~load_dict()); + tuple list = null(); + + int secp_key = -1; + do { + (secp_key, slice sig, int found?) = signatures.udict_get_next?(256, secp_key); + if (found?) { + (int r, int s, int v) = (sig~load_uint(256), + sig~load_uint(256), + sig~load_uint(8)); + list = cons( pair( secp_key, triple(r,s,v)), list); + } + } until (~ found?); + return (list); +} diff --git a/crypto/func/auto-tests/legacy_tests/config/config-code.fc b/crypto/func/auto-tests/legacy_tests/config/config-code.fc new file mode 100644 index 00000000..85a4ee00 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/config/config-code.fc @@ -0,0 +1,643 @@ +;; Simple configuration smart contract + +#include "stdlib.fc"; + +() set_conf_param(int index, cell value) impure { + var cs = get_data().begin_parse(); + var cfg_dict = cs~load_ref(); + cfg_dict~idict_set_ref(32, index, value); + set_data(begin_cell().store_ref(cfg_dict).store_slice(cs).end_cell()); +} + +(cell, int, int, cell) load_data() inline { + var cs = get_data().begin_parse(); + var res = (cs~load_ref(), cs~load_uint(32), cs~load_uint(256), cs~load_dict()); + cs.end_parse(); + return res; +} + +() store_data(cfg_dict, stored_seqno, public_key, vote_dict) impure inline { + set_data(begin_cell() + .store_ref(cfg_dict) + .store_uint(stored_seqno, 32) + .store_uint(public_key, 256) + .store_dict(vote_dict) + .end_cell()); +} + +;; (min_tot_rounds, max_tot_rounds, min_wins, max_losses, min_store_sec, max_store_sec, bit_price, cell_price) +_ parse_vote_config(cell c) inline { + var cs = c.begin_parse(); + throw_unless(44, cs~load_uint(8) == 0x36); + var res = (cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(32), cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); + cs.end_parse(); + return res; +} + +;; cfg_vote_setup#91 normal_params:^ConfigProposalSetup critical_params:^ConfigProposalSetup = ConfigVotingSetup; +_ get_vote_config_internal(int critical?, cell cparam11) inline_ref { + var cs = cparam11.begin_parse(); + throw_unless(44, cs~load_uint(8) == 0x91); + if (critical?) { + cs~load_ref(); + } + return parse_vote_config(cs.preload_ref()); +} + +_ get_vote_config(int critical?) inline { + return get_vote_config_internal(critical?, config_param(11)); +} + +(int, int) check_validator_set(cell vset) { + var cs = vset.begin_parse(); + throw_unless(9, cs~load_uint(8) == 0x12); ;; validators_ext#12 only + int utime_since = cs~load_uint(32); + int utime_until = cs~load_uint(32); + int total = cs~load_uint(16); + int main = cs~load_uint(16); + throw_unless(9, main > 0); + throw_unless(9, total >= main); + return (utime_since, utime_until); +} + +() send_answer(addr, query_id, ans_tag, mode) impure { + ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 + send_raw_message(begin_cell() + .store_uint(0x18, 6) + .store_slice(addr) + .store_uint(0, 5 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(ans_tag, 32) + .store_uint(query_id, 64) + .end_cell(), mode); +} + +() send_confirmation(addr, query_id, ans_tag) impure inline { + return send_answer(addr, query_id, ans_tag, 64); +} + +() send_error(addr, query_id, ans_tag) impure inline { + return send_answer(addr, query_id, ans_tag, 64); +} + +;; forward a message to elector smart contract to make it upgrade its code +() change_elector_code(slice cs) impure { + var dest_addr = config_param(1).begin_parse().preload_uint(256); + var query_id = now(); + send_raw_message(begin_cell() + .store_uint(0xc4ff, 17) + .store_uint(dest_addr, 256) + .store_grams(1 << 30) ;; ~ 1 Gram (will be returned back) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0x4e436f64, 32) ;; action + .store_uint(query_id, 64) + .store_slice(cs) + .end_cell(), 0); +} + +() after_code_upgrade(slice param, cont old_code) impure method_id(1666) { +} + +_ perform_action(cfg_dict, public_key, action, cs) inline_ref { + if (action == 0x43665021) { + ;; change one configuration parameter + var param_index = cs~load_int(32); + var param_value = cs~load_ref(); + cs.end_parse(); + cfg_dict~idict_set_ref(32, param_index, param_value); + return (cfg_dict, public_key); + } elseif (action == 0x4e436f64) { + ;; change configuration smart contract code + var new_code = cs~load_ref(); + set_code(new_code); + var old_code = get_c3(); + set_c3(new_code.begin_parse().bless()); + after_code_upgrade(cs, old_code); + throw(0); + return (cfg_dict, public_key); + } elseif (action == 0x50624b21) { + ;; change configuration master public key + public_key = cs~load_uint(256); + cs.end_parse(); + return (cfg_dict, public_key); + } elseif (action == 0x4e43ef05) { + ;; change election smart contract code + change_elector_code(cs); + return (cfg_dict, public_key); + } else { + throw_if(32, action); + return (cfg_dict, public_key); + } +} + +(cell, int, cell) get_current_vset() inline_ref { + var vset = config_param(34); + var cs = begin_parse(vset); + ;; validators_ext#12 utime_since:uint32 utime_until:uint32 + ;; total:(## 16) main:(## 16) { main <= total } { main >= 1 } + ;; total_weight:uint64 + throw_unless(40, cs~load_uint(8) == 0x12); + cs~skip_bits(32 + 32 + 16 + 16); + var (total_weight, dict) = (cs~load_uint(64), cs~load_dict()); + cs.end_parse(); + return (vset, total_weight, dict); +} + +(slice, int) get_validator_descr(int idx) inline_ref { + var (vset, total_weight, dict) = get_current_vset(); + var (value, _) = dict.udict_get?(16, idx); + return (value, total_weight); +} + +(int, int) unpack_validator_descr(slice cs) inline { + ;; ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey; + ;; validator#53 public_key:SigPubKey weight:uint64 = ValidatorDescr; + ;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr; + throw_unless(41, (cs~load_uint(8) & ~ 0x20) == 0x53); + throw_unless(41, cs~load_uint(32) == 0x8e81278a); + return (cs~load_uint(256), cs~load_uint(64)); +} + +;; cfg_proposal#f3 param_id:int32 param_value:(Maybe ^Cell) if_hash_equal:(Maybe uint256) +;; c -> (param-id param-cell maybe-hash) +(int, cell, int) parse_config_proposal(cell c) inline_ref { + var cs = c.begin_parse(); + throw_unless(44, cs~load_int(8) == 0xf3 - 0x100); + var (id, val, hash) = (cs~load_int(32), cs~load_maybe_ref(), cs~load_int(1)); + if (hash) { + hash = cs~load_uint(256); + } else { + hash = -1; + } + cs.end_parse(); + return (id, val, hash); +} + +(cell, int, cell) accept_proposal(cell cfg_dict, cell proposal, int critical?) inline_ref { + var (param_id, param_val, req_hash) = parse_config_proposal(proposal); + cell cur_val = cfg_dict.idict_get_ref(32, param_id); + int cur_hash = null?(cur_val) ? 0 : cell_hash(cur_val); + if ((cur_hash != req_hash) & (req_hash >= 0)) { + ;; current value has incorrect hash, do not apply changes + return (cfg_dict, 0, null()); + } + cell mparams = cfg_dict.idict_get_ref(32, 9); ;; mandatory parameters + var (_, found?) = mparams.idict_get?(32, param_id); + if (found? & param_val.null?()) { + ;; cannot set a mandatory parameter to (null) + return (cfg_dict, 0, null()); + } + cell cparams = cfg_dict.idict_get_ref(32, 10); ;; critical parameters + (_, found?) = cparams.idict_get?(32, param_id); + if (found? < critical?) { + ;; trying to set a critical parameter after a non-critical voting + return (cfg_dict, 0, null()); + } + ;; CHANGE ONE CONFIGURATION PARAMETER (!) + cfg_dict~idict_set_ref(32, param_id, param_val); + return (cfg_dict, param_id, param_val); +} + +(cell, int) perform_proposed_action(cell cfg_dict, int public_key, int param_id, cell param_val) inline_ref { + if (param_id == -999) { + ;; appoint or depose dictator + return (cfg_dict, param_val.null?() ? 0 : param_val.begin_parse().preload_uint(256)); + } + if (param_val.null?()) { + return (cfg_dict, public_key); + } + if (param_id == -1000) { + ;; upgrade code + var cs = param_val.begin_parse(); + var new_code = cs~load_ref(); + set_code(new_code); + var old_code = get_c3(); + set_c3(new_code.begin_parse().bless()); + after_code_upgrade(cs, old_code); + throw(0); + return (cfg_dict, public_key); + } + if (param_id == -1001) { + ;; update elector code + var cs = param_val.begin_parse(); + change_elector_code(cs); + } + return (cfg_dict, public_key); +} + +;; cfg_proposal_status#ce expires:uint32 proposal:^ConfigProposal is_critical:Bool +;; voters:(HashmapE 16 True) remaining_weight:int64 validator_set_id:uint256 +;; rounds_remaining:uint8 wins:uint8 losses:uint8 = ConfigProposalStatus; +(int, cell, int, cell, int, int, slice) unpack_proposal_status(slice cs) inline_ref { + throw_unless(44, cs~load_int(8) == 0xce - 0x100); + return (cs~load_uint(32), cs~load_ref(), cs~load_int(1), cs~load_dict(), cs~load_int(64), cs~load_uint(256), cs); +} + +slice update_proposal_status(slice rest, int weight_remaining, int critical?) inline_ref { + var (min_tot_rounds, max_tot_rounds, min_wins, max_losses, _, _, _, _) = get_vote_config(critical?); + var (rounds_remaining, wins, losses) = (rest~load_uint(8), rest~load_uint(8), rest~load_uint(8)); + losses -= (weight_remaining >= 0); + if (losses > max_losses) { + ;; lost too many times + return null(); + } + rounds_remaining -= 1; + if (rounds_remaining < 0) { + ;; existed for too many rounds + return null(); + } + return begin_cell() + .store_uint(rounds_remaining, 8) + .store_uint(wins, 8) + .store_uint(losses, 8) + .end_cell().begin_parse(); +} + +builder begin_pack_proposal_status(int expires, cell proposal, int critical?, cell voters, int weight_remaining, int vset_id) inline { + return begin_cell() + .store_int(0xce - 0x100, 8) + .store_uint(expires, 32) + .store_ref(proposal) + .store_int(critical?, 1) + .store_dict(voters) + .store_int(weight_remaining, 64) + .store_uint(vset_id, 256); +} + +(cell, cell, int) register_vote(vote_dict, phash, idx, weight) inline_ref { + var (pstatus, found?) = vote_dict.udict_get?(256, phash); + ifnot (found?) { + ;; config proposal not found + return (vote_dict, null(), -1); + } + var (cur_vset, total_weight, _) = get_current_vset(); + int cur_vset_id = cur_vset.cell_hash(); + var (expires, proposal, critical?, voters, weight_remaining, vset_id, rest) = unpack_proposal_status(pstatus); + if (expires <= now()) { + ;; config proposal expired, delete and report not found + vote_dict~udict_delete?(256, phash); + return (vote_dict, null(), -1); + } + if (vset_id != cur_vset_id) { + ;; config proposal belongs to a previous validator set + vset_id = cur_vset_id; + rest = update_proposal_status(rest, weight_remaining, critical?); + voters = null(); + weight_remaining = muldiv(total_weight, 3, 4); + } + if (rest.null?()) { + ;; discard proposal (existed for too many rounds, or too many losses) + vote_dict~udict_delete?(256, phash); + return (vote_dict, null(), -1); + } + var (_, found?) = voters.udict_get?(16, idx); + if (found?) { + ;; already voted for this proposal, ignore vote + return (vote_dict, null(), -2); + } + ;; register vote + voters~udict_set_builder(16, idx, begin_cell().store_uint(now(), 32)); + int old_wr = weight_remaining; + weight_remaining -= weight; + if ((weight_remaining ^ old_wr) >= 0) { + ;; not enough votes, or proposal already accepted in this round + ;; simply update weight_remaining + vote_dict~udict_set_builder(256, phash, begin_pack_proposal_status(expires, proposal, critical?, voters, weight_remaining, vset_id).store_slice(rest)); + return (vote_dict, null(), 2); + } + ;; proposal wins in this round + var (min_tot_rounds, max_tot_rounds, min_wins, max_losses, _, _, _, _) = get_vote_config(critical?); + var (rounds_remaining, wins, losses) = (rest~load_uint(8), rest~load_uint(8), rest~load_uint(8)); + wins += 1; + if (wins >= min_wins) { + ;; proposal is accepted, remove and process + vote_dict~udict_delete?(256, phash); + return (vote_dict, proposal, 6 - critical?); + } + ;; update proposal info + vote_dict~udict_set_builder(256, phash, + begin_pack_proposal_status(expires, proposal, critical?, voters, weight_remaining, vset_id) + .store_uint(rounds_remaining, 8) + .store_uint(wins, 8) + .store_uint(losses, 8)); + return (vote_dict, null(), 2); +} + +int proceed_register_vote(phash, idx, weight) impure inline_ref { + var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data(); + (vote_dict, var accepted_proposal, var status) = register_vote(vote_dict, phash, idx, weight); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + ifnot (accepted_proposal.null?()) { + var critical? = 6 - status; + (cfg_dict, var param_id, var param_val) = accept_proposal(cfg_dict, accepted_proposal, critical?); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + if (param_id) { + commit(); + (cfg_dict, public_key) = perform_proposed_action(cfg_dict, public_key, param_id, param_val); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + } + } + return status; +} + +(slice, int) scan_proposal(int phash, slice pstatus) inline_ref { + var (cur_vset, total_weight, _) = get_current_vset(); + int cur_vset_id = cur_vset.cell_hash(); + var (expires, proposal, critical?, voters, weight_remaining, vset_id, rest) = unpack_proposal_status(pstatus); + if (expires <= now()) { + ;; config proposal expired, delete + return (null(), true); + } + if (vset_id == cur_vset_id) { + ;; config proposal already processed or voted for in this round, change nothing + return (pstatus, false); + } + ;; config proposal belongs to a previous validator set + vset_id = cur_vset_id; + rest = update_proposal_status(rest, weight_remaining, critical?); + voters = null(); + weight_remaining = muldiv(total_weight, 3, 4); + if (rest.null?()) { + ;; discard proposal (existed for too many rounds, or too many losses) + return (null(), true); + } + ;; return updated proposal + return (begin_pack_proposal_status(expires, proposal, critical?, voters, weight_remaining, vset_id).store_slice(rest).end_cell().begin_parse(), true); +} + +cell scan_random_proposal(cell vote_dict) inline_ref { + var (phash, pstatus, found?) = vote_dict.udict_get_nexteq?(256, random()); + ifnot (found?) { + return vote_dict; + } + (pstatus, var changed?) = scan_proposal(phash, pstatus); + if (changed?) { + if (pstatus.null?()) { + vote_dict~udict_delete?(256, phash); + } else { + vote_dict~udict_set(256, phash, pstatus); + } + } + return vote_dict; +} + +int register_voting_proposal(slice cs, int msg_value) impure inline_ref { + var (expire_at, proposal, critical?) = (cs~load_uint(32), cs~load_ref(), cs~load_int(1)); + if (expire_at >> 30) { + expire_at -= now(); + } + var (param_id, param_val, hash) = parse_config_proposal(proposal); + if (hash >= 0) { + cell cur_val = config_param(param_id); + int cur_hash = null?(cur_val) ? 0 : cell_hash(cur_val); + if (cur_hash != hash) { + hash = -0xe2646356; ;; bad current value + } + } else { + var m_params = config_param(9); + var (_, found?) = m_params.idict_get?(32, param_id); + if (found?) { + hash = -0xcd506e6c; ;; cannot set mandatory parameter to null + } + } + if (param_val.cell_depth() >= 256) { + hash = -0xc2616456; ;; bad value + } + if (hash < -1) { + return hash; ;; return error if any + } + ifnot (critical?) { + var crit_params = config_param(10); + var (_, found?) = crit_params.idict_get?(32, param_id); + if (found?) { + hash = -0xc3726954; ;; trying to set a critical parameter without critical flag + } + } + if (hash < -1) { + return hash; + } + ;; obtain vote proposal configuration + var vote_cfg = get_vote_config(critical?); + var (min_tot_rounds, max_tot_rounds, min_wins, max_losses, min_store_sec, max_store_sec, bit_price, cell_price) = vote_cfg; + if (expire_at < min_store_sec) { + return -0xc5787069; ;; expired + } + expire_at = min(expire_at, max_store_sec); + ;; compute price + var (_, bits, refs) = compute_data_size(param_val, 1024); + var pps = bit_price * (bits + 1024) + cell_price * (refs + 2); + var price = pps * expire_at; + expire_at += now(); + var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data(); + int phash = proposal.cell_hash(); + var (pstatus, found?) = vote_dict.udict_get?(256, phash); + if (found?) { + ;; proposal already exists; we can only extend it + var (expires, r_proposal, r_critical?, voters, weight_remaining, vset_id, rest) = unpack_proposal_status(pstatus); + if (r_critical? != critical?) { + return -0xc3726955; ;; cannot upgrade critical parameter to non-critical... + } + if (expires >= expire_at) { + return -0xc16c7245; ;; proposal already exists + } + ;; recompute price + price = pps * (expire_at - expires + 16384); + if (msg_value - price < (1 << 30)) { + return -0xf0617924; ;; need more money + } + ;; update expiration time + vote_dict~udict_set_builder(256, phash, begin_pack_proposal_status(expire_at, r_proposal, r_critical?, voters, weight_remaining, vset_id).store_slice(rest)); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + return price; + } + if (msg_value - price < (1 << 30)) { + return -0xf0617924; ;; need more money + } + ;; obtain current validator set data + var (vset, total_weight, _) = get_current_vset(); + int weight_remaining = muldiv(total_weight, 3, 4); + ;; create new proposal + vote_dict~udict_set_builder(256, phash, + begin_pack_proposal_status(expire_at, proposal, critical?, null(), weight_remaining, vset.cell_hash()) + .store_uint(max_tot_rounds, 8).store_uint(0, 16)); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + return price; +} + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + var cs = in_msg_cell.begin_parse(); + var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + var s_addr = cs~load_msg_addr(); + (int src_wc, int src_addr) = s_addr.parse_std_addr(); + if ((src_wc + 1) | (flags & 1) | in_msg.slice_empty?()) { + ;; source not in masterchain, or a bounced message, or a simple transfer + return (); + } + int tag = in_msg~load_uint(32); + int query_id = in_msg~load_uint(64); + if (tag == 0x4e565354) { + ;; set next validator set + var vset = in_msg~load_ref(); + in_msg.end_parse(); + var elector_param = config_param(1); + var elector_addr = cell_null?(elector_param) ? -1 : elector_param.begin_parse().preload_uint(256); + var ok = false; + if (src_addr == elector_addr) { + ;; message from elector smart contract + ;; set next validator set + (var t_since, var t_until) = check_validator_set(vset); + var t = now(); + ok = (t_since > t) & (t_until > t_since); + } + if (ok) { + set_conf_param(36, vset); + ;; send confirmation + return send_confirmation(s_addr, query_id, 0xee764f4b); + } else { + return send_error(s_addr, query_id, 0xee764f6f); + } + } + if (tag == 0x6e565052) { + ;; new voting proposal + var price = register_voting_proposal(in_msg, msg_value); + int mode = 64; + int ans_tag = - price; + if (price >= 0) { + ;; ok, debit price + raw_reserve(price, 4); + ans_tag = 0xee565052; + mode = 128; + } + return send_answer(s_addr, query_id, ans_tag, mode); + } + if (tag == 0x566f7465) { + ;; vote for a configuration proposal + var signature = in_msg~load_bits(512); + var msg_body = in_msg; + var (sign_tag, idx, phash) = (in_msg~load_uint(32), in_msg~load_uint(16), in_msg~load_uint(256)); + in_msg.end_parse(); + throw_unless(37, sign_tag == 0x566f7445); + var (vdescr, total_weight) = get_validator_descr(idx); + var (val_pubkey, weight) = unpack_validator_descr(vdescr); + throw_unless(34, check_data_signature(msg_body, signature, val_pubkey)); + int res = proceed_register_vote(phash, idx, weight); + return send_confirmation(s_addr, query_id, res + 0xd6745240); + } + ;; if tag is non-zero and its higher bit is zero, throw an exception (the message is an unsupported query) + ;; to bounce message back to sender + throw_unless(37, (tag == 0) | (tag & (1 << 31))); + ;; do nothing for other internal messages +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + int action = cs~load_uint(32); + int msg_seqno = cs~load_uint(32); + var valid_until = cs~load_uint(32); + throw_if(35, valid_until < now()); + throw_if(39, slice_depth(cs) > 128); + var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data(); + throw_unless(33, msg_seqno == stored_seqno); + if (action == 0x566f7465) { + ;; vote for a configuration proposal + var (idx, phash) = (cs~load_uint(16), cs~load_uint(256)); + cs.end_parse(); + var (vdescr, total_weight) = get_validator_descr(idx); + var (val_pubkey, weight) = unpack_validator_descr(vdescr); + throw_unless(34, check_data_signature(in_msg, signature, val_pubkey)); + accept_message(); + stored_seqno = (stored_seqno + 1) % (1 << 32); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + commit(); + proceed_register_vote(phash, idx, weight); + return (); + } + throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key)); + accept_message(); + stored_seqno = (stored_seqno + 1) % (1 << 32); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + commit(); + (cfg_dict, public_key) = perform_action(cfg_dict, public_key, action, cs); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); +} + +() run_ticktock(int is_tock) impure { + var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data(); + int kl = 32; + var next_vset = cfg_dict.idict_get_ref(kl, 36); + var updated? = false; + ifnot (next_vset.null?()) { + ;; check whether we have to set next_vset as the current validator set + var ds = next_vset.begin_parse(); + if (ds.slice_bits() >= 40) { + var tag = ds~load_uint(8); + var since = ds.preload_uint(32); + if ((since <= now()) & (tag == 0x12)) { + ;; next validator set becomes active! + var cur_vset = cfg_dict~idict_set_get_ref(kl, 34, next_vset); ;; next_vset -> cur_vset + cfg_dict~idict_set_get_ref(kl, 32, cur_vset); ;; cur_vset -> prev_vset + cfg_dict~idict_delete?(kl, 36); ;; (null) -> next_vset + updated? = true; + } + } + } + ifnot (updated?) { + ;; if nothing has been done so far, scan a random voting proposal instead + vote_dict = scan_random_proposal(vote_dict); + } + ;; save data and return + return store_data(cfg_dict, stored_seqno, public_key, vote_dict); +} + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +_ unpack_proposal(slice pstatus) inline_ref { + (int expires, cell proposal, int critical?, cell voters, int weight_remaining, int vset_id, slice rest) = unpack_proposal_status(pstatus); + var voters_list = null(); + var voter_id = (1 << 32); + do { + (voter_id, _, var f) = voters.udict_get_prev?(16, voter_id); + if (f) { + voters_list = cons(voter_id, voters_list); + } + } until (~ f); + var (rounds_remaining, losses, wins) = (rest~load_uint(8), rest~load_uint(8), rest~load_uint(8)); + rest.end_parse(); + var (param_id, param_val, param_hash) = parse_config_proposal(proposal); + return [expires, critical?, [param_id, param_val, param_hash], vset_id, voters_list, weight_remaining, rounds_remaining, losses, wins]; +} + +_ get_proposal(int phash) method_id { + (_, _, _, var vote_dict) = load_data(); + var (pstatus, found?) = vote_dict.udict_get?(256, phash); + ifnot (found?) { + return null(); + } + return unpack_proposal(pstatus); +} + +_ list_proposals() method_id { + (_, _, _, var vote_dict) = load_data(); + var phash = (1 << 255) + ((1 << 255) - 1); + var list = null(); + do { + (phash, var pstatus, var f) = vote_dict.udict_get_prev?(256, phash); + if (f) { + list = cons([phash, unpack_proposal(pstatus)], list); + } + } until (~ f); + return list; +} + +_ proposal_storage_price(int critical?, int seconds, int bits, int refs) method_id { + var cfg_dict = get_data().begin_parse().preload_ref(); + var cparam11 = cfg_dict.idict_get_ref(32, 11); + var (min_tot_rounds, max_tot_rounds, min_wins, max_losses, min_store_sec, max_store_sec, bit_price, cell_price) = get_vote_config_internal(critical?, cparam11); + if (seconds < min_store_sec) { + return -1; + } + seconds = min(seconds, max_store_sec); + return (bit_price * (bits + 1024) + cell_price * (refs + 2)) * seconds; +} diff --git a/crypto/func/auto-tests/legacy_tests/config/stdlib.fc b/crypto/func/auto-tests/legacy_tests/config/stdlib.fc new file mode 100644 index 00000000..0b98eeb4 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/config/stdlib.fc @@ -0,0 +1,208 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; diff --git a/crypto/func/auto-tests/legacy_tests/dns-collection/dns-utils.fc b/crypto/func/auto-tests/legacy_tests/dns-collection/dns-utils.fc new file mode 100644 index 00000000..2b1061e9 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/dns-collection/dns-utils.fc @@ -0,0 +1,110 @@ +const int one_month = 2592000; ;; 1 month in seconds = 60 * 60 * 24 * 30 +const int one_year = 31622400; ;; 1 year in seconds = 60 * 60 * 24 * 366 +const int auction_start_time = 1659171600; ;; GMT: Monday, 30 July 2022 г., 09:00:00 +const int one_ton = 1000000000; +const int dns_next_resolver_prefix = 0xba93; ;; dns_next_resolver prefix - https://github.com/ton-blockchain/ton/blob/7e3df93ca2ab336716a230fceb1726d81bac0a06/crypto/block/block.tlb#L819 + +const int dns_config_id = 80; ;; dns black list config param; in testnet -80 + +const int op::fill_up = 0x370fec51; +const int op::outbid_notification = 0x557cea20; +const int op::change_dns_record = 0x4eb1f0f9; +const int op::process_governance_decision = 0x44beae41; +const int op::dns_balance_release = 0x4ed14b65; + +int mod(int x, int y) asm "MOD"; + +slice zero_address() { + return begin_cell().store_uint(0, 2).end_cell().begin_parse(); +} + +;; "ton\0test\0" -> "ton" +int get_top_domain_bits(slice domain) { + int i = 0; + int need_break = 0; + do { + int char = domain~load_uint(8); ;; we do not check domain.length because it MUST contains \0 character + need_break = char == 0; + if (~ need_break) { + i += 8; + } + } until (need_break); + throw_if(201, i == 0); ;; starts with \0 + return i; +} + +slice read_domain_from_comment(slice in_msg_body) { + int need_break = 0; + builder result = begin_cell(); + do { + result = result.store_slice(in_msg_body~load_bits(in_msg_body.slice_bits())); + int refs_len = in_msg_body.slice_refs(); + need_break = refs_len == 0; + if (~ need_break) { + throw_unless(202, refs_len == 1); + in_msg_body = in_msg_body~load_ref().begin_parse(); + } + } until (need_break); + return result.end_cell().begin_parse(); +} + +int check_domain_string(slice domain) { + int i = 0; + int len = slice_bits(domain); + int need_break = 0; + do { + need_break = i == len; + if (~ need_break) { + int char = domain~load_uint(8); + ;; we can do it because additional UTF-8 character's octets >= 128 -- https://www.ietf.org/rfc/rfc3629.txt + int is_hyphen = (char == 45); + int valid_char = (is_hyphen & (i > 0) & (i < len - 8)) | ((char >= 48) & (char <= 57)) | ((char >= 97) & (char <= 122)); ;; '-' or 0-9 or a-z + + need_break = ~ valid_char; + if (~ need_break) { + i += 8; + } + } + } until (need_break); + return i == len; +} + +(int, int) get_min_price_config(int domain_char_count) { + if (domain_char_count == 4) { + return (1000, 100); + } + if (domain_char_count == 5) { + return (500, 50); + } + if (domain_char_count == 6) { + return (400, 40); + } + if (domain_char_count == 7) { + return (300, 30); + } + if (domain_char_count == 8) { + return (200, 20); + } + if (domain_char_count == 9) { + return (100, 10); + } + if (domain_char_count == 10) { + return (50, 5); + } + return (10, 1); +} + +int get_min_price(int domain_bits_length, int now_time) { + (int start_min_price, int end_min_price) = get_min_price_config(domain_bits_length / 8); + start_min_price *= one_ton; + end_min_price *= one_ton; + int seconds = now_time - auction_start_time; + int months = seconds / one_month; + if (months > 21) { + return end_min_price; + } + repeat (months) { + start_min_price = start_min_price * 90 / 100; + } + return start_min_price; +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/dns-collection/nft-collection.fc b/crypto/func/auto-tests/legacy_tests/dns-collection/nft-collection.fc new file mode 100644 index 00000000..4e142587 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/dns-collection/nft-collection.fc @@ -0,0 +1,144 @@ +;; DNS resolver smart contract (implements NFT Collection interface) + + +#include "stdlib.fc"; +#include "params.fc"; +#include "dns-utils.fc"; + +;; storage scheme +;; storage#_ collection_content:^Cell +;; nft_item_code:^Cell +;; = Storage; + +(cell, cell) load_data() inline { + var ds = get_data().begin_parse(); + return ( + ds~load_ref(), ;; content + ds~load_ref() ;; nft_item_code + ); +} + +() save_data(cell content, cell nft_item_code) impure inline { + set_data(begin_cell() + .store_ref(content) + .store_ref(nft_item_code) + .end_cell()); +} + +cell calculate_nft_item_state_init(int item_index, cell nft_item_code) { + cell data = begin_cell().store_uint(item_index, 256).store_slice(my_address()).end_cell(); + return begin_cell().store_uint(0, 2).store_dict(nft_item_code).store_dict(data).store_uint(0, 1).end_cell(); +} + +slice calculate_nft_item_address(int wc, cell state_init) { + return begin_cell() + .store_uint(4, 3) + .store_int(wc, 8) + .store_uint(cell_hash(state_init), 256) + .end_cell() + .begin_parse(); +} + +() deploy_nft_item(int item_index, cell nft_item_code, cell nft_content) impure { + cell state_init = calculate_nft_item_state_init(item_index, nft_item_code); + slice nft_address = calculate_nft_item_address(workchain(), state_init); + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(nft_address) + .store_coins(0) + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init) + .store_ref(nft_content); + send_raw_message(msg.end_cell(), 64); ;; carry all the remaining value of the inbound message, fee deducted from amount +} + +() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure { + if (in_msg_body.slice_empty?()) { ;; bounce back empty messages + throw(0xffff); + } + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + + if (flags & 1) { ;; ignore all bounced messages + return (); + } + slice sender_address = cs~load_msg_addr(); + + int op = in_msg_body~load_uint(32); + + var (content, nft_item_code) = load_data(); + + if (op == 0) { ;; deploy new nft + int now_time = now(); + throw_unless(199, now_time > auction_start_time); ;; start of auction + slice domain = read_domain_from_comment(in_msg_body); + int len = slice_bits(domain); + throw_unless(200, len > 3 * 8); ;; minimum 4 characters + throw_unless(201, len <= 126 * 8); ;; maxmimum 126 characters + throw_unless(202, mod(len, 8) == 0); + throw_unless(203, check_domain_string(domain)); + int min_price = get_min_price(len, now_time); + throw_unless(204, msg_value >= min_price); + + int item_index = slice_hash(domain); + + cell config_cell = config_param(dns_config_id); + if (~ cell_null?(config_cell)) { + slice config_cs = config_cell.begin_parse(); + cell config = config_cs~load_dict(); + (slice config_value, int found) = config.udict_get?(256, item_index); + throw_if(205, found); + } + + cell nft_content = begin_cell() + .store_slice(sender_address) + .store_ref(begin_cell().store_slice(domain).end_cell()) + .end_cell(); + deploy_nft_item(item_index, nft_item_code, nft_content); + return (); + } + + if (op == op::fill_up) { ;; just fill-up balance + return (); + } + throw(0xffff); +} + +;; Get methods + +(int, cell, slice) get_collection_data() method_id { + var (content, nft_item_code) = load_data(); + return (-1, content, zero_address()); +} + +slice get_nft_address_by_index(int index) method_id { + var (content, nft_item_code) = load_data(); + cell state_init = calculate_nft_item_state_init(index, nft_item_code); + return calculate_nft_item_address(workchain(), state_init); +} + +cell get_nft_content(int index, cell individual_nft_content) method_id { + return individual_nft_content; +} + +(int, cell) dnsresolve(slice subdomain, int category) method_id { + throw_unless(70, mod(slice_bits(subdomain), 8) == 0); + + int starts_with_zero_byte = subdomain.preload_int(8) == 0; + + if (starts_with_zero_byte & (slice_bits(subdomain) == 8)) { ;; "." requested + return (8, null()); ;; resolved but no dns-records + } + if (starts_with_zero_byte) { + subdomain~load_uint(8); + } + + int top_subdomain_bits = get_top_domain_bits(subdomain); + slice top_subdomain = subdomain~load_bits(top_subdomain_bits); + int item_index = slice_hash(top_subdomain); + cell result = begin_cell() + .store_uint(dns_next_resolver_prefix, 16) + .store_slice(get_nft_address_by_index(item_index)) + .end_cell(); + return (top_subdomain_bits + (starts_with_zero_byte ? 8 : 0), result); +} diff --git a/crypto/func/auto-tests/legacy_tests/dns-collection/params.fc b/crypto/func/auto-tests/legacy_tests/dns-collection/params.fc new file mode 100644 index 00000000..e28eac45 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/dns-collection/params.fc @@ -0,0 +1,6 @@ +int workchain() asm "0 PUSHINT"; + +() force_chain(slice addr) impure { + (int wc, _) = parse_std_addr(addr); + throw_unless(333, wc == workchain()); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/dns-collection/stdlib.fc b/crypto/func/auto-tests/legacy_tests/dns-collection/stdlib.fc new file mode 100644 index 00000000..40bac583 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/dns-collection/stdlib.fc @@ -0,0 +1,216 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +builder store_coins(builder b, int x) asm "STVARUINT16"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDVARUINT16"; + +int equal_slices (slice a, slice b) asm "SDEQ"; +int builder_null?(builder b) asm "ISNULL"; +builder store_builder(builder to, builder from) asm "STBR"; + diff --git a/crypto/func/auto-tests/legacy_tests/elector/elector-code.fc b/crypto/func/auto-tests/legacy_tests/elector/elector-code.fc new file mode 100644 index 00000000..a0652442 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/elector/elector-code.fc @@ -0,0 +1,1189 @@ +;; Elector smartcontract + +#include "stdlib.fc"; + +;; cur_elect credits past_elections grams active_id active_hash +(cell, cell, cell, int, int, int) load_data() inline_ref { + var cs = get_data().begin_parse(); + var res = (cs~load_dict(), cs~load_dict(), cs~load_dict(), cs~load_grams(), cs~load_uint(32), cs~load_uint(256)); + cs.end_parse(); + return res; +} + +;; cur_elect credits past_elections grams active_id active_hash +() store_data(elect, credits, past_elections, grams, active_id, active_hash) impure inline_ref { + set_data(begin_cell() + .store_dict(elect) + .store_dict(credits) + .store_dict(past_elections) + .store_grams(grams) + .store_uint(active_id, 32) + .store_uint(active_hash, 256) + .end_cell()); +} + +;; elect -> elect_at elect_close min_stake total_stake members failed finished +_ unpack_elect(elect) inline_ref { + var es = elect.begin_parse(); + var res = (es~load_uint(32), es~load_uint(32), es~load_grams(), es~load_grams(), es~load_dict(), es~load_int(1), es~load_int(1)); + es.end_parse(); + return res; +} + +cell pack_elect(elect_at, elect_close, min_stake, total_stake, members, failed, finished) inline_ref { + return begin_cell() + .store_uint(elect_at, 32) + .store_uint(elect_close, 32) + .store_grams(min_stake) + .store_grams(total_stake) + .store_dict(members) + .store_int(failed, 1) + .store_int(finished, 1) + .end_cell(); +} + +;; slice -> unfreeze_at stake_held vset_hash frozen_dict total_stake bonuses complaints +_ unpack_past_election(slice fs) inline_ref { + var res = (fs~load_uint(32), fs~load_uint(32), fs~load_uint(256), fs~load_dict(), fs~load_grams(), fs~load_grams(), fs~load_dict()); + fs.end_parse(); + return res; +} + +builder pack_past_election(int unfreeze_at, int stake_held, int vset_hash, cell frozen_dict, int total_stake, int bonuses, cell complaints) inline_ref { + return begin_cell() + .store_uint(unfreeze_at, 32) + .store_uint(stake_held, 32) + .store_uint(vset_hash, 256) + .store_dict(frozen_dict) + .store_grams(total_stake) + .store_grams(bonuses) + .store_dict(complaints); +} + +;; complaint_status#2d complaint:^ValidatorComplaint voters:(HashmapE 16 True) +;; vset_id:uint256 weight_remaining:int64 = ValidatorComplaintStatus; +_ unpack_complaint_status(slice cs) inline_ref { + throw_unless(9, cs~load_uint(8) == 0x2d); + var res = (cs~load_ref(), cs~load_dict(), cs~load_uint(256), cs~load_int(64)); + cs.end_parse(); + return res; +} + +builder pack_complaint_status(cell complaint, cell voters, int vset_id, int weight_remaining) inline_ref { + return begin_cell() + .store_uint(0x2d, 8) + .store_ref(complaint) + .store_dict(voters) + .store_uint(vset_id, 256) + .store_int(weight_remaining, 64); +} + +;; validator_complaint#bc validator_pubkey:uint256 description:^ComplaintDescr +;; created_at:uint32 severity:uint8 reward_addr:uint256 paid:Grams suggested_fine:Grams +;; suggested_fine_part:uint32 = ValidatorComplaint; +_ unpack_complaint(slice cs) inline_ref { + throw_unless(9, cs~load_int(8) == 0xbc - 0x100); + var res = (cs~load_uint(256), cs~load_ref(), cs~load_uint(32), cs~load_uint(8), cs~load_uint(256), cs~load_grams(), cs~load_grams(), cs~load_uint(32)); + cs.end_parse(); + return res; +} + +builder pack_complaint(int validator_pubkey, cell description, int created_at, int severity, int reward_addr, int paid, int suggested_fine, int suggested_fine_part) inline_ref { + return begin_cell() + .store_int(0xbc - 0x100, 8) + .store_uint(validator_pubkey, 256) + .store_ref(description) + .store_uint(created_at, 32) + .store_uint(severity, 8) + .store_uint(reward_addr, 256) + .store_grams(paid) + .store_grams(suggested_fine) + .store_uint(suggested_fine_part, 32); +} + +;; complaint_prices#1a deposit:Grams bit_price:Grams cell_price:Grams = ComplaintPricing; +(int, int, int) parse_complaint_prices(cell info) inline { + var cs = info.begin_parse(); + throw_unless(9, cs~load_uint(8) == 0x1a); + var res = (cs~load_grams(), cs~load_grams(), cs~load_grams()); + cs.end_parse(); + return res; +} + +;; deposit bit_price cell_price +(int, int, int) get_complaint_prices() inline_ref { + var info = config_param(13); + return info.null?() ? (1 << 36, 1, 512) : info.parse_complaint_prices(); +} + +;; elected_for elections_begin_before elections_end_before stake_held_for +(int, int, int, int) get_validator_conf() { + var cs = config_param(15).begin_parse(); + return (cs~load_int(32), cs~load_int(32), cs~load_int(32), cs.preload_int(32)); +} + +;; next three functions return information about current validator set (config param #34) +;; they are borrowed from config-code.fc +(cell, int, cell) get_current_vset() inline_ref { + var vset = config_param(34); + var cs = begin_parse(vset); + ;; validators_ext#12 utime_since:uint32 utime_until:uint32 + ;; total:(## 16) main:(## 16) { main <= total } { main >= 1 } + ;; total_weight:uint64 + throw_unless(40, cs~load_uint(8) == 0x12); + cs~skip_bits(32 + 32 + 16 + 16); + var (total_weight, dict) = (cs~load_uint(64), cs~load_dict()); + cs.end_parse(); + return (vset, total_weight, dict); +} + +(slice, int) get_validator_descr(int idx) inline_ref { + var (vset, total_weight, dict) = get_current_vset(); + var (value, _) = dict.udict_get?(16, idx); + return (value, total_weight); +} + +(int, int) unpack_validator_descr(slice cs) inline { + ;; ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey; + ;; validator#53 public_key:SigPubKey weight:uint64 = ValidatorDescr; + ;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr; + throw_unless(41, (cs~load_uint(8) & ~ 0x20) == 0x53); + throw_unless(41, cs~load_uint(32) == 0x8e81278a); + return (cs~load_uint(256), cs~load_uint(64)); +} + +() send_message_back(addr, ans_tag, query_id, body, grams, mode) impure inline_ref { + ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(addr) + .store_grams(grams) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(ans_tag, 32) + .store_uint(query_id, 64); + if (body >= 0) { + msg~store_uint(body, 32); + } + send_raw_message(msg.end_cell(), mode); +} + +() return_stake(addr, query_id, reason) impure inline_ref { + return send_message_back(addr, 0xee6f454c, query_id, reason, 0, 64); +} + +() send_confirmation(addr, query_id, comment) impure inline_ref { + return send_message_back(addr, 0xf374484c, query_id, comment, 1000000000, 2); +} + +() send_validator_set_to_config(config_addr, vset, query_id) impure inline_ref { + var msg = begin_cell() + .store_uint(0xc4ff, 17) ;; 0 11000100 0xff + .store_uint(config_addr, 256) + .store_grams(1 << 30) ;; ~1 gram of value to process and obtain answer + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0x4e565354, 32) + .store_uint(query_id, 64) + .store_ref(vset); + send_raw_message(msg.end_cell(), 1); +} + +;; credits 'amount' to 'addr' inside credit dictionary 'credits' +_ ~credit_to(credits, addr, amount) inline_ref { + var (val, f) = credits.udict_get?(256, addr); + if (f) { + amount += val~load_grams(); + } + credits~udict_set_builder(256, addr, begin_cell().store_grams(amount)); + return (credits, ()); +} + +() process_new_stake(s_addr, msg_value, cs, query_id) impure inline_ref { + var (src_wc, src_addr) = parse_std_addr(s_addr); + var ds = get_data().begin_parse(); + var elect = ds~load_dict(); + if (elect.null?() | (src_wc + 1)) { + ;; no elections active, or source is not in masterchain + ;; bounce message + return return_stake(s_addr, query_id, 0); + } + ;; parse the remainder of new stake message + var validator_pubkey = cs~load_uint(256); + var stake_at = cs~load_uint(32); + var max_factor = cs~load_uint(32); + var adnl_addr = cs~load_uint(256); + var signature = cs~load_ref().begin_parse().preload_bits(512); + cs.end_parse(); + ifnot (check_data_signature(begin_cell() + .store_uint(0x654c5074, 32) + .store_uint(stake_at, 32) + .store_uint(max_factor, 32) + .store_uint(src_addr, 256) + .store_uint(adnl_addr, 256) + .end_cell().begin_parse(), signature, validator_pubkey)) { + ;; incorrect signature, return stake + return return_stake(s_addr, query_id, 1); + } + if (max_factor < 0x10000) { + ;; factor must be >= 1. = 65536/65536 + return return_stake(s_addr, query_id, 6); + } + ;; parse current election data + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + ;; elect_at~dump(); + msg_value -= 1000000000; ;; deduct GR$1 for sending confirmation + if ((msg_value << 12) < total_stake) { + ;; stake smaller than 1/4096 of the total accumulated stakes, return + return return_stake(s_addr, query_id, 2); + } + total_stake += msg_value; ;; (provisionally) increase total stake + if (stake_at != elect_at) { + ;; stake for some other elections, return + return return_stake(s_addr, query_id, 3); + } + if (finished) { + ;; elections already finished, return stake + return return_stake(s_addr, query_id, 0); + } + var (mem, found) = members.udict_get?(256, validator_pubkey); + if (found) { + ;; entry found, merge stakes + msg_value += mem~load_grams(); + mem~load_uint(64); ;; skip timestamp and max_factor + found = (src_addr != mem~load_uint(256)); + } + if (found) { + ;; can make stakes for a public key from one address only + return return_stake(s_addr, query_id, 4); + } + if (msg_value < min_stake) { + ;; stake too small, return it + return return_stake(s_addr, query_id, 5); + } + throw_unless(44, msg_value); + accept_message(); + ;; store stake in the dictionary + members~udict_set_builder(256, validator_pubkey, begin_cell() + .store_grams(msg_value) + .store_uint(now(), 32) + .store_uint(max_factor, 32) + .store_uint(src_addr, 256) + .store_uint(adnl_addr, 256)); + ;; gather and save election data + elect = pack_elect(elect_at, elect_close, min_stake, total_stake, members, false, false); + set_data(begin_cell().store_dict(elect).store_slice(ds).end_cell()); + ;; return confirmation message + if (query_id) { + return send_confirmation(s_addr, query_id, 0); + } + return (); +} + +(cell, int) unfreeze_without_bonuses(credits, freeze_dict, tot_stakes) inline_ref { + var total = var recovered = 0; + var pubkey = -1; + do { + (pubkey, var cs, var f) = freeze_dict.udict_get_next?(256, pubkey); + if (f) { + var (addr, weight, stake, banned) = (cs~load_uint(256), cs~load_uint(64), cs~load_grams(), cs~load_int(1)); + cs.end_parse(); + if (banned) { + recovered += stake; + } else { + credits~credit_to(addr, stake); + } + total += stake; + } + } until (~ f); + throw_unless(59, total == tot_stakes); + return (credits, recovered); +} + +(cell, int) unfreeze_with_bonuses(credits, freeze_dict, tot_stakes, tot_bonuses) inline_ref { + var total = var recovered = var returned_bonuses = 0; + var pubkey = -1; + do { + (pubkey, var cs, var f) = freeze_dict.udict_get_next?(256, pubkey); + if (f) { + var (addr, weight, stake, banned) = (cs~load_uint(256), cs~load_uint(64), cs~load_grams(), cs~load_int(1)); + cs.end_parse(); + if (banned) { + recovered += stake; + } else { + var bonus = muldiv(tot_bonuses, stake, tot_stakes); + returned_bonuses += bonus; + credits~credit_to(addr, stake + bonus); + } + total += stake; + } + } until (~ f); + throw_unless(59, (total == tot_stakes) & (returned_bonuses <= tot_bonuses)); + return (credits, recovered + tot_bonuses - returned_bonuses); +} + +int stakes_sum(frozen_dict) inline_ref { + var total = 0; + var pubkey = -1; + do { + (pubkey, var cs, var f) = frozen_dict.udict_get_next?(256, pubkey); + if (f) { + cs~skip_bits(256 + 64); + total += cs~load_grams(); + } + } until (~ f); + return total; +} + +_ unfreeze_all(credits, past_elections, elect_id) inline_ref { + var (fs, f) = past_elections~udict_delete_get?(32, elect_id); + ifnot (f) { + ;; no elections with this id + return (credits, past_elections, 0); + } + var (unfreeze_at, stake_held, vset_hash, fdict, tot_stakes, bonuses, complaints) = fs.unpack_past_election(); + ;; tot_stakes = fdict.stakes_sum(); ;; TEMP BUGFIX + var unused_prizes = (bonuses > 0) ? + credits~unfreeze_with_bonuses(fdict, tot_stakes, bonuses) : + credits~unfreeze_without_bonuses(fdict, tot_stakes); + return (credits, past_elections, unused_prizes); +} + +() config_set_confirmed(s_addr, cs, query_id, ok) impure inline_ref { + var (src_wc, src_addr) = parse_std_addr(s_addr); + var config_addr = config_param(0).begin_parse().preload_uint(256); + var ds = get_data().begin_parse(); + var elect = ds~load_dict(); + if ((src_wc + 1) | (src_addr != config_addr) | elect.null?()) { + ;; not from config smc, somebody's joke? + ;; or no elections active (or just completed) + return (); + } + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + if ((elect_at != query_id) | ~ finished) { + ;; not these elections, or elections not finished yet + return (); + } + accept_message(); + ifnot (ok) { + ;; cancel elections, return stakes + var (credits, past_elections, grams) = (ds~load_dict(), ds~load_dict(), ds~load_grams()); + (credits, past_elections, var unused_prizes) = unfreeze_all(credits, past_elections, elect_at); + set_data(begin_cell() + .store_int(false, 1) + .store_dict(credits) + .store_dict(past_elections) + .store_grams(grams + unused_prizes) + .store_slice(ds) + .end_cell()); + } + ;; ... do not remove elect until we see this set as the next elected validator set +} + +() process_simple_transfer(s_addr, msg_value) impure inline_ref { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + (int src_wc, int src_addr) = parse_std_addr(s_addr); + if (src_addr | (src_wc + 1) | (active_id == 0)) { + ;; simple transfer to us (credit "nobody's" account) + ;; (or no known active validator set) + grams += msg_value; + return store_data(elect, credits, past_elections, grams, active_id, active_hash); + } + ;; zero source address -1:00..00 (collecting validator fees) + var (fs, f) = past_elections.udict_get?(32, active_id); + ifnot (f) { + ;; active validator set not found (?) + grams += msg_value; + } else { + ;; credit active validator set bonuses + var (unfreeze_at, stake_held, hash, dict, total_stake, bonuses, complaints) = fs.unpack_past_election(); + bonuses += msg_value; + past_elections~udict_set_builder(32, active_id, + pack_past_election(unfreeze_at, stake_held, hash, dict, total_stake, bonuses, complaints)); + } + return store_data(elect, credits, past_elections, grams, active_id, active_hash); +} + +() recover_stake(op, s_addr, cs, query_id) impure inline_ref { + (int src_wc, int src_addr) = parse_std_addr(s_addr); + if (src_wc + 1) { + ;; not from masterchain, return error + return send_message_back(s_addr, 0xfffffffe, query_id, op, 0, 64); + } + var ds = get_data().begin_parse(); + var (elect, credits) = (ds~load_dict(), ds~load_dict()); + var (cs, f) = credits~udict_delete_get?(256, src_addr); + ifnot (f) { + ;; no credit for sender, return error + return send_message_back(s_addr, 0xfffffffe, query_id, op, 0, 64); + } + var amount = cs~load_grams(); + cs.end_parse(); + ;; save data + set_data(begin_cell().store_dict(elect).store_dict(credits).store_slice(ds).end_cell()); + ;; send amount to sender in a new message + send_raw_message(begin_cell() + .store_uint(0x18, 6) + .store_slice(s_addr) + .store_grams(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0xf96f7324, 32) + .store_uint(query_id, 64) + .end_cell(), 64); +} + +() after_code_upgrade(slice s_addr, slice cs, int query_id) impure method_id(1666) { + var op = 0x4e436f64; + return send_message_back(s_addr, 0xce436f64, query_id, op, 0, 64); +} + +int upgrade_code(s_addr, cs, query_id) inline_ref { + var c_addr = config_param(0); + if (c_addr.null?()) { + ;; no configuration smart contract known + return false; + } + var config_addr = c_addr.begin_parse().preload_uint(256); + var (src_wc, src_addr) = parse_std_addr(s_addr); + if ((src_wc + 1) | (src_addr != config_addr)) { + ;; not from configuration smart contract, return error + return false; + } + accept_message(); + var code = cs~load_ref(); + set_code(code); + ifnot(cs.slice_empty?()) { + set_c3(code.begin_parse().bless()); + after_code_upgrade(s_addr, cs, query_id); + throw(0); + } + return true; +} + +int register_complaint(s_addr, complaint, msg_value) { + var (src_wc, src_addr) = parse_std_addr(s_addr); + if (src_wc + 1) { ;; not from masterchain, return error + return -1; + } + if (complaint.slice_depth() >= 128) { + return -3; ;; invalid complaint + } + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var election_id = complaint~load_uint(32); + var (fs, f) = past_elections.udict_get?(32, election_id); + ifnot (f) { ;; election not found + return -2; + } + var expire_in = fs.preload_uint(32) - now(); + if (expire_in <= 0) { ;; already expired + return -4; + } + var (validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part) = unpack_complaint(complaint); + reward_addr = src_addr; + created_at = now(); + ;; compute complaint storage/creation price + var (deposit, bit_price, cell_price) = get_complaint_prices(); + var (_, bits, refs) = slice_compute_data_size(complaint, 4096); + var pps = (bits + 1024) * bit_price + (refs + 2) * cell_price; + paid = pps * expire_in + deposit; + if (msg_value < paid + (1 << 30)) { ;; not enough money + return -5; + } + ;; re-pack modified complaint + cell complaint = pack_complaint(validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part).end_cell(); + var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs); + var (fs, f) = frozen_dict.udict_get?(256, validator_pubkey); + ifnot (f) { ;; no such validator, cannot complain + return -6; + } + fs~skip_bits(256 + 64); ;; addr weight + var validator_stake = fs~load_grams(); + int fine = suggested_fine + muldiv(validator_stake, suggested_fine_part, 1 << 32); + if (fine > validator_stake) { ;; validator's stake is less than suggested fine + return -7; + } + if (fine <= paid) { ;; fine is less than the money paid for creating complaint + return -8; + } + ;; create complaint status + var cstatus = pack_complaint_status(complaint, null(), 0, 0); + ;; save complaint status into complaints + var cpl_id = complaint.cell_hash(); + ifnot (complaints~udict_add_builder?(256, cpl_id, cstatus)) { + return -9; ;; complaint already exists + } + ;; pack past election info + past_elections~udict_set_builder(32, election_id, pack_past_election(unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints)); + ;; pack persistent data + ;; next line can be commented, but it saves a lot of stack manipulations + var (elect, credits, _, grams, active_id, active_hash) = load_data(); + store_data(elect, credits, past_elections, grams, active_id, active_hash); + return paid; +} + +(cell, cell, int, int) punish(credits, frozen, complaint) inline_ref { + var (validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part) = complaint.begin_parse().unpack_complaint(); + var (cs, f) = frozen.udict_get?(256, validator_pubkey); + ifnot (f) { + ;; no validator to punish + return (credits, frozen, 0, 0); + } + var (addr, weight, stake, banned) = (cs~load_uint(256), cs~load_uint(64), cs~load_grams(), cs~load_int(1)); + cs.end_parse(); + int fine = min(stake, suggested_fine + muldiv(stake, suggested_fine_part, 1 << 32)); + stake -= fine; + frozen~udict_set_builder(256, validator_pubkey, begin_cell() + .store_uint(addr, 256) + .store_uint(weight, 64) + .store_grams(stake) + .store_int(banned, 1)); + int reward = min(fine >> 3, paid * 8); + credits~credit_to(reward_addr, reward); + return (credits, frozen, fine - reward, fine); +} + +(cell, cell, int) register_vote(complaints, chash, idx, weight) inline_ref { + var (cstatus, found?) = complaints.udict_get?(256, chash); + ifnot (found?) { + ;; complaint not found + return (complaints, null(), -1); + } + var (cur_vset, total_weight, _) = get_current_vset(); + int cur_vset_id = cur_vset.cell_hash(); + var (complaint, voters, vset_id, weight_remaining) = unpack_complaint_status(cstatus); + int vset_old? = (vset_id != cur_vset_id); + if ((weight_remaining < 0) & vset_old?) { + ;; previous validator set already collected 2/3 votes, skip new votes + return (complaints, null(), -3); + } + if (vset_old?) { + ;; complaint votes belong to a previous validator set, reset voting + vset_id = cur_vset_id; + voters = null(); + weight_remaining = muldiv(total_weight, 2, 3); + } + var (_, found?) = voters.udict_get?(16, idx); + if (found?) { + ;; already voted for this proposal, ignore vote + return (complaints, null(), 0); + } + ;; register vote + voters~udict_set_builder(16, idx, begin_cell().store_uint(now(), 32)); + int old_wr = weight_remaining; + weight_remaining -= weight; + old_wr ^= weight_remaining; + ;; save voters and weight_remaining + complaints~udict_set_builder(256, chash, pack_complaint_status(complaint, voters, vset_id, weight_remaining)); + if (old_wr >= 0) { + ;; not enough votes or already accepted + return (complaints, null(), 1); + } + ;; complaint wins, prepare punishment + return (complaints, complaint, 2); +} + +int proceed_register_vote(election_id, chash, idx, weight) impure inline_ref { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var (fs, f) = past_elections.udict_get?(32, election_id); + ifnot (f) { ;; election not found + return -2; + } + var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs); + (complaints, var accepted_complaint, var status) = register_vote(complaints, chash, idx, weight); + if (status <= 0) { + return status; + } + ifnot (accepted_complaint.null?()) { + (credits, frozen_dict, int fine_unalloc, int fine_collected) = punish(credits, frozen_dict, accepted_complaint); + grams += fine_unalloc; + total_stake -= fine_collected; + } + past_elections~udict_set_builder(32, election_id, pack_past_election(unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints)); + store_data(elect, credits, past_elections, grams, active_id, active_hash); + return status; +} + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + ;; do nothing for internal messages + var cs = in_msg_cell.begin_parse(); + var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + if (flags & 1) { + ;; ignore all bounced messages + return (); + } + var s_addr = cs~load_msg_addr(); + if (in_msg.slice_empty?()) { + ;; inbound message has empty body + return process_simple_transfer(s_addr, msg_value); + } + int op = in_msg~load_uint(32); + if (op == 0) { + ;; simple transfer with comment, return + return process_simple_transfer(s_addr, msg_value); + } + int query_id = in_msg~load_uint(64); + if (op == 0x4e73744b) { + ;; new stake message + return process_new_stake(s_addr, msg_value, in_msg, query_id); + } + if (op == 0x47657424) { + ;; recover stake request + return recover_stake(op, s_addr, in_msg, query_id); + } + if (op == 0x4e436f64) { + ;; upgrade code (accepted only from configuration smart contract) + var ok = upgrade_code(s_addr, in_msg, query_id); + return send_message_back(s_addr, ok ? 0xce436f64 : 0xffffffff, query_id, op, 0, 64); + } + var cfg_ok = (op == 0xee764f4b); + if (cfg_ok | (op == 0xee764f6f)) { + ;; confirmation from configuration smart contract + return config_set_confirmed(s_addr, in_msg, query_id, cfg_ok); + } + if (op == 0x52674370) { + ;; new complaint + var price = register_complaint(s_addr, in_msg, msg_value); + int mode = 64; + int ans_tag = - price; + if (price >= 0) { + ;; ok, debit price + raw_reserve(price, 4); + ans_tag = 0; + mode = 128; + } + return send_message_back(s_addr, ans_tag + 0xf2676350, query_id, op, 0, mode); + } + if (op == 0x56744370) { + ;; vote for a complaint + var signature = in_msg~load_bits(512); + var msg_body = in_msg; + var (sign_tag, idx, elect_id, chash) = (in_msg~load_uint(32), in_msg~load_uint(16), in_msg~load_uint(32), in_msg~load_uint(256)); + in_msg.end_parse(); + throw_unless(37, sign_tag == 0x56744350); + var (vdescr, total_weight) = get_validator_descr(idx); + var (val_pubkey, weight) = unpack_validator_descr(vdescr); + throw_unless(34, check_data_signature(msg_body, signature, val_pubkey)); + int res = proceed_register_vote(elect_id, chash, idx, weight); + return send_message_back(s_addr, res + 0xd6745240, query_id, op, 0, 64); + } + + ifnot (op & (1 << 31)) { + ;; unknown query, return error + return send_message_back(s_addr, 0xffffffff, query_id, op, 0, 64); + } + ;; unknown answer, ignore + return (); +} + +int postpone_elections() impure { + return false; +} + +;; computes the total stake out of the first n entries of list l +_ compute_total_stake(l, n, m_stake) inline_ref { + int tot_stake = 0; + repeat (n) { + (var h, l) = uncons(l); + var stake = h.at(0); + var max_f = h.at(1); + stake = min(stake, (max_f * m_stake) >> 16); + tot_stake += stake; + } + return tot_stake; +} + +(cell, cell, int, cell, int, int) try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor) { + var cs = 16.config_param().begin_parse(); + var (max_validators, _, min_validators) = (cs~load_uint(16), cs~load_uint(16), cs~load_uint(16)); + cs.end_parse(); + min_validators = max(min_validators, 1); + int n = 0; + var sdict = new_dict(); + var pubkey = -1; + do { + (pubkey, var cs, var f) = members.udict_get_next?(256, pubkey); + if (f) { + var (stake, time, max_factor, addr, adnl_addr) = (cs~load_grams(), cs~load_uint(32), cs~load_uint(32), cs~load_uint(256), cs~load_uint(256)); + cs.end_parse(); + var key = begin_cell() + .store_uint(stake, 128) + .store_int(- time, 32) + .store_uint(pubkey, 256) + .end_cell().begin_parse(); + sdict~dict_set_builder(128 + 32 + 256, key, begin_cell() + .store_uint(min(max_factor, max_stake_factor), 32) + .store_uint(addr, 256) + .store_uint(adnl_addr, 256)); + n += 1; + } + } until (~ f); + n = min(n, max_validators); + if (n < min_validators) { + return (credits, new_dict(), 0, new_dict(), 0, 0); + } + var l = nil; + do { + var (key, cs, f) = sdict~dict::delete_get_min(128 + 32 + 256); + if (f) { + var (stake, _, pubkey) = (min(key~load_uint(128), max_stake), key~load_uint(32), key.preload_uint(256)); + var (max_f, _, adnl_addr) = (cs~load_uint(32), cs~load_uint(256), cs.preload_uint(256)); + l = cons([stake, max_f, pubkey, adnl_addr], l); + } + } until (~ f); + ;; l is the list of all stakes in decreasing order + int i = min_validators - 1; + var l1 = l; + repeat (i) { + l1 = cdr(l1); + } + var (best_stake, m) = (0, 0); + do { + var stake = l1~list_next().at(0); + i += 1; + if (stake >= min_stake) { + var tot_stake = compute_total_stake(l, i, stake); + if (tot_stake > best_stake) { + (best_stake, m) = (tot_stake, i); + } + } + } until (i >= n); + if ((m == 0) | (best_stake < min_total_stake)) { + return (credits, new_dict(), 0, new_dict(), 0, 0); + } + ;; we have to select first m validators from list l + l1 = touch(l); + ;; l1~dump(); ;; DEBUG + repeat (m - 1) { + l1 = cdr(l1); + } + var m_stake = car(l1).at(0); ;; minimal stake + ;; create both the new validator set and the refund set + int i = 0; + var tot_stake = 0; + var tot_weight = 0; + var vset = new_dict(); + var frozen = new_dict(); + do { + var [stake, max_f, pubkey, adnl_addr] = l~list_next(); + ;; lookup source address first + var (val, f) = members.udict_get?(256, pubkey); + throw_unless(61, f); + (_, _, var src_addr) = (val~load_grams(), val~load_uint(64), val.preload_uint(256)); + if (i < m) { + ;; one of the first m members, include into validator set + var true_stake = min(stake, (max_f * m_stake) >> 16); + stake -= true_stake; + ;; ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey; // 288 bits + ;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr; + var weight = (true_stake << 60) / best_stake; + tot_stake += true_stake; + tot_weight += weight; + var vinfo = begin_cell() + .store_uint(adnl_addr ? 0x73 : 0x53, 8) ;; validator_addr#73 or validator#53 + .store_uint(0x8e81278a, 32) ;; ed25519_pubkey#8e81278a + .store_uint(pubkey, 256) ;; pubkey:bits256 + .store_uint(weight, 64); ;; weight:uint64 + if (adnl_addr) { + vinfo~store_uint(adnl_addr, 256); ;; adnl_addr:bits256 + } + vset~udict_set_builder(16, i, vinfo); + frozen~udict_set_builder(256, pubkey, begin_cell() + .store_uint(src_addr, 256) + .store_uint(weight, 64) + .store_grams(true_stake) + .store_int(false, 1)); + } + if (stake) { + ;; non-zero unused part of the stake, credit to the source address + credits~credit_to(src_addr, stake); + } + i += 1; + } until (l.null?()); + throw_unless(49, tot_stake == best_stake); + return (credits, vset, tot_weight, frozen, tot_stake, m); +} + +int conduct_elections(ds, elect, credits) impure { + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + if (now() < elect_close) { + ;; elections not finished yet + return false; + } + if (config_param(0).null?()) { + ;; no configuration smart contract to send result to + return postpone_elections(); + } + var cs = config_param(17).begin_parse(); + min_stake = cs~load_grams(); + var max_stake = cs~load_grams(); + var min_total_stake = cs~load_grams(); + var max_stake_factor = cs~load_uint(32); + cs.end_parse(); + if (total_stake < min_total_stake) { + ;; insufficient total stake, postpone elections + return postpone_elections(); + } + if (failed) { + ;; do not retry failed elections until new stakes arrive + return postpone_elections(); + } + if (finished) { + ;; elections finished + return false; + } + (credits, var vdict, var total_weight, var frozen, var total_stakes, var cnt) = try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor); + ;; pack elections; if cnt==0, set failed=true, finished=false. + failed = (cnt == 0); + finished = ~ failed; + elect = pack_elect(elect_at, elect_close, min_stake, total_stake, members, failed, finished); + ifnot (cnt) { + ;; elections failed, set elect_failed to true + set_data(begin_cell().store_dict(elect).store_dict(credits).store_slice(ds).end_cell()); + return postpone_elections(); + } + ;; serialize a query to the configuration smart contract + ;; to install the computed validator set as the next validator set + var (elect_for, elect_begin_before, elect_end_before, stake_held) = get_validator_conf(); + var start = max(now() + elect_end_before - 60, elect_at); + var main_validators = config_param(16).begin_parse().skip_bits(16).preload_uint(16); + var vset = begin_cell() + .store_uint(0x12, 8) ;; validators_ext#12 + .store_uint(start, 32) ;; utime_since:uint32 + .store_uint(start + elect_for, 32) ;; utime_until:uint32 + .store_uint(cnt, 16) ;; total:(## 16) + .store_uint(min(cnt, main_validators), 16) ;; main:(## 16) + .store_uint(total_weight, 64) ;; total_weight:uint64 + .store_dict(vdict) ;; list:(HashmapE 16 ValidatorDescr) + .end_cell(); + var config_addr = config_param(0).begin_parse().preload_uint(256); + send_validator_set_to_config(config_addr, vset, elect_at); + ;; add frozen to the dictionary of past elections + var past_elections = ds~load_dict(); + past_elections~udict_set_builder(32, elect_at, pack_past_election( + start + elect_for + stake_held, stake_held, vset.cell_hash(), + frozen, total_stakes, 0, null())); + ;; store credits and frozen until end + set_data(begin_cell() + .store_dict(elect) + .store_dict(credits) + .store_dict(past_elections) + .store_slice(ds) + .end_cell()); + return true; +} + +int update_active_vset_id() impure { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var cur_hash = config_param(34).cell_hash(); + if (cur_hash == active_hash) { + ;; validator set unchanged + return false; + } + if (active_id) { + ;; active_id becomes inactive + var (fs, f) = past_elections.udict_get?(32, active_id); + if (f) { + ;; adjust unfreeze time of this validator set + var unfreeze_time = fs~load_uint(32); + var fs0 = fs; + var (stake_held, hash) = (fs~load_uint(32), fs~load_uint(256)); + throw_unless(57, hash == active_hash); + unfreeze_time = now() + stake_held; + past_elections~udict_set_builder(32, active_id, begin_cell() + .store_uint(unfreeze_time, 32) + .store_slice(fs0)); + } + } + ;; look up new active_id by hash + var id = -1; + do { + (id, var fs, var f) = past_elections.udict_get_next?(32, id); + if (f) { + var (tm, hash) = (fs~load_uint(64), fs~load_uint(256)); + if (hash == cur_hash) { + ;; parse more of this record + var (dict, total_stake, bonuses) = (fs~load_dict(), fs~load_grams(), fs~load_grams()); + ;; transfer 1/8 of accumulated everybody's grams to this validator set as bonuses + var amount = (grams >> 3); + grams -= amount; + bonuses += amount; + ;; serialize back + past_elections~udict_set_builder(32, id, begin_cell() + .store_uint(tm, 64) + .store_uint(hash, 256) + .store_dict(dict) + .store_grams(total_stake) + .store_grams(bonuses) + .store_slice(fs)); + ;; found + f = false; + } + } + } until (~ f); + active_id = (id.null?() ? 0 : id); + active_hash = cur_hash; + store_data(elect, credits, past_elections, grams, active_id, active_hash); + return true; +} + +int cell_hash_eq?(cell vset, int expected_vset_hash) inline_ref { + return vset.null?() ? false : cell_hash(vset) == expected_vset_hash; +} + +int validator_set_installed(ds, elect, credits) impure { + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + ifnot (finished) { + ;; elections not finished yet + return false; + } + var past_elections = ds~load_dict(); + var (fs, f) = past_elections.udict_get?(32, elect_at); + ifnot (f) { + ;; no election data in dictionary + return false; + } + ;; recover validator set hash + var vset_hash = fs.skip_bits(64).preload_uint(256); + if (config_param(34).cell_hash_eq?(vset_hash) | config_param(36).cell_hash_eq?(vset_hash)) { + ;; this validator set has been installed, forget elections + set_data(begin_cell() + .store_int(false, 1) ;; forget current elections + .store_dict(credits) + .store_dict(past_elections) + .store_slice(ds) + .end_cell()); + update_active_vset_id(); + return true; + } + return false; +} + +int check_unfreeze() impure { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + int id = -1; + do { + (id, var fs, var f) = past_elections.udict_get_next?(32, id); + if (f) { + var unfreeze_at = fs~load_uint(32); + if ((unfreeze_at <= now()) & (id != active_id)) { + ;; unfreeze! + (credits, past_elections, var unused_prizes) = unfreeze_all(credits, past_elections, id); + grams += unused_prizes; + ;; unfreeze only one at time, exit loop + store_data(elect, credits, past_elections, grams, active_id, active_hash); + ;; exit loop + f = false; + } + } + } until (~ f); + return ~ id.null?(); +} + +int announce_new_elections(ds, elect, credits) { + var next_vset = config_param(36); ;; next validator set + ifnot (next_vset.null?()) { + ;; next validator set exists, no elections needed + return false; + } + var elector_addr = config_param(1).begin_parse().preload_uint(256); + var (my_wc, my_addr) = my_address().parse_std_addr(); + if ((my_wc + 1) | (my_addr != elector_addr)) { + ;; this smart contract is not the elections smart contract anymore, no new elections + return false; + } + var cur_vset = config_param(34); ;; current validator set + if (cur_vset.null?()) { + return false; + } + var (elect_for, elect_begin_before, elect_end_before, stake_held) = get_validator_conf(); + var cur_valid_until = cur_vset.begin_parse().skip_bits(8 + 32).preload_uint(32); + var t = now(); + var t0 = cur_valid_until - elect_begin_before; + if (t < t0) { + ;; too early for the next elections + return false; + } + ;; less than elect_before_begin seconds left, create new elections + if (t - t0 < 60) { + ;; pretend that the elections started at t0 + t = t0; + } + ;; get stake parameters + (_, var min_stake) = config_param(17).begin_parse().load_grams(); + ;; announce new elections + var elect_at = t + elect_begin_before; + ;; elect_at~dump(); + var elect_close = elect_at - elect_end_before; + elect = pack_elect(elect_at, elect_close, min_stake, 0, new_dict(), false, false); + set_data(begin_cell().store_dict(elect).store_dict(credits).store_slice(ds).end_cell()); + return true; +} + +() run_ticktock(int is_tock) impure { + ;; check whether an election is being conducted + var ds = get_data().begin_parse(); + var (elect, credits) = (ds~load_dict(), ds~load_dict()); + ifnot (elect.null?()) { + ;; have an active election + throw_if(0, conduct_elections(ds, elect, credits)); ;; elections conducted, exit + throw_if(0, validator_set_installed(ds, elect, credits)); ;; validator set installed, current elections removed + } else { + throw_if(0, announce_new_elections(ds, elect, credits)); ;; new elections announced, exit + } + throw_if(0, update_active_vset_id()); ;; active validator set id updated, exit + check_unfreeze(); +} + +;; Get methods + +;; returns active election id or 0 +int active_election_id() method_id { + var elect = get_data().begin_parse().preload_dict(); + return elect.null?() ? 0 : elect.begin_parse().preload_uint(32); +} + +;; checks whether a public key participates in current elections +int participates_in(int validator_pubkey) method_id { + var elect = get_data().begin_parse().preload_dict(); + if (elect.null?()) { + return 0; + } + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + var (mem, found) = members.udict_get?(256, validator_pubkey); + return found ? mem~load_grams() : 0; +} + +;; returns the list of all participants of current elections with their stakes +_ participant_list() method_id { + var elect = get_data().begin_parse().preload_dict(); + if (elect.null?()) { + return nil; + } + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + var l = nil; + var id = (1 << 255) + ((1 << 255) - 1); + do { + (id, var fs, var f) = members.udict_get_prev?(256, id); + if (f) { + l = cons([id, fs~load_grams()], l); + } + } until (~ f); + return l; +} + +;; returns the list of all participants of current elections with their data +_ participant_list_extended() method_id { + var elect = get_data().begin_parse().preload_dict(); + if (elect.null?()) { + return (0, 0, 0, 0, nil, 0, 0); + } + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + var l = nil; + var id = (1 << 255) + ((1 << 255) - 1); + do { + (id, var cs, var f) = members.udict_get_prev?(256, id); + if (f) { + var (stake, time, max_factor, addr, adnl_addr) = (cs~load_grams(), cs~load_uint(32), cs~load_uint(32), cs~load_uint(256), cs~load_uint(256)); + cs.end_parse(); + l = cons([id, [stake, max_factor, addr, adnl_addr]], l); + } + } until (~ f); + return (elect_at, elect_close, min_stake, total_stake, l, failed, finished); +} + +;; computes the return stake +int compute_returned_stake(int wallet_addr) method_id { + var cs = get_data().begin_parse(); + (_, var credits) = (cs~load_dict(), cs~load_dict()); + var (val, f) = credits.udict_get?(256, wallet_addr); + return f ? val~load_grams() : 0; +} + +;; returns the list of past election ids +tuple past_election_ids() method_id { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var id = (1 << 32); + var list = null(); + do { + (id, var fs, var f) = past_elections.udict_get_prev?(32, id); + if (f) { + list = cons(id, list); + } + } until (~ f); + return list; +} + +tuple past_elections() method_id { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var id = (1 << 32); + var list = null(); + do { + (id, var fs, var found) = past_elections.udict_get_prev?(32, id); + if (found) { + list = cons([id, unpack_past_election(fs)], list); + } + } until (~ found); + return list; +} + +tuple past_elections_list() method_id { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var id = (1 << 32); + var list = null(); + do { + (id, var fs, var found) = past_elections.udict_get_prev?(32, id); + if (found) { + var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs); + list = cons([id, unfreeze_at, vset_hash, stake_held], list); + } + } until (~ found); + return list; +} + +_ complete_unpack_complaint(slice cs) inline_ref { + var (complaint, voters, vset_id, weight_remaining) = cs.unpack_complaint_status(); + var voters_list = null(); + var voter_id = (1 << 32); + do { + (voter_id, _, var f) = voters.udict_get_prev?(16, voter_id); + if (f) { + voters_list = cons(voter_id, voters_list); + } + } until (~ f); + return [[complaint.begin_parse().unpack_complaint()], voters_list, vset_id, weight_remaining]; +} + +cell get_past_complaints(int election_id) inline_ref method_id { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var (fs, found?) = past_elections.udict_get?(32, election_id); + ifnot (found?) { + return null(); + } + var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs); + return complaints; +} + +_ show_complaint(int election_id, int chash) method_id { + var complaints = get_past_complaints(election_id); + var (cs, found) = complaints.udict_get?(256, chash); + return found ? complete_unpack_complaint(cs) : null(); +} + +tuple list_complaints(int election_id) method_id { + var complaints = get_past_complaints(election_id); + int id = (1 << 255) + ((1 << 255) - 1); + var list = null(); + do { + (id, var cs, var found?) = complaints.udict_get_prev?(256, id); + if (found?) { + list = cons(pair(id, complete_unpack_complaint(cs)), list); + } + } until (~ found?); + return list; +} + +int complaint_storage_price(int bits, int refs, int expire_in) method_id { + ;; compute complaint storage/creation price + var (deposit, bit_price, cell_price) = get_complaint_prices(); + var pps = (bits + 1024) * bit_price + (refs + 2) * cell_price; + var paid = pps * expire_in + deposit; + return paid + (1 << 30); +} diff --git a/crypto/func/auto-tests/legacy_tests/elector/stdlib.fc b/crypto/func/auto-tests/legacy_tests/elector/stdlib.fc new file mode 100644 index 00000000..0b98eeb4 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/elector/stdlib.fc @@ -0,0 +1,208 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; diff --git a/crypto/func/auto-tests/legacy_tests/eth-bridge-multisig/multisig-code.fc b/crypto/func/auto-tests/legacy_tests/eth-bridge-multisig/multisig-code.fc new file mode 100644 index 00000000..0bfdfb72 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/eth-bridge-multisig/multisig-code.fc @@ -0,0 +1,382 @@ +;; Simple wallet smart contract + +#include "stdlib.fc"; + +(int, int) get_bridge_config() impure inline_ref { + cell bridge_config = config_param(71); + if (bridge_config.cell_null?()) { + bridge_config = config_param(-71); + } + if (bridge_config.cell_null?()) { + return (0, 0); + } + slice ds = bridge_config.begin_parse(); + if (ds.slice_bits() < 512) { + return (0, 0); + } + ;; wc always equals to -1 + int bridge_address = ds~load_uint(256); + int oracles_address = ds~load_uint(256); + return (bridge_address, oracles_address); +} + +_ unpack_state() inline_ref { + var ds = begin_parse(get_data()); + var res = (ds~load_uint(32), ds~load_uint(8), ds~load_uint(8), ds~load_uint(64), ds~load_dict(), ds~load_dict(), ds~load_uint(32)); + ds.end_parse(); + return res; +} + +_ pack_state(cell pending_queries, cell owner_infos, int last_cleaned, int k, int n, int wallet_id, int spend_delay) inline_ref { + return begin_cell() + .store_uint(wallet_id, 32) + .store_uint(n, 8) + .store_uint(k, 8) + .store_uint(last_cleaned, 64) + .store_dict(owner_infos) + .store_dict(pending_queries) + .store_uint(spend_delay,32) + .end_cell(); +} + +_ pack_owner_info(int public_key, int flood) inline_ref { + return begin_cell() + .store_uint(public_key, 256) + .store_uint(flood, 8); +} + +_ unpack_owner_info(slice cs) inline_ref { + return (cs~load_uint(256), cs~load_uint(8)); +} + +(int, int) check_signatures(cell public_keys, cell signatures, int hash, int cnt_bits) inline_ref { + int cnt = 0; + + do { + slice cs = signatures.begin_parse(); + slice signature = cs~load_bits(512); + + int i = cs~load_uint(8); + signatures = cs~load_dict(); + + (slice public_key, var found?) = public_keys.udict_get?(8, i); + throw_unless(37, found?); + throw_unless(38, check_signature(hash, signature, public_key.preload_uint(256))); + + int mask = (1 << i); + int old_cnt_bits = cnt_bits; + cnt_bits |= mask; + int should_check = cnt_bits != old_cnt_bits; + cnt -= should_check; + } until (cell_null?(signatures)); + + return (cnt, cnt_bits); +} + +() recv_internal(slice in_msg) impure { + ;; do nothing for internal messages +} + +(int, cell, int, int) parse_msg(slice in_msg) inline_ref { + int mode = in_msg~load_uint(8); + var msg = in_msg~load_ref(); + var msg' = msg.begin_parse(); + msg'~load_uint(4); ;; flags + msg'~load_msg_addr(); ;; src + (int wc, int addr) = parse_std_addr(msg'~load_msg_addr()); ;; dest + return (mode, msg, wc, addr); +} + +() check_proposed_query(slice in_msg) impure inline { + throw_unless(43, (slice_refs(in_msg) == 1) & (slice_bits(in_msg) == 8)); + (_, _, int wc, _) = parse_msg(in_msg); + wc~impure_touch(); +} + +(int, int, int, slice) unpack_query_data(slice in_msg, int n, slice query, var found?, int root_i) inline_ref { + if (found?) { + throw_unless(35, query~load_int(1)); + (int creator_i, int cnt, int cnt_bits, slice msg) = (query~load_uint(8), query~load_uint(8), query~load_uint(n), query); + throw_unless(36, slice_hash(msg) == slice_hash(in_msg)); + return (creator_i, cnt, cnt_bits, msg); + } + check_proposed_query(in_msg); + + return (root_i, 0, 0, in_msg); +} + +(cell, ()) dec_flood(cell owner_infos, int creator_i) { + (slice owner_info, var found?) = owner_infos.udict_get?(8, creator_i); + (int public_key, int flood) = unpack_owner_info(owner_info); + owner_infos~udict_set_builder(8, creator_i, pack_owner_info(public_key, flood - 1)); + return (owner_infos, ()); +} + +() try_init() impure inline_ref { + ;; first query without signatures is always accepted + (int wallet_id, int n, int k, int last_cleaned, cell owner_infos, cell pending_queries, int spend_delay) = unpack_state(); + throw_if(37, last_cleaned); + accept_message(); + set_data(pack_state(pending_queries, owner_infos, 1, k, n, wallet_id, spend_delay)); +} + +(cell, cell) update_pending_queries(cell pending_queries, cell owner_infos, slice msg, int query_id, int creator_i, int cnt, int cnt_bits, int n, int k) impure inline_ref { + if (cnt >= k) { + accept_message(); + (int bridge_address, int oracles_address) = get_bridge_config(); + (_, int my_addr) = parse_std_addr(my_address()); + var (mode, msg', wc, addr) = parse_msg(msg); + if ( ((wc == -1) & (addr == bridge_address)) | (oracles_address != my_addr) ) { + send_raw_message(msg', mode); + } + pending_queries~udict_set_builder(64, query_id, begin_cell().store_int(0, 1)); + owner_infos~dec_flood(creator_i); + } else { + pending_queries~udict_set_builder(64, query_id, begin_cell() + .store_uint(1, 1) + .store_uint(creator_i, 8) + .store_uint(cnt, 8) + .store_uint(cnt_bits, n) + .store_slice(msg)); + } + return (pending_queries, owner_infos); +} + +(int, int) calc_boc_size(int cells, int bits, slice root) { + cells += 1; + bits += root.slice_bits(); + + while (root.slice_refs()) { + (cells, bits) = calc_boc_size(cells, bits, root~load_ref().begin_parse()); + } + + return (cells, bits); +} + +() recv_external(slice in_msg) impure { + ;; empty message triggers init + if (slice_empty?(in_msg)) { + return try_init(); + } + + ;; Check root signature + slice root_signature = in_msg~load_bits(512); + int root_hash = slice_hash(in_msg); + int root_i = in_msg~load_uint(8); + + (int wallet_id, int n, int k, int last_cleaned, cell owner_infos, cell pending_queries, int spend_delay) = unpack_state(); + + throw_unless(38, now() > spend_delay); + last_cleaned -= last_cleaned == 0; + + (slice owner_info, var found?) = owner_infos.udict_get?(8, root_i); + throw_unless(31, found?); + (int public_key, int flood) = unpack_owner_info(owner_info); + throw_unless(32, check_signature(root_hash, root_signature, public_key)); + + cell signatures = in_msg~load_dict(); + + var hash = slice_hash(in_msg); + int query_wallet_id = in_msg~load_uint(32); + throw_unless(42, query_wallet_id == wallet_id); + + int query_id = in_msg~load_uint(64); + + (int cnt, int bits) = calc_boc_size(0, 0, in_msg); + throw_if(40, (cnt > 8) | (bits > 2048)); + + (slice query, var found?) = pending_queries.udict_get?(64, query_id); + + ifnot (found?) { + flood += 1; + throw_if(39, flood > 10); + } + + var bound = (now() << 32); + throw_if(33, query_id < bound); + + (int creator_i, int cnt, int cnt_bits, slice msg) = unpack_query_data(in_msg, n, query, found?, root_i); + int mask = 1 << root_i; + throw_if(34, cnt_bits & mask); + cnt_bits |= mask; + cnt += 1; + + throw_if(41, ~ found? & (cnt < k) & (bound + ((60 * 60) << 32) > query_id)); + + set_gas_limit(100000); + + ifnot (found?) { + owner_infos~udict_set_builder(8, root_i, pack_owner_info(public_key, flood)); + } + + (pending_queries, owner_infos) = update_pending_queries(pending_queries, owner_infos, msg, query_id, creator_i, cnt, cnt_bits, n, k); + set_data(pack_state(pending_queries, owner_infos, last_cleaned, k, n, wallet_id, spend_delay)); + + commit(); + + int need_save = 0; + ifnot (cell_null?(signatures) | (cnt >= k)) { + (int new_cnt, cnt_bits) = check_signatures(owner_infos, signatures, hash, cnt_bits); + cnt += new_cnt; + (pending_queries, owner_infos) = update_pending_queries(pending_queries, owner_infos, msg, query_id, creator_i, cnt, cnt_bits, n, k); + need_save = -1; + } + + accept_message(); + bound -= (64 << 32); ;; clean up records expired more than 64 seconds ago + int old_last_cleaned = last_cleaned; + do { + var (pending_queries', i, query, f) = pending_queries.udict_delete_get_min(64); + f~touch(); + if (f) { + f = (i < bound); + } + if (f) { + if (query~load_int(1)) { + owner_infos~dec_flood(query~load_uint(8)); + } + pending_queries = pending_queries'; + last_cleaned = i; + need_save = -1; + } + } until (~ f); + + if (need_save) { + set_data(pack_state(pending_queries, owner_infos, last_cleaned, k, n, wallet_id, spend_delay)); + } +} + +;; Get methods +;; returns -1 for processed queries, 0 for unprocessed, 1 for unknown (forgotten) +(int, int) get_query_state(int query_id) method_id { + (_, int n, _, int last_cleaned, _, cell pending_queries, _) = unpack_state(); + (slice cs, var found) = pending_queries.udict_get?(64, query_id); + if (found) { + if (cs~load_int(1)) { + cs~load_uint(8 + 8); + return (0, cs~load_uint(n)); + } else { + return (-1, 0); + } + } else { + return (-(query_id <= last_cleaned), 0); + } +} + +int processed?(int query_id) method_id { + (int x, _) = get_query_state(query_id); + return x; +} + +cell create_init_state(int wallet_id, int n, int k, cell owners_info, int spend_delay) method_id { + return pack_state(new_dict(), owners_info, 0, k, n, wallet_id, spend_delay); +} + +cell merge_list(cell a, cell b) { + if (cell_null?(a)) { + return b; + } + if (cell_null?(b)) { + return a; + } + slice as = a.begin_parse(); + if (as.slice_refs() != 0) { + cell tail = merge_list(as~load_ref(), b); + return begin_cell().store_slice(as).store_ref(tail).end_cell(); + } + + as~skip_last_bits(1); + ;; as~skip_bits(1); + return begin_cell().store_slice(as).store_dict(b).end_cell(); + +} + +cell get_public_keys() method_id { + (_, _, _, _, cell public_keys, _, _) = unpack_state(); + return public_keys; +} + +(int, int) check_query_signatures(cell query) method_id { + slice cs = query.begin_parse(); + slice root_signature = cs~load_bits(512); + int root_hash = slice_hash(cs); + int root_i = cs~load_uint(8); + + cell public_keys = get_public_keys(); + (slice public_key, var found?) = public_keys.udict_get?(8, root_i); + throw_unless(31, found?); + throw_unless(32, check_signature(root_hash, root_signature, public_key.preload_uint(256))); + + int mask = 1 << root_i; + + cell signatures = cs~load_dict(); + if (cell_null?(signatures)) { + return (1, mask); + } + (int cnt, mask) = check_signatures(public_keys, signatures, slice_hash(cs), mask); + return (cnt + 1, mask); +} + +int message_signed_by_id?(int id, int query_id) method_id { + (_, int n, _, _, _, cell pending_queries, _) = unpack_state(); + (var cs, var f) = pending_queries.udict_get?(64, query_id); + if (f) { + if (cs~load_int(1)) { + int cnt_bits = cs.skip_bits(8 + 8).preload_uint(n); + if (cnt_bits & (1 << id)) { + return -1; + } + return 0; + } + return -1; + } + return 0; +} + +cell messages_by_mask(int mask) method_id { + (_, int n, _, _, _, cell pending_queries, _) = unpack_state(); + int i = -1; + cell a = new_dict(); + do { + (i, var cs, var f) = pending_queries.udict_get_next?(64, i); + if (f) { + if (cs~load_int(1)) { + int cnt_bits = cs.skip_bits(8 + 8).preload_uint(n); + if (cnt_bits & mask) { + a~udict_set_builder(64, i, begin_cell().store_slice(cs)); + } + } + } + } until (~ f); + return a; +} + +cell get_messages_unsigned_by_id(int id) method_id { + return messages_by_mask(1 << id); +} + +cell get_messages_unsigned() method_id { + return messages_by_mask(~ 0); +} + +(int, int) get_n_k() method_id { + (_, int n, int k, _, _, _, _) = unpack_state(); + return (n, k); +} + +cell merge_inner_queries(cell a, cell b) method_id { + slice ca = a.begin_parse(); + slice cb = b.begin_parse(); + cell list_a = ca~load_dict(); + cell list_b = cb~load_dict(); + throw_unless(31, slice_hash(ca) == slice_hash(cb)); + return begin_cell() + .store_dict(merge_list(list_a, list_b)) + .store_slice(ca) + .end_cell(); +} + +int get_lock_timeout() method_id { + (_, _, _, _, _, _, int spend_delay) = unpack_state(); + return spend_delay; +} diff --git a/crypto/func/auto-tests/legacy_tests/eth-bridge-multisig/stdlib.fc b/crypto/func/auto-tests/legacy_tests/eth-bridge-multisig/stdlib.fc new file mode 100644 index 00000000..1d144040 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/eth-bridge-multisig/stdlib.fc @@ -0,0 +1,209 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + diff --git a/crypto/func/auto-tests/legacy_tests/gg-marketplace/nft-marketplace-v2.fc b/crypto/func/auto-tests/legacy_tests/gg-marketplace/nft-marketplace-v2.fc new file mode 100644 index 00000000..58444723 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/gg-marketplace/nft-marketplace-v2.fc @@ -0,0 +1,110 @@ +;; NFT marketplace smart contract v2 +;; Extends wallet v3r2 & adds ability to deploy sales + +#include "stdlib.fc"; + +;; +;; storage scheme +;; +;; storage#_ seqno:uint32 subwallet:uint32 public_key:uint25 +;; = Storage; +;; +_ load_data() { + var ds = get_data().begin_parse(); + return ( + ds~load_uint(32), ;; seqno + ds~load_uint(32), ;; subwallet + ds~load_uint(256) ;; public_key + ); +} + +() store_data(var data) impure { + ( + int seqno, + int subwallet, + int public_key + ) = data; + + set_data( + begin_cell() + .store_uint(seqno, 32) + .store_uint(subwallet, 32) + .store_uint(public_key, 256) + .end_cell() + ); +} + +() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { + if (in_msg_body.slice_empty?()) { ;; ignore empty messages + return (); + } + + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + + if (flags & 1) { ;; ignore all bounced messages + return (); + } + slice sender_address = cs~load_msg_addr(); + var (seqno, subwallet, public_key) = load_data(); + + int op = in_msg_body~load_uint(32); + + if (op == 1) { ;; deploy new signed sale + var signature = in_msg_body~load_bits(512); + throw_unless(35, check_signature(slice_hash(in_msg_body), signature, public_key)); + + (cell state_init, cell body) = (in_msg_body~load_ref(), in_msg_body~load_ref()); + + int state_init_hash = cell_hash(state_init); + slice dest_address = begin_cell().store_int(0, 8).store_uint(state_init_hash, 256).end_cell().begin_parse(); + + var msg = begin_cell() + .store_uint(0x18, 6) + .store_uint(4, 3).store_slice(dest_address) + .store_grams(0) + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init) + .store_ref(body); + + send_raw_message(msg.end_cell(), 64); ;; carry remaining value of message + return (); + } + + return (); +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); + throw_if(35, valid_until <= now()); + var (seqno, subwallet, public_key) = load_data(); + throw_unless(33, msg_seqno == seqno); + throw_unless(34, subwallet_id == subwallet); + throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key)); + accept_message(); + cs~touch(); + while (cs.slice_refs()) { + var mode = cs~load_uint(8); + send_raw_message(cs~load_ref(), mode); + } + + store_data( + seqno + 1, + subwallet, + public_key + ); +} + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +int get_public_key() method_id { + var cs = get_data().begin_parse(); + cs~load_uint(64); + return cs.preload_uint(256); +} diff --git a/crypto/func/auto-tests/legacy_tests/gg-marketplace/stdlib.fc b/crypto/func/auto-tests/legacy_tests/gg-marketplace/stdlib.fc new file mode 100644 index 00000000..ba9177e9 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/gg-marketplace/stdlib.fc @@ -0,0 +1,215 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; + builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +builder store_coins(builder b, int x) asm "STVARUINT16"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDVARUINT16"; + +int equal_slices (slice a, slice b) asm "SDEQ"; +int builder_null?(builder b) asm "ISNULL"; +builder store_builder(builder to, builder from) asm "STBR"; \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/constants.fc b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/constants.fc new file mode 100644 index 00000000..c260f95e --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/constants.fc @@ -0,0 +1,13 @@ +;; operations (constant values taken from crc32 on op message in the companion .tlb files and appear during build) +int op::increment() asm "0x37491f2f PUSHINT"; +int op::deposit() asm "0x47d54391 PUSHINT"; +int op::withdraw() asm "0x41836980 PUSHINT"; +int op::transfer_ownership() asm "0x2da38aaf PUSHINT"; + +;; errors +int error::unknown_op() asm "101 PUSHINT"; +int error::access_denied() asm "102 PUSHINT"; +int error::insufficient_balance() asm "103 PUSHINT"; + +;; other +int const::min_tons_for_storage() asm "10000000 PUSHINT"; ;; 0.01 TON \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/jetton-utils.fc b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/jetton-utils.fc new file mode 100644 index 00000000..296816a1 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/jetton-utils.fc @@ -0,0 +1,30 @@ +cell pack_jetton_wallet_data(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { + return begin_cell() + .store_coins(balance) + .store_slice(owner_address) + .store_slice(jetton_master_address) + .store_ref(jetton_wallet_code) + .end_cell(); +} + +cell calculate_jetton_wallet_state_init(slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { + return begin_cell() + .store_uint(0, 2) + .store_dict(jetton_wallet_code) + .store_dict(pack_jetton_wallet_data(0, owner_address, jetton_master_address, jetton_wallet_code)) + .store_uint(0, 1) + .end_cell(); +} + +slice calculate_jetton_wallet_address(cell state_init) inline { + return begin_cell().store_uint(4, 3) + .store_int(workchain(), 8) + .store_uint(cell_hash(state_init), 256) + .end_cell() + .begin_parse(); +} + +slice calculate_user_jetton_wallet_address(slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { + return calculate_jetton_wallet_address(calculate_jetton_wallet_state_init(owner_address, jetton_master_address, jetton_wallet_code)); +} + diff --git a/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/op-codes.fc b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/op-codes.fc new file mode 100644 index 00000000..3b0df046 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/op-codes.fc @@ -0,0 +1,9 @@ +int op::transfer() asm "0xf8a7ea5 PUSHINT"; +int op::transfer_notification() asm "0x7362d09c PUSHINT"; +int op::internal_transfer() asm "0x178d4519 PUSHINT"; +int op::excesses() asm "0xd53276db PUSHINT"; +int op::burn() asm "0x595f07bc PUSHINT"; +int op::burn_notification() asm "0x7bdd97de PUSHINT"; + +;; Minter +int op::mint() asm "21 PUSHINT"; diff --git a/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/params.fc b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/params.fc new file mode 100644 index 00000000..e28eac45 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/params.fc @@ -0,0 +1,6 @@ +int workchain() asm "0 PUSHINT"; + +() force_chain(slice addr) impure { + (int wc, _) = parse_std_addr(addr); + throw_unless(333, wc == workchain()); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/stdlib.fc b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/stdlib.fc new file mode 100644 index 00000000..05c3a4c0 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/stdlib.fc @@ -0,0 +1,215 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +builder store_coins(builder b, int x) asm "STVARUINT16"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDVARUINT16"; + +int equal_slices (slice a, slice b) asm "SDEQ"; +int builder_null?(builder b) asm "ISNULL"; +builder store_builder(builder to, builder from) asm "STBR"; diff --git a/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/utils.fc b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/utils.fc new file mode 100644 index 00000000..230a8f79 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/utils.fc @@ -0,0 +1,9 @@ +() send_grams(slice address, int amount) impure { + cell msg = begin_cell() + .store_uint (0x18, 6) ;; bounce + .store_slice(address) ;; 267 bit address + .store_grams(amount) + .store_uint(0, 107) ;; 106 zeroes + 0 as an indicator that there is no cell with the data + .end_cell(); + send_raw_message(msg, 3); ;; mode, 2 for ignoring errors, 1 for sender pays fees, 64 for returning inbound message value +} diff --git a/crypto/func/auto-tests/legacy_tests/jetton-minter/jetton-minter.fc b/crypto/func/auto-tests/legacy_tests/jetton-minter/jetton-minter.fc new file mode 100644 index 00000000..73776e64 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-minter/jetton-minter.fc @@ -0,0 +1,122 @@ +;; Jettons minter smart contract + +;; storage scheme +;; storage#_ total_supply:Coins admin_address:MsgAddress content:^Cell jetton_wallet_code:^Cell = Storage; + +#include "imports/stdlib.fc"; +#include "imports/params.fc"; +#include "imports/constants.fc"; +#include "imports/jetton-utils.fc"; +#include "imports/op-codes.fc"; +#include "imports/utils.fc"; +#pragma version >=0.2.0; + +(int, slice, cell, cell) load_data() inline { + slice ds = get_data().begin_parse(); + return ( + ds~load_coins(), ;; total_supply + ds~load_msg_addr(), ;; admin_address + ds~load_ref(), ;; content + ds~load_ref() ;; jetton_wallet_code + ); +} + +() save_data(int total_supply, slice admin_address, cell content, cell jetton_wallet_code) impure inline { + set_data(begin_cell() + .store_coins(total_supply) + .store_slice(admin_address) + .store_ref(content) + .store_ref(jetton_wallet_code) + .end_cell() + ); +} + +() mint_tokens(slice to_address, cell jetton_wallet_code, int amount, cell master_msg) impure { + cell state_init = calculate_jetton_wallet_state_init(to_address, my_address(), jetton_wallet_code); + slice to_wallet_address = calculate_jetton_wallet_address(state_init); + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(to_wallet_address) + .store_coins(amount) + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init) + .store_ref(master_msg); + send_raw_message(msg.end_cell(), 1); ;; pay transfer fees separately, revert on errors +} + +() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure { + if (in_msg_body.slice_empty?()) { ;; ignore empty messages + return (); + } + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + + if (flags & 1) { ;; ignore all bounced messages + return (); + } + slice sender_address = cs~load_msg_addr(); + + int op = in_msg_body~load_uint(32); + int query_id = in_msg_body~load_uint(64); + + (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data(); + + if (op == op::mint()) { + throw_unless(73, equal_slices(sender_address, admin_address)); + slice to_address = in_msg_body~load_msg_addr(); + int amount = in_msg_body~load_coins(); + cell master_msg = in_msg_body~load_ref(); + slice master_msg_cs = master_msg.begin_parse(); + master_msg_cs~skip_bits(32 + 64); ;; op + query_id + int jetton_amount = master_msg_cs~load_coins(); + mint_tokens(to_address, jetton_wallet_code, amount, master_msg); + save_data(total_supply + jetton_amount, admin_address, content, jetton_wallet_code); + return (); + } + + if (op == op::burn_notification()) { + int jetton_amount = in_msg_body~load_coins(); + slice from_address = in_msg_body~load_msg_addr(); + throw_unless(74, + equal_slices(calculate_user_jetton_wallet_address(from_address, my_address(), jetton_wallet_code), sender_address) + ); + save_data(total_supply - jetton_amount, admin_address, content, jetton_wallet_code); + slice response_address = in_msg_body~load_msg_addr(); + if (response_address.preload_uint(2) != 0) { + var msg = begin_cell() + .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 + .store_slice(response_address) + .store_coins(0) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op::excesses(), 32) + .store_uint(query_id, 64); + send_raw_message(msg.end_cell(), 2 + 64); + } + return (); + } + + if (op == 3) { ;; change admin + throw_unless(73, equal_slices(sender_address, admin_address)); + slice new_admin_address = in_msg_body~load_msg_addr(); + save_data(total_supply, new_admin_address, content, jetton_wallet_code); + return (); + } + + if (op == 4) { ;; change content, delete this for immutable tokens + throw_unless(73, equal_slices(sender_address, admin_address)); + save_data(total_supply, admin_address, in_msg_body~load_ref(), jetton_wallet_code); + return (); + } + + throw(0xffff); +} + +(int, int, slice, cell, cell) get_jetton_data() method_id { + (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data(); + return (total_supply, -1, admin_address, content, jetton_wallet_code); +} + +slice get_wallet_address(slice owner_address) method_id { + (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data(); + return calculate_user_jetton_wallet_address(owner_address, my_address(), jetton_wallet_code); +} diff --git a/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/constants.fc b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/constants.fc new file mode 100644 index 00000000..02e239f7 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/constants.fc @@ -0,0 +1,17 @@ +;; operations (constant values taken from crc32 on op message in the companion .tlb files and appear during build) +int op::increment() asm "0x37491f2f PUSHINT"; +int op::deposit() asm "0x47d54391 PUSHINT"; +int op::withdraw() asm "0x41836980 PUSHINT"; +int op::transfer_ownership() asm "0x2da38aaf PUSHINT"; + +;; errors +int error::unknown_op() asm "101 PUSHINT"; +int error::access_denied() asm "102 PUSHINT"; +int error::insufficient_balance() asm "103 PUSHINT"; + +;; other +int const::min_tons_for_storage() asm "10000000 PUSHINT"; ;; 0.01 TON + +;; 6905(computational_gas_price) * 1000(cur_gas_price) = 6905000 +;; ceil(6905000) = 10000000 ~= 0.01 TON +int const::provide_address_gas_consumption() asm "10000000 PUSHINT"; \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/jetton-utils.fc b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/jetton-utils.fc new file mode 100644 index 00000000..296816a1 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/jetton-utils.fc @@ -0,0 +1,30 @@ +cell pack_jetton_wallet_data(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { + return begin_cell() + .store_coins(balance) + .store_slice(owner_address) + .store_slice(jetton_master_address) + .store_ref(jetton_wallet_code) + .end_cell(); +} + +cell calculate_jetton_wallet_state_init(slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { + return begin_cell() + .store_uint(0, 2) + .store_dict(jetton_wallet_code) + .store_dict(pack_jetton_wallet_data(0, owner_address, jetton_master_address, jetton_wallet_code)) + .store_uint(0, 1) + .end_cell(); +} + +slice calculate_jetton_wallet_address(cell state_init) inline { + return begin_cell().store_uint(4, 3) + .store_int(workchain(), 8) + .store_uint(cell_hash(state_init), 256) + .end_cell() + .begin_parse(); +} + +slice calculate_user_jetton_wallet_address(slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { + return calculate_jetton_wallet_address(calculate_jetton_wallet_state_init(owner_address, jetton_master_address, jetton_wallet_code)); +} + diff --git a/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/op-codes.fc b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/op-codes.fc new file mode 100644 index 00000000..3b0df046 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/op-codes.fc @@ -0,0 +1,9 @@ +int op::transfer() asm "0xf8a7ea5 PUSHINT"; +int op::transfer_notification() asm "0x7362d09c PUSHINT"; +int op::internal_transfer() asm "0x178d4519 PUSHINT"; +int op::excesses() asm "0xd53276db PUSHINT"; +int op::burn() asm "0x595f07bc PUSHINT"; +int op::burn_notification() asm "0x7bdd97de PUSHINT"; + +;; Minter +int op::mint() asm "21 PUSHINT"; diff --git a/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/params.fc b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/params.fc new file mode 100644 index 00000000..e28eac45 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/params.fc @@ -0,0 +1,6 @@ +int workchain() asm "0 PUSHINT"; + +() force_chain(slice addr) impure { + (int wc, _) = parse_std_addr(addr); + throw_unless(333, wc == workchain()); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/stdlib.fc b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/stdlib.fc new file mode 100644 index 00000000..05c3a4c0 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/stdlib.fc @@ -0,0 +1,215 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +builder store_coins(builder b, int x) asm "STVARUINT16"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDVARUINT16"; + +int equal_slices (slice a, slice b) asm "SDEQ"; +int builder_null?(builder b) asm "ISNULL"; +builder store_builder(builder to, builder from) asm "STBR"; diff --git a/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/utils.fc b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/utils.fc new file mode 100644 index 00000000..230a8f79 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/utils.fc @@ -0,0 +1,9 @@ +() send_grams(slice address, int amount) impure { + cell msg = begin_cell() + .store_uint (0x18, 6) ;; bounce + .store_slice(address) ;; 267 bit address + .store_grams(amount) + .store_uint(0, 107) ;; 106 zeroes + 0 as an indicator that there is no cell with the data + .end_cell(); + send_raw_message(msg, 3); ;; mode, 2 for ignoring errors, 1 for sender pays fees, 64 for returning inbound message value +} diff --git a/crypto/func/auto-tests/legacy_tests/jetton-wallet/jetton-wallet.fc b/crypto/func/auto-tests/legacy_tests/jetton-wallet/jetton-wallet.fc new file mode 100644 index 00000000..01cc91bc --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-wallet/jetton-wallet.fc @@ -0,0 +1,250 @@ +;; Jetton Wallet Smart Contract + +#include "imports/stdlib.fc"; +#include "imports/params.fc"; +#include "imports/constants.fc"; +#include "imports/jetton-utils.fc"; +#include "imports/op-codes.fc"; +#include "imports/utils.fc"; +#pragma version >=0.2.0; + +{- + +NOTE that this tokens can be transferred within the same workchain. + +This is suitable for most tokens, if you need tokens transferable between workchains there are two solutions: + +1) use more expensive but universal function to calculate message forward fee for arbitrary destination (see `misc/forward-fee-calc.cs`) + +2) use token holder proxies in target workchain (that way even 'non-universal' token can be used from any workchain) + +-} + +const min_tons_for_storage = 10000000; ;; 0.01 TON +const gas_consumption = 10000000; ;; 0.01 TON + +{- + Storage + storage#_ balance:Coins owner_address:MsgAddressInt jetton_master_address:MsgAddressInt jetton_wallet_code:^Cell = Storage; +-} + +(int, slice, slice, cell) load_data() inline { + slice ds = get_data().begin_parse(); + return (ds~load_coins(), ds~load_msg_addr(), ds~load_msg_addr(), ds~load_ref()); +} + +() save_data (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) impure inline { + set_data(pack_jetton_wallet_data(balance, owner_address, jetton_master_address, jetton_wallet_code)); +} + +{- + transfer query_id:uint64 amount:(VarUInteger 16) destination:MsgAddress + response_destination:MsgAddress custom_payload:(Maybe ^Cell) + forward_ton_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell) + = InternalMsgBody; + internal_transfer query_id:uint64 amount:(VarUInteger 16) from:MsgAddress + response_address:MsgAddress + forward_ton_amount:(VarUInteger 16) + forward_payload:(Either Cell ^Cell) + = InternalMsgBody; +-} + +() send_tokens (slice in_msg_body, slice sender_address, int msg_value, int fwd_fee) impure { + int query_id = in_msg_body~load_uint(64); + int jetton_amount = in_msg_body~load_coins(); + slice to_owner_address = in_msg_body~load_msg_addr(); + force_chain(to_owner_address); + (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data(); + balance -= jetton_amount; + + throw_unless(705, equal_slices(owner_address, sender_address)); + throw_unless(706, balance >= 0); + + cell state_init = calculate_jetton_wallet_state_init(to_owner_address, jetton_master_address, jetton_wallet_code); + slice to_wallet_address = calculate_jetton_wallet_address(state_init); + slice response_address = in_msg_body~load_msg_addr(); + cell custom_payload = in_msg_body~load_dict(); + int forward_ton_amount = in_msg_body~load_coins(); + throw_unless(708, slice_bits(in_msg_body) >= 1); + slice either_forward_payload = in_msg_body; + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(to_wallet_address) + .store_coins(0) + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init); + var msg_body = begin_cell() + .store_uint(op::internal_transfer(), 32) + .store_uint(query_id, 64) + .store_coins(jetton_amount) + .store_slice(owner_address) + .store_slice(response_address) + .store_coins(forward_ton_amount) + .store_slice(either_forward_payload) + .end_cell(); + + msg = msg.store_ref(msg_body); + int fwd_count = forward_ton_amount ? 2 : 1; + throw_unless(709, msg_value > + forward_ton_amount + + ;; 3 messages: wal1->wal2, wal2->owner, wal2->response + ;; but last one is optional (it is ok if it fails) + fwd_count * fwd_fee + + (2 * gas_consumption + min_tons_for_storage)); ;; TODO(shahar) ? + ;; universal message send fee calculation may be activated here + ;; by using this instead of fwd_fee + ;; msg_fwd_fee(to_wallet, msg_body, state_init, 15) + + send_raw_message(msg.end_cell(), 64); ;; revert on errors + save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); +} + +{- + internal_transfer query_id:uint64 amount:(VarUInteger 16) from:MsgAddress + response_address:MsgAddress + forward_ton_amount:(VarUInteger 16) + forward_payload:(Either Cell ^Cell) + = InternalMsgBody; +-} + +() receive_tokens (slice in_msg_body, slice sender_address, int my_ton_balance, int fwd_fee, int msg_value) impure { + ;; NOTE we can not allow fails in action phase since in that case there will be + ;; no bounce. Thus check and throw in computation phase. + (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data(); + int query_id = in_msg_body~load_uint(64); + int jetton_amount = in_msg_body~load_coins(); + balance += jetton_amount; + slice from_address = in_msg_body~load_msg_addr(); + slice response_address = in_msg_body~load_msg_addr(); + throw_unless(707, + equal_slices(jetton_master_address, sender_address) + | + equal_slices(calculate_user_jetton_wallet_address(from_address, jetton_master_address, jetton_wallet_code), sender_address) + ); + int forward_ton_amount = in_msg_body~load_coins(); + + int ton_balance_before_msg = my_ton_balance - msg_value; + int storage_fee = min_tons_for_storage - min(ton_balance_before_msg, min_tons_for_storage); + msg_value -= (storage_fee + gas_consumption); + if(forward_ton_amount) { + msg_value -= (forward_ton_amount + fwd_fee); + slice either_forward_payload = in_msg_body; + + var msg_body = begin_cell() + .store_uint(op::transfer_notification(), 32) + .store_uint(query_id, 64) + .store_coins(jetton_amount) + .store_slice(from_address) + .store_slice(either_forward_payload) + .end_cell(); + + var msg = begin_cell() + .store_uint(0x10, 6) ;; we should not bounce here cause receiver can have uninitialized contract + .store_slice(owner_address) + .store_coins(forward_ton_amount) + .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_ref(msg_body); + + send_raw_message(msg.end_cell(), 1); + } + + if ((response_address.preload_uint(2) != 0) & (msg_value > 0)) { + var msg = begin_cell() + .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 010000 + .store_slice(response_address) + .store_coins(msg_value) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op::excesses(), 32) + .store_uint(query_id, 64); + send_raw_message(msg.end_cell(), 2); + } + + save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); +} + +() burn_tokens (slice in_msg_body, slice sender_address, int msg_value, int fwd_fee) impure { + ;; NOTE we can not allow fails in action phase since in that case there will be + ;; no bounce. Thus check and throw in computation phase. + (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data(); + int query_id = in_msg_body~load_uint(64); + int jetton_amount = in_msg_body~load_coins(); + slice response_address = in_msg_body~load_msg_addr(); + ;; ignore custom payload + ;; slice custom_payload = in_msg_body~load_dict(); + balance -= jetton_amount; + throw_unless(705, equal_slices(owner_address, sender_address)); + throw_unless(706, balance >= 0); + throw_unless(707, msg_value > fwd_fee + 2 * gas_consumption); + + var msg_body = begin_cell() + .store_uint(op::burn_notification(), 32) + .store_uint(query_id, 64) + .store_coins(jetton_amount) + .store_slice(owner_address) + .store_slice(response_address) + .end_cell(); + + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(jetton_master_address) + .store_coins(0) + .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_ref(msg_body); + + send_raw_message(msg.end_cell(), 64); + + save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); +} + +() on_bounce (slice in_msg_body) impure { + in_msg_body~skip_bits(32); ;; 0xFFFFFFFF + (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data(); + int op = in_msg_body~load_uint(32); + throw_unless(709, (op == op::internal_transfer()) | (op == op::burn_notification())); + int query_id = in_msg_body~load_uint(64); + int jetton_amount = in_msg_body~load_coins(); + balance += jetton_amount; + save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); +} + +() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { + if (in_msg_body.slice_empty?()) { ;; ignore empty messages + return (); + } + + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + if (flags & 1) { + on_bounce(in_msg_body); + return (); + } + slice sender_address = cs~load_msg_addr(); + cs~load_msg_addr(); ;; skip dst + cs~load_coins(); ;; skip value + cs~skip_bits(1); ;; skip extracurrency collection + cs~load_coins(); ;; skip ihr_fee + int fwd_fee = muldiv(cs~load_coins(), 3, 2); ;; we use message fwd_fee for estimation of forward_payload costs + + int op = in_msg_body~load_uint(32); + + if (op == op::transfer()) { ;; outgoing transfer + send_tokens(in_msg_body, sender_address, msg_value, fwd_fee); + return (); + } + + if (op == op::internal_transfer()) { ;; incoming transfer + receive_tokens(in_msg_body, sender_address, my_balance, fwd_fee, msg_value); + return (); + } + + if (op == op::burn()) { ;; burn + burn_tokens(in_msg_body, sender_address, msg_value, fwd_fee); + return (); + } + + throw(0xffff); +} + +(int, slice, slice, cell) get_wallet_data() method_id { + return load_data(); +} diff --git a/crypto/func/auto-tests/legacy_tests/nft-collection/nft-collection-editable.fc b/crypto/func/auto-tests/legacy_tests/nft-collection/nft-collection-editable.fc new file mode 100644 index 00000000..73d86b16 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/nft-collection/nft-collection-editable.fc @@ -0,0 +1,173 @@ +;; NFT collection smart contract + +;; storage scheme +;; default#_ royalty_factor:uint16 royalty_base:uint16 royalty_address:MsgAddress = RoyaltyParams; +;; storage#_ owner_address:MsgAddress next_item_index:uint64 +;; ^[collection_content:^Cell common_content:^Cell] +;; nft_item_code:^Cell +;; royalty_params:^RoyaltyParams +;; = Storage; + +#include "op-codes.fc"; +#include "stdlib.fc"; +#include "params.fc"; + +(slice, int, cell, cell, cell) load_data() inline { + var ds = get_data().begin_parse(); + return + (ds~load_msg_addr(), ;; owner_address + ds~load_uint(64), ;; next_item_index + ds~load_ref(), ;; content + ds~load_ref(), ;; nft_item_code + ds~load_ref() ;; royalty_params + ); +} + +() save_data(slice owner_address, int next_item_index, cell content, cell nft_item_code, cell royalty_params) impure inline { + set_data(begin_cell() + .store_slice(owner_address) + .store_uint(next_item_index, 64) + .store_ref(content) + .store_ref(nft_item_code) + .store_ref(royalty_params) + .end_cell()); +} + +cell calculate_nft_item_state_init(int item_index, cell nft_item_code) { + cell data = begin_cell().store_uint(item_index, 64).store_slice(my_address()).end_cell(); + return begin_cell().store_uint(0, 2).store_dict(nft_item_code).store_dict(data).store_uint(0, 1).end_cell(); +} + +slice calculate_nft_item_address(int wc, cell state_init) { + return begin_cell().store_uint(4, 3) + .store_int(wc, 8) + .store_uint(cell_hash(state_init), 256) + .end_cell() + .begin_parse(); +} + +() deploy_nft_item(int item_index, cell nft_item_code, int amount, cell nft_content) impure { + cell state_init = calculate_nft_item_state_init(item_index, nft_item_code); + slice nft_address = calculate_nft_item_address(workchain(), state_init); + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(nft_address) + .store_coins(amount) + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init) + .store_ref(nft_content); + send_raw_message(msg.end_cell(), 1); ;; pay transfer fees separately, revert on errors +} + +() send_royalty_params(slice to_address, int query_id, slice data) impure inline { + var msg = begin_cell() + .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool packages:MsgAddress -> 011000 + .store_slice(to_address) + .store_coins(0) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op::report_royalty_params(), 32) + .store_uint(query_id, 64) + .store_slice(data); + send_raw_message(msg.end_cell(), 64); ;; carry all the remaining value of the inbound message +} + +() recv_internal(cell in_msg_full, slice in_msg_body) impure { + if (in_msg_body.slice_empty?()) { ;; ignore empty messages + return (); + } + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + + if (flags & 1) { ;; ignore all bounced messages + return (); + } + slice sender_address = cs~load_msg_addr(); + + int op = in_msg_body~load_uint(32); + int query_id = in_msg_body~load_uint(64); + + var (owner_address, next_item_index, content, nft_item_code, royalty_params) = load_data(); + + if (op == op::get_royalty_params()) { + send_royalty_params(sender_address, query_id, royalty_params.begin_parse()); + return (); + } + + throw_unless(401, equal_slices(sender_address, owner_address)); + + + if (op == 1) { ;; deploy new nft + int item_index = in_msg_body~load_uint(64); + throw_unless(402, item_index <= next_item_index); + var is_last = item_index == next_item_index; + deploy_nft_item(item_index, nft_item_code, in_msg_body~load_coins(), in_msg_body~load_ref()); + if (is_last) { + next_item_index += 1; + save_data(owner_address, next_item_index, content, nft_item_code, royalty_params); + } + return (); + } + if (op == 2) { ;; batch deploy of new nfts + int counter = 0; + cell deploy_list = in_msg_body~load_ref(); + do { + var (item_index, item, f?) = deploy_list~udict::delete_get_min(64); + if (f?) { + counter += 1; + if (counter >= 250) { ;; Limit due to limits of action list size + throw(399); + } + + throw_unless(403 + counter, item_index <= next_item_index); + deploy_nft_item(item_index, nft_item_code, item~load_coins(), item~load_ref()); + if (item_index == next_item_index) { + next_item_index += 1; + } + } + } until ( ~ f?); + save_data(owner_address, next_item_index, content, nft_item_code, royalty_params); + return (); + } + if (op == 3) { ;; change owner + slice new_owner = in_msg_body~load_msg_addr(); + save_data(new_owner, next_item_index, content, nft_item_code, royalty_params); + return (); + } + if (op == 4) { ;; change content + save_data(owner_address, next_item_index, in_msg_body~load_ref(), nft_item_code, in_msg_body~load_ref()); + return (); + } + throw(0xffff); +} + +;; Get methods + +(int, cell, slice) get_collection_data() method_id { + var (owner_address, next_item_index, content, _, _) = load_data(); + slice cs = content.begin_parse(); + return (next_item_index, cs~load_ref(), owner_address); +} + +slice get_nft_address_by_index(int index) method_id { + var (_, _, _, nft_item_code, _) = load_data(); + cell state_init = calculate_nft_item_state_init(index, nft_item_code); + return calculate_nft_item_address(0, state_init); +} + +(int, int, slice) royalty_params() method_id { + var (_, _, _, _, royalty) = load_data(); + slice rs = royalty.begin_parse(); + return (rs~load_uint(16), rs~load_uint(16), rs~load_msg_addr()); +} + +cell get_nft_content(int index, cell individual_nft_content) method_id { + var (_, _, content, _, _) = load_data(); + slice cs = content.begin_parse(); + cs~load_ref(); + slice common_content = cs~load_ref().begin_parse(); + return (begin_cell() + .store_uint(1, 8) ;; offchain tag + .store_slice(common_content) + .store_ref(individual_nft_content) + .end_cell()); +} diff --git a/crypto/func/auto-tests/legacy_tests/nft-collection/op-codes.fc b/crypto/func/auto-tests/legacy_tests/nft-collection/op-codes.fc new file mode 100644 index 00000000..948a977b --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/nft-collection/op-codes.fc @@ -0,0 +1,24 @@ +int op::transfer() asm "0x5fcc3d14 PUSHINT"; +int op::ownership_assigned() asm "0x05138d91 PUSHINT"; +int op::excesses() asm "0xd53276db PUSHINT"; +int op::get_static_data() asm "0x2fcb26a2 PUSHINT"; +int op::report_static_data() asm "0x8b771735 PUSHINT"; +int op::get_royalty_params() asm "0x693d3950 PUSHINT"; +int op::report_royalty_params() asm "0xa8cb00ad PUSHINT"; + +;; NFTEditable +int op::edit_content() asm "0x1a0b9d51 PUSHINT"; +int op::transfer_editorship() asm "0x1c04412a PUSHINT"; +int op::editorship_assigned() asm "0x511a4463 PUSHINT"; + +;; SBT +int op::request_owner() asm "0xd0c3bfea PUSHINT"; +int op::owner_info() asm "0x0dd607e3 PUSHINT"; + +int op::prove_ownership() asm "0x04ded148 PUSHINT"; +int op::ownership_proof() asm "0x0524c7ae PUSHINT"; +int op::ownership_proof_bounced() asm "0xc18e86d2 PUSHINT"; + +int op::destroy() asm "0x1f04537a PUSHINT"; +int op::revoke() asm "0x6f89f5e3 PUSHINT"; +int op::take_excess() asm "0xd136d3b3 PUSHINT"; diff --git a/crypto/func/auto-tests/legacy_tests/nft-collection/params.fc b/crypto/func/auto-tests/legacy_tests/nft-collection/params.fc new file mode 100644 index 00000000..27a42748 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/nft-collection/params.fc @@ -0,0 +1,10 @@ +int workchain() asm "0 PUSHINT"; + +() force_chain(slice addr) impure { + (int wc, _) = parse_std_addr(addr); + throw_unless(333, wc == workchain()); +} + +slice null_addr() asm "b{00} PUSHSLICE"; +int flag::regular() asm "0x10 PUSHINT"; +int flag::bounce() asm "0x8 PUSHINT"; \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/nft-collection/stdlib.fc b/crypto/func/auto-tests/legacy_tests/nft-collection/stdlib.fc new file mode 100644 index 00000000..ba9177e9 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/nft-collection/stdlib.fc @@ -0,0 +1,215 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; + builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +builder store_coins(builder b, int x) asm "STVARUINT16"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDVARUINT16"; + +int equal_slices (slice a, slice b) asm "SDEQ"; +int builder_null?(builder b) asm "ISNULL"; +builder store_builder(builder to, builder from) asm "STBR"; \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/nominator-pool/pool.fc b/crypto/func/auto-tests/legacy_tests/nominator-pool/pool.fc new file mode 100644 index 00000000..25d6cb38 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/nominator-pool/pool.fc @@ -0,0 +1,746 @@ +;; The validator has his own wallet in the masterchain, on which he holds his own coins for operating. +;; From this wallet he sends commands to this nominator pool (mostly `new_stake`, `update_validator_set` and `recover_stake`). +;; Register/vote_for complaints and register/vote_for config proposals are sent from validator's wallet. +;; +;; Pool contract must be in masterchain. +;; Nominators' wallets must be in the basechain. +;; The validator in most cases have two pools (for even and odd validation rounds). + +#include "stdlib.fc"; + +int op::new_stake() asm "0x4e73744b PUSHINT"; ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L621 +int op::new_stake_error() asm "0xee6f454c PUSHINT"; ;; return_stake https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L169 +int op::new_stake_ok() asm "0xf374484c PUSHINT"; ;; send_confirmation https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L173 + +int op::recover_stake() asm "0x47657424 PUSHINT"; ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L625 +int op::recover_stake_error() asm "0xfffffffe PUSHINT"; ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L407 +int op::recover_stake_ok() asm "0xf96f7324 PUSHINT"; ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L426 + +int ADDR_SIZE() asm "256 PUSHINT"; +int BOUNCEABLE() asm "0x18 PUSHINT"; +int NON_BOUNCEABLE() asm "0x10 PUSHINT"; +int SEND_MODE_PAY_FEE_SEPARATELY() asm "1 PUSHINT"; ;; means that the sender wants to pay transfer fees separately +int SEND_MODE_IGNORE_ERRORS() asm "2 PUSHINT"; ;; means that any errors arising while processing this message during the action phase should be ignored +int SEND_MODE_REMAINING_AMOUNT() asm "64 PUSHINT"; ;; is used for messages that carry all the remaining value of the inbound message in addition to the value initially indicated in the new message +int ONE_TON() asm "1000000000 PUSHINT"; +int MIN_TONS_FOR_STORAGE() asm "10000000000 PUSHINT"; ;; 10 TON +int DEPOSIT_PROCESSING_FEE() asm "1000000000 PUSHINT"; ;; 1 TON +int MIN_STAKE_TO_SEND() asm "500000000000 PUSHINT"; ;; 500 TON +int VOTES_LIFETIME() asm "2592000 PUSHINT"; ;; 30 days + +int binary_log_ceil(int x) asm "UBITSIZE"; + +;; hex parse same with bridge https://github.com/ton-blockchain/bridge-func/blob/d03dbdbe9236e01efe7f5d344831bf770ac4c613/func/text_utils.fc +(slice, int) ~load_hex_symbol(slice comment) { + int n = comment~load_uint(8); + n = n - 48; + throw_unless(329, n >= 0); + if (n < 10) { + return (comment, (n)); + } + n = n - 7; + throw_unless(329, n >= 0); + if (n < 16) { + return (comment, (n)); + } + n = n - 32; + throw_unless(329, (n >= 0) & (n < 16)); + return (comment, n); +} + +(slice, int) ~load_text_hex_number(slice comment, int byte_length) { + int current_slice_length = comment.slice_bits() / 8; + int result = 0; + int counter = 0; + repeat (2 * byte_length) { + result = result * 16 + comment~load_hex_symbol(); + counter = counter + 1; + if (counter == current_slice_length) { + if (comment.slice_refs() == 1) { + cell _cont = comment~load_ref(); + comment = _cont.begin_parse(); + current_slice_length = comment.slice_bits() / 8; + counter = 0; + } + } + } + return (comment, result); +} + +slice make_address(int wc, int addr) inline_ref { + return begin_cell() + .store_uint(4, 3).store_int(wc, 8).store_uint(addr, ADDR_SIZE()).end_cell().begin_parse(); +} + +;; https://github.com/ton-blockchain/ton/blob/ae5c0720143e231c32c3d2034cfe4e533a16d969/crypto/block/block.tlb#L584 +int is_elector_address(int wc, int addr) inline_ref { + return (wc == -1) & (config_param(1).begin_parse().preload_uint(ADDR_SIZE()) == addr); +} + +slice elector_address() inline_ref { + int elector = config_param(1).begin_parse().preload_uint(ADDR_SIZE()); + return make_address(-1, elector); +} + +;; https://github.com/ton-blockchain/ton/blob/ae5c0720143e231c32c3d2034cfe4e533a16d969/crypto/block/block.tlb#L721 +int max_recommended_punishment_for_validator_misbehaviour(int stake) inline_ref { + cell cp = config_param(40); + if (cell_null?(cp)) { + return 101000000000; ;; 101 TON - https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/lite-client/lite-client.cpp#L3678 + } + + slice cs = cp.begin_parse(); + + (int prefix, + int default_flat_fine, int default_proportional_fine, + int severity_flat_mult, int severity_proportional_mult, + int unpunishable_interval, + int long_interval, int long_flat_mult, int long_proportional_mult) = + (cs~load_uint(8), + cs~load_coins(), cs~load_uint(32), + cs~load_uint(16), cs~load_uint(16), + cs~load_uint(16), + cs~load_uint(16), cs~load_uint(16), cs~load_uint(16) + ); + + ;; https://github.com/ton-blockchain/ton/blob/master/lite-client/lite-client.cpp#L3721 + int fine = default_flat_fine; + int fine_part = default_proportional_fine; + + fine *= severity_flat_mult; fine >>= 8; + fine_part *= severity_proportional_mult; fine_part >>= 8; + + fine *= long_flat_mult; fine >>= 8; + fine_part *= long_proportional_mult; fine_part >>= 8; + + return min(stake, fine + muldiv(stake, fine_part, 1 << 32)); ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L529 +} + +;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/block/block.tlb#L632 +;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L118 +int get_validator_config() inline_ref { + slice cs = config_param(15).begin_parse(); + (int validators_elected_for, int elections_start_before, int elections_end_before, int stake_held_for) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32), cs.preload_uint(32)); + return stake_held_for; +} + +;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/block/block.tlb#L712 +(int, int, cell) get_current_validator_set() inline_ref { + cell vset = config_param(34); ;; current validator set + slice cs = vset.begin_parse(); + ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/block/block.tlb#L579 + ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/config-code.fc#L49 + throw_unless(9, cs~load_uint(8) == 0x12); ;; validators_ext#12 only + int utime_since = cs~load_uint(32); ;; actual start unixtime of current validation round + int utime_until = cs~load_uint(32); ;; supposed end unixtime of current validation round (utime_until = utime_since + validators_elected_for); unfreeze_at = utime_until + stake_held_for + return (utime_since, utime_until, vset); +} + +;; check the validity of the new_stake message +;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L208 +int check_new_stake_msg(slice cs) impure inline_ref { + var validator_pubkey = cs~load_uint(256); + var stake_at = cs~load_uint(32); + var max_factor = cs~load_uint(32); + var adnl_addr = cs~load_uint(256); + var signature = cs~load_ref().begin_parse().preload_bits(512); + cs.end_parse(); + return stake_at; ;; supposed start of next validation round (utime_since) +} + +builder pack_nominator(int amount, int pending_deposit_amount) inline_ref { + return begin_cell().store_coins(amount).store_coins(pending_deposit_amount); +} + +(int, int) unpack_nominator(slice ds) inline_ref { + return ( + ds~load_coins(), ;; amount + ds~load_coins() ;; pending_deposit_amount + ); +} + +cell pack_config(int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake) inline_ref { + return begin_cell() + .store_uint(validator_address, ADDR_SIZE()) + .store_uint(validator_reward_share, 16) + .store_uint(max_nominators_count, 16) + .store_coins(min_validator_stake) + .store_coins(min_nominator_stake) + .end_cell(); +} + +(int, int, int, int, int) unpack_config(slice ds) inline_ref { + return ( + ds~load_uint(ADDR_SIZE()), ;; validator_address + ds~load_uint(16), ;; validator_reward_share + ds~load_uint(16), ;; max_nominators_count + ds~load_coins(), ;; min_validator_stake + ds~load_coins() ;; min_nominator_stake + ); +} + +() save_data(int state, int nominators_count, int stake_amount_sent, int validator_amount, cell config, cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) impure inline_ref { + set_data(begin_cell() + .store_uint(state, 8) + .store_uint(nominators_count, 16) + .store_coins(stake_amount_sent) + .store_coins(validator_amount) + .store_ref(config) + .store_dict(nominators) + .store_dict(withdraw_requests) + .store_uint(stake_at, 32) + .store_uint(saved_validator_set_hash, 256) + .store_uint(validator_set_changes_count, 8) + .store_uint(validator_set_change_time, 32) + .store_uint(stake_held_for, 32) + .store_dict(config_proposal_votings) + .end_cell()); +} + +(int, int, int, int, (int, int, int, int, int), cell, cell, int, int, int, int, int, cell) load_data() inline_ref { + slice ds = get_data().begin_parse(); + return ( + ds~load_uint(8), ;; state + ds~load_uint(16), ;; nominators_count + ds~load_coins(), ;; stake_amount_sent + ds~load_coins(), ;; validator_amount + unpack_config(ds~load_ref().begin_parse()), ;; config + ds~load_dict(), ;; nominators + ds~load_dict(), ;; withdraw_requests + ds~load_uint(32), ;; stake_at + ds~load_uint(256), ;; saved_validator_set_hash + ds~load_uint(8), ;; validator_set_changes_count + ds~load_uint(32), ;; validator_set_change_time + ds~load_uint(32), ;; stake_held_for + ds~load_dict() ;; config_proposal_votings + ); +} + +() send_msg(slice to_address, int amount, cell payload, int flags, int send_mode) impure inline_ref { + int has_payload = ~ cell_null?(payload); + + builder msg = begin_cell() + .store_uint(flags, 6) + .store_slice(to_address) + .store_coins(amount) + .store_uint(has_payload ? 1 : 0, 1 + 4 + 4 + 64 + 32 + 1 + 1); + + if (has_payload) { + msg = msg.store_ref(payload); + } + + send_raw_message(msg.end_cell(), send_mode); +} + +() send_excesses(slice sender_address) impure inline_ref { + send_msg(sender_address, 0, null(), NON_BOUNCEABLE(), SEND_MODE_REMAINING_AMOUNT() + SEND_MODE_IGNORE_ERRORS()); ;; non-bouneable, remaining inbound message amount, fee deducted from amount, ignore errors +} + +(cell, cell, int, int) withdraw_nominator(int address, cell nominators, cell withdraw_requests, int balance, int nominators_count) impure inline_ref { + (slice nominator, int found) = nominators.udict_get?(ADDR_SIZE(), address); + throw_unless(60, found); + (int amount, int pending_deposit_amount) = unpack_nominator(nominator); + int withdraw_amount = amount + pending_deposit_amount; + + if (withdraw_amount > balance - MIN_TONS_FOR_STORAGE()) { + return (nominators, withdraw_requests, balance, nominators_count); + } + + nominators~udict_delete?(ADDR_SIZE(), address); + withdraw_requests~udict_delete?(ADDR_SIZE(), address); + nominators_count -= 1; + balance -= withdraw_amount; + + if (withdraw_amount >= ONE_TON()) { + send_msg(make_address(0, address), withdraw_amount, null(), NON_BOUNCEABLE(), 0); ;; non-bouneable, fee deducted from amount, revert on errors + } + return (nominators, withdraw_requests, balance, nominators_count); +} + +(cell, cell, int, int) process_withdraw_requests(cell nominators, cell withdraw_requests, int balance, int nominators_count, int limit) impure inline_ref { + int count = 0; + int address = -1; + int need_break = 0; + do { + (address, slice cs, int f) = withdraw_requests.udict_get_next?(ADDR_SIZE(), address); + if (f) { + (nominators, withdraw_requests, int new_balance, nominators_count) = withdraw_nominator(address, nominators, withdraw_requests, balance, nominators_count); + need_break = (new_balance == balance); + balance = new_balance; + count += 1; + if (count >= limit) { + need_break = -1; + } + } + } until ((~ f) | (need_break)); + + return (nominators, withdraw_requests, nominators_count, balance); +} + +int calculate_total_nominators_amount(cell nominators) inline_ref { + int total = 0; + int address = -1; + do { + (address, slice cs, int f) = nominators.udict_get_next?(ADDR_SIZE(), address); + if (f) { + (int amount, int pending_deposit_amount) = unpack_nominator(cs); + total += (amount + pending_deposit_amount); + } + } until (~ f); + return total; +} + +cell distribute_share(int reward, cell nominators) inline_ref { + int total_amount = 0; + int address = -1; + do { + (address, slice cs, int f) = nominators.udict_get_next?(ADDR_SIZE(), address); + if (f) { + (int amount, int pending_deposit_amount) = unpack_nominator(cs); + total_amount += amount; + } + } until (~ f); + + cell new_nominators = new_dict(); + address = -1; + do { + (address, slice cs, int f) = nominators.udict_get_next?(ADDR_SIZE(), address); + if (f) { + (int amount, int pending_deposit_amount) = unpack_nominator(cs); + if (total_amount > 0) { + amount += muldiv(reward, amount, total_amount); + if (amount < 0) { + amount = 0; + } + } + amount += pending_deposit_amount; + new_nominators~udict_set_builder(ADDR_SIZE(), address, pack_nominator(amount, 0)); + } + } until (~ f); + + return new_nominators; +} + +() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure { + int balance = pair_first(get_balance()); + + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + + slice sender_address = cs~load_msg_addr(); + (int sender_wc, int sender_addr) = parse_std_addr(sender_address); + + if (flags & 1) { ;; bounced messages + if (in_msg_body.slice_bits() >= 64) { + in_msg_body~skip_bits(32); ;; skip 0xFFFFFFFF bounced prefix + int op = in_msg_body~load_uint(32); + if ((op == op::new_stake()) & (is_elector_address(sender_wc, sender_addr))) { + ;; `new_stake` from nominator-pool should always be handled without throws by elector + ;; because nominator-pool do `check_new_stake_msg` and `msg_value` checks before sending `new_stake`. + ;; If the stake is not accepted elector will send `new_stake_error` response message. + ;; Nevertheless we do process theoretically possible bounced `new_stake`. + + (int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data(); + if (state == 1) { + state = 0; + } + save_data( + state, + nominators_count, + stake_amount_sent, + validator_amount, + pack_config(validator_address, validator_reward_share, max_nominators_count, min_validator_stake, min_nominator_stake), + nominators, + withdraw_requests, + stake_at, + saved_validator_set_hash, + validator_set_changes_count, + validator_set_change_time, + stake_held_for, + config_proposal_votings + ); + } + } + return (); ;; ignore other bounces messages + } + + int op = in_msg_body~load_uint(32); + + (int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data(); + + if (op == 0) { + ;; We use simple text comments for nominator operations so nominators can do it from any wallet app. + ;; In other cases, they will need to put a stake on a browser extension, or use scripts, which can be inconvenient. + + ;; Throw on any unexpected request so that the stake is bounced back to the nominator in case of a typo. + + int action = in_msg_body~load_uint(8); + int is_vote = (action == 121) | (action == 110); ;; "y" or "n" + throw_unless(64, (action == 100) | (action == 119) | is_vote); ;; "d" or "w" or "y" or "n" + + if (~ is_vote) { + in_msg_body.end_parse(); + throw_unless(61, sender_wc == 0); ;; nominators only in basechain + throw_unless(62, sender_addr != validator_address); + } + + if (action == 100) { ;; "d" - deposit nominator (any time, will take effect in the next round) + (slice nominator, int found) = nominators.udict_get?(ADDR_SIZE(), sender_addr); + + if (~ found) { + nominators_count += 1; + } + throw_unless(65, nominators_count <= max_nominators_count); + + msg_value -= DEPOSIT_PROCESSING_FEE(); + throw_unless(66, msg_value > 0); + + (int amount, int pending_deposit_amount) = found ? unpack_nominator(nominator) : (0, 0); + if (state == 0) { + amount += msg_value; + } else { + pending_deposit_amount += msg_value; + } + throw_unless(67, amount + pending_deposit_amount >= min_nominator_stake); + throw_unless(68, cell_depth(nominators) < max(5, binary_log_ceil(nominators_count) * 2) ); ;; prevent dict depth ddos + nominators~udict_set_builder(ADDR_SIZE(), sender_addr, pack_nominator(amount, pending_deposit_amount)); + } + + if (action == 119) { ;; "w" - withdraw request (any time) + if (state == 0) { + (nominators, withdraw_requests, int new_balance, nominators_count) = withdraw_nominator(sender_addr, nominators, withdraw_requests, balance, nominators_count); + if (new_balance - msg_value >= MIN_TONS_FOR_STORAGE()) { + send_excesses(sender_address); + } + } else { + (slice nominator, int found) = nominators.udict_get?(ADDR_SIZE(), sender_addr); + throw_unless(69, found); + withdraw_requests~udict_set_builder(ADDR_SIZE(), sender_addr, begin_cell()); + send_excesses(sender_address); + } + } + + if (is_vote) { + int authorized = (sender_wc == -1) & (sender_addr == validator_address); + + if (~ authorized) { + throw_unless(121, sender_wc == 0); + (slice nominator, authorized) = nominators.udict_get?(ADDR_SIZE(), sender_addr); + throw_unless(122, authorized); + (int amount, int pending_deposit_amount) = unpack_nominator(nominator); + throw_unless(123, amount > 0); + } + + int proposal_hash = in_msg_body~load_text_hex_number(32); + in_msg_body.end_parse(); + int support = action == 121; + + (slice votes_slice, int found) = config_proposal_votings.udict_get?(256, proposal_hash); + + if (~ found) { + ;; require higher fee to prevent dictionary spam + int fee = ONE_TON(); + int power = cell_depth(config_proposal_votings); + repeat (power) { + fee = muldiv(fee, 15, 10); + } + throw_unless(123, msg_value >= fee); + } + + (cell votes_dict, int votes_create_time) = found ? (votes_slice~load_dict(), votes_slice~load_uint(32)) : (new_dict(), now()); + + (_, int vote_found) = votes_dict.udict_get?(256, sender_addr); + throw_if(124, vote_found); + votes_dict~udict_set_builder(256, sender_addr, begin_cell().store_int(support, 1).store_uint(now(), 32)); + + builder new_votes = begin_cell().store_dict(votes_dict).store_uint(votes_create_time, 32); + config_proposal_votings~udict_set_builder(256, proposal_hash, new_votes); + + if (found) { + send_excesses(sender_address); + } + } + + } else { + + int query_id = in_msg_body~load_uint(64); + + if (is_elector_address(sender_wc, sender_addr)) { ;; response from elector + + accept_message(); + + if (op == op::recover_stake_ok()) { + state = 0; + + int reward = msg_value - stake_amount_sent; + int nominators_reward = 0; + + if (reward <= 0) { + validator_amount += reward; + if (validator_amount < 0) { + ;; even this should never happen + nominators_reward = validator_amount; + validator_amount = 0; + } + } else { + int validator_reward = (reward * validator_reward_share) / 10000; + if (validator_reward > reward) { ;; Theoretical invalid case if validator_reward_share > 10000 + validator_reward = reward; + } + validator_amount += validator_reward; + nominators_reward = reward - validator_reward; + } + + nominators = distribute_share(nominators_reward, nominators); ;; call even if there was no reward to process deposit requests + stake_amount_sent = 0; + } + + if (state == 1) { + if (op == op::new_stake_error()) { ;; error when new_stake; stake returned + state = 0; + } + + if (op == op::new_stake_ok()) { + state = 2; + } + } + + ;; else just accept coins from elector + + } else { + + ;; throw on any unexpected request so that the coins is bounced back to the sender in case of a typo + throw_unless(70, ((op >= 1) & (op <= 7)) | (op == op::recover_stake()) | (op == op::new_stake())); + + if (op == 1) { + ;; just accept coins + } + + if (op == 2) { ;; process withdraw requests (at any time while the balance is enough) + int limit = in_msg_body~load_uint(8); + + (nominators, withdraw_requests, nominators_count, int new_balance) = process_withdraw_requests(nominators, withdraw_requests, balance, nominators_count, limit); + + if (new_balance - msg_value >= MIN_TONS_FOR_STORAGE()) { + send_excesses(sender_address); + } + } + + if (op == 3) { ;; emergency process withdraw request (at any time if the balance is enough) + int request_address = in_msg_body~load_uint(ADDR_SIZE()); + (slice withdraw_request, int found) = withdraw_requests.udict_get?(ADDR_SIZE(), request_address); + throw_unless(71, found); + (nominators, withdraw_requests, int new_balance, nominators_count) = withdraw_nominator(request_address, nominators, withdraw_requests, balance, nominators_count); + if (new_balance - msg_value >= MIN_TONS_FOR_STORAGE()) { + send_excesses(sender_address); + } + } + + if (op == 6) { ;; update current valudator set hash (anyone can invoke) + throw_unless(113, validator_set_changes_count < 3); + (int utime_since, int utime_until, cell vset) = get_current_validator_set(); + int current_hash = cell_hash(vset); + if (saved_validator_set_hash != current_hash) { + saved_validator_set_hash = current_hash; + validator_set_changes_count += 1; + validator_set_change_time = now(); + } + send_excesses(sender_address); + } + + if (op == 7) { ;; clean up outdating votings + int t = now(); + int proposal_hash = -1; + do { + (proposal_hash, slice votes_slice, int found) = config_proposal_votings.udict_get_next?(256, proposal_hash); + if (found) { + (cell votes_dict, int votes_create_time) = (votes_slice~load_dict(), votes_slice~load_uint(32)); + if (t - votes_create_time > VOTES_LIFETIME()) { + config_proposal_votings~udict_delete?(256, proposal_hash); + } + } + } until (~ found); + send_excesses(sender_address); + } + + if (op == op::recover_stake()) { ;; send recover_stake to elector (anyone can send) + + ;; We need to take all credits from the elector at once, + ;; because if we do not take all at once, then it will be processed as a fine by pool. + ;; In the elector, credits (`credit_to`) are accrued in three places: + ;; 1) return of surplus stake in elections (`try_elect`) + ;; 2) reward for complaint when punish (`punish`) - before unfreezing + ;; 3) unfreeze round (`unfreeze_without_bonuses`/`unfreeze_with_bonuses`) + ;; We need to be guaranteed to wait for unfreezing round and only then send `recover_stake`. + ;; So we are waiting for the change of 3 validator sets. + + ;; ADDITIONAL NOTE: + ;; In a special case (if the network was down), the config theoretically can refuse the elector to save a new round after election - https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/config-code.fc#L494 + ;; and the elector will start a new election - https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L364 + ;; in this case, our pool will have to skip the round, but it will be able to recover stake later + + throw_unless(111, validator_set_changes_count >= 2); + throw_unless(112, (validator_set_changes_count > 2) | (now() - validator_set_change_time > stake_held_for + 60)); + ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L887 + + cell payload = begin_cell().store_uint(op::recover_stake(), 32).store_uint(query_id, 64).end_cell(); + send_msg(elector_address(), 0, payload, BOUNCEABLE(), SEND_MODE_REMAINING_AMOUNT()); ;; bounceable, carry all the remaining value of the inbound message, fee deducted from amount, revert on errors + } + + ;; message from validator + + if (op == 4) { ;; deposit validator (any time) + throw_unless(73, (sender_wc == -1) & (sender_addr == validator_address)); + msg_value -= DEPOSIT_PROCESSING_FEE(); + throw_unless(74, msg_value > 0); + validator_amount += msg_value; + } + + if (op == 5) { ;; withdraw validator (after recover_stake and before new_stake) + throw_unless(74, state == 0); ;; no withdraw request because validator software can wait right time + throw_unless(75, (sender_wc == -1) & (sender_addr == validator_address)); + int request_amount = in_msg_body~load_coins(); + throw_unless(78, request_amount > 0); + + int total_nominators_amount = calculate_total_nominators_amount(nominators); + ;; the validator can withdraw everything that does not belong to the nominators + throw_unless(76, request_amount <= balance - MIN_TONS_FOR_STORAGE() - total_nominators_amount); + validator_amount -= request_amount; + if (validator_amount < 0) { + validator_amount = 0; + } + send_msg(make_address(-1, validator_address), request_amount, null(), NON_BOUNCEABLE(), 0); ;; non-bouneable, fee deducted from amount, revert on errors + int new_balance = balance - request_amount; + if (new_balance - msg_value >= MIN_TONS_FOR_STORAGE()) { + send_excesses(sender_address); + } + } + + if (op == op::new_stake()) { + throw_unless(78, (sender_wc == -1) & (sender_addr == validator_address)); + + throw_unless(79, state == 0); + + throw_unless(80, query_id); ;; query_id must be greater then 0 to receive confirmation message from elector + + throw_unless(86, msg_value >= ONE_TON()); ;; must be greater then new_stake sending to elector fee + + int value = in_msg_body~load_coins(); + + slice msg = in_msg_body; + + stake_at = check_new_stake_msg(in_msg_body); + + stake_amount_sent = value - ONE_TON(); + + throw_unless(81, value >= MIN_STAKE_TO_SEND()); + + throw_unless(82, value <= balance - MIN_TONS_FOR_STORAGE()); + + throw_unless(83, validator_amount >= min_validator_stake); + + throw_unless(84, validator_amount >= max_recommended_punishment_for_validator_misbehaviour(stake_amount_sent)); + + throw_unless(85, cell_null?(withdraw_requests)); ;; no withdraw requests + + state = 1; + (int utime_since, int utime_until, cell vset) = get_current_validator_set(); + saved_validator_set_hash = cell_hash(vset); ;; current validator set, we will be in next validator set + validator_set_changes_count = 0; + validator_set_change_time = utime_since; + stake_held_for = get_validator_config(); ;; save `stake_held_for` in case the config changes in the process + + send_msg(elector_address(), value, begin_cell().store_uint(op, 32).store_uint(query_id, 64).store_slice(msg).end_cell(), BOUNCEABLE(), SEND_MODE_PAY_FEE_SEPARATELY()); ;; pay fee separately, rever on errors + } + } + } + + save_data( + state, + nominators_count, + stake_amount_sent, + validator_amount, + pack_config(validator_address, validator_reward_share, max_nominators_count, min_validator_stake, min_nominator_stake), + nominators, + withdraw_requests, + stake_at, + saved_validator_set_hash, + validator_set_changes_count, + validator_set_change_time, + stake_held_for, + config_proposal_votings + ); +} + +;; Get methods + +_ get_pool_data() method_id { + return load_data(); +} + +int has_withdraw_requests() method_id { + (int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data(); + return ~ cell_null?(withdraw_requests); +} + +(int, int, int) get_nominator_data(int nominator_address) method_id { + (int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data(); + + (slice nominator, int found) = nominators.udict_get?(ADDR_SIZE(), nominator_address); + throw_unless(86, found); + (int amount, int pending_deposit_amount) = unpack_nominator(nominator); + (slice withdraw_request, int withdraw_found) = withdraw_requests.udict_get?(ADDR_SIZE(), nominator_address); + + return (amount, pending_deposit_amount, withdraw_found); +} + +int get_max_punishment(int stake) method_id { + return max_recommended_punishment_for_validator_misbehaviour(stake); +} + +tuple list_nominators() method_id { + (int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data(); + var list = null(); + int address = -1; + do { + (address, slice nominator, int found) = nominators.udict_get_next?(ADDR_SIZE(), address); + if (found) { + (int amount, int pending_deposit_amount) = unpack_nominator(nominator); + (_, int withdraw_requested) = withdraw_requests.udict_get?(ADDR_SIZE(), address); + list = cons(tuple4(address, amount, pending_deposit_amount, withdraw_requested), list); + } + } until (~ found); + return list; +} + +tuple list_votes() method_id { + (int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data(); + var list = null(); + int proposal_hash = -1; + do { + (proposal_hash, slice votes_slice, int found) = config_proposal_votings.udict_get_next?(256, proposal_hash); + if (found) { + (cell votes_dict, int votes_create_time) = (votes_slice~load_dict(), votes_slice~load_uint(32)); + list = cons(pair(proposal_hash, votes_create_time), list); + } + } until (~ found); + return list; +} + +tuple list_voters(int proposal_hash) method_id { + (int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data(); + var list = null(); + (slice votes_slice, int found) = config_proposal_votings.udict_get?(256, proposal_hash); + throw_unless(133, found); + cell votes_dict = votes_slice~load_dict(); + + int address = -1; + do { + (address, slice cs, int found) = votes_dict.udict_get_next?(ADDR_SIZE(), address); + if (found) { + (int support, int vote_time) = (cs~load_int(1), cs~load_uint(32)); + list = cons(triple(address, support, vote_time), list); + } + } until (~ found); + return list; +} diff --git a/crypto/func/auto-tests/legacy_tests/nominator-pool/stdlib.fc b/crypto/func/auto-tests/legacy_tests/nominator-pool/stdlib.fc new file mode 100644 index 00000000..0431d32d --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/nominator-pool/stdlib.fc @@ -0,0 +1,211 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +builder store_coins(builder b, int x) asm "STVARUINT16"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDVARUINT16"; \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/storage/constants.fc b/crypto/func/auto-tests/legacy_tests/storage/constants.fc new file mode 100644 index 00000000..479cdfa3 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/storage/constants.fc @@ -0,0 +1,23 @@ +const op::offer_storage_contract = 0x107c49ef; +const op::close_contract = 0x79f937ea; +const op::contract_deployed = 0xbf7bd0c1; +const op::storage_contract_confirmed = 0xd4caedcd; +const op::reward_withdrawal = 0xa91baf56; +const op::storage_contract_terminated = 0xb6236d63; +const op::accept_storage_contract = 0x7a361688; +const op::withdraw = 0x46ed2e94; +const op::proof_storage = 0x419d5d4d; + +const op::update_pubkey = 0x53f34cd6; +const op::update_storage_params = 0x54cbf19b; + +const error::not_enough_money = 1001; +const error::unauthorized = 401; +const error::wrong_proof = 1002; +const error::contract_not_active = 1003; +const error::file_too_small = 1004; +const error::file_too_big = 1005; +const error::no_new_contracts = 1006; +const error::contract_already_active = 1007; +const error::no_microchunk_hash = 1008; +const error::provider_params_changed = 1009; diff --git a/crypto/func/auto-tests/legacy_tests/storage/stdlib.fc b/crypto/func/auto-tests/legacy_tests/storage/stdlib.fc new file mode 100644 index 00000000..781fdcbc --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/storage/stdlib.fc @@ -0,0 +1,625 @@ +;; Standard library for funC +;; + +{- + # Tuple manipulation primitives + The names and the types are mostly self-explaining. + See [polymorhism with forall](https://ton.org/docs/#/func/functions?id=polymorphism-with-forall) + for more info on the polymorphic functions. + + Note that currently values of atomic type `tuple` can't be cast to composite tuple type (e.g. `[int, cell]`) + and vise versa. +-} + +{- + # Lisp-style lists + + Lists can be represented as nested 2-elements tuples. + Empty list is conventionally represented as TVM `null` value (it can be obtained by calling [null()]). + For example, tuple `(1, (2, (3, null)))` represents list `[1, 2, 3]`. Elements of a list can be of different types. +-} + +;;; Adds an element to the beginning of lisp-style list. +forall X -> tuple cons(X head, tuple tail) asm "CONS"; + +;;; Extracts the head and the tail of lisp-style list. +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; + +;;; Extracts the tail and the head of lisp-style list. +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; + +;;; Returns the head of lisp-style list. +forall X -> X car(tuple list) asm "CAR"; + +;;; Returns the tail of lisp-style list. +tuple cdr(tuple list) asm "CDR"; + +;;; Creates tuple with zero elements. +tuple empty_tuple() asm "NIL"; + +;;; Appends a value `x` to a `Tuple t = (x1, ..., xn)`, but only if the resulting `Tuple t' = (x1, ..., xn, x)` +;;; is of length at most 255. Otherwise throws a type check exception. +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; + +;;; Creates a tuple of length one with given argument as element. +forall X -> [X] single(X x) asm "SINGLE"; + +;;; Unpacks a tuple of length one +forall X -> X unsingle([X] t) asm "UNSINGLE"; + +;;; Creates a tuple of length two with given arguments as elements. +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; + +;;; Unpacks a tuple of length two +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; + +;;; Creates a tuple of length three with given arguments as elements. +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; + +;;; Unpacks a tuple of length three +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; + +;;; Creates a tuple of length four with given arguments as elements. +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; + +;;; Unpacks a tuple of length four +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; + +;;; Returns the first element of a tuple (with unknown element types). +forall X -> X first(tuple t) asm "FIRST"; + +;;; Returns the second element of a tuple (with unknown element types). +forall X -> X second(tuple t) asm "SECOND"; + +;;; Returns the third element of a tuple (with unknown element types). +forall X -> X third(tuple t) asm "THIRD"; + +;;; Returns the fourth element of a tuple (with unknown element types). +forall X -> X fourth(tuple t) asm "3 INDEX"; + +;;; Returns the first element of a pair tuple. +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; + +;;; Returns the second element of a pair tuple. +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; + +;;; Returns the first element of a triple tuple. +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; + +;;; Returns the second element of a triple tuple. +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; + +;;; Returns the third element of a triple tuple. +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; + + +;;; Push null element (casted to given type) +;;; By the TVM type `Null` FunC represents absence of a value of some atomic type. +;;; So `null` can actually have any atomic type. +forall X -> X null() asm "PUSHNULL"; + +;;; Moves a variable [x] to the top of the stack +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + + + +;;; Returns the current Unix time as an Integer +int now() asm "NOW"; + +;;; Returns the internal address of the current smart contract as a Slice with a `MsgAddressInt`. +;;; If necessary, it can be parsed further using primitives such as [parse_std_addr]. +slice my_address() asm "MYADDR"; + +;;; Returns the balance of the smart contract as a tuple consisting of an int +;;; (balance in nanotoncoins) and a `cell` +;;; (a dictionary with 32-bit keys representing the balance of "extra currencies") +;;; at the start of Computation Phase. +;;; Note that RAW primitives such as [send_raw_message] do not update this field. +[int, cell] get_balance() asm "BALANCE"; + +;;; Returns the logical time of the current transaction. +int cur_lt() asm "LTIME"; + +;;; Returns the starting logical time of the current block. +int block_lt() asm "BLOCKLT"; + +;;; Computes the representation hash of a `cell` [c] and returns it as a 256-bit unsigned integer `x`. +;;; Useful for signing and checking signatures of arbitrary entities represented by a tree of cells. +int cell_hash(cell c) asm "HASHCU"; + +;;; Computes the hash of a `slice s` and returns it as a 256-bit unsigned integer `x`. +;;; The result is the same as if an ordinary cell containing only data and references from `s` had been created +;;; and its hash computed by [cell_hash]. +int slice_hash(slice s) asm "HASHSU"; + +;;; Computes sha256 of the data bits of `slice` [s]. If the bit length of `s` is not divisible by eight, +;;; throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer `x`. +int string_hash(slice s) asm "SHA256U"; + +{- + # Signature checks +-} + +;;; Checks the Ed25519-`signature` of a `hash` (a 256-bit unsigned integer, usually computed as the hash of some data) +;;; using [public_key] (also represented by a 256-bit unsigned integer). +;;; The signature must contain at least 512 data bits; only the first 512 bits are used. +;;; The result is `−1` if the signature is valid, `0` otherwise. +;;; Note that `CHKSIGNU` creates a 256-bit slice with the hash and calls `CHKSIGNS`. +;;; That is, if [hash] is computed as the hash of some data, these data are hashed twice, +;;; the second hashing occurring inside `CHKSIGNS`. +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; + +;;; Checks whether [signature] is a valid Ed25519-signature of the data portion of `slice data` using `public_key`, +;;; similarly to [check_signature]. +;;; If the bit length of [data] is not divisible by eight, throws a cell underflow exception. +;;; The verification of Ed25519 signatures is the standard one, +;;; with sha256 used to reduce [data] to the 256-bit number that is actually signed. +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +{--- + # Computation of boc size + The primitives below may be useful for computing storage fees of user-provided data. +-} + +;;; Returns `(x, y, z, -1)` or `(null, null, null, 0)`. +;;; Recursively computes the count of distinct cells `x`, data bits `y`, and cell references `z` +;;; in the DAG rooted at `cell` [c], effectively returning the total storage used by this DAG taking into account +;;; the identification of equal cells. +;;; The values of `x`, `y`, and `z` are computed by a depth-first traversal of this DAG, +;;; with a hash table of visited cell hashes used to prevent visits of already-visited cells. +;;; The total count of visited cells `x` cannot exceed non-negative [max_cells]; +;;; otherwise the computation is aborted before visiting the `(max_cells + 1)`-st cell and +;;; a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`. +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; + +;;; Similar to [compute_data_size?], but accepting a `slice` [s] instead of a `cell`. +;;; The returned value of `x` does not take into account the cell that contains the `slice` [s] itself; +;;; however, the data bits and the cell references of [s] are accounted for in `y` and `z`. +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; + +;;; A non-quiet version of [compute_data_size?] that throws a cell overflow exception (`8`) on failure. +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;;; A non-quiet version of [slice_compute_data_size?] that throws a cell overflow exception (8) on failure. +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;;; Throws an exception with exit_code excno if cond is not 0 (commented since implemented in compilator) +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +{-- + # Debug primitives + Only works for local TVM execution with debug level verbosity +-} +;;; Dumps the stack (at most the top 255 values) and shows the total stack depth. +() dump_stack() impure asm "DUMPSTK"; + +{- + # Persistent storage save and load +-} + +;;; Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later. +cell get_data() asm "c4 PUSH"; + +;;; Sets `cell` [c] as persistent contract data. You can update persistent contract storage with this primitive. +() set_data(cell c) impure asm "c4 POP"; + +{- + # Continuation primitives +-} +;;; Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls. +;;; The primitive returns the current value of `c3`. +cont get_c3() impure asm "c3 PUSH"; + +;;; Updates the current value of `c3`. Usually, it is used for updating smart contract code in run-time. +;;; Note that after execution of this primitive the current code +;;; (and the stack of recursive function calls) won't change, +;;; but any other function call will use a function from the new code. +() set_c3(cont c) impure asm "c3 POP"; + +;;; Transforms a `slice` [s] into a simple ordinary continuation `c`, with `c.code = s` and an empty stack and savelist. +cont bless(slice s) impure asm "BLESS"; + +{--- + # Gas related primitives +-} + +;;; Sets current gas limit `gl` to its maximal allowed value `gm`, and resets the gas credit `gc` to zero, +;;; decreasing the value of `gr` by `gc` in the process. +;;; In other words, the current smart contract agrees to buy some gas to finish the current transaction. +;;; This action is required to process external messages, which bring no value (hence no gas) with themselves. +;;; +;;; For more details check [accept_message effects](https://ton.org/docs/#/smart-contracts/accept). +() accept_message() impure asm "ACCEPT"; + +;;; Sets current gas limit `gl` to the minimum of limit and `gm`, and resets the gas credit `gc` to zero. +;;; If the gas consumed so far (including the present instruction) exceeds the resulting value of `gl`, +;;; an (unhandled) out of gas exception is thrown before setting new gas limits. +;;; Notice that [set_gas_limit] with an argument `limit ≥ 2^63 − 1` is equivalent to [accept_message]. +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; + +;;; Commits the current state of registers `c4` (“persistent data”) and `c5` (“actions”) +;;; so that the current execution is considered “successful” with the saved values even if an exception +;;; in Computation Phase is thrown later. +() commit() impure asm "COMMIT"; + +;;; Not implemented +;;() buy_gas(int gram) impure asm "BUYGAS"; + +;;; Computes the amount of gas that can be bought for `amount` nanoTONs, +;;; and sets `gl` accordingly in the same way as [set_gas_limit]. +() buy_gas(int amount) impure asm "BUYGAS"; + +;;; Computes the minimum of two integers [x] and [y]. +int min(int x, int y) asm "MIN"; + +;;; Computes the maximum of two integers [x] and [y]. +int max(int x, int y) asm "MAX"; + +;;; Sorts two integers. +(int, int) minmax(int x, int y) asm "MINMAX"; + +;;; Computes the absolute value of an integer [x]. +int abs(int x) asm "ABS"; + +{- + # Slice primitives + + It is said that a primitive _loads_ some data, + if it returns the data and the remainder of the slice + (so it can also be used as [modifying method](https://ton.org/docs/#/func/statements?id=modifying-methods)). + + It is said that a primitive _preloads_ some data, if it returns only the data + (it can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods)). + + Unless otherwise stated, loading and preloading primitives read the data from a prefix of the slice. +-} + + +;;; Converts a `cell` [c] into a `slice`. Notice that [c] must be either an ordinary cell, +;;; or an exotic cell (see [TVM.pdf](https://ton-blockchain.github.io/docs/tvm.pdf), 3.1.2) +;;; which is automatically loaded to yield an ordinary cell `c'`, converted into a `slice` afterwards. +slice begin_parse(cell c) asm "CTOS"; + +;;; Checks if [s] is empty. If not, throws an exception. +() end_parse(slice s) impure asm "ENDS"; + +;;; Loads the first reference from the slice. +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; + +;;; Preloads the first reference from the slice. +cell preload_ref(slice s) asm "PLDREF"; + + {- Functions below are commented because are implemented on compilator level for optimisation -} + +;;; Loads a signed [len]-bit integer from a slice [s]. +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; + +;;; Loads an unsigned [len]-bit integer from a slice [s]. +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; + +;;; Preloads a signed [len]-bit integer from a slice [s]. +;; int preload_int(slice s, int len) asm "PLDIX"; + +;;; Preloads an unsigned [len]-bit integer from a slice [s]. +;; int preload_uint(slice s, int len) asm "PLDUX"; + +;;; Loads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; + +;;; Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; + +;;; Loads serialized amount of TonCoins (any unsigned integer up to `2^128 - 1`). +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDGRAMS"; + +;;; Returns all but the first `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; + +;;; Returns the first `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice first_bits(slice s, int len) asm "SDCUTFIRST"; + +;;; Returns all but the last `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; + +;;; Returns the last `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice slice_last(slice s, int len) asm "SDCUTLAST"; + +;;; Loads a dictionary `D` (HashMapE) from `slice` [s]. +;;; (returns `null` if `nothing` constructor is used). +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; + +;;; Preloads a dictionary `D` from `slice` [s]. +cell preload_dict(slice s) asm "PLDDICT"; + +;;; Loads a dictionary as [load_dict], but returns only the remainder of the slice. +slice skip_dict(slice s) asm "SKIPDICT"; + +;;; Loads (Maybe ^Cell) from `slice` [s]. +;;; In other words loads 1 bit and if it is true +;;; loads first ref and return it with slice remainder +;;; otherwise returns `null` and slice remainder +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; + +;;; Preloads (Maybe ^Cell) from `slice` [s]. +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; + + +;;; Returns the depth of `cell` [c]. +;;; If [c] has no references, then return `0`; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [c]. +;;; If [c] is a `null` instead of a cell, returns zero. +int cell_depth(cell c) asm "CDEPTH"; + + +{- + # Slice size primitives +-} + +;;; Returns the number of references in `slice` [s]. +int slice_refs(slice s) asm "SREFS"; + +;;; Returns the number of data bits in `slice` [s]. +int slice_bits(slice s) asm "SBITS"; + +;;; Returns both the number of data bits and the number of references in `slice` [s]. +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; + +;;; Checks whether a `slice` [s] is empty (i.e., contains no bits of data and no cell references). +int slice_empty?(slice s) asm "SEMPTY"; + +;;; Checks whether `slice` [s] has no bits of data. +int slice_data_empty?(slice s) asm "SDEMPTY"; + +;;; Checks whether `slice` [s] has no references. +int slice_refs_empty?(slice s) asm "SREMPTY"; + +;;; Returns the depth of `slice` [s]. +;;; If [s] has no references, then returns `0`; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [s]. +int slice_depth(slice s) asm "SDEPTH"; + +{- + # Builder size primitives +-} + +;;; Returns the number of cell references already stored in `builder` [b] +int builder_refs(builder b) asm "BREFS"; + +;;; Returns the number of data bits already stored in `builder` [b]. +int builder_bits(builder b) asm "BBITS"; + +;;; Returns the depth of `builder` [b]. +;;; If no cell references are stored in [b], then returns 0; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [b]. +int builder_depth(builder b) asm "BDEPTH"; + +{- + # Builder primitives + It is said that a primitive _stores_ a value `x` into a builder `b` + if it returns a modified version of the builder `b'` with the value `x` stored at the end of it. + It can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods). + + All the primitives below first check whether there is enough space in the `builder`, + and only then check the range of the value being serialized. +-} + +;;; Creates a new empty `builder`. +builder begin_cell() asm "NEWC"; + +;;; Converts a `builder` into an ordinary `cell`. +cell end_cell(builder b) asm "ENDC"; + +;;; Stores a reference to `cell` [c] into `builder` [b]. +builder store_ref(builder b, cell c) asm(c b) "STREF"; + +;;; Stores an unsigned [len]-bit integer `x` into `b` for `0 ≤ len ≤ 256`. +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; + +;;; Stores a signed [len]-bit integer `x` into `b` for` 0 ≤ len ≤ 257`. +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; + + +;;; Stores `slice` [s] into `builder` [b] +builder store_slice(builder b, slice s) asm "STSLICER"; + +;;; Stores (serializes) an integer [x] in the range `0..2^128 − 1` into `builder` [b]. +;;; The serialization of [x] consists of a 4-bit unsigned big-endian integer `l`, +;;; which is the smallest integer `l ≥ 0`, such that `x < 2^8l`, +;;; followed by an `8l`-bit unsigned big-endian representation of [x]. +;;; If [x] does not belong to the supported range, a range check exception is thrown. +;;; +;;; Store amounts of TonCoins to the builder as VarUInteger 16 +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_coins(builder b, int x) asm "STGRAMS"; + +;;; Stores dictionary `D` represented by `cell` [c] or `null` into `builder` [b]. +;;; In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise. +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +;;; Stores (Maybe ^Cell) to builder: +;;; if cell is null store 1 zero bit +;;; otherwise store 1 true bit and ref to cell +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + + +{- + # Address manipulation primitives + The address manipulation primitives listed below serialize and deserialize values according to the following TL-B scheme: + ```TL-B + addr_none$00 = MsgAddressExt; + addr_extern$01 len:(## 8) external_address:(bits len) + = MsgAddressExt; + anycast_info$_ depth:(#<= 30) { depth >= 1 } + rewrite_pfx:(bits depth) = Anycast; + addr_std$10 anycast:(Maybe Anycast) + workchain_id:int8 address:bits256 = MsgAddressInt; + addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) + workchain_id:int32 address:(bits addr_len) = MsgAddressInt; + _ _:MsgAddressInt = MsgAddress; + _ _:MsgAddressExt = MsgAddress; + + 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; + ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + ``` + A deserialized `MsgAddress` is represented by a tuple `t` as follows: + + - `addr_none` is represented by `t = (0)`, + i.e., a tuple containing exactly one integer equal to zero. + - `addr_extern` is represented by `t = (1, s)`, + where slice `s` contains the field `external_address`. In other words, ` + t` is a pair (a tuple consisting of two entries), containing an integer equal to one and slice `s`. + - `addr_std` is represented by `t = (2, u, x, s)`, + where `u` is either a `null` (if `anycast` is absent) or a slice `s'` containing `rewrite_pfx` (if anycast is present). + Next, integer `x` is the `workchain_id`, and slice `s` contains the address. + - `addr_var` is represented by `t = (3, u, x, s)`, + where `u`, `x`, and `s` have the same meaning as for `addr_std`. +-} + +;;; Loads from slice [s] the only prefix that is a valid `MsgAddress`, +;;; and returns both this prefix `s'` and the remainder `s''` of [s] as slices. +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; + +;;; Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`. +;;; If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown. +tuple parse_addr(slice s) asm "PARSEMSGADDR"; + +;;; Parses slice [s] containing a valid `MsgAddressInt` (usually a `msg_addr_std`), +;;; applies rewriting from the anycast (if present) to the same-length prefix of the address, +;;; and returns both the workchain and the 256-bit address as integers. +;;; If the address is not 256-bit, or if [s] is not a valid serialization of `MsgAddressInt`, +;;; throws a cell deserialization exception. +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; + +;;; A variant of [parse_std_addr] that returns the (rewritten) address as a slice [s], +;;; even if it is not exactly 256 bit long (represented by a `msg_addr_var`). +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +{- + # Dictionary primitives +-} + + +;;; Sets the value associated with [key_len]-bit key signed index in dictionary [dict] to [value] (cell), +;;; and returns the resulting dictionary. +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; + +;;; Sets the value associated with [key_len]-bit key unsigned index in dictionary [dict] to [value] (cell), +;;; and returns the resulting dictionary. +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; + +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; + +;;; Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL +cell new_dict() asm "NEWDICT"; +;;; Checks whether a dictionary is empty. Equivalent to cell_null?. +int dict_empty?(cell c) asm "DICTEMPTY"; + + +{- Prefix dictionary primitives -} +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +;;; Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value. +cell config_param(int x) asm "CONFIGOPTPARAM"; +;;; Checks whether c is a null. Note, that FunC also has polymorphic null? built-in. +int cell_null?(cell c) asm "ISNULL"; + +;;; Creates an output action which would reserve exactly amount nanotoncoins (if mode = 0), at most amount nanotoncoins (if mode = 2), or all but amount nanotoncoins (if mode = 1 or mode = 3), from the remaining balance of the account. It is roughly equivalent to creating an outbound message carrying amount nanotoncoins (or b − amount nanotoncoins, where b is the remaining balance) to oneself, so that the subsequent output actions would not be able to spend more money than the remainder. Bit +2 in mode means that the external action does not fail if the specified amount cannot be reserved; instead, all remaining balance is reserved. Bit +8 in mode means `amount <- -amount` before performing any further actions. Bit +4 in mode means that amount is increased by the original balance of the current account (before the compute phase), including all extra currencies, before performing any other checks and actions. Currently, amount must be a non-negative integer, and mode must be in the range 0..15. +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +;;; Similar to raw_reserve, but also accepts a dictionary extra_amount (represented by a cell or null) with extra currencies. In this way currencies other than TonCoin can be reserved. +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +;;; Sends a raw message contained in msg, which should contain a correctly serialized object Message X, with the only exception that the source address is allowed to have dummy value addr_none (to be automatically replaced with the current smart contract address), and ihr_fee, fwd_fee, created_lt and created_at fields can have arbitrary values (to be rewritten with correct values during the action phase of the current transaction). Integer parameter mode contains the flags. Currently mode = 0 is used for ordinary messages; mode = 128 is used for messages that are to carry all the remaining balance of the current smart contract (instead of the value originally indicated in the message); mode = 64 is used for messages that carry all the remaining value of the inbound message in addition to the value initially indicated in the new message (if bit 0 is not set, the gas fees are deducted from this amount); mode' = mode + 1 means that the sender wants to pay transfer fees separately; mode' = mode + 2 means that any errors arising while processing this message during the action phase should be ignored. Finally, mode' = mode + 32 means that the current account must be destroyed if its resulting balance is zero. This flag is usually employed together with +128. +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +;;; Creates an output action that would change this smart contract code to that given by cell new_code. Notice that this change will take effect only after the successful termination of the current run of the smart contract +() set_code(cell new_code) impure asm "SETCODE"; + +;;; Generates a new pseudo-random unsigned 256-bit integer x. The algorithm is as follows: if r is the old value of the random seed, considered as a 32-byte array (by constructing the big-endian representation of an unsigned 256-bit integer), then its sha512(r) is computed; the first 32 bytes of this hash are stored as the new value r' of the random seed, and the remaining 32 bytes are returned as the next random value x. +int random() impure asm "RANDU256"; +;;; Generates a new pseudo-random integer z in the range 0..range−1 (or range..−1, if range < 0). More precisely, an unsigned random value x is generated as in random; then z := x * range / 2^256 is computed. +int rand(int range) impure asm "RAND"; +;;; Returns the current random seed as an unsigned 256-bit Integer. +int get_seed() impure asm "RANDSEED"; +;;; Sets the random seed to unsigned 256-bit seed. +() set_seed(int) impure asm "SETRAND"; +;;; Mixes unsigned 256-bit integer x into the random seed r by setting the random seed to sha256 of the concatenation of two 32-byte strings: the first with the big-endian representation of the old seed r, and the second with the big-endian representation of x. +() randomize(int x) impure asm "ADDRAND"; +;;; Equivalent to randomize(cur_lt());. +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +;;; Checks whether the data parts of two slices coinside +int equal_slice_bits (slice a, slice b) asm "SDEQ"; + +;;; Concatenates two builders +builder store_builder(builder to, builder from) asm "STBR"; + diff --git a/crypto/func/auto-tests/legacy_tests/storage/storage-contract.fc b/crypto/func/auto-tests/legacy_tests/storage/storage-contract.fc new file mode 100644 index 00000000..3dfe3ff1 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/storage/storage-contract.fc @@ -0,0 +1,266 @@ +#include "stdlib.fc"; +#include "constants.fc"; + +const CHUNK_SIZE = 64; +const fee::receipt_value = 20000000; +const fee::storage = 10000000; + + + +{- + storage#_ active:Bool + balance:Coins provider:MsgAddress + merkle_hash:uint256 file_size:uint64 next_proof_byte:uint64 + rate_per_mb_day:Coins + max_span:uint32 last_proof_time:uint32 + ^[client:MsgAddress torrent_hash:uint256] = Storage; +-} + +(slice, int) begin_parse_special(cell c) asm "x{D739} s,"; + +int check_proof(int merkle_hash, int byte_to_proof, int file_size, cell file_dict_proof) { + (slice cs, int special) = file_dict_proof.begin_parse_special(); + if (~ special) { + return false; + } + if (cs~load_uint(8) != 3) { ;; Merkle proof + return false; + } + if (cs~load_uint(256) != merkle_hash) { + return false; + } + cell file_dict = cs~load_ref(); + int key_len = 0; + while ((CHUNK_SIZE << key_len) < file_size) { + key_len += 1; + } + (slice data, int found?) = file_dict.udict_get?(key_len, byte_to_proof / CHUNK_SIZE); + if(found?) { + return true; + } + return false; +} + +() add_to_balance(int amount) impure inline_ref { + var ds = get_data().begin_parse(); + var (active, balance, residue) = (ds~load_int(1), ds~load_grams(), ds); + balance += amount; + begin_cell() + .store_int(active, 1) + .store_coins(balance) + .store_slice(residue) + .end_cell().set_data(); +} + +(slice, int) get_client_data(ds) { + ds = ds.preload_ref().begin_parse(); + return (ds~load_msg_addr(), ds~load_uint(256)); +} + +() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure { + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + + if (flags & 1) { ;; ignore all bounced messages + return (); + } + slice sender_address = cs~load_msg_addr(); + + if (in_msg_body.slice_empty?()) { + return add_to_balance(msg_value); + } + int op = in_msg_body~load_uint(32); + if (op == 0) { + return add_to_balance(msg_value); + } + + int query_id = in_msg_body~load_uint(64); + + if(op == op::offer_storage_contract) { + add_to_balance(msg_value - 2 * fee::receipt_value); + var (client, torrent_hash) = get_client_data(get_data().begin_parse()); + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(client) + .store_coins(fee::receipt_value) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op::contract_deployed, 32) + .store_uint(query_id, 64) + .store_uint(torrent_hash, 256) + .end_cell(); + send_raw_message(msg, 0); + } + + if (op == op::accept_storage_contract) { + var ds = get_data().begin_parse(); + (int active, int balance, slice provider, slice rest) = + (ds~load_int(1), ds~load_coins(), ds~load_msg_addr(), ds); + throw_unless(error::contract_already_active, ~ active); + throw_unless(error::unauthorized, equal_slice_bits(sender_address, provider)); + begin_cell() + .store_int(true, 1) + .store_coins(balance) + .store_slice(provider) + .store_slice(rest) + .end_cell().set_data(); + var (client, torrent_hash) = get_client_data(rest); + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(client) + .store_coins(fee::receipt_value) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op::storage_contract_confirmed, 32) + .store_uint(cur_lt(), 64) + .store_uint(torrent_hash, 256) + .end_cell(); + send_raw_message(msg, 0); + } + + if (op == op::close_contract) { + var ds = get_data().begin_parse(); + (int active, int balance, slice provider, slice rest) = + (ds~load_int(1), ds~load_coins(), ds~load_msg_addr(), ds); + var (client, torrent_hash) = get_client_data(rest); + throw_unless(error::unauthorized, equal_slice_bits(sender_address, provider) | equal_slice_bits(sender_address, client)); + var client_msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(client) + .store_coins(balance) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op::storage_contract_terminated, 32) + .store_uint(cur_lt(), 64) + .store_uint(torrent_hash, 256) + .end_cell(); + if(~ active) { + return send_raw_message(client_msg, 128 + 32); + } + send_raw_message(client_msg, 64); + var provider_msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(provider) + .store_coins(0) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op::storage_contract_terminated, 32) + .store_uint(cur_lt(), 64) + .store_uint(torrent_hash, 256) + .end_cell(); + return send_raw_message(provider_msg, 128 + 32); + } + + if (op == op::withdraw) { + var ds = get_data().begin_parse(); + (int active, int balance, slice provider) = (ds~load_int(1), ds~load_coins(), ds~load_msg_addr()); + throw_unless(error::contract_not_active, active); + throw_unless(error::unauthorized, equal_slice_bits(sender_address, provider)); + if(balance > 0) { + raw_reserve(balance + fee::storage, 2); + } + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(provider) + .store_coins(fee::receipt_value) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op::reward_withdrawal, 32) + .store_uint(query_id, 64) + .end_cell(); + send_raw_message(msg, 128 + 32); + } + + if (op == op::proof_storage) { + cell file_dict_proof = in_msg_body~load_ref(); + var ds = get_data().begin_parse(); + var (active, + balance, + provider, + merkle_hash, + file_size, + next_proof, + rate_per_mb_day, + max_span, + last_proof_time, + client_data) = (ds~load_int(1), + ds~load_coins(), + ds~load_msg_addr(), + ds~load_uint(256), + ds~load_uint(64), + ds~load_uint(64), + ds~load_coins(), + ds~load_uint(32), + ds~load_uint(32), + ds~load_ref()); + throw_unless(error::contract_not_active, active); + throw_unless(error::unauthorized, equal_slice_bits(sender_address, provider)); + throw_unless(error::wrong_proof, check_proof(merkle_hash, next_proof, file_size, file_dict_proof)); + next_proof = rand(file_size); + int actual_span = min(now() - last_proof_time, max_span); + int bounty = muldiv(file_size * rate_per_mb_day, actual_span, 24 * 60 * 60 * 1024 * 1024); + balance = max(0, balance - bounty); + last_proof_time = now(); + begin_cell() + .store_int(true, 1) + .store_coins(balance) + .store_slice(provider) + .store_uint(merkle_hash, 256) + .store_uint(file_size, 64) + .store_uint(next_proof, 64) + .store_coins(rate_per_mb_day) + .store_uint(max_span, 32) + .store_uint(last_proof_time, 32) + .store_ref(client_data) + .end_cell().set_data(); + + ;; Send remaining balance back + cell msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(sender_address) + .store_uint(0, 4 + 1 + 4 + 4 + 64 + 32 + 1 + 1) + .end_cell(); + send_raw_message(msg, 64 + 2); + } +} + +_ get_storage_contract_data() method_id { + var ds = get_data().begin_parse(); + var (active, + balance, + provider, + merkle_hash, + file_size, + next_proof, + rate_per_mb_day, + max_span, + last_proof_time, + rest) = (ds~load_int(1), + ds~load_coins(), + ds~load_msg_addr(), + ds~load_uint(256), + ds~load_uint(64), + ds~load_uint(64), + ds~load_coins(), + ds~load_uint(32), + ds~load_uint(32), + ds); + var (client, torrent_hash) = get_client_data(rest); + return (active, balance, provider, merkle_hash, file_size, + next_proof, rate_per_mb_day, max_span, last_proof_time, + client, torrent_hash); +} + +_ get_torrent_hash() method_id { + var (active, balance, provider, merkle_hash, file_size, + next_proof, rate_per_mb_day, max_span, last_proof_time, + client, torrent_hash) = get_storage_contract_data(); + return torrent_hash; +} + +_ is_active() method_id { + return get_data().begin_parse().preload_int(1); +} + +;; next_proof, last_proof_time, max_span +_ get_next_proof_info() method_id { + var (active, balance, provider, merkle_hash, file_size, + next_proof, rate_per_mb_day, max_span, last_proof_time, + client, torrent_hash) = get_storage_contract_data(); + return (next_proof, last_proof_time, max_span); +} diff --git a/crypto/func/auto-tests/legacy_tests/storage/storage-provider.fc b/crypto/func/auto-tests/legacy_tests/storage/storage-provider.fc new file mode 100644 index 00000000..7df5739d --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/storage/storage-provider.fc @@ -0,0 +1,228 @@ +;; Storage contract fabric + +#include "stdlib.fc"; +#include "constants.fc"; + +const min_deploy_amount = 50000000; + +cell storage_contract_code() asm """ "storage-contract-code.boc" file>B B>boc PUSHREF """; + +slice calculate_address_by_stateinit(cell state_init) { + return begin_cell().store_uint(4, 3) + .store_int(0, 8) + .store_uint(cell_hash(state_init), 256) + .end_cell() + .begin_parse(); +} + +cell build_storage_contract_stateinit(int merkle_hash, int file_size, int rate_per_mb_day, + int max_span, slice client, int torrent_hash) { + cell data = begin_cell() + .store_int(0, 1) ;; active + .store_coins(0) ;; client balance + .store_slice(my_address()) + .store_uint(merkle_hash, 256) + .store_uint(file_size, 64) + .store_uint(0, 64) ;; next_proof + .store_coins(rate_per_mb_day) + .store_uint(max_span, 32) + .store_uint(now(), 32) ;; last_proof_time + .store_ref(begin_cell() + .store_slice(client) + .store_uint(torrent_hash, 256) + .end_cell()) + .end_cell(); + + cell state_init = begin_cell() + .store_uint(0, 2) + .store_maybe_ref(storage_contract_code()) + .store_maybe_ref(data) + .store_uint(0, 1) .end_cell(); + return state_init; +} + +() deploy_storage_contract (slice client, int query_id, int file_size, int merkle_hash, int torrent_hash, + int expected_rate, int expected_max_span) impure { + var ds = get_data().begin_parse(); + var (wallet_data, + accept_new_contracts?, + rate_per_mb_day, + max_span, + minimal_file_size, + maximal_file_size) = (ds~load_bits(32 + 32 + 256), + ds~load_int(1), + ds~load_coins(), + ds~load_uint(32), + ds~load_uint(64), + ds~load_uint(64)); + throw_unless(error::no_new_contracts, accept_new_contracts?); + throw_unless(error::file_too_small, file_size >= minimal_file_size); + throw_unless(error::file_too_big, file_size <= maximal_file_size); + throw_unless(error::provider_params_changed, expected_rate == rate_per_mb_day); + throw_unless(error::provider_params_changed, expected_max_span == max_span); + cell state_init = build_storage_contract_stateinit(merkle_hash, file_size, rate_per_mb_day, + max_span, client, torrent_hash); + cell msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(calculate_address_by_stateinit(state_init)) + .store_coins(0) + .store_uint(4 + 2, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init) + .store_uint(op::offer_storage_contract, 32) + .store_uint(query_id, 64) + .end_cell(); + send_raw_message(msg, 64); +} + +() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure { + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + + if ((flags & 1) | in_msg_body.slice_empty?()) { ;; ignore all bounced and empty messages + return (); + } + slice sender_address = cs~load_msg_addr(); + + int op = in_msg_body~load_uint(32); + if (op == 0) { ;; transfer with text message + return (); + } + int query_id = in_msg_body~load_uint(64); + + if(op == op::offer_storage_contract) { + throw_unless(error::not_enough_money, msg_value >= min_deploy_amount); + ;; torrent_info piece_size:uint32 file_size:uint64 root_hash:(## 256) header_size:uint64 header_hash:(## 256) + ;; microchunk_hash:(Maybe (## 256)) description:Text = TorrentInfo; + ;; + ;; new_storage_contract#00000001 query_id:uint64 info:(^ TorrentInfo) microchunk_hash:uint256 + ;; expected_rate:Coins expected_max_span:uint32 = NewStorageContract; + cell torrent_info = in_msg_body~load_ref(); + int torrent_hash = cell_hash(torrent_info); + slice info_cs = torrent_info.begin_parse(); + info_cs~skip_bits(32); + int file_size = info_cs~load_uint(64); + int merkle_hash = in_msg_body~load_uint(256); + + int expected_rate = in_msg_body~load_coins(); + int expected_max_span = in_msg_body~load_uint(32); + deploy_storage_contract(sender_address, query_id, file_size, merkle_hash, torrent_hash, + expected_rate, expected_max_span); + return (); + } + if(op == op::storage_contract_terminated) { + return (); + } + + if(op == op::update_pubkey) { + if(~ equal_slice_bits(my_address(), sender_address)) { + return (); + } + var ds = get_data().begin_parse(); + var (seqno_subwallet, + _, + non_wallet_data) = (ds~load_bits(32 + 32), + ds~load_uint(256), + ds); + int new_pubkey = in_msg_body~load_uint(256); + set_data(begin_cell() + .store_slice(seqno_subwallet) + .store_uint(new_pubkey, 256) + .store_slice(non_wallet_data) + .end_cell()); + } + if(op == op::update_storage_params) { + if(~ equal_slice_bits(my_address(), sender_address)) { + return (); + } + var ds = get_data().begin_parse(); + var wallet_data = ds~load_bits(32 + 32 + 256); + var(accept_new_contracts?, + rate_per_mb_day, + max_span, + minimal_file_size, + maximal_file_size) = (in_msg_body~load_int(1), + in_msg_body~load_coins(), + in_msg_body~load_uint(32), + in_msg_body~load_uint(64), + in_msg_body~load_uint(64)); + set_data(begin_cell() + .store_slice(wallet_data) + .store_int(accept_new_contracts?, 1) + .store_coins(rate_per_mb_day) + .store_uint(max_span, 32) + .store_uint(minimal_file_size, 64) + .store_uint(maximal_file_size, 64) + .end_cell()); + } +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); + throw_if(35, valid_until <= now()); + var ds = get_data().begin_parse(); + var (stored_seqno, + stored_subwallet, + public_key, + non_wallet_data) = (ds~load_uint(32), + ds~load_uint(32), + ds~load_uint(256), + ds); + throw_unless(33, msg_seqno == stored_seqno); + throw_unless(34, subwallet_id == stored_subwallet); + throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key)); + accept_message(); + cs~touch(); + while (cs.slice_refs()) { + var mode = cs~load_uint(8); + send_raw_message(cs~load_ref(), mode); + } + set_data(begin_cell() + .store_uint(stored_seqno + 1, 32) + .store_uint(stored_subwallet, 32) + .store_uint(public_key, 256) + .store_slice(non_wallet_data) + .end_cell()); +} + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +int get_public_key() method_id { + var cs = get_data().begin_parse(); + cs~load_uint(64); + return cs.preload_uint(256); +} + +;; seqno, subwallet, key +_ get_wallet_params() method_id { + var ds = get_data().begin_parse(); + var (stored_seqno, stored_subwallet, public_key) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256)); + return (stored_seqno, stored_subwallet, public_key); +} + +_ get_storage_params() method_id { + var ds = get_data().begin_parse(); + var (wallet_data, + accept_new_contracts?, + rate_per_mb_day, + max_span, + minimal_file_size, + maximal_file_size) = (ds~load_bits(32 + 32 + 256), + ds~load_int(1), + ds~load_coins(), + ds~load_uint(32), + ds~load_uint(64), + ds~load_uint(64)); + return (accept_new_contracts?, rate_per_mb_day, max_span, minimal_file_size, maximal_file_size); +} + +slice get_storage_contract_address(int merkle_hash, int file_size, slice client, int torrent_hash) method_id { + var (_, rate_per_mb_day, max_span, _, _) = get_storage_params(); + cell state_init = build_storage_contract_stateinit(merkle_hash, file_size, rate_per_mb_day, max_span, client, torrent_hash); + return calculate_address_by_stateinit(state_init); +} diff --git a/crypto/func/auto-tests/legacy_tests/tact-examples/jetton_JettonDefaultWallet.code.fc b/crypto/func/auto-tests/legacy_tests/tact-examples/jetton_JettonDefaultWallet.code.fc new file mode 100644 index 00000000..522fa9e1 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/tact-examples/jetton_JettonDefaultWallet.code.fc @@ -0,0 +1,439 @@ +#include "stdlib.fc"; + +int __tact_my_balance() inline { + return pair_first(get_balance()); +} + +forall X -> X __tact_not_null(X x) { throw_if(128, null?(x)); return x; } + +global (int, slice, int, slice) __tact_context; +global cell __tact_context_sys; + +(int, slice, int, slice) __tact_context_get() inline { return __tact_context; } + +() __tact_verify_address(slice address) inline { + throw_unless(136, address.slice_bits() != 267); +} + +builder __tact_store_bool(builder b, int v) inline { + b = b.store_int(v, 1); + return b; +} + +(slice, slice) __tact_load_address(slice cs) inline { + slice raw = cs~load_msg_addr(); + __tact_verify_address(raw); + return (cs, raw); +} + +(slice, slice) __tact_load_address_opt(slice cs) inline { + slice raw = cs~load_msg_addr(); + if (raw.preload_uint(2) != 0) { + __tact_verify_address(raw); + return (cs, raw); + } else { + return (cs, null()); + } +} + +builder __tact_store_address(builder b, slice address) inline { + __tact_verify_address(address); + b = b.store_slice(address); + return b; +} + +builder __tact_store_address_opt(builder b, slice address) inline { + if (null?(address)) { + b = b.store_uint(0, 2); + return b; + } else { + return __tact_store_address(b, address); + } +} + +slice __tact_create_address(int chain, int hash) inline { + var b = begin_cell(); + b = b.store_uint(2, 2); + b = b.store_uint(0, 1); + b = b.store_int(chain, 8); + b = b.store_uint(hash, 256); + return b.end_cell().begin_parse(); +} + +slice __tact_compute_contract_address(int chain, cell code, cell data) inline { + var b = begin_cell(); + b = b.store_uint(0, 2); + b = b.store_uint(3, 2); + b = b.store_uint(0, 1); + b = b.store_ref(code); + b = b.store_ref(data); + var hash = cell_hash(b.end_cell()); + return __tact_create_address(chain, hash); +} + +int __tact_address_eq(slice a, slice b) inline { + return equal_slice_bits(a, b); +} + +int __tact_address_neq(slice a, slice b) inline { + return ~ equal_slice_bits(a, b); +} + +cell __tact_dict_set_code(cell dict, int id, cell code) inline { + return udict_set_ref(dict, 16, id, code); +} + +cell __tact_dict_get_code(cell dict, int id) inline { + var (data, ok) = udict_get_ref?(dict, 16, id); + throw_unless(135, ok); + return data; +} + +(slice, ((int, int, slice, slice, cell, int, slice))) __gen_read_TokenTransfer(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 260734629); + var v'queryId = sc_0~load_uint(64); + var v'amount = sc_0~load_coins(); + var v'destination = sc_0~__tact_load_address(); + var v'responseDestination = sc_0~__tact_load_address_opt(); + var v'customPayload = sc_0~load_int(1) ? sc_0~load_ref() : null(); + var v'forwardTonAmount = sc_0~load_coins(); + var v'forwardPayload = sc_0; + return (sc_0, (v'queryId, v'amount, v'destination, v'responseDestination, v'customPayload, v'forwardTonAmount, v'forwardPayload)); +} + +builder __gen_write_TokenTransferInternal(builder build_0, (int, int, slice, slice, int, slice) v) inline_ref { + var (v'queryId, v'amount, v'from, v'responseAddress, v'forwardTonAmount, v'forwardPayload) = v; + build_0 = store_uint(build_0, 395134233, 32); + build_0 = build_0.store_uint(v'queryId, 64); + build_0 = build_0.store_coins(v'amount); + build_0 = __tact_store_address(build_0, v'from); + build_0 = __tact_store_address_opt(build_0, v'responseAddress); + build_0 = build_0.store_coins(v'forwardTonAmount); + build_0 = build_0.store_slice(v'forwardPayload); + return build_0; +} + +cell __gen_writecell_TokenTransferInternal((int, int, slice, slice, int, slice) v) inline_ref { + return __gen_write_TokenTransferInternal(begin_cell(), v).end_cell(); +} + +(slice, ((int, int, slice, slice, int, slice))) __gen_read_TokenTransferInternal(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 395134233); + var v'queryId = sc_0~load_uint(64); + var v'amount = sc_0~load_coins(); + var v'from = sc_0~__tact_load_address(); + var v'responseAddress = sc_0~__tact_load_address_opt(); + var v'forwardTonAmount = sc_0~load_coins(); + var v'forwardPayload = sc_0; + return (sc_0, (v'queryId, v'amount, v'from, v'responseAddress, v'forwardTonAmount, v'forwardPayload)); +} + +builder __gen_write_TokenNotification(builder build_0, (int, int, slice, slice) v) inline_ref { + var (v'queryId, v'amount, v'from, v'forwardPayload) = v; + build_0 = store_uint(build_0, 1935855772, 32); + build_0 = build_0.store_uint(v'queryId, 64); + build_0 = build_0.store_coins(v'amount); + build_0 = __tact_store_address(build_0, v'from); + build_0 = build_0.store_slice(v'forwardPayload); + return build_0; +} + +cell __gen_writecell_TokenNotification((int, int, slice, slice) v) inline_ref { + return __gen_write_TokenNotification(begin_cell(), v).end_cell(); +} + +(slice, ((int, int, slice, slice))) __gen_read_TokenBurn(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 1499400124); + var v'queryId = sc_0~load_uint(64); + var v'amount = sc_0~load_coins(); + var v'owner = sc_0~__tact_load_address(); + var v'responseAddress = sc_0~__tact_load_address_opt(); + return (sc_0, (v'queryId, v'amount, v'owner, v'responseAddress)); +} + +builder __gen_write_TokenBurnNotification(builder build_0, (int, int, slice, slice) v) inline_ref { + var (v'queryId, v'amount, v'owner, v'responseAddress) = v; + build_0 = store_uint(build_0, 2078119902, 32); + build_0 = build_0.store_uint(v'queryId, 64); + build_0 = build_0.store_coins(v'amount); + build_0 = __tact_store_address(build_0, v'owner); + build_0 = __tact_store_address_opt(build_0, v'responseAddress); + return build_0; +} + +cell __gen_writecell_TokenBurnNotification((int, int, slice, slice) v) inline_ref { + return __gen_write_TokenBurnNotification(begin_cell(), v).end_cell(); +} + +builder __gen_write_TokenExcesses(builder build_0, (int) v) inline_ref { + var (v'queryId) = v; + build_0 = store_uint(build_0, 3576854235, 32); + build_0 = build_0.store_uint(v'queryId, 64); + return build_0; +} + +cell __gen_writecell_TokenExcesses((int) v) inline_ref { + return __gen_write_TokenExcesses(begin_cell(), v).end_cell(); +} + +builder __gen_write_JettonDefaultWallet(builder build_0, (int, slice, slice) v) inline_ref { + var (v'balance, v'owner, v'master) = v; + build_0 = build_0.store_int(v'balance, 257); + build_0 = __tact_store_address(build_0, v'owner); + build_0 = __tact_store_address(build_0, v'master); + return build_0; +} + +(slice, ((int, slice, slice))) __gen_read_JettonDefaultWallet(slice sc_0) inline_ref { + var v'balance = sc_0~load_int(257); + var v'owner = sc_0~__tact_load_address(); + var v'master = sc_0~__tact_load_address(); + return (sc_0, (v'balance, v'owner, v'master)); +} + +_ __gen_StateInit_get_code((cell, cell) v) inline { + var (v'code, v'data) = v; + return v'code; +} + +(int, slice, slice, cell) __gen_JettonWalletData_to_external(((int, slice, slice, cell)) v) { + var (v'balance, v'owner, v'master, v'walletCode) = v; + return (v'balance, v'owner, v'master, v'walletCode); +} + +(int, slice, slice) __gen_load_JettonDefaultWallet() inline_ref { + slice sc = get_data().begin_parse(); + __tact_context_sys = sc~load_ref(); + return sc~__gen_read_JettonDefaultWallet(); +} + +() __gen_store_JettonDefaultWallet((int, slice, slice) v) impure inline_ref { + builder b = begin_cell(); + b = b.store_ref(__tact_context_sys); + b = __gen_write_JettonDefaultWallet(b, v); + set_data(b.end_cell()); +} + +slice $contractAddress((cell, cell) $s) impure { + var (($s'code, $s'data)) = $s; + return __tact_compute_contract_address(0, $s'code, $s'data); +} + +() $send((int, slice, int, int, cell, cell, cell) $params) impure { + var (($params'bounce, $params'to, $params'value, $params'mode, $params'body, $params'code, $params'data)) = $params; + builder $b = begin_cell(); + $b = store_int($b, 1, 2); + $b = __tact_store_bool($b, $params'bounce); + $b = store_int($b, 0, 3); + $b = __tact_store_address($b, $params'to); + $b = store_coins($b, $params'value); + $b = store_int($b, 0, ((((1 + 4) + 4) + 64) + 32)); + if (((~ null?($params'code)) | (~ null?($params'data)))) { + $b = __tact_store_bool($b, true); + builder $bc = begin_cell(); + $bc = __tact_store_bool($bc, false); + $bc = __tact_store_bool($bc, false); + if ((~ null?($params'code))) { + $bc = __tact_store_bool($bc, true); + $bc = store_ref($bc, __tact_not_null($params'code)); + } else { + $bc = __tact_store_bool($bc, false); + } + if ((~ null?($params'data))) { + $bc = __tact_store_bool($bc, true); + $bc = store_ref($bc, __tact_not_null($params'data)); + } else { + $bc = __tact_store_bool($bc, false); + } + $bc = __tact_store_bool($bc, false); + $b = __tact_store_bool($b, true); + $b = store_ref($b, end_cell($bc)); + } else { + $b = __tact_store_bool($b, false); + } + cell $body = $params'body; + if ((~ null?($body))) { + $b = __tact_store_bool($b, true); + $b = store_ref($b, __tact_not_null($body)); + } else { + $b = __tact_store_bool($b, false); + } + cell $c = end_cell($b); + send_raw_message($c, $params'mode); +} + +int $__gen_Context_readForwardFee((int, slice, int, slice) $self) impure { + var (($self'bounced, $self'sender, $self'value, $self'raw)) = $self; + var (($self'bounced, $self'sender, $self'value, $self'raw)) = $self; + slice $sc = $self'raw; + $sc~load_coins(); + $sc~skip_bits(1); + $sc~load_coins(); + return (($sc~load_coins() * 3) / 2); +} + +cell $__gen_JettonDefaultWallet_init(cell sys', slice $master, slice $owner) { + var (($self'balance, $self'owner, $self'master)) = (null(), null(), null()); + $self'balance = 0; + $self'owner = $owner; + $self'master = $master; + var b' = begin_cell(); + b' = b'.store_ref(sys'); + b' = __gen_write_JettonDefaultWallet(b', ($self'balance, $self'owner, $self'master)); + return b'.end_cell(); +} + +(cell, cell) $__gen_JettonDefaultWallet_init_child(cell sys', slice $master, slice $owner) { + slice sc' = sys'.begin_parse(); + cell source = sc'~load_dict(); + cell contracts = new_dict(); + + ;; Contract Code: JettonDefaultWallet + cell mine = __tact_dict_get_code(source, 55471); + contracts = __tact_dict_set_code(contracts, 55471, mine); + cell sys = begin_cell().store_dict(contracts).end_cell(); + return (mine, $__gen_JettonDefaultWallet_init(sys, $master, $owner)); +} + +(int, slice, slice, cell) $__gen_JettonDefaultWallet_get_wallet_data((int, slice, slice) $self) impure { + var (($self'balance, $self'owner, $self'master)) = $self; + return ($self'balance, $self'owner, $self'master, __gen_StateInit_get_code($__gen_JettonDefaultWallet_init_child(__tact_context_sys, $self'master, $self'owner))); +} + +_ $__gen_get_get_wallet_data() method_id(97026) { + var self = __gen_load_JettonDefaultWallet(); + var res = $__gen_JettonDefaultWallet_get_wallet_data(self); + return __gen_JettonWalletData_to_external(res); +} + +(((int, slice, slice)), ()) $__gen_JettonDefaultWallet_receive_TokenTransfer((int, slice, slice) $self, (int, int, slice, slice, cell, int, slice) $msg) impure { + var ($self'balance, $self'owner, $self'master) = $self; + var ($msg'queryId, $msg'amount, $msg'destination, $msg'responseDestination, $msg'customPayload, $msg'forwardTonAmount, $msg'forwardPayload) = $msg; + var ($ctx'bounced, $ctx'sender, $ctx'value, $ctx'raw) = __tact_context_get(); + throw_unless(4429, __tact_address_eq($ctx'sender, $self'owner)); + $self'balance = ($self'balance - $msg'amount); + throw_unless(62972, ($self'balance >= 0)); + int $fwdFee = $__gen_Context_readForwardFee(($ctx'bounced, $ctx'sender, $ctx'value, $ctx'raw)); + int $fwdCount = 1; + if (($msg'forwardTonAmount > 0)) { + $fwdCount = 2; + } + throw_unless(16059, ($ctx'value > ((($fwdCount * $fwdFee) + (2 * 10000000)) + 10000000))); + var ($init'code, $init'data) = $__gen_JettonDefaultWallet_init_child(__tact_context_sys, $self'master, $msg'destination); + slice $walletAddress = $contractAddress(($init'code, $init'data)); + $send((true, $walletAddress, 0, 64, __gen_writecell_TokenTransferInternal(($msg'queryId, $msg'amount, $self'owner, $self'owner, $msg'forwardTonAmount, $msg'forwardPayload)), $init'code, $init'data)); + return (($self'balance, $self'owner, $self'master), ()); +} + +(((int, slice, slice)), ()) $__gen_JettonDefaultWallet_receive_TokenTransferInternal((int, slice, slice) $self, (int, int, slice, slice, int, slice) $msg) impure { + var ($self'balance, $self'owner, $self'master) = $self; + var ($msg'queryId, $msg'amount, $msg'from, $msg'responseAddress, $msg'forwardTonAmount, $msg'forwardPayload) = $msg; + var ($ctx'bounced, $ctx'sender, $ctx'value, $ctx'raw) = __tact_context_get(); + if (__tact_address_neq($ctx'sender, $self'master)) { + var ($sinit'code, $sinit'data) = $__gen_JettonDefaultWallet_init_child(__tact_context_sys, $self'master, $msg'from); + throw_unless(4429, __tact_address_eq($contractAddress(($sinit'code, $sinit'data)), $ctx'sender)); + } + $self'balance = ($self'balance + $msg'amount); + throw_unless(62972, ($self'balance >= 0)); + int $msgValue = $ctx'value; + int $tonBalanceBeforeMsg = (__tact_my_balance() - $msgValue); + int $storageFee = (10000000 - min($tonBalanceBeforeMsg, 10000000)); + $msgValue = ($msgValue - ($storageFee + 10000000)); + if (($msg'forwardTonAmount > 0)) { + int $fwdFee = $__gen_Context_readForwardFee(($ctx'bounced, $ctx'sender, $ctx'value, $ctx'raw)); + $msgValue = ($msgValue - ($msg'forwardTonAmount + $fwdFee)); + $send((false, $self'owner, $msg'forwardTonAmount, 0, __gen_writecell_TokenNotification(($msg'queryId, $msg'amount, $msg'from, $msg'forwardPayload)), null(), null())); + } + if (((~ null?($msg'responseAddress)) & ($msgValue > 0))) { + $send((false, __tact_not_null($msg'responseAddress), $msgValue, 0, __gen_writecell_TokenExcesses(($msg'queryId)), null(), null())); + } + return (($self'balance, $self'owner, $self'master), ()); +} + +(((int, slice, slice)), ()) $__gen_JettonDefaultWallet_receive_TokenBurn((int, slice, slice) $self, (int, int, slice, slice) $msg) impure { + var ($self'balance, $self'owner, $self'master) = $self; + var ($msg'queryId, $msg'amount, $msg'owner, $msg'responseAddress) = $msg; + var ($ctx'bounced, $ctx'sender, $ctx'value, $ctx'raw) = __tact_context_get(); + throw_unless(4429, __tact_address_eq($ctx'sender, $self'owner)); + $self'balance = ($self'balance - $msg'amount); + throw_unless(62972, ($self'balance >= 0)); + int $fwdFee = $__gen_Context_readForwardFee(($ctx'bounced, $ctx'sender, $ctx'value, $ctx'raw)); + throw_unless(16059, ($ctx'value > (($fwdFee + (2 * 10000000)) + 10000000))); + $send((true, $self'master, 0, 64, __gen_writecell_TokenBurnNotification(($msg'queryId, $msg'amount, $self'owner, $self'owner)), null(), null())); + return (($self'balance, $self'owner, $self'master), ()); +} + +((int, slice, slice), ()) $__gen_JettonDefaultWallet_receive_bounced((int, slice, slice) $self, slice $msg) impure { + var ($self'balance, $self'owner, $self'master) = $self; + $msg~skip_bits(32); + int $op = $msg~load_uint(32); + int $queryId = $msg~load_uint(64); + int $jettonAmount = $msg~load_coins(); + throw_unless(13650, (($op == 395134233) | ($op == 2078119902))); + $self'balance = ($self'balance + $jettonAmount); + return (($self'balance, $self'owner, $self'master), ()); +} + + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + + ;; Parse incoming message + int op = 0; + if (slice_bits(in_msg) >= 32) { + op = in_msg.preload_uint(32); + } + var cs = in_msg_cell.begin_parse(); + var msg_flags = cs~load_uint(4); + var msg_bounced = ((msg_flags & 1) == 1 ? true : false); + slice msg_sender_addr = cs~load_msg_addr(); + __tact_context = (msg_bounced, msg_sender_addr, msg_value, cs); + + ;; Handle bounced messages + if (msg_bounced) { + var self = __gen_load_JettonDefaultWallet(); + self~$__gen_JettonDefaultWallet_receive_bounced(in_msg); + __gen_store_JettonDefaultWallet(self); + return (); + } + + ;; Receive TokenTransfer message + if (op == 260734629) { + var self = __gen_load_JettonDefaultWallet(); + var msg = in_msg~__gen_read_TokenTransfer(); + self~$__gen_JettonDefaultWallet_receive_TokenTransfer(msg); + __gen_store_JettonDefaultWallet(self); + return (); + } + + ;; Receive TokenTransferInternal message + if (op == 395134233) { + var self = __gen_load_JettonDefaultWallet(); + var msg = in_msg~__gen_read_TokenTransferInternal(); + self~$__gen_JettonDefaultWallet_receive_TokenTransferInternal(msg); + __gen_store_JettonDefaultWallet(self); + return (); + } + + ;; Receive TokenBurn message + if (op == 1499400124) { + var self = __gen_load_JettonDefaultWallet(); + var msg = in_msg~__gen_read_TokenBurn(); + self~$__gen_JettonDefaultWallet_receive_TokenBurn(msg); + __gen_store_JettonDefaultWallet(self); + return (); + } + + throw(130); +} + +_ supported_interfaces() method_id { + return ( + "org.ton.introspection.v0"H >> 128, + "org.ton.abi.ipfs.v0"H >> 128, + "org.ton.jetton.wallet"H >> 128 + ); +} + +_ get_abi_ipfs() { + return "ipfs://QmXBfqbQzeN1uT55MyYpwhU9RV47Sq3quVt3qFLgWH8NhD"; +} diff --git a/crypto/func/auto-tests/legacy_tests/tact-examples/jetton_SampleJetton.code.fc b/crypto/func/auto-tests/legacy_tests/tact-examples/jetton_SampleJetton.code.fc new file mode 100644 index 00000000..30bff4e1 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/tact-examples/jetton_SampleJetton.code.fc @@ -0,0 +1,440 @@ +#include "stdlib.fc"; + +forall X -> X __tact_not_null(X x) { throw_if(128, null?(x)); return x; } + +global (int, slice, int, slice) __tact_context; +global cell __tact_context_sys; + +(int, slice, int, slice) __tact_context_get() inline { return __tact_context; } + +() __tact_verify_address(slice address) inline { + throw_unless(136, address.slice_bits() != 267); +} + +builder __tact_store_bool(builder b, int v) inline { + b = b.store_int(v, 1); + return b; +} + +(slice, slice) __tact_load_address(slice cs) inline { + slice raw = cs~load_msg_addr(); + __tact_verify_address(raw); + return (cs, raw); +} + +(slice, slice) __tact_load_address_opt(slice cs) inline { + slice raw = cs~load_msg_addr(); + if (raw.preload_uint(2) != 0) { + __tact_verify_address(raw); + return (cs, raw); + } else { + return (cs, null()); + } +} + +builder __tact_store_address(builder b, slice address) inline { + __tact_verify_address(address); + b = b.store_slice(address); + return b; +} + +builder __tact_store_address_opt(builder b, slice address) inline { + if (null?(address)) { + b = b.store_uint(0, 2); + return b; + } else { + return __tact_store_address(b, address); + } +} + +slice __tact_create_address(int chain, int hash) inline { + var b = begin_cell(); + b = b.store_uint(2, 2); + b = b.store_uint(0, 1); + b = b.store_int(chain, 8); + b = b.store_uint(hash, 256); + return b.end_cell().begin_parse(); +} + +slice __tact_compute_contract_address(int chain, cell code, cell data) inline { + var b = begin_cell(); + b = b.store_uint(0, 2); + b = b.store_uint(3, 2); + b = b.store_uint(0, 1); + b = b.store_ref(code); + b = b.store_ref(data); + var hash = cell_hash(b.end_cell()); + return __tact_create_address(chain, hash); +} + +int __tact_address_eq(slice a, slice b) inline { + return equal_slice_bits(a, b); +} + +cell __tact_dict_set_code(cell dict, int id, cell code) inline { + return udict_set_ref(dict, 16, id, code); +} + +cell __tact_dict_get_code(cell dict, int id) inline { + var (data, ok) = udict_get_ref?(dict, 16, id); + throw_unless(135, ok); + return data; +} + +builder __gen_write_TokenTransferInternal(builder build_0, (int, int, slice, slice, int, slice) v) inline_ref { + var (v'queryId, v'amount, v'from, v'responseAddress, v'forwardTonAmount, v'forwardPayload) = v; + build_0 = store_uint(build_0, 395134233, 32); + build_0 = build_0.store_uint(v'queryId, 64); + build_0 = build_0.store_coins(v'amount); + build_0 = __tact_store_address(build_0, v'from); + build_0 = __tact_store_address_opt(build_0, v'responseAddress); + build_0 = build_0.store_coins(v'forwardTonAmount); + build_0 = build_0.store_slice(v'forwardPayload); + return build_0; +} + +cell __gen_writecell_TokenTransferInternal((int, int, slice, slice, int, slice) v) inline_ref { + return __gen_write_TokenTransferInternal(begin_cell(), v).end_cell(); +} + +(slice, ((int, int, slice, slice))) __gen_read_TokenBurnNotification(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 2078119902); + var v'queryId = sc_0~load_uint(64); + var v'amount = sc_0~load_coins(); + var v'owner = sc_0~__tact_load_address(); + var v'responseAddress = sc_0~__tact_load_address_opt(); + return (sc_0, (v'queryId, v'amount, v'owner, v'responseAddress)); +} + +builder __gen_write_TokenExcesses(builder build_0, (int) v) inline_ref { + var (v'queryId) = v; + build_0 = store_uint(build_0, 3576854235, 32); + build_0 = build_0.store_uint(v'queryId, 64); + return build_0; +} + +cell __gen_writecell_TokenExcesses((int) v) inline_ref { + return __gen_write_TokenExcesses(begin_cell(), v).end_cell(); +} + +(slice, ((cell))) __gen_read_TokenUpdateContent(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 201882270); + var v'content = sc_0~load_int(1) ? sc_0~load_ref() : null(); + return (sc_0, (v'content)); +} + +(slice, ((int))) __gen_read_Mint(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 33240155); + var v'amount = sc_0~load_int(257); + return (sc_0, (v'amount)); +} + +builder __gen_write_JettonDefaultWallet(builder build_0, (int, slice, slice) v) inline_ref { + var (v'balance, v'owner, v'master) = v; + build_0 = build_0.store_int(v'balance, 257); + build_0 = __tact_store_address(build_0, v'owner); + build_0 = __tact_store_address(build_0, v'master); + return build_0; +} + +builder __gen_write_SampleJetton(builder build_0, (int, slice, cell, int) v) inline_ref { + var (v'totalSupply, v'owner, v'content, v'mintable) = v; + build_0 = build_0.store_coins(v'totalSupply); + build_0 = __tact_store_address(build_0, v'owner); + build_0 = ~ null?(v'content) ? build_0.store_int(true, 1).store_ref(v'content) : build_0.store_int(false, 1); + build_0 = build_0.store_int(v'mintable, 1); + return build_0; +} + +(slice, ((int, slice, cell, int))) __gen_read_SampleJetton(slice sc_0) inline_ref { + var v'totalSupply = sc_0~load_coins(); + var v'owner = sc_0~__tact_load_address(); + var v'content = sc_0~load_int(1) ? sc_0~load_ref() : null(); + var v'mintable = sc_0~load_int(1); + return (sc_0, (v'totalSupply, v'owner, v'content, v'mintable)); +} + +_ __gen_StateInit_get_code((cell, cell) v) inline { + var (v'code, v'data) = v; + return v'code; +} + +_ __gen_Context_get_sender((int, slice, int, slice) v) inline { + var (v'bounced, v'sender, v'value, v'raw) = v; + return v'sender; +} + +(int, int, slice, cell, cell) __gen_JettonData_to_external(((int, int, slice, cell, cell)) v) { + var (v'totalSupply, v'mintable, v'owner, v'content, v'walletCode) = v; + return (v'totalSupply, v'mintable, v'owner, v'content, v'walletCode); +} + +(int, slice, cell, int) __gen_load_SampleJetton() inline_ref { + slice sc = get_data().begin_parse(); + __tact_context_sys = sc~load_ref(); + return sc~__gen_read_SampleJetton(); +} + +() __gen_store_SampleJetton((int, slice, cell, int) v) impure inline_ref { + builder b = begin_cell(); + b = b.store_ref(__tact_context_sys); + b = __gen_write_SampleJetton(b, v); + set_data(b.end_cell()); +} + +cell $emptyCell() impure { + return end_cell(begin_cell()); +} + +slice $__gen_Cell_asSlice(cell $self) impure { + var ($self) = $self; + return begin_parse($self); +} + +slice $emptySlice() impure { + return $__gen_Cell_asSlice($emptyCell()); +} + +slice $contractAddress((cell, cell) $s) impure { + var (($s'code, $s'data)) = $s; + return __tact_compute_contract_address(0, $s'code, $s'data); +} + +() $send((int, slice, int, int, cell, cell, cell) $params) impure { + var (($params'bounce, $params'to, $params'value, $params'mode, $params'body, $params'code, $params'data)) = $params; + builder $b = begin_cell(); + $b = store_int($b, 1, 2); + $b = __tact_store_bool($b, $params'bounce); + $b = store_int($b, 0, 3); + $b = __tact_store_address($b, $params'to); + $b = store_coins($b, $params'value); + $b = store_int($b, 0, ((((1 + 4) + 4) + 64) + 32)); + if (((~ null?($params'code)) | (~ null?($params'data)))) { + $b = __tact_store_bool($b, true); + builder $bc = begin_cell(); + $bc = __tact_store_bool($bc, false); + $bc = __tact_store_bool($bc, false); + if ((~ null?($params'code))) { + $bc = __tact_store_bool($bc, true); + $bc = store_ref($bc, __tact_not_null($params'code)); + } else { + $bc = __tact_store_bool($bc, false); + } + if ((~ null?($params'data))) { + $bc = __tact_store_bool($bc, true); + $bc = store_ref($bc, __tact_not_null($params'data)); + } else { + $bc = __tact_store_bool($bc, false); + } + $bc = __tact_store_bool($bc, false); + $b = __tact_store_bool($b, true); + $b = store_ref($b, end_cell($bc)); + } else { + $b = __tact_store_bool($b, false); + } + cell $body = $params'body; + if ((~ null?($body))) { + $b = __tact_store_bool($b, true); + $b = store_ref($b, __tact_not_null($body)); + } else { + $b = __tact_store_bool($b, false); + } + cell $c = end_cell($b); + send_raw_message($c, $params'mode); +} + +cell $__gen_JettonDefaultWallet_init(cell sys', slice $master, slice $owner) { + var (($self'balance, $self'owner, $self'master)) = (null(), null(), null()); + $self'balance = 0; + $self'owner = $owner; + $self'master = $master; + var b' = begin_cell(); + b' = b'.store_ref(sys'); + b' = __gen_write_JettonDefaultWallet(b', ($self'balance, $self'owner, $self'master)); + return b'.end_cell(); +} + +(cell, cell) $__gen_JettonDefaultWallet_init_child(cell sys', slice $master, slice $owner) { + slice sc' = sys'.begin_parse(); + cell source = sc'~load_dict(); + cell contracts = new_dict(); + + ;; Contract Code: JettonDefaultWallet + cell mine = __tact_dict_get_code(source, 55471); + contracts = __tact_dict_set_code(contracts, 55471, mine); + cell sys = begin_cell().store_dict(contracts).end_cell(); + return (mine, $__gen_JettonDefaultWallet_init(sys, $master, $owner)); +} + +((int, slice, cell, int), (cell, cell)) $__gen_SampleJetton_getJettonWalletInit((int, slice, cell, int) $self, slice $address) impure { + var (($self'totalSupply, $self'owner, $self'content, $self'mintable)) = $self; + return (($self'totalSupply, $self'owner, $self'content, $self'mintable), $__gen_JettonDefaultWallet_init_child(__tact_context_sys, my_address(), $address)); +} + +slice $__gen_SampleJetton_get_wallet_address((int, slice, cell, int) $self, slice $owner) impure { + var (($self'totalSupply, $self'owner, $self'content, $self'mintable)) = $self; + var ($winit'code, $winit'data) = ($self'totalSupply, $self'owner, $self'content, $self'mintable)~$__gen_SampleJetton_getJettonWalletInit($owner); + return $contractAddress(($winit'code, $winit'data)); +} + +_ $__gen_get_get_wallet_address(slice $$owner) method_id(103289) { + slice $owner = $$owner; + var self = __gen_load_SampleJetton(); + var res = $__gen_SampleJetton_get_wallet_address(self, $owner); + return res; +} + +(int, int, slice, cell, cell) $__gen_SampleJetton_get_jetton_data((int, slice, cell, int) $self) impure { + var (($self'totalSupply, $self'owner, $self'content, $self'mintable)) = $self; + cell $code = __gen_StateInit_get_code(($self'totalSupply, $self'owner, $self'content, $self'mintable)~$__gen_SampleJetton_getJettonWalletInit(my_address())); + return ($self'totalSupply, $self'mintable, $self'owner, $self'content, $code); +} + +_ $__gen_get_get_jetton_data() method_id(106029) { + var self = __gen_load_SampleJetton(); + var res = $__gen_SampleJetton_get_jetton_data(self); + return __gen_JettonData_to_external(res); +} + +((int, slice, cell, int), ()) $__gen_SampleJetton_mint((int, slice, cell, int) $self, slice $to, int $amount, slice $responseAddress) impure { + var (($self'totalSupply, $self'owner, $self'content, $self'mintable)) = $self; + $self'totalSupply = ($self'totalSupply + $amount); + var ($winit'code, $winit'data) = ($self'totalSupply, $self'owner, $self'content, $self'mintable)~$__gen_SampleJetton_getJettonWalletInit($to); + slice $walletAddress = $contractAddress(($winit'code, $winit'data)); + $send((false, $walletAddress, 0, 64, __gen_writecell_TokenTransferInternal((0, $amount, my_address(), $responseAddress, 0, $emptySlice())), $winit'code, $winit'data)); + return (($self'totalSupply, $self'owner, $self'content, $self'mintable), ()); +} + +((int, slice, cell, int), ()) $__gen_SampleJetton_requireWallet((int, slice, cell, int) $self, slice $owner) impure { + var (($self'totalSupply, $self'owner, $self'content, $self'mintable)) = $self; + var ($ctx'bounced, $ctx'sender, $ctx'value, $ctx'raw) = __tact_context_get(); + var ($winit'code, $winit'data) = ($self'totalSupply, $self'owner, $self'content, $self'mintable)~$__gen_SampleJetton_getJettonWalletInit($owner); + throw_unless(4429, __tact_address_eq($contractAddress(($winit'code, $winit'data)), $ctx'sender)); + return (($self'totalSupply, $self'owner, $self'content, $self'mintable), ()); +} + +((int, slice, cell, int), ()) $__gen_SampleJetton_requireOwner((int, slice, cell, int) $self) impure { + var (($self'totalSupply, $self'owner, $self'content, $self'mintable)) = $self; + throw_unless(132, __tact_address_eq(__gen_Context_get_sender(__tact_context_get()), $self'owner)); + return (($self'totalSupply, $self'owner, $self'content, $self'mintable), ()); +} + +slice $__gen_SampleJetton_owner((int, slice, cell, int) $self) impure { + var (($self'totalSupply, $self'owner, $self'content, $self'mintable)) = $self; + return $self'owner; +} + +_ $__gen_get_owner() method_id(83229) { + var self = __gen_load_SampleJetton(); + var res = $__gen_SampleJetton_owner(self); + return res; +} + +(((int, slice, cell, int)), ()) $__gen_SampleJetton_receive_Mint((int, slice, cell, int) $self, (int) $msg) impure { + var ($self'totalSupply, $self'owner, $self'content, $self'mintable) = $self; + var ($msg'amount) = $msg; + var ($ctx'bounced, $ctx'sender, $ctx'value, $ctx'raw) = __tact_context_get(); + ($self'totalSupply, $self'owner, $self'content, $self'mintable)~$__gen_SampleJetton_mint($ctx'sender, $msg'amount, $ctx'sender); + return (($self'totalSupply, $self'owner, $self'content, $self'mintable), ()); +} + +((int, slice, cell, int), ()) $__gen_SampleJetton_receive_comment_cd0d986cb1a2f468ae7089f4fc3162c116e5f53fbd11a6839f52dbf5040830b2((int, slice, cell, int) $self) impure { + var ($self'totalSupply, $self'owner, $self'content, $self'mintable) = $self; + var ($ctx'bounced, $ctx'sender, $ctx'value, $ctx'raw) = __tact_context_get(); + ($self'totalSupply, $self'owner, $self'content, $self'mintable)~$__gen_SampleJetton_mint($ctx'sender, 1000000000, $ctx'sender); + return (($self'totalSupply, $self'owner, $self'content, $self'mintable), ()); +} + +(((int, slice, cell, int)), ()) $__gen_SampleJetton_receive_TokenUpdateContent((int, slice, cell, int) $self, (cell) $msg) impure { + var ($self'totalSupply, $self'owner, $self'content, $self'mintable) = $self; + var ($msg'content) = $msg; + ($self'totalSupply, $self'owner, $self'content, $self'mintable)~$__gen_SampleJetton_requireOwner(); + $self'content = $msg'content; + return (($self'totalSupply, $self'owner, $self'content, $self'mintable), ()); +} + +(((int, slice, cell, int)), ()) $__gen_SampleJetton_receive_TokenBurnNotification((int, slice, cell, int) $self, (int, int, slice, slice) $msg) impure { + var ($self'totalSupply, $self'owner, $self'content, $self'mintable) = $self; + var ($msg'queryId, $msg'amount, $msg'owner, $msg'responseAddress) = $msg; + ($self'totalSupply, $self'owner, $self'content, $self'mintable)~$__gen_SampleJetton_requireWallet($msg'owner); + $self'totalSupply = ($self'totalSupply - $msg'amount); + if ((~ null?($msg'responseAddress))) { + $send((false, $msg'responseAddress, 0, (64 + 2), __gen_writecell_TokenExcesses(($msg'queryId)), null(), null())); + } + return (($self'totalSupply, $self'owner, $self'content, $self'mintable), ()); +} + + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + + ;; Parse incoming message + int op = 0; + if (slice_bits(in_msg) >= 32) { + op = in_msg.preload_uint(32); + } + var cs = in_msg_cell.begin_parse(); + var msg_flags = cs~load_uint(4); + var msg_bounced = ((msg_flags & 1) == 1 ? true : false); + slice msg_sender_addr = cs~load_msg_addr(); + __tact_context = (msg_bounced, msg_sender_addr, msg_value, cs); + + ;; Handle bounced messages + if (msg_bounced) { + return (); + } + + ;; Receive Mint message + if (op == 33240155) { + var self = __gen_load_SampleJetton(); + var msg = in_msg~__gen_read_Mint(); + self~$__gen_SampleJetton_receive_Mint(msg); + __gen_store_SampleJetton(self); + return (); + } + + ;; Receive TokenUpdateContent message + if (op == 201882270) { + var self = __gen_load_SampleJetton(); + var msg = in_msg~__gen_read_TokenUpdateContent(); + self~$__gen_SampleJetton_receive_TokenUpdateContent(msg); + __gen_store_SampleJetton(self); + return (); + } + + ;; Receive TokenBurnNotification message + if (op == 2078119902) { + var self = __gen_load_SampleJetton(); + var msg = in_msg~__gen_read_TokenBurnNotification(); + self~$__gen_SampleJetton_receive_TokenBurnNotification(msg); + __gen_store_SampleJetton(self); + return (); + } + + ;; Text Receivers + if (op == 0) { + var text_op = slice_hash(in_msg); + + ;; Receive "Mint!" message + if (text_op == 0xcd0d986cb1a2f468ae7089f4fc3162c116e5f53fbd11a6839f52dbf5040830b2) { + var self = __gen_load_SampleJetton(); + self~$__gen_SampleJetton_receive_comment_cd0d986cb1a2f468ae7089f4fc3162c116e5f53fbd11a6839f52dbf5040830b2(); + __gen_store_SampleJetton(self); + return (); + } + } + + throw(130); +} + +_ supported_interfaces() method_id { + return ( + "org.ton.introspection.v0"H >> 128, + "org.ton.abi.ipfs.v0"H >> 128, + "org.ton.jetton.master"H >> 128, + "org.ton.ownable"H >> 128 + ); +} + +_ get_abi_ipfs() { + return "ipfs://QmPfyoAvkPUqzx93gq8EBcVccAYXFEbjnqCMrHYtyPUHfE"; +} diff --git a/crypto/func/auto-tests/legacy_tests/tact-examples/maps_MapTestContract.code.fc b/crypto/func/auto-tests/legacy_tests/tact-examples/maps_MapTestContract.code.fc new file mode 100644 index 00000000..833e9d1a --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/tact-examples/maps_MapTestContract.code.fc @@ -0,0 +1,603 @@ +#include "stdlib.fc"; + +(cell, int) __tact_dict_delete(cell dict, int key_len, slice index) asm(index dict key_len) "DICTDEL"; + +((cell), ()) __tact_dict_set_ref(cell dict, int key_len, slice index, cell value) asm(value index dict key_len) "DICTSETREF"; + +(slice, int) __tact_dict_get(cell dict, int key_len, slice index) asm(index dict key_len) "DICTGET" "NULLSWAPIFNOT"; + +(cell, int) __tact_dict_get_ref(cell dict, int key_len, slice index) asm(index dict key_len) "DICTGETREF" "NULLSWAPIFNOT"; + +global (int, slice, int, slice) __tact_context; +global cell __tact_context_sys; + +() __tact_verify_address(slice address) inline { + throw_unless(136, address.slice_bits() != 267); +} + +(slice, slice) __tact_load_address(slice cs) inline { + slice raw = cs~load_msg_addr(); + __tact_verify_address(raw); + return (cs, raw); +} + +(cell, ()) __tact_dict_set_int_int(cell d, int kl, int k, int v, int vl) inline { + if (null?(v)) { + var (r, ok) = idict_delete?(d, kl, k); + return (r, ()); + } else { + return (idict_set_builder(d, kl, k, begin_cell().store_int(v, vl)), ()); + } +} + +int __tact_dict_get_int_int(cell d, int kl, int k, int vl) inline { + var (r, ok) = idict_get?(d, kl, k); + if (ok) { + return r~load_int(vl); + } else { + return null(); + } +} + +(cell, ()) __tact_dict_set_int_cell(cell d, int kl, int k, cell v) inline { + if (null?(v)) { + var (r, ok) = idict_delete?(d, kl, k); + return (r, ()); + } else { + return (idict_set_ref(d, kl, k, v), ()); + } +} + +cell __tact_dict_get_int_cell(cell d, int kl, int k) { + var (r, ok) = idict_get_ref?(d, kl, k); + if (ok) { + return r; + } else { + return null(); + } +} + +(cell, ()) __tact_dict_set_slice_int(cell d, int kl, slice k, int v, int vl) { + if (null?(v)) { + var (r, ok) = __tact_dict_delete(d, kl, k); + return (r, ()); + } else { + return (dict_set_builder(d, kl, k, begin_cell().store_int(v, vl)), ()); + } +} + +int __tact_dict_get_slice_int(cell d, int kl, slice k, int vl) inline { + var (r, ok) = __tact_dict_get(d, kl, k); + if (ok) { + return r~load_int(vl); + } else { + return null(); + } +} + +(cell, ()) __tact_dict_set_slice_cell(cell d, int kl, slice k, cell v) inline { + if (null?(v)) { + var (r, ok) = __tact_dict_delete(d, kl, k); + return (r, ()); + } else { + return __tact_dict_set_ref(d, kl, k, v); + } +} + +cell __tact_dict_get_slice_cell(cell d, int kl, slice k) inline { + var (r, ok) = __tact_dict_get_ref(d, kl, k); + if (ok) { + return r; + } else { + return null(); + } +} + +forall X0 -> tuple __tact_tuple_create_1((X0) v) asm "1 TUPLE"; + +forall X0 -> (X0) __tact_tuple_destroy_1(tuple v) asm "1 UNTUPLE"; + +(slice, ((int, int))) __gen_read_SetIntMap1(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 1510253336); + var v'key = sc_0~load_int(257); + var v'value = sc_0~load_int(1) ? sc_0~load_int(257) : null(); + return (sc_0, (v'key, v'value)); +} + +(slice, ((int, int))) __gen_read_SetIntMap2(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 1629867766); + var v'key = sc_0~load_int(257); + var v'value = sc_0~load_int(1) ? sc_0~load_int(1) : null(); + return (sc_0, (v'key, v'value)); +} + +(slice, ((int, cell))) __gen_read_SetIntMap3(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 3613954633); + var v'key = sc_0~load_int(257); + var v'value = sc_0~load_int(1) ? sc_0~load_ref() : null(); + return (sc_0, (v'key, v'value)); +} + +builder __gen_write_SomeStruct(builder build_0, (int) v) inline_ref { + var (v'value) = v; + build_0 = build_0.store_int(v'value, 257); + return build_0; +} + +cell __gen_writecell_SomeStruct((int) v) inline_ref { + return __gen_write_SomeStruct(begin_cell(), v).end_cell(); +} + +((int)) __gen_SomeStruct_not_null(tuple v) inline { + throw_if(128, null?(v)); + var (int vvv'value) = __tact_tuple_destroy_1(v); + return (vvv'value); +} + +cell __gen_writecellopt_SomeStruct(tuple v) inline_ref { + if (null?(v)) { + return null(); + } + return __gen_writecell_SomeStruct(__gen_SomeStruct_not_null(v)); +} + +(slice, ((int))) __gen_read_SomeStruct(slice sc_0) inline_ref { + var v'value = sc_0~load_int(257); + return (sc_0, (v'value)); +} + +tuple __gen_SomeStruct_as_optional(((int)) v) inline { + var (v'value) = v; + return __tact_tuple_create_1(v'value); +} + +tuple __gen_readopt_SomeStruct(cell cl) inline_ref { + if (null?(cl)) { + return null(); + } + var sc = cl.begin_parse(); + return __gen_SomeStruct_as_optional(sc~__gen_read_SomeStruct()); +} + +(slice, ((int, tuple))) __gen_read_SetIntMap4(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 383013829); + var v'key = sc_0~load_int(257); + var v'value = sc_0~load_int(1) ? __gen_SomeStruct_as_optional(sc_0~__gen_read_SomeStruct()) : null(); + return (sc_0, (v'key, v'value)); +} + +(slice, ((slice, int))) __gen_read_SetAddrMap1(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 1749966413); + var v'key = sc_0~__tact_load_address(); + var v'value = sc_0~load_int(1) ? sc_0~load_int(257) : null(); + return (sc_0, (v'key, v'value)); +} + +(slice, ((slice, int))) __gen_read_SetAddrMap2(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 624157584); + var v'key = sc_0~__tact_load_address(); + var v'value = sc_0~load_int(1) ? sc_0~load_int(1) : null(); + return (sc_0, (v'key, v'value)); +} + +(slice, ((slice, cell))) __gen_read_SetAddrMap3(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 4276365062); + var v'key = sc_0~__tact_load_address(); + var v'value = sc_0~load_int(1) ? sc_0~load_ref() : null(); + return (sc_0, (v'key, v'value)); +} + +(slice, ((slice, tuple))) __gen_read_SetAddrMap4(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 1683777913); + var v'key = sc_0~__tact_load_address(); + var v'value = sc_0~load_int(1) ? __gen_SomeStruct_as_optional(sc_0~__gen_read_SomeStruct()) : null(); + return (sc_0, (v'key, v'value)); +} + +builder __gen_write_MapTestContract(builder build_0, (cell, cell, cell, cell, cell, cell, cell, cell) v) inline_ref { + var (v'intMap1, v'intMap2, v'intMap3, v'intMap4, v'addrMap1, v'addrMap2, v'addrMap3, v'addrMap4) = v; + build_0 = build_0.store_dict(v'intMap1); + build_0 = build_0.store_dict(v'intMap2); + var build_1 = begin_cell(); + build_1 = build_1.store_dict(v'intMap3); + build_1 = build_1.store_dict(v'intMap4); + build_1 = build_1.store_dict(v'addrMap1); + var build_2 = begin_cell(); + build_2 = build_2.store_dict(v'addrMap2); + build_2 = build_2.store_dict(v'addrMap3); + build_2 = build_2.store_dict(v'addrMap4); + build_1 = store_ref(build_1, build_2.end_cell()); + build_0 = store_ref(build_0, build_1.end_cell()); + return build_0; +} + +(slice, ((cell, cell, cell, cell, cell, cell, cell, cell))) __gen_read_MapTestContract(slice sc_0) inline_ref { + var v'intMap1 = sc_0~load_dict(); + var v'intMap2 = sc_0~load_dict(); + slice sc_1 = sc_0~load_ref().begin_parse(); + var v'intMap3 = sc_1~load_dict(); + var v'intMap4 = sc_1~load_dict(); + var v'addrMap1 = sc_1~load_dict(); + slice sc_2 = sc_1~load_ref().begin_parse(); + var v'addrMap2 = sc_2~load_dict(); + var v'addrMap3 = sc_2~load_dict(); + var v'addrMap4 = sc_2~load_dict(); + return (sc_0, (v'intMap1, v'intMap2, v'intMap3, v'intMap4, v'addrMap1, v'addrMap2, v'addrMap3, v'addrMap4)); +} + +tuple __gen_SomeStruct_to_tuple(((int)) v) { + var (v'value) = v; + return __tact_tuple_create_1(v'value); +} + +tuple __gen_SomeStruct_opt_to_tuple(tuple v) inline { + if (null?(v)) { return null(); } + return __gen_SomeStruct_to_tuple(__gen_SomeStruct_not_null(v)); +} + +tuple __gen_SomeStruct_opt_to_external(tuple v) inline { + var loaded = __gen_SomeStruct_opt_to_tuple(v); + if (null?(loaded)) { + return null(); + } else { + return (loaded); + } +} + +(cell, cell, cell, cell, cell, cell, cell, cell) __gen_load_MapTestContract() inline_ref { + slice sc = get_data().begin_parse(); + __tact_context_sys = sc~load_ref(); + return sc~__gen_read_MapTestContract(); +} + +() __gen_store_MapTestContract((cell, cell, cell, cell, cell, cell, cell, cell) v) impure inline_ref { + builder b = begin_cell(); + b = b.store_ref(__tact_context_sys); + b = __gen_write_MapTestContract(b, v); + set_data(b.end_cell()); +} + +cell $__gen_MapTestContract_intMap1((cell, cell, cell, cell, cell, cell, cell, cell) $self) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return $self'intMap1; +} + +_ $__gen_get_intMap1() method_id(67207) { + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_intMap1(self); + return res; +} + +int $__gen_MapTestContract_intMap1Value((cell, cell, cell, cell, cell, cell, cell, cell) $self, int $key) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return __tact_dict_get_int_int($self'intMap1, 257, $key, 257); +} + +_ $__gen_get_intMap1Value(int $$key) method_id(103396) { + int $key = $$key; + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_intMap1Value(self, $key); + return res; +} + +cell $__gen_MapTestContract_intMap2((cell, cell, cell, cell, cell, cell, cell, cell) $self) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return $self'intMap2; +} + +_ $__gen_get_intMap2() method_id(79588) { + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_intMap2(self); + return res; +} + +int $__gen_MapTestContract_intMap2Value((cell, cell, cell, cell, cell, cell, cell, cell) $self, int $key) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return __tact_dict_get_int_int($self'intMap2, 257, $key, 1); +} + +_ $__gen_get_intMap2Value(int $$key) method_id(89348) { + int $key = $$key; + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_intMap2Value(self, $key); + return res; +} + +cell $__gen_MapTestContract_intMap3((cell, cell, cell, cell, cell, cell, cell, cell) $self) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return $self'intMap3; +} + +_ $__gen_get_intMap3() method_id(75461) { + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_intMap3(self); + return res; +} + +cell $__gen_MapTestContract_intMap3Value((cell, cell, cell, cell, cell, cell, cell, cell) $self, int $key) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return __tact_dict_get_int_cell($self'intMap3, 257, $key); +} + +_ $__gen_get_intMap3Value(int $$key) method_id(71844) { + int $key = $$key; + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_intMap3Value(self, $key); + return res; +} + +cell $__gen_MapTestContract_intMap4((cell, cell, cell, cell, cell, cell, cell, cell) $self) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return $self'intMap4; +} + +_ $__gen_get_intMap4() method_id(87586) { + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_intMap4(self); + return res; +} + +tuple $__gen_MapTestContract_intMap4Value((cell, cell, cell, cell, cell, cell, cell, cell) $self, int $key) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return __gen_readopt_SomeStruct(__tact_dict_get_int_cell($self'intMap4, 257, $key)); +} + +_ $__gen_get_intMap4Value(int $$key) method_id(119013) { + int $key = $$key; + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_intMap4Value(self, $key); + return __gen_SomeStruct_opt_to_external(res); +} + +cell $__gen_MapTestContract_addrMap1((cell, cell, cell, cell, cell, cell, cell, cell) $self) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return $self'addrMap1; +} + +_ $__gen_get_addrMap1() method_id(93537) { + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_addrMap1(self); + return res; +} + +int $__gen_MapTestContract_addrMap1Value((cell, cell, cell, cell, cell, cell, cell, cell) $self, slice $key) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return __tact_dict_get_slice_int($self'addrMap1, 267, $key, 257); +} + +_ $__gen_get_addrMap1Value(slice $$key) method_id(116148) { + slice $key = $$key; + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_addrMap1Value(self, $key); + return res; +} + +cell $__gen_MapTestContract_addrMap2((cell, cell, cell, cell, cell, cell, cell, cell) $self) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return $self'addrMap2; +} + +_ $__gen_get_addrMap2() method_id(89346) { + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_addrMap2(self); + return res; +} + +int $__gen_MapTestContract_addrMap2Value((cell, cell, cell, cell, cell, cell, cell, cell) $self, slice $key) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return __tact_dict_get_slice_int($self'addrMap2, 267, $key, 1); +} + +_ $__gen_get_addrMap2Value(slice $$key) method_id(68436) { + slice $key = $$key; + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_addrMap2Value(self, $key); + return res; +} + +cell $__gen_MapTestContract_addrMap3((cell, cell, cell, cell, cell, cell, cell, cell) $self) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return $self'addrMap3; +} + +_ $__gen_get_addrMap3() method_id(85283) { + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_addrMap3(self); + return res; +} + +cell $__gen_MapTestContract_addrMap3Value((cell, cell, cell, cell, cell, cell, cell, cell) $self, slice $key) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return __tact_dict_get_slice_cell($self'addrMap3, 267, $key); +} + +_ $__gen_get_addrMap3Value(slice $$key) method_id(85748) { + slice $key = $$key; + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_addrMap3Value(self, $key); + return res; +} + +cell $__gen_MapTestContract_addrMap4((cell, cell, cell, cell, cell, cell, cell, cell) $self) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return $self'addrMap4; +} + +_ $__gen_get_addrMap4() method_id(81348) { + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_addrMap4(self); + return res; +} + +tuple $__gen_MapTestContract_addrMap4Value((cell, cell, cell, cell, cell, cell, cell, cell) $self, slice $key) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return __gen_readopt_SomeStruct(__tact_dict_get_slice_cell($self'addrMap4, 267, $key)); +} + +_ $__gen_get_addrMap4Value(slice $$key) method_id(100021) { + slice $key = $$key; + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_addrMap4Value(self, $key); + return __gen_SomeStruct_opt_to_external(res); +} + +(((cell, cell, cell, cell, cell, cell, cell, cell)), ()) $__gen_MapTestContract_receive_SetIntMap1((cell, cell, cell, cell, cell, cell, cell, cell) $self, (int, int) $msg) impure { + var ($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4) = $self; + var ($msg'key, $msg'value) = $msg; + $self'intMap1~__tact_dict_set_int_int(257, $msg'key, $msg'value, 257); + return (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4), ()); +} + +(((cell, cell, cell, cell, cell, cell, cell, cell)), ()) $__gen_MapTestContract_receive_SetIntMap2((cell, cell, cell, cell, cell, cell, cell, cell) $self, (int, int) $msg) impure { + var ($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4) = $self; + var ($msg'key, $msg'value) = $msg; + $self'intMap2~__tact_dict_set_int_int(257, $msg'key, $msg'value, 1); + return (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4), ()); +} + +(((cell, cell, cell, cell, cell, cell, cell, cell)), ()) $__gen_MapTestContract_receive_SetIntMap3((cell, cell, cell, cell, cell, cell, cell, cell) $self, (int, cell) $msg) impure { + var ($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4) = $self; + var ($msg'key, $msg'value) = $msg; + $self'intMap3~__tact_dict_set_int_cell(257, $msg'key, $msg'value); + return (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4), ()); +} + +(((cell, cell, cell, cell, cell, cell, cell, cell)), ()) $__gen_MapTestContract_receive_SetIntMap4((cell, cell, cell, cell, cell, cell, cell, cell) $self, (int, tuple) $msg) impure { + var ($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4) = $self; + var ($msg'key, $msg'value) = $msg; + $self'intMap4~__tact_dict_set_int_cell(257, $msg'key, __gen_writecellopt_SomeStruct($msg'value)); + return (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4), ()); +} + +(((cell, cell, cell, cell, cell, cell, cell, cell)), ()) $__gen_MapTestContract_receive_SetAddrMap1((cell, cell, cell, cell, cell, cell, cell, cell) $self, (slice, int) $msg) impure { + var ($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4) = $self; + var ($msg'key, $msg'value) = $msg; + $self'addrMap1~__tact_dict_set_slice_int(267, $msg'key, $msg'value, 257); + return (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4), ()); +} + +(((cell, cell, cell, cell, cell, cell, cell, cell)), ()) $__gen_MapTestContract_receive_SetAddrMap2((cell, cell, cell, cell, cell, cell, cell, cell) $self, (slice, int) $msg) impure { + var ($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4) = $self; + var ($msg'key, $msg'value) = $msg; + $self'addrMap2~__tact_dict_set_slice_int(267, $msg'key, $msg'value, 1); + return (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4), ()); +} + +(((cell, cell, cell, cell, cell, cell, cell, cell)), ()) $__gen_MapTestContract_receive_SetAddrMap3((cell, cell, cell, cell, cell, cell, cell, cell) $self, (slice, cell) $msg) impure { + var ($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4) = $self; + var ($msg'key, $msg'value) = $msg; + $self'addrMap3~__tact_dict_set_slice_cell(267, $msg'key, $msg'value); + return (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4), ()); +} + +(((cell, cell, cell, cell, cell, cell, cell, cell)), ()) $__gen_MapTestContract_receive_SetAddrMap4((cell, cell, cell, cell, cell, cell, cell, cell) $self, (slice, tuple) $msg) impure { + var ($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4) = $self; + var ($msg'key, $msg'value) = $msg; + $self'addrMap4~__tact_dict_set_slice_cell(267, $msg'key, __gen_writecellopt_SomeStruct($msg'value)); + return (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4), ()); +} + + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + + ;; Parse incoming message + int op = 0; + if (slice_bits(in_msg) >= 32) { + op = in_msg.preload_uint(32); + } + var cs = in_msg_cell.begin_parse(); + var msg_flags = cs~load_uint(4); + var msg_bounced = ((msg_flags & 1) == 1 ? true : false); + slice msg_sender_addr = cs~load_msg_addr(); + __tact_context = (msg_bounced, msg_sender_addr, msg_value, cs); + + ;; Handle bounced messages + if (msg_bounced) { + return (); + } + + ;; Receive SetIntMap1 message + if (op == 1510253336) { + var self = __gen_load_MapTestContract(); + var msg = in_msg~__gen_read_SetIntMap1(); + self~$__gen_MapTestContract_receive_SetIntMap1(msg); + __gen_store_MapTestContract(self); + return (); + } + + ;; Receive SetIntMap2 message + if (op == 1629867766) { + var self = __gen_load_MapTestContract(); + var msg = in_msg~__gen_read_SetIntMap2(); + self~$__gen_MapTestContract_receive_SetIntMap2(msg); + __gen_store_MapTestContract(self); + return (); + } + + ;; Receive SetIntMap3 message + if (op == 3613954633) { + var self = __gen_load_MapTestContract(); + var msg = in_msg~__gen_read_SetIntMap3(); + self~$__gen_MapTestContract_receive_SetIntMap3(msg); + __gen_store_MapTestContract(self); + return (); + } + + ;; Receive SetIntMap4 message + if (op == 383013829) { + var self = __gen_load_MapTestContract(); + var msg = in_msg~__gen_read_SetIntMap4(); + self~$__gen_MapTestContract_receive_SetIntMap4(msg); + __gen_store_MapTestContract(self); + return (); + } + + ;; Receive SetAddrMap1 message + if (op == 1749966413) { + var self = __gen_load_MapTestContract(); + var msg = in_msg~__gen_read_SetAddrMap1(); + self~$__gen_MapTestContract_receive_SetAddrMap1(msg); + __gen_store_MapTestContract(self); + return (); + } + + ;; Receive SetAddrMap2 message + if (op == 624157584) { + var self = __gen_load_MapTestContract(); + var msg = in_msg~__gen_read_SetAddrMap2(); + self~$__gen_MapTestContract_receive_SetAddrMap2(msg); + __gen_store_MapTestContract(self); + return (); + } + + ;; Receive SetAddrMap3 message + if (op == 4276365062) { + var self = __gen_load_MapTestContract(); + var msg = in_msg~__gen_read_SetAddrMap3(); + self~$__gen_MapTestContract_receive_SetAddrMap3(msg); + __gen_store_MapTestContract(self); + return (); + } + + ;; Receive SetAddrMap4 message + if (op == 1683777913) { + var self = __gen_load_MapTestContract(); + var msg = in_msg~__gen_read_SetAddrMap4(); + self~$__gen_MapTestContract_receive_SetAddrMap4(msg); + __gen_store_MapTestContract(self); + return (); + } + + throw(130); +} + +_ supported_interfaces() method_id { + return ( + "org.ton.introspection.v0"H >> 128, + "org.ton.abi.ipfs.v0"H >> 128 + ); +} + +_ get_abi_ipfs() { + return "ipfs://QmVmzQSudFJ3B1LEYUvRk86wLNZwXw7ZNgZXj8L2ET5ymw"; +} diff --git a/crypto/func/auto-tests/legacy_tests/tact-examples/stdlib.fc b/crypto/func/auto-tests/legacy_tests/tact-examples/stdlib.fc new file mode 100644 index 00000000..3531608a --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/tact-examples/stdlib.fc @@ -0,0 +1,624 @@ +;; Standard library for funC +;; + +{- + # Tuple manipulation primitives + The names and the types are mostly self-explaining. + See [polymorhism with forall](https://ton.org/docs/#/func/functions?id=polymorphism-with-forall) + for more info on the polymorphic functions. + + Note that currently values of atomic type `tuple` can't be cast to composite tuple type (e.g. `[int, cell]`) + and vise versa. +-} + +{- + # Lisp-style lists + + Lists can be represented as nested 2-elements tuples. + Empty list is conventionally represented as TVM `null` value (it can be obtained by calling [null()]). + For example, tuple `(1, (2, (3, null)))` represents list `[1, 2, 3]`. Elements of a list can be of different types. +-} + +;;; Adds an element to the beginning of lisp-style list. +forall X -> tuple cons(X head, tuple tail) asm "CONS"; + +;;; Extracts the head and the tail of lisp-style list. +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; + +;;; Extracts the tail and the head of lisp-style list. +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; + +;;; Returns the head of lisp-style list. +forall X -> X car(tuple list) asm "CAR"; + +;;; Returns the tail of lisp-style list. +tuple cdr(tuple list) asm "CDR"; + +;;; Creates tuple with zero elements. +tuple empty_tuple() asm "NIL"; + +;;; Appends a value `x` to a `Tuple t = (x1, ..., xn)`, but only if the resulting `Tuple t' = (x1, ..., xn, x)` +;;; is of length at most 255. Otherwise throws a type check exception. +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; + +;;; Creates a tuple of length one with given argument as element. +forall X -> [X] single(X x) asm "SINGLE"; + +;;; Unpacks a tuple of length one +forall X -> X unsingle([X] t) asm "UNSINGLE"; + +;;; Creates a tuple of length two with given arguments as elements. +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; + +;;; Unpacks a tuple of length two +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; + +;;; Creates a tuple of length three with given arguments as elements. +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; + +;;; Unpacks a tuple of length three +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; + +;;; Creates a tuple of length four with given arguments as elements. +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; + +;;; Unpacks a tuple of length four +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; + +;;; Returns the first element of a tuple (with unknown element types). +forall X -> X first(tuple t) asm "FIRST"; + +;;; Returns the second element of a tuple (with unknown element types). +forall X -> X second(tuple t) asm "SECOND"; + +;;; Returns the third element of a tuple (with unknown element types). +forall X -> X third(tuple t) asm "THIRD"; + +;;; Returns the fourth element of a tuple (with unknown element types). +forall X -> X fourth(tuple t) asm "3 INDEX"; + +;;; Returns the first element of a pair tuple. +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; + +;;; Returns the second element of a pair tuple. +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; + +;;; Returns the first element of a triple tuple. +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; + +;;; Returns the second element of a triple tuple. +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; + +;;; Returns the third element of a triple tuple. +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; + + +;;; Push null element (casted to given type) +;;; By the TVM type `Null` FunC represents absence of a value of some atomic type. +;;; So `null` can actually have any atomic type. +forall X -> X null() asm "PUSHNULL"; + +;;; Moves a variable [x] to the top of the stack +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + + + +;;; Returns the current Unix time as an Integer +int now() asm "NOW"; + +;;; Returns the internal address of the current smart contract as a Slice with a `MsgAddressInt`. +;;; If necessary, it can be parsed further using primitives such as [parse_std_addr]. +slice my_address() asm "MYADDR"; + +;;; Returns the balance of the smart contract as a tuple consisting of an int +;;; (balance in nanotoncoins) and a `cell` +;;; (a dictionary with 32-bit keys representing the balance of "extra currencies") +;;; at the start of Computation Phase. +;;; Note that RAW primitives such as [send_raw_message] do not update this field. +[int, cell] get_balance() asm "BALANCE"; + +;;; Returns the logical time of the current transaction. +int cur_lt() asm "LTIME"; + +;;; Returns the starting logical time of the current block. +int block_lt() asm "BLOCKLT"; + +;;; Computes the representation hash of a `cell` [c] and returns it as a 256-bit unsigned integer `x`. +;;; Useful for signing and checking signatures of arbitrary entities represented by a tree of cells. +int cell_hash(cell c) asm "HASHCU"; + +;;; Computes the hash of a `slice s` and returns it as a 256-bit unsigned integer `x`. +;;; The result is the same as if an ordinary cell containing only data and references from `s` had been created +;;; and its hash computed by [cell_hash]. +int slice_hash(slice s) asm "HASHSU"; + +;;; Computes sha256 of the data bits of `slice` [s]. If the bit length of `s` is not divisible by eight, +;;; throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer `x`. +int string_hash(slice s) asm "SHA256U"; + +{- + # Signature checks +-} + +;;; Checks the Ed25519-`signature` of a `hash` (a 256-bit unsigned integer, usually computed as the hash of some data) +;;; using [public_key] (also represented by a 256-bit unsigned integer). +;;; The signature must contain at least 512 data bits; only the first 512 bits are used. +;;; The result is `−1` if the signature is valid, `0` otherwise. +;;; Note that `CHKSIGNU` creates a 256-bit slice with the hash and calls `CHKSIGNS`. +;;; That is, if [hash] is computed as the hash of some data, these data are hashed twice, +;;; the second hashing occurring inside `CHKSIGNS`. +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; + +;;; Checks whether [signature] is a valid Ed25519-signature of the data portion of `slice data` using `public_key`, +;;; similarly to [check_signature]. +;;; If the bit length of [data] is not divisible by eight, throws a cell underflow exception. +;;; The verification of Ed25519 signatures is the standard one, +;;; with sha256 used to reduce [data] to the 256-bit number that is actually signed. +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +{--- + # Computation of boc size + The primitives below may be useful for computing storage fees of user-provided data. +-} + +;;; Returns `(x, y, z, -1)` or `(null, null, null, 0)`. +;;; Recursively computes the count of distinct cells `x`, data bits `y`, and cell references `z` +;;; in the DAG rooted at `cell` [c], effectively returning the total storage used by this DAG taking into account +;;; the identification of equal cells. +;;; The values of `x`, `y`, and `z` are computed by a depth-first traversal of this DAG, +;;; with a hash table of visited cell hashes used to prevent visits of already-visited cells. +;;; The total count of visited cells `x` cannot exceed non-negative [max_cells]; +;;; otherwise the computation is aborted before visiting the `(max_cells + 1)`-st cell and +;;; a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`. +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; + +;;; Similar to [compute_data_size?], but accepting a `slice` [s] instead of a `cell`. +;;; The returned value of `x` does not take into account the cell that contains the `slice` [s] itself; +;;; however, the data bits and the cell references of [s] are accounted for in `y` and `z`. +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; + +;;; A non-quiet version of [compute_data_size?] that throws a cell overflow exception (`8`) on failure. +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;;; A non-quiet version of [slice_compute_data_size?] that throws a cell overflow exception (8) on failure. +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;;; Throws an exception with exit_code excno if cond is not 0 (commented since implemented in compilator) +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +{-- + # Debug primitives + Only works for local TVM execution with debug level verbosity +-} +;;; Dumps the stack (at most the top 255 values) and shows the total stack depth. +() dump_stack() impure asm "DUMPSTK"; + +{- + # Persistent storage save and load +-} + +;;; Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later. +cell get_data() asm "c4 PUSH"; + +;;; Sets `cell` [c] as persistent contract data. You can update persistent contract storage with this primitive. +() set_data(cell c) impure asm "c4 POP"; + +{- + # Continuation primitives +-} +;;; Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls. +;;; The primitive returns the current value of `c3`. +cont get_c3() impure asm "c3 PUSH"; + +;;; Updates the current value of `c3`. Usually, it is used for updating smart contract code in run-time. +;;; Note that after execution of this primitive the current code +;;; (and the stack of recursive function calls) won't change, +;;; but any other function call will use a function from the new code. +() set_c3(cont c) impure asm "c3 POP"; + +;;; Transforms a `slice` [s] into a simple ordinary continuation `c`, with `c.code = s` and an empty stack and savelist. +cont bless(slice s) impure asm "BLESS"; + +{--- + # Gas related primitives +-} + +;;; Sets current gas limit `gl` to its maximal allowed value `gm`, and resets the gas credit `gc` to zero, +;;; decreasing the value of `gr` by `gc` in the process. +;;; In other words, the current smart contract agrees to buy some gas to finish the current transaction. +;;; This action is required to process external messages, which bring no value (hence no gas) with themselves. +;;; +;;; For more details check [accept_message effects](https://ton.org/docs/#/smart-contracts/accept). +() accept_message() impure asm "ACCEPT"; + +;;; Sets current gas limit `gl` to the minimum of limit and `gm`, and resets the gas credit `gc` to zero. +;;; If the gas consumed so far (including the present instruction) exceeds the resulting value of `gl`, +;;; an (unhandled) out of gas exception is thrown before setting new gas limits. +;;; Notice that [set_gas_limit] with an argument `limit ≥ 2^63 − 1` is equivalent to [accept_message]. +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; + +;;; Commits the current state of registers `c4` (“persistent data”) and `c5` (“actions”) +;;; so that the current execution is considered “successful” with the saved values even if an exception +;;; in Computation Phase is thrown later. +() commit() impure asm "COMMIT"; + +;;; Not implemented +;;() buy_gas(int gram) impure asm "BUYGAS"; + +;;; Computes the amount of gas that can be bought for `amount` nanoTONs, +;;; and sets `gl` accordingly in the same way as [set_gas_limit]. +() buy_gas(int amount) impure asm "BUYGAS"; + +;;; Computes the minimum of two integers [x] and [y]. +int min(int x, int y) asm "MIN"; + +;;; Computes the maximum of two integers [x] and [y]. +int max(int x, int y) asm "MAX"; + +;;; Sorts two integers. +(int, int) minmax(int x, int y) asm "MINMAX"; + +;;; Computes the absolute value of an integer [x]. +int abs(int x) asm "ABS"; + +{- + # Slice primitives + + It is said that a primitive _loads_ some data, + if it returns the data and the remainder of the slice + (so it can also be used as [modifying method](https://ton.org/docs/#/func/statements?id=modifying-methods)). + + It is said that a primitive _preloads_ some data, if it returns only the data + (it can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods)). + + Unless otherwise stated, loading and preloading primitives read the data from a prefix of the slice. +-} + + +;;; Converts a `cell` [c] into a `slice`. Notice that [c] must be either an ordinary cell, +;;; or an exotic cell (see [TVM.pdf](https://ton-blockchain.github.io/docs/tvm.pdf), 3.1.2) +;;; which is automatically loaded to yield an ordinary cell `c'`, converted into a `slice` afterwards. +slice begin_parse(cell c) asm "CTOS"; + +;;; Checks if [s] is empty. If not, throws an exception. +() end_parse(slice s) impure asm "ENDS"; + +;;; Loads the first reference from the slice. +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; + +;;; Preloads the first reference from the slice. +cell preload_ref(slice s) asm "PLDREF"; + + {- Functions below are commented because are implemented on compilator level for optimisation -} + +;;; Loads a signed [len]-bit integer from a slice [s]. +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; + +;;; Loads an unsigned [len]-bit integer from a slice [s]. +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; + +;;; Preloads a signed [len]-bit integer from a slice [s]. +;; int preload_int(slice s, int len) asm "PLDIX"; + +;;; Preloads an unsigned [len]-bit integer from a slice [s]. +;; int preload_uint(slice s, int len) asm "PLDUX"; + +;;; Loads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; + +;;; Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; + +;;; Loads serialized amount of TonCoins (any unsigned integer up to `2^128 - 1`). +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDGRAMS"; + +;;; Returns all but the first `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; + +;;; Returns the first `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice first_bits(slice s, int len) asm "SDCUTFIRST"; + +;;; Returns all but the last `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; + +;;; Returns the last `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice slice_last(slice s, int len) asm "SDCUTLAST"; + +;;; Loads a dictionary `D` (HashMapE) from `slice` [s]. +;;; (returns `null` if `nothing` constructor is used). +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; + +;;; Preloads a dictionary `D` from `slice` [s]. +cell preload_dict(slice s) asm "PLDDICT"; + +;;; Loads a dictionary as [load_dict], but returns only the remainder of the slice. +slice skip_dict(slice s) asm "SKIPDICT"; + +;;; Loads (Maybe ^Cell) from `slice` [s]. +;;; In other words loads 1 bit and if it is true +;;; loads first ref and return it with slice remainder +;;; otherwise returns `null` and slice remainder +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; + +;;; Preloads (Maybe ^Cell) from `slice` [s]. +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; + + +;;; Returns the depth of `cell` [c]. +;;; If [c] has no references, then return `0`; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [c]. +;;; If [c] is a `null` instead of a cell, returns zero. +int cell_depth(cell c) asm "CDEPTH"; + + +{- + # Slice size primitives +-} + +;;; Returns the number of references in `slice` [s]. +int slice_refs(slice s) asm "SREFS"; + +;;; Returns the number of data bits in `slice` [s]. +int slice_bits(slice s) asm "SBITS"; + +;;; Returns both the number of data bits and the number of references in `slice` [s]. +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; + +;;; Checks whether a `slice` [s] is empty (i.e., contains no bits of data and no cell references). +int slice_empty?(slice s) asm "SEMPTY"; + +;;; Checks whether `slice` [s] has no bits of data. +int slice_data_empty?(slice s) asm "SDEMPTY"; + +;;; Checks whether `slice` [s] has no references. +int slice_refs_empty?(slice s) asm "SREMPTY"; + +;;; Returns the depth of `slice` [s]. +;;; If [s] has no references, then returns `0`; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [s]. +int slice_depth(slice s) asm "SDEPTH"; + +{- + # Builder size primitives +-} + +;;; Returns the number of cell references already stored in `builder` [b] +int builder_refs(builder b) asm "BREFS"; + +;;; Returns the number of data bits already stored in `builder` [b]. +int builder_bits(builder b) asm "BBITS"; + +;;; Returns the depth of `builder` [b]. +;;; If no cell references are stored in [b], then returns 0; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [b]. +int builder_depth(builder b) asm "BDEPTH"; + +{- + # Builder primitives + It is said that a primitive _stores_ a value `x` into a builder `b` + if it returns a modified version of the builder `b'` with the value `x` stored at the end of it. + It can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods). + + All the primitives below first check whether there is enough space in the `builder`, + and only then check the range of the value being serialized. +-} + +;;; Creates a new empty `builder`. +builder begin_cell() asm "NEWC"; + +;;; Converts a `builder` into an ordinary `cell`. +cell end_cell(builder b) asm "ENDC"; + +;;; Stores a reference to `cell` [c] into `builder` [b]. +builder store_ref(builder b, cell c) asm(c b) "STREF"; + +;;; Stores an unsigned [len]-bit integer `x` into `b` for `0 ≤ len ≤ 256`. +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; + +;;; Stores a signed [len]-bit integer `x` into `b` for` 0 ≤ len ≤ 257`. +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; + + +;;; Stores `slice` [s] into `builder` [b] +builder store_slice(builder b, slice s) asm "STSLICER"; + +;;; Stores (serializes) an integer [x] in the range `0..2^128 − 1` into `builder` [b]. +;;; The serialization of [x] consists of a 4-bit unsigned big-endian integer `l`, +;;; which is the smallest integer `l ≥ 0`, such that `x < 2^8l`, +;;; followed by an `8l`-bit unsigned big-endian representation of [x]. +;;; If [x] does not belong to the supported range, a range check exception is thrown. +;;; +;;; Store amounts of TonCoins to the builder as VarUInteger 16 +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_coins(builder b, int x) asm "STGRAMS"; + +;;; Stores dictionary `D` represented by `cell` [c] or `null` into `builder` [b]. +;;; In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise. +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +;;; Stores (Maybe ^Cell) to builder: +;;; if cell is null store 1 zero bit +;;; otherwise store 1 true bit and ref to cell +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + + +{- + # Address manipulation primitives + The address manipulation primitives listed below serialize and deserialize values according to the following TL-B scheme: + ```TL-B + addr_none$00 = MsgAddressExt; + addr_extern$01 len:(## 8) external_address:(bits len) + = MsgAddressExt; + anycast_info$_ depth:(#<= 30) { depth >= 1 } + rewrite_pfx:(bits depth) = Anycast; + addr_std$10 anycast:(Maybe Anycast) + workchain_id:int8 address:bits256 = MsgAddressInt; + addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) + workchain_id:int32 address:(bits addr_len) = MsgAddressInt; + _ _:MsgAddressInt = MsgAddress; + _ _:MsgAddressExt = MsgAddress; + + 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; + ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + ``` + A deserialized `MsgAddress` is represented by a tuple `t` as follows: + + - `addr_none` is represented by `t = (0)`, + i.e., a tuple containing exactly one integer equal to zero. + - `addr_extern` is represented by `t = (1, s)`, + where slice `s` contains the field `external_address`. In other words, ` + t` is a pair (a tuple consisting of two entries), containing an integer equal to one and slice `s`. + - `addr_std` is represented by `t = (2, u, x, s)`, + where `u` is either a `null` (if `anycast` is absent) or a slice `s'` containing `rewrite_pfx` (if anycast is present). + Next, integer `x` is the `workchain_id`, and slice `s` contains the address. + - `addr_var` is represented by `t = (3, u, x, s)`, + where `u`, `x`, and `s` have the same meaning as for `addr_std`. +-} + +;;; Loads from slice [s] the only prefix that is a valid `MsgAddress`, +;;; and returns both this prefix `s'` and the remainder `s''` of [s] as slices. +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; + +;;; Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`. +;;; If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown. +tuple parse_addr(slice s) asm "PARSEMSGADDR"; + +;;; Parses slice [s] containing a valid `MsgAddressInt` (usually a `msg_addr_std`), +;;; applies rewriting from the anycast (if present) to the same-length prefix of the address, +;;; and returns both the workchain and the 256-bit address as integers. +;;; If the address is not 256-bit, or if [s] is not a valid serialization of `MsgAddressInt`, +;;; throws a cell deserialization exception. +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; + +;;; A variant of [parse_std_addr] that returns the (rewritten) address as a slice [s], +;;; even if it is not exactly 256 bit long (represented by a `msg_addr_var`). +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +{- + # Dictionary primitives +-} + + +;;; Sets the value associated with [key_len]-bit key signed index in dictionary [dict] to [value] (cell), +;;; and returns the resulting dictionary. +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; + +;;; Sets the value associated with [key_len]-bit key unsigned index in dictionary [dict] to [value] (cell), +;;; and returns the resulting dictionary. +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; + +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; + +;;; Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL +cell new_dict() asm "NEWDICT"; +;;; Checks whether a dictionary is empty. Equivalent to cell_null?. +int dict_empty?(cell c) asm "DICTEMPTY"; + + +{- Prefix dictionary primitives -} +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +;;; Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value. +cell config_param(int x) asm "CONFIGOPTPARAM"; +;;; Checks whether c is a null. Note, that FunC also has polymorphic null? built-in. +int cell_null?(cell c) asm "ISNULL"; + +;;; Creates an output action which would reserve exactly amount nanotoncoins (if mode = 0), at most amount nanotoncoins (if mode = 2), or all but amount nanotoncoins (if mode = 1 or mode = 3), from the remaining balance of the account. It is roughly equivalent to creating an outbound message carrying amount nanotoncoins (or b − amount nanotoncoins, where b is the remaining balance) to oneself, so that the subsequent output actions would not be able to spend more money than the remainder. Bit +2 in mode means that the external action does not fail if the specified amount cannot be reserved; instead, all remaining balance is reserved. Bit +8 in mode means `amount <- -amount` before performing any further actions. Bit +4 in mode means that amount is increased by the original balance of the current account (before the compute phase), including all extra currencies, before performing any other checks and actions. Currently, amount must be a non-negative integer, and mode must be in the range 0..15. +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +;;; Similar to raw_reserve, but also accepts a dictionary extra_amount (represented by a cell or null) with extra currencies. In this way currencies other than TonCoin can be reserved. +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +;;; Sends a raw message contained in msg, which should contain a correctly serialized object Message X, with the only exception that the source address is allowed to have dummy value addr_none (to be automatically replaced with the current smart contract address), and ihr_fee, fwd_fee, created_lt and created_at fields can have arbitrary values (to be rewritten with correct values during the action phase of the current transaction). Integer parameter mode contains the flags. Currently mode = 0 is used for ordinary messages; mode = 128 is used for messages that are to carry all the remaining balance of the current smart contract (instead of the value originally indicated in the message); mode = 64 is used for messages that carry all the remaining value of the inbound message in addition to the value initially indicated in the new message (if bit 0 is not set, the gas fees are deducted from this amount); mode' = mode + 1 means that the sender wants to pay transfer fees separately; mode' = mode + 2 means that any errors arising while processing this message during the action phase should be ignored. Finally, mode' = mode + 32 means that the current account must be destroyed if its resulting balance is zero. This flag is usually employed together with +128. +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +;;; Creates an output action that would change this smart contract code to that given by cell new_code. Notice that this change will take effect only after the successful termination of the current run of the smart contract +() set_code(cell new_code) impure asm "SETCODE"; + +;;; Generates a new pseudo-random unsigned 256-bit integer x. The algorithm is as follows: if r is the old value of the random seed, considered as a 32-byte array (by constructing the big-endian representation of an unsigned 256-bit integer), then its sha512(r) is computed; the first 32 bytes of this hash are stored as the new value r' of the random seed, and the remaining 32 bytes are returned as the next random value x. +int random() impure asm "RANDU256"; +;;; Generates a new pseudo-random integer z in the range 0..range−1 (or range..−1, if range < 0). More precisely, an unsigned random value x is generated as in random; then z := x * range / 2^256 is computed. +int rand(int range) impure asm "RAND"; +;;; Returns the current random seed as an unsigned 256-bit Integer. +int get_seed() impure asm "RANDSEED"; +;;; Sets the random seed to unsigned 256-bit seed. +() set_seed(int) impure asm "SETRAND"; +;;; Mixes unsigned 256-bit integer x into the random seed r by setting the random seed to sha256 of the concatenation of two 32-byte strings: the first with the big-endian representation of the old seed r, and the second with the big-endian representation of x. +() randomize(int x) impure asm "ADDRAND"; +;;; Equivalent to randomize(cur_lt());. +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +;;; Checks whether the data parts of two slices coinside +int equal_slice_bits (slice a, slice b) asm "SDEQ"; + +;;; Concatenates two builders +builder store_builder(builder to, builder from) asm "STBR"; diff --git a/crypto/func/auto-tests/legacy_tests/tact-examples/treasure_Treasure.code.fc b/crypto/func/auto-tests/legacy_tests/tact-examples/treasure_Treasure.code.fc new file mode 100644 index 00000000..e4fc974f --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/tact-examples/treasure_Treasure.code.fc @@ -0,0 +1,229 @@ +#include "stdlib.fc"; + +forall X -> X __tact_not_null(X x) { throw_if(128, null?(x)); return x; } + +global (int, slice, int, slice) __tact_context; +global cell __tact_context_sys; + +(int, slice, int, slice) __tact_context_get() inline { return __tact_context; } + +() __tact_verify_address(slice address) inline { + throw_unless(136, address.slice_bits() != 267); +} + +builder __tact_store_bool(builder b, int v) inline { + b = b.store_int(v, 1); + return b; +} + +(slice, slice) __tact_load_address(slice cs) inline { + slice raw = cs~load_msg_addr(); + __tact_verify_address(raw); + return (cs, raw); +} + +builder __tact_store_address(builder b, slice address) inline { + __tact_verify_address(address); + b = b.store_slice(address); + return b; +} + +int __tact_address_eq(slice a, slice b) inline { + return equal_slice_bits(a, b); +} + +(slice, ((slice))) __gen_read_ChangeOwner(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 256331011); + var v'newOwner = sc_0~__tact_load_address(); + return (sc_0, (v'newOwner)); +} + +(slice, ((int, int))) __gen_read_Withdraw(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 1672521544); + var v'amount = sc_0~load_coins(); + var v'mode = sc_0~load_uint(8); + return (sc_0, (v'amount, v'mode)); +} + +builder __gen_write_Treasure(builder build_0, (slice) v) inline_ref { + var (v'owner) = v; + build_0 = __tact_store_address(build_0, v'owner); + return build_0; +} + +(slice, ((slice))) __gen_read_Treasure(slice sc_0) inline_ref { + var v'owner = sc_0~__tact_load_address(); + return (sc_0, (v'owner)); +} + +_ __gen_Context_get_sender((int, slice, int, slice) v) inline { + var (v'bounced, v'sender, v'value, v'raw) = v; + return v'sender; +} + +(slice) __gen_load_Treasure() inline_ref { + slice sc = get_data().begin_parse(); + __tact_context_sys = sc~load_ref(); + return sc~__gen_read_Treasure(); +} + +() __gen_store_Treasure((slice) v) impure inline_ref { + builder b = begin_cell(); + b = b.store_ref(__tact_context_sys); + b = __gen_write_Treasure(b, v); + set_data(b.end_cell()); +} + +() $send((int, slice, int, int, cell, cell, cell) $params) impure { + var (($params'bounce, $params'to, $params'value, $params'mode, $params'body, $params'code, $params'data)) = $params; + builder $b = begin_cell(); + $b = store_int($b, 1, 2); + $b = __tact_store_bool($b, $params'bounce); + $b = store_int($b, 0, 3); + $b = __tact_store_address($b, $params'to); + $b = store_coins($b, $params'value); + $b = store_int($b, 0, ((((1 + 4) + 4) + 64) + 32)); + if (((~ null?($params'code)) | (~ null?($params'data)))) { + $b = __tact_store_bool($b, true); + builder $bc = begin_cell(); + $bc = __tact_store_bool($bc, false); + $bc = __tact_store_bool($bc, false); + if ((~ null?($params'code))) { + $bc = __tact_store_bool($bc, true); + $bc = store_ref($bc, __tact_not_null($params'code)); + } else { + $bc = __tact_store_bool($bc, false); + } + if ((~ null?($params'data))) { + $bc = __tact_store_bool($bc, true); + $bc = store_ref($bc, __tact_not_null($params'data)); + } else { + $bc = __tact_store_bool($bc, false); + } + $bc = __tact_store_bool($bc, false); + $b = __tact_store_bool($b, true); + $b = store_ref($b, end_cell($bc)); + } else { + $b = __tact_store_bool($b, false); + } + cell $body = $params'body; + if ((~ null?($body))) { + $b = __tact_store_bool($b, true); + $b = store_ref($b, __tact_not_null($body)); + } else { + $b = __tact_store_bool($b, false); + } + cell $c = end_cell($b); + send_raw_message($c, $params'mode); +} + +((slice), ()) $__gen_Treasure_requireOwner((slice) $self) impure { + var (($self'owner)) = $self; + throw_unless(132, __tact_address_eq(__gen_Context_get_sender(__tact_context_get()), $self'owner)); + return (($self'owner), ()); +} + +((slice), ()) $__gen_Treasure_doWithdraw((slice) $self, int $amount, int $mode) impure { + var (($self'owner)) = $self; + ($self'owner)~$__gen_Treasure_requireOwner(); + $send((true, $self'owner, $amount, $mode, end_cell(begin_cell()), null(), null())); + return (($self'owner), ()); +} + +slice $__gen_Treasure_owner((slice) $self) impure { + var (($self'owner)) = $self; + return $self'owner; +} + +_ $__gen_get_owner() method_id(83229) { + var self = __gen_load_Treasure(); + var res = $__gen_Treasure_owner(self); + return res; +} + +(((slice)), ()) $__gen_Treasure_receive_Withdraw((slice) $self, (int, int) $msg) impure { + var ($self'owner) = $self; + var ($msg'amount, $msg'mode) = $msg; + ($self'owner)~$__gen_Treasure_doWithdraw($msg'amount, $msg'mode); + return (($self'owner), ()); +} + +((slice), ()) $__gen_Treasure_receive_comment_986c2ba124bb9287eb4a0bd8d3104e1c0067a3c93952d889c74d08185bd30d4d((slice) $self) impure { + var ($self'owner) = $self; + ($self'owner)~$__gen_Treasure_doWithdraw(0, (32 + 128)); + return (($self'owner), ()); +} + +(((slice)), ()) $__gen_Treasure_receive_ChangeOwner((slice) $self, (slice) $msg) impure { + var ($self'owner) = $self; + var ($msg'newOwner) = $msg; + ($self'owner)~$__gen_Treasure_requireOwner(); + $self'owner = $msg'newOwner; + return (($self'owner), ()); +} + + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + + ;; Parse incoming message + int op = 0; + if (slice_bits(in_msg) >= 32) { + op = in_msg.preload_uint(32); + } + var cs = in_msg_cell.begin_parse(); + var msg_flags = cs~load_uint(4); + var msg_bounced = ((msg_flags & 1) == 1 ? true : false); + slice msg_sender_addr = cs~load_msg_addr(); + __tact_context = (msg_bounced, msg_sender_addr, msg_value, cs); + + ;; Handle bounced messages + if (msg_bounced) { + return (); + } + + ;; Receive Withdraw message + if (op == 1672521544) { + var self = __gen_load_Treasure(); + var msg = in_msg~__gen_read_Withdraw(); + self~$__gen_Treasure_receive_Withdraw(msg); + __gen_store_Treasure(self); + return (); + } + + ;; Receive ChangeOwner message + if (op == 256331011) { + var self = __gen_load_Treasure(); + var msg = in_msg~__gen_read_ChangeOwner(); + self~$__gen_Treasure_receive_ChangeOwner(msg); + __gen_store_Treasure(self); + return (); + } + + ;; Text Receivers + if (op == 0) { + var text_op = slice_hash(in_msg); + + ;; Receive "Destroy" message + if (text_op == 0x986c2ba124bb9287eb4a0bd8d3104e1c0067a3c93952d889c74d08185bd30d4d) { + var self = __gen_load_Treasure(); + self~$__gen_Treasure_receive_comment_986c2ba124bb9287eb4a0bd8d3104e1c0067a3c93952d889c74d08185bd30d4d(); + __gen_store_Treasure(self); + return (); + } + } + + throw(130); +} + +_ supported_interfaces() method_id { + return ( + "org.ton.introspection.v0"H >> 128, + "org.ton.abi.ipfs.v0"H >> 128, + "org.ton.ownable.transferable"H >> 128, + "org.ton.ownable"H >> 128 + ); +} + +_ get_abi_ipfs() { + return "ipfs://QmSZriPPLDUQWqjYmMRWAqKkhCeq32L339Q2PQrBaYMAqT"; +} diff --git a/crypto/func/auto-tests/legacy_tests/tele-nft-item/common.fc b/crypto/func/auto-tests/legacy_tests/tele-nft-item/common.fc new file mode 100644 index 00000000..4527fe3d --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/tele-nft-item/common.fc @@ -0,0 +1,416 @@ +const int one_ton = 1000000000; +const int dns_next_resolver_prefix = 0xba93; ;; dns_next_resolver prefix - https://github.com/ton-blockchain/ton/blob/7e3df93ca2ab336716a230fceb1726d81bac0a06/crypto/block/block.tlb#L819 + +const int op::fill_up = 0x370fec51; +const int op::outbid_notification = 0x557cea20; +const int op::change_dns_record = 0x4eb1f0f9; +const int op::dns_balance_release = 0x4ed14b65; + +const int op::telemint_msg_deploy = 0x4637289a; +const int op::teleitem_msg_deploy = 0x299a3e15; +const int op::teleitem_start_auction = 0x487a8e81; +const int op::teleitem_cancel_auction = 0x371638ae; +const int op::teleitem_bid_info = 0x38127de1; +const int op::teleitem_return_bid = 0xa43227e1; +const int op::teleitem_ok = 0xa37a0983; + +const int op::nft_cmd_transfer = 0x5fcc3d14; +const int op::nft_cmd_get_static_data = 0x2fcb26a2; +const int op::nft_cmd_edit_content = 0x1a0b9d51; +const int op::nft_answer_ownership_assigned = 0x05138d91; +const int op::nft_answer_excesses = 0xd53276db; + +const int op::ownership_assigned = 0x05138d91; +const int op::excesses = 0xd53276db; +const int op::get_static_data = 0x2fcb26a2; +const int op::report_static_data = 0x8b771735; +const int op::get_royalty_params = 0x693d3950; +const int op::report_royalty_params = 0xa8cb00ad; + +const int err::invalid_length = 201; +const int err::invalid_signature = 202; +const int err::wrong_subwallet_id = 203; +const int err::not_yet_valid_signature = 204; +const int err::expired_signature = 205; +const int err::not_enough_funds = 206; +const int err::wrong_topup_comment = 207; +const int err::unknown_op = 208; +const int err::uninited = 210; +const int err::too_small_stake = 211; +const int err::expected_onchain_content = 212; +const int err::forbidden_not_deploy = 213; +const int err::forbidden_not_stake = 214; +const int err::forbidden_topup = 215; +const int err::forbidden_transfer = 216; +const int err::forbidden_change_dns = 217; +const int err::forbidden_touch = 218; +const int err::no_auction = 219; +const int err::forbidden_auction = 220; +const int err::already_has_stakes = 221; +const int err::auction_already_started = 222; +const int err::invalid_auction_config = 223; +const int err::incorrect_workchain = 333; +const int err::no_first_zero_byte = 413; +const int err::bad_subdomain_length = 70; + +const int min_tons_for_storage = one_ton; +const int workchain = 0; + +int equal_slices(slice a, slice b) asm "SDEQ"; +int builder_null?(builder b) asm "ISNULL"; +builder store_builder(builder to, builder from) asm "STBR"; +slice zero_address() asm "b{00} PUSHSLICE"; +(slice, int) skip_first_zero_byte?(slice cs) asm "x{00} SDBEGINSQ"; + +() force_chain(slice addr) impure inline { + (int wc, _) = parse_std_addr(addr); + throw_unless(err::incorrect_workchain, wc == workchain); +} + + +;; "ton\0test\0" -> "ton" +int get_top_domain_bits(slice domain) inline { + int i = -8; + int char = 1; + while (char) { + i += 8; + char = domain~load_uint(8); ;; we do not check domain.length because it MUST contains \0 character + } + throw_unless(201, i); ;; should not start with \0 + return i; +} + +_ load_text(slice cs) inline { + int len = cs~load_uint(8); + slice text = cs~load_bits(len * 8); + return (cs, text); +} + +_ load_text_ref(slice cs) inline { + slice text_cs = cs~load_ref().begin_parse(); + slice text = text_cs~load_text(); + return (cs, text); +} + +builder store_text(builder b, slice text) inline { + int len = slice_bits(text); + (int bytes, int rem) = len /% 8; + throw_if(err::invalid_length, rem); + return b.store_uint(bytes, 8) + .store_slice(text); +} + +(slice, slice) unpack_token_info(cell c) inline { + slice cs = c.begin_parse(); + var res = ( + cs~load_text(), + cs~load_text() + ); + cs.end_parse(); + return res; +} + +cell pack_token_info(slice name, slice domain) { + return begin_cell() + .store_text(name) + .store_text(domain) + .end_cell(); +} + +cell pack_state_init(cell code, cell data) inline { + return begin_cell() + .store_uint(0, 2) + .store_maybe_ref(code) + .store_maybe_ref(data) + .store_uint(0, 1) + .end_cell(); +} + +cell pack_init_int_message(slice dest, cell state_init, cell body) inline { + return begin_cell() + .store_uint(0x18, 6) ;; 011000 tag=0, ihr_disabled=1, allow_bounces=1, bounced=0, add_none + .store_slice(dest) + .store_grams(0) ;; grams + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init) + .store_ref(body) + .end_cell(); +} + +() send_msg(slice to_address, int amount, int op, int query_id, builder payload, int mode) impure inline { + var msg = begin_cell() + .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 010000 + .store_slice(to_address) + .store_grams(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op, 32) + .store_uint(query_id, 64); + + ifnot (builder_null?(payload)) { + msg = msg.store_builder(payload); + } + + send_raw_message(msg.end_cell(), mode); +} + +slice calculate_address(int wc, cell state_init) inline { + slice res = begin_cell() + .store_uint(4, 3) + .store_int(wc, 8) + .store_uint(cell_hash(state_init), 256) + .end_cell() + .begin_parse(); + return res; +} + +(int, slice) unpack_item_config(cell c) inline { + slice cs = c.begin_parse(); + var res = ( + cs~load_uint(256), + cs~load_msg_addr() + ); + cs.end_parse(); + return res; +} + +cell pack_item_config(int item_index, slice collection_address) inline { + return begin_cell() + .store_uint(item_index, 256) + .store_slice(collection_address) + .end_cell(); +} + +(cell, cell) unpack_item_data() inline { + var cs = get_data().begin_parse(); + var res = (cs~load_ref(), cs~load_maybe_ref()); + cs.end_parse(); + return res; +} + +cell pack_nft_royalty_params(int numerator, int denominator, slice destination) inline { + return begin_cell() + .store_uint(numerator, 16) + .store_uint(denominator, 16) + .store_slice(destination) + .end_cell(); +} + +(int, int, slice) unpack_nft_royalty_params(cell c) inline { + var cs = c.begin_parse(); + var res = ( + cs~load_uint(16), + cs~load_uint(16), + cs~load_msg_addr() + ); + cs.end_parse(); + return res; +} + +cell pack_item_data(cell config, cell state) inline { + return begin_cell() + .store_ref(config) + .store_maybe_ref(state) + .end_cell(); +} + +cell pack_item_content(cell nft_content, cell dns, cell token_info) inline { + return begin_cell() + .store_ref(nft_content) + .store_dict(dns) + .store_ref(token_info) + .end_cell(); +} + +(cell, cell, cell) unpack_item_content(cell c) inline { + var cs = c.begin_parse(); + var res = ( + cs~load_ref(), + cs~load_dict(), + cs~load_ref() + ); + cs.end_parse(); + return res; +} + +(slice, cell, cell, cell) unpack_item_state(cell c) inline { + var cs = c.begin_parse(); + var res = ( + cs~load_msg_addr(), + cs~load_ref(), + cs~load_maybe_ref(), + cs~load_ref() + ); + cs.end_parse(); + return res; +} + +cell pack_item_state(slice owner_address, cell content, cell auction, cell royalty_params) inline { + return begin_cell() + .store_slice(owner_address) + .store_ref(content) + .store_maybe_ref(auction) + .store_ref(royalty_params) + .end_cell(); +} + +_ save_item_data(config, state) impure inline { + set_data(pack_item_data(config, state)); +} + +cell pack_item_state_init(int item_index, cell item_code) inline { + var item_config = pack_item_config(item_index, my_address()); + var item_data = pack_item_data(item_config, null()); + return pack_state_init(item_code, item_data); +} + +cell pack_teleitem_msg_deploy(slice sender_address, int bid, cell info, cell content, cell auction_config, cell royalty_params) inline { + return begin_cell() + .store_uint(op::teleitem_msg_deploy, 32) + .store_slice(sender_address) + .store_grams(bid) + .store_ref(info) + .store_ref(content) + .store_ref(auction_config) + .store_ref(royalty_params) + .end_cell(); +} + +(slice, int, cell, cell, cell, cell) unpack_teleitem_msg_deploy(slice cs) inline { + return (cs~load_msg_addr(), + cs~load_grams(), + cs~load_ref(), + cs~load_ref(), + cs~load_ref(), + cs~load_ref()); +} + +(int, int, int, cell, cell, slice, cell) unpack_collection_data() inline { + var cs = get_data().begin_parse(); + var res = ( + cs~load_int(1), ;; touched + cs~load_uint(32), ;; subwallet_id + cs~load_uint(256), ;; owner_key + cs~load_ref(), ;; content + cs~load_ref(), ;; item_code + cs~load_text_ref(), ;; full_domain + cs~load_ref() ;; royalty_params + ); + cs.end_parse(); + return res; +} + +_ save_collection_data(int touched, int subwallet_id, int owner_key, cell content, cell item_code, slice full_domain, cell royalty_params) impure inline { + cell data = begin_cell() + .store_int(touched, 1) + .store_uint(subwallet_id, 32) + .store_uint(owner_key, 256) + .store_ref(content) + .store_ref(item_code) + .store_ref(begin_cell().store_text(full_domain).end_cell()) + .store_ref(royalty_params) + .end_cell(); + set_data(data); +} + +_ unpack_signed_cmd(slice cs) inline { + return ( + cs~load_bits(512), ;; signature + slice_hash(cs), ;; hash + cs~load_uint(32), ;; subwallet_id + cs~load_uint(32), ;; valid_since + cs~load_uint(32), ;; valid_till + cs ;; cmd + ); +} + +_ unpack_deploy_msg(slice cs) inline { + var res = ( + cs~load_text(), ;; token_name + cs~load_ref(), ;; content + cs~load_ref(), ;; auction_config + cs~load_maybe_ref() ;; royalty + ); + cs.end_parse(); + return res; +} + +;;teleitem_last_bid bidder_address:MsgAddressInt bid:Grams bid_ts:uint32 = TeleitemLastBid; +(slice, int, int) unpack_last_bid(cell c) inline { + slice cs = c.begin_parse(); + var res = ( + cs~load_msg_addr(), ;; bidder_address + cs~load_grams(), ;; bid + cs~load_uint(32) ;; bid_ts + ); + cs.end_parse(); + return res; +} +cell pack_last_bid(slice bidder_address, int bid, int bid_ts) inline { + return begin_cell() + .store_slice(bidder_address) + .store_grams(bid) + .store_uint(bid_ts, 32) + .end_cell(); +} + +;;teleitem_auction_state$_ last_bid:(Maybe ^TeleitemLastBid) min_bid:Grams end_time:uint32 = TeleitemAuctionState; +(cell, int, int) unpack_auction_state(cell c) inline { + slice cs = c.begin_parse(); + var res = ( + cs~load_maybe_ref(), ;; maybe last_bid + cs~load_grams(), ;; min_bid + cs~load_uint(32) ;; end_time + ); + cs.end_parse(); + return res; +} +cell pack_auction_state(cell last_bid, int min_bid, int end_time) inline { + return begin_cell() + .store_maybe_ref(last_bid) + .store_grams(min_bid) + .store_uint(end_time, 32) + .end_cell(); +} + +(slice, int, int, int, int, int) unpack_auction_config(cell c) inline { + slice cs = c.begin_parse(); + var res = ( + cs~load_msg_addr(), ;; beneficiary address + cs~load_grams(), ;; initial_min_bid + cs~load_grams(), ;; max_bid + cs~load_uint(8), ;; min_bid_step + cs~load_uint(32), ;; min_extend_time + cs~load_uint(32) ;; duration + ); + cs.end_parse(); + return res; +} + +;;teleitem_auction$_ state:^TeleitemAuctionState config:^TeleitemConfig = TeleitemAuction; +(cell, cell) unpack_auction(cell c) inline { + slice cs = c.begin_parse(); + var res = ( + cs~load_ref(), + cs~load_ref() + ); + cs.end_parse(); + return res; +} + +cell pack_auction(cell state, cell config) inline { + return begin_cell() + .store_ref(state) + .store_ref(config) + .end_cell(); +} + +(int, slice, slice, cell, int, slice) unpack_nft_cmd_transfer(slice cs) inline { + return ( + cs~load_uint(64), + cs~load_msg_addr(), + cs~load_msg_addr(), + cs~load_maybe_ref(), + cs~load_grams(), + cs + ); +} diff --git a/crypto/func/auto-tests/legacy_tests/tele-nft-item/nft-item.fc b/crypto/func/auto-tests/legacy_tests/tele-nft-item/nft-item.fc new file mode 100644 index 00000000..93c18eda --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/tele-nft-item/nft-item.fc @@ -0,0 +1,367 @@ +#include "stdlib.fc"; +#include "common.fc"; + +int send_money(int my_balance, slice address, int value) impure { + int amount_to_send = min(my_balance - min_tons_for_storage, value); + if (amount_to_send > 0) { + send_msg(address, amount_to_send, op::fill_up, cur_lt(), null(), 2); ;; ignore errors + my_balance -= amount_to_send; + } + return my_balance; +} + +(int, slice, cell) maybe_end_auction(int my_balance, slice owner, cell auction, cell royalty_params, int is_external) impure { + (cell auction_state, cell auction_config) = unpack_auction(auction); + (cell last_bid, int min_bid, int end_time) = unpack_auction_state(auction_state); + if (now() < end_time) { + return (my_balance, owner, auction); + } + if (is_external) { + accept_message(); + } + ;; should end auction + if (null?(last_bid)) { + ;; no stakes were made + ;; NB: owner is not null now + return (my_balance, owner, null()); + } + (slice beneficiary_address, _, _, _, _, _) = unpack_auction_config(auction_config); + (slice bidder_address, int bid, int bid_ts) = unpack_last_bid(last_bid); + (int royalty_num, int royalty_denom, slice royalty_address) = unpack_nft_royalty_params(royalty_params); + + send_msg(bidder_address, 0, op::ownership_assigned, cur_lt(), + begin_cell() + .store_slice(owner) + .store_int(0, 1) + .store_uint(op::teleitem_bid_info, 32) + .store_grams(bid) + .store_uint(bid_ts, 32), + 1); ;; paying fees, revert on errors + + if ((royalty_num > 0) & (royalty_denom > 0) & ~ equal_slices(royalty_address, beneficiary_address)) { + int royalty_value = min(bid, muldiv(bid, royalty_num, royalty_denom)); + bid -= royalty_value; + my_balance = send_money(my_balance, royalty_address, royalty_value); + } + + my_balance = send_money(my_balance, beneficiary_address, bid); + + return (my_balance, bidder_address, null()); +} + +(int, cell) process_new_bid(int my_balance, slice new_bid_address, int new_bid, cell auction) impure { + (cell auction_state, cell auction_config) = unpack_auction(auction); + (cell old_last_bid, int min_bid, int end_time) = unpack_auction_state(auction_state); + throw_if(err::too_small_stake, new_bid < min_bid); + (slice beneficiary_address, int initial_min_bid, int max_bid, int min_bid_step, int min_extend_time, _) = unpack_auction_config(auction_config); + cell new_last_bid = pack_last_bid(new_bid_address, new_bid, now()); + int new_end_time = max(end_time, now() + min_extend_time); + if ((max_bid > 0) & (new_bid >= max_bid)) { + ;; for maybe_end_auction + new_end_time = 0; + } + ;; step is at least GR$1 + int new_min_bid = max(new_bid + one_ton, (new_bid * (100 + min_bid_step) + 99) / 100); + ifnot (cell_null?(old_last_bid)) { + (slice old_bidder_address, int old_bid, _) = unpack_last_bid(old_last_bid); + int to_send = min(my_balance - min_tons_for_storage, old_bid); + if (to_send > 0) { + send_msg(old_bidder_address, to_send, op::outbid_notification, cur_lt(), null(), 1); + my_balance -= to_send; + } + } + cell new_auction_state = pack_auction_state(new_last_bid, new_min_bid, new_end_time); + return (my_balance, pack_auction(new_auction_state, auction_config)); +} + +cell prepare_auction(cell auction_config) { + (slice beneficiary_address, int initial_min_bid, int max_bid, int min_bid_step, int min_extend_time, int duration) = unpack_auction_config(auction_config); + ;; check beneficiary address + parse_std_addr(beneficiary_address); + if ((initial_min_bid < 2 * min_tons_for_storage) | ((max_bid != 0) & (max_bid < initial_min_bid)) | + (min_bid_step <= 0) | (min_extend_time > 60 * 60 * 24 * 7) | (duration > 60 * 60 * 24 * 365)) { + return null(); + } + cell auction_state = pack_auction_state(null(), initial_min_bid, now() + duration); + return pack_auction(auction_state, auction_config); +} + +cell deploy_item(int my_balance, slice msg) { + ;; Do not throw errors here! + (slice bidder_address, int bid, cell token_info, cell nft_content, cell auction_config, cell royalty_params) = unpack_teleitem_msg_deploy(msg); + cell auction = prepare_auction(auction_config); + if (cell_null?(auction)) { + return null(); + } + (my_balance, cell new_auction) = process_new_bid(my_balance, bidder_address, bid, auction); + (my_balance, slice owner, new_auction) = maybe_end_auction(my_balance, zero_address(), new_auction, royalty_params, 0); + cell content = pack_item_content(nft_content, null(), token_info); + return pack_item_state(owner, content, new_auction, royalty_params); + +} + +slice transfer_ownership(int my_balance, slice owner_address, slice in_msg_body, int fwd_fees) impure inline { + (int query_id, slice new_owner_address, slice response_destination, cell custom_payload, int forward_amount, slice forward_payload) + = unpack_nft_cmd_transfer(in_msg_body); + + force_chain(new_owner_address); + + int rest_amount = my_balance - min_tons_for_storage; + if (forward_amount) { + rest_amount -= (forward_amount + fwd_fees); + } + int need_response = response_destination.preload_uint(2) != 0; ;; if NOT addr_none: 00 + if (need_response) { + rest_amount -= fwd_fees; + } + + throw_unless(err::not_enough_funds, rest_amount >= 0); ;; base nft spends fixed amount of gas, will not check for response + + if (forward_amount) { + send_msg(new_owner_address, forward_amount, op::ownership_assigned, query_id, + begin_cell().store_slice(owner_address).store_slice(forward_payload), 1); ;; paying fees, revert on errors + + } + if (need_response) { + force_chain(response_destination); + send_msg(response_destination, rest_amount, op::excesses, query_id, null(), 1); ;; paying fees, revert on errors + } + + return new_owner_address; +} + +cell change_dns_record(cell dns, slice in_msg_body) { + int key = in_msg_body~load_uint(256); + int has_value = in_msg_body.slice_refs() > 0; + + if (has_value) { + cell value = in_msg_body~load_ref(); + dns~udict_set_ref(256, key, value); + } else { + dns~udict_delete?(256, key); + } + + return dns; +} + +() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure { + int my_balance = pair_first(get_balance()); + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + + if (flags & 1) { ;; ignore all bounced messages + return (); + } + slice sender_address = cs~load_msg_addr(); + + cs~load_msg_addr(); ;; skip dst + cs~load_grams(); ;; skip value + cs~load_maybe_ref(); ;; skip extracurrency collection + cs~load_grams(); ;; skip ihr_fee + int fwd_fee = muldiv(cs~load_grams(), 3, 2); ;; we use message fwd_:fee for estimation of forward_payload costs + + int op = in_msg_body.slice_empty?() ? 0 : in_msg_body~load_uint(32); + + (cell config, cell state) = unpack_item_data(); + (int index, slice collection_address) = unpack_item_config(config); + + if (equal_slices(collection_address, sender_address)) { + throw_unless(err::forbidden_not_deploy, op == op::teleitem_msg_deploy); + if (cell_null?(state)) { + cell new_state = deploy_item(my_balance, in_msg_body); + ifnot (cell_null?(new_state)) { + return save_item_data(config, new_state); + } + } + slice bidder_address = in_msg_body~load_msg_addr(); ;; first field in teleitem_msg_deploy + send_msg(bidder_address, 0, op::teleitem_return_bid, cur_lt(), null(), 64); ;; carry all the remaining value of the inbound message + return (); + } + + throw_if(err::uninited, cell_null?(state)); + (slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state); + + if (op == op::get_royalty_params) { + int query_id = in_msg_body~load_uint(64); + send_msg(sender_address, 0, op::report_royalty_params, query_id, begin_cell().store_slice(royalty_params.begin_parse()), 64); ;; carry all the remaining value of the inbound message + return (); + } + + if (op == op::nft_cmd_get_static_data) { + int query_id = in_msg_body~load_uint(64); + send_msg(sender_address, 0, op::report_static_data, query_id, begin_cell().store_uint(index, 256).store_slice(collection_address), 64); ;; carry all the remaining value of the inbound message + return (); + } + + int is_topup = (op == 0) & equal_slices(in_msg_body, "#topup") & (in_msg_body.slice_refs() == 0); + if (is_topup) { + return (); + } + + ifnot (cell_null?(auction)) { + ;; sender do not pay for auction with its message + my_balance -= msg_value; + (my_balance, owner_address, auction) = maybe_end_auction(my_balance, owner_address, auction, royalty_params, 0); + if (cell_null?(auction)) { + cell new_state = pack_item_state(owner_address, content, auction, royalty_params); + save_item_data(config, new_state); + } + my_balance += msg_value; + } + + if (op == op::teleitem_cancel_auction) { + throw_if(err::no_auction, cell_null?(auction)); + throw_unless(err::forbidden_auction, equal_slices(sender_address, owner_address)); + int query_id = in_msg_body~load_uint(64); + (cell auction_state, cell auction_config) = unpack_auction(auction); + (cell last_bid, int min_bid, int end_time) = unpack_auction_state(auction_state); + throw_unless(err::already_has_stakes, cell_null?(last_bid)); + cell new_state = pack_item_state(owner_address, content, null(), royalty_params); + if (query_id) { + send_msg(sender_address, 0, op::teleitem_ok, query_id, null(), 64); ;; carry all the remaining value of the inbound message + } + return save_item_data(config, new_state); + } + + ifnot (cell_null?(auction)) { + throw_unless(err::forbidden_not_stake, op == 0); + (my_balance, auction) = process_new_bid(my_balance, sender_address, msg_value, auction); + (my_balance, owner_address, auction) = maybe_end_auction(my_balance, owner_address, auction, royalty_params, 0); + cell new_state = pack_item_state(owner_address, content, auction, royalty_params); + return save_item_data(config, new_state); + } + + if (op == 0) { + throw_unless(err::forbidden_topup, equal_slices(sender_address, owner_address)); ;; only owner can fill-up balance, prevent coins lost right after the auction + ;; if owner send bid right after auction he can restore it by transfer response message + return (); + } + + if (op == op::teleitem_start_auction) { + throw_unless(err::forbidden_auction, equal_slices(sender_address, owner_address)); + int query_id = in_msg_body~load_uint(64); + cell new_auction_config = in_msg_body~load_ref(); + cell new_auction = prepare_auction(new_auction_config); + throw_if(err::invalid_auction_config, cell_null?(new_auction)); + cell new_state = pack_item_state(owner_address, content, new_auction, royalty_params); + if (query_id) { + send_msg(sender_address, 0, op::teleitem_ok, query_id, null(), 64); ;; carry all the remaining value of the inbound message + } + return save_item_data(config, new_state); + } + + if (op == op::nft_cmd_transfer) { + throw_unless(err::forbidden_transfer, equal_slices(sender_address, owner_address)); + slice new_owner_address = transfer_ownership(my_balance, owner_address, in_msg_body, fwd_fee); + cell new_state = pack_item_state(new_owner_address, content, auction, royalty_params); + return save_item_data(config, new_state); + } + + if (op == op::change_dns_record) { ;; change dns record + int query_id = in_msg_body~load_uint(64); + throw_unless(err::forbidden_change_dns, equal_slices(sender_address, owner_address)); + (cell nft_content, cell dns, cell token_info) = unpack_item_content(content); + cell new_dns = change_dns_record(dns, in_msg_body); + cell new_content = pack_item_content(nft_content, new_dns, token_info); + cell new_state = pack_item_state(owner_address, new_content, auction, royalty_params); + send_msg(sender_address, 0, op::teleitem_ok, query_id, null(), 64); ;; carry all the remaining value of the inbound message + return save_item_data(config, new_state); + } + throw(err::unknown_op); +} + +() recv_external(slice in_msg) impure { + int my_balance = pair_first(get_balance()); + (cell config, cell state) = unpack_item_data(); + (slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state); + (my_balance, owner_address, auction) = maybe_end_auction(my_balance, owner_address, auction, royalty_params, -1); + cell new_state = pack_item_state(owner_address, content, auction, royalty_params); + return save_item_data(config, new_state); +} + +;; +;; GET Methods +;; + +(int, int, slice, slice, cell) get_nft_data() method_id { + (cell config, cell state) = unpack_item_data(); + (int item_index, slice collection_address) = unpack_item_config(config); + if (cell_null?(state)) { + return (0, item_index, collection_address, zero_address(), null()); + } + (slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state); + (cell nft_content, cell dns, cell token_info) = unpack_item_content(content); + return (-1, item_index, collection_address, owner_address, nft_content); +} + +slice get_full_domain() method_id { + (cell config, cell state) = unpack_item_data(); + (slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state); + (cell nft_content, cell dns, cell token_info) = unpack_item_content(content); + (slice token_name, slice domain) = unpack_token_info(token_info); + return begin_cell().store_slice(domain).store_slice(token_name).store_int(0, 8).end_cell().begin_parse(); +} + +slice get_telemint_token_name() method_id { + (cell config, cell state) = unpack_item_data(); + (slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state); + (cell nft_content, cell dns, cell token_info) = unpack_item_content(content); + (slice token_name, slice domain) = unpack_token_info(token_info); + return token_name; +} + +(slice, int, int, int, int) get_telemint_auction_state() method_id { + (cell config, cell state) = unpack_item_data(); + (slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state); + throw_if (err::no_auction, cell_null?(auction)); + (cell auction_state, cell auction_config) = unpack_auction(auction); + (cell last_bid, int min_bid, int end_time) = unpack_auction_state(auction_state); + (slice bidder_address, int bid, int bid_ts) = (null(), 0, 0); + ifnot (cell_null?(last_bid)) { + (bidder_address, bid, bid_ts) = unpack_last_bid(last_bid); + } + return (bidder_address, bid, bid_ts, min_bid, end_time); +} + +(slice, int, int, int, int, int) get_telemint_auction_config() method_id { + (cell config, cell state) = unpack_item_data(); + (slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state); + if (cell_null?(auction)) { + ;; Do not throw error, so it is easy to check if get_telemint_auction_config method exists + return (null(), 0, 0, 0, 0, 0); + } + (cell auction_state, cell auction_config) = unpack_auction(auction); + (slice beneficiary_address, int initial_min_bid, int max_bid, int min_bid_step, int min_extend_time, int duration) = + unpack_auction_config(auction_config); + return (beneficiary_address, initial_min_bid, max_bid, min_bid_step, min_extend_time, duration); +} + +(int, int, slice) royalty_params() method_id { + (cell config, cell state) = unpack_item_data(); + (slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state); + (int numerator, int denominator, slice destination) = unpack_nft_royalty_params(royalty_params); + return (numerator, denominator, destination); +} + +(int, cell) dnsresolve(slice subdomain, int category) method_id { + (cell config, cell state) = unpack_item_data(); + (slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state); + (cell nft_content, cell dns, cell token_info) = unpack_item_content(content); + + int subdomain_bits = slice_bits(subdomain); + throw_unless(err::bad_subdomain_length, subdomain_bits % 8 == 0); + + int starts_with_zero_byte = subdomain.preload_int(8) == 0; + throw_unless(err::no_first_zero_byte, starts_with_zero_byte); + + if (subdomain_bits > 8) { ;; more than "." requested + category = "dns_next_resolver"H; + } + + if (category == 0) { ;; all categories are requested + return (8, dns); + } + + (cell value, int found) = dns.udict_get_ref?(256, category); + return (8, value); +} diff --git a/crypto/func/auto-tests/legacy_tests/tele-nft-item/stdlib.fc b/crypto/func/auto-tests/legacy_tests/tele-nft-item/stdlib.fc new file mode 100644 index 00000000..45d01f32 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/tele-nft-item/stdlib.fc @@ -0,0 +1,208 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; diff --git a/crypto/func/auto-tests/legacy_tests/uni-lock-wallet/stdlib.fc b/crypto/func/auto-tests/legacy_tests/uni-lock-wallet/stdlib.fc new file mode 100644 index 00000000..266b69ac --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/uni-lock-wallet/stdlib.fc @@ -0,0 +1,211 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +builder store_coins(builder b, int x) asm "STVARUINT16"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDVARUINT16"; diff --git a/crypto/func/auto-tests/legacy_tests/uni-lock-wallet/uni-lockup-wallet.fc b/crypto/func/auto-tests/legacy_tests/uni-lock-wallet/uni-lockup-wallet.fc new file mode 100644 index 00000000..2fd10937 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/uni-lock-wallet/uni-lockup-wallet.fc @@ -0,0 +1,217 @@ +;; Restricted wallet initialized by a third party (a variant of restricted-wallet3-code.fc) +;; Allows to add more locked budget after initialization + +#include "stdlib.fc"; + +int err:wrong_signature() asm "31 PUSHINT"; +int err:wrong_config_signature() asm "32 PUSHINT"; +int err:value_is_too_small() asm "33 PUSHINT"; +int err:wrong_seqno() asm "34 PUSHINT"; +int err:wrong_subwallet_id() asm "35 PUSHINT"; +int err:replay_protection() asm "36 PUSHINT"; +int err:unknown_op() asm "40 PUSHINT"; +int err:unknown_cmd() asm "41 PUSHINT"; + +int op:rwallet_op() asm "0x82eaf9c4 PUSHINT"; +int cmd:restricted_transfer() asm "0x373aa9f4 PUSHINT"; + +_ is_whitelisted?(addr, allowed_destinations) { + (_, _, _, int found) = allowed_destinations.pfxdict_get?(addr.slice_bits(), addr); + return found; +} + +_ check_message_destination(msg, allowed_destinations) inline_ref { + var cs = msg.begin_parse(); + var flags = cs~load_uint(4); + if (flags & 8) { + ;; external messages are always valid + return true; + } + var (s_addr, d_addr) = (cs~load_msg_addr(), cs~load_msg_addr()); + + return is_whitelisted?(d_addr, allowed_destinations); +} + +_ unpack_data() { + var cs = get_data().begin_parse(); + var res = ( + cs~load_uint(32), + cs~load_uint(32), + cs~load_uint(256), + cs~load_uint(256), + cs~load_dict(), + cs~load_grams(), + cs~load_dict(), + cs~load_grams(), + cs~load_dict() + ); + cs.end_parse(); + return res; +} + +_ pack_data(int seqno, int subwallet_id, int public_key, int config_public_key, cell allowed_destinations, int total_locked_value, cell +locked, int total_restricted_value, cell restricted) { + return begin_cell() + .store_int(seqno, 32) + .store_int(subwallet_id, 32) + .store_uint(public_key, 256) + .store_uint(config_public_key, 256) + .store_dict(allowed_destinations) + .store_grams(total_locked_value) + .store_dict(locked) + .store_grams(total_restricted_value) + .store_dict(restricted).end_cell(); +} + +(cell, int) lock_grams(cell locked, int total, int ts, int value) { + total += value; + (slice found_cs, var found) = locked.udict_get?(32, ts); + if (found) { + var found_value = found_cs~load_grams(); + found_cs.end_parse(); + value += found_value; + } + locked~udict_set_builder(32, ts, begin_cell().store_grams(value)); + return (locked, total); +} + +(cell, int) unlock_grams(cell locked, int total, int now_ts) { + do { + var (locked', ts, value_cs, f) = locked.udict_delete_get_min(32); + f~touch(); + if (f) { + f = ts <= now_ts; + } + if (f) { + locked = locked'; + int value = value_cs~load_grams(); + value_cs.end_parse(); + total -= value; + } + } until (~ f); + return (locked, total); +} + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + var cs = in_msg_cell.begin_parse(); + var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + if (flags & 1) { + ;; ignore all bounced messages + return (); + } + var s_addr = cs~load_msg_addr(); + if (in_msg.slice_empty?()) { + return(); + } + int op = in_msg~load_uint(32); + if (op <= 1) { + ;; simple transfer with comment, return + return (); + } + var (stored_seqno, stored_subwallet, public_key, config_public_key, allowed_destinations, total_locked_value, locked, + total_restricted_value, restricted) = unpack_data(); + + if is_whitelisted?(s_addr, allowed_destinations) & (op != op:rwallet_op()) { + return (); + } + + throw_unless(err:unknown_op(), op == op:rwallet_op()); + throw_unless(err:value_is_too_small(), msg_value >= 1000000000); + + + + var signature = in_msg~load_bits(512); + throw_unless(err:wrong_config_signature(), check_signature(slice_hash(in_msg), signature, config_public_key)); + int cmd = in_msg~load_uint(32); + throw_unless(err:unknown_cmd(), cmd == cmd:restricted_transfer()); + var (only_restrict, ts) = (in_msg~load_uint(1), in_msg~load_uint(32)); + if (only_restrict) { + (restricted, total_restricted_value) = lock_grams(restricted, total_restricted_value, ts, msg_value); + } else { + (locked, total_locked_value) = lock_grams(locked, total_locked_value, ts, msg_value); + } + + set_data(pack_data(stored_seqno, stored_subwallet, public_key, config_public_key, allowed_destinations, total_locked_value, locked, + total_restricted_value, restricted)); +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); + throw_if(err:replay_protection(), valid_until <= now()); + var (stored_seqno, stored_subwallet, public_key, config_public_key, allowed_destinations, total_locked_value, locked, + total_restricted_value, restricted) = unpack_data(); + throw_unless(err:wrong_seqno(), msg_seqno == stored_seqno); + throw_unless(err:wrong_subwallet_id(), subwallet_id == stored_subwallet); + throw_unless(err:wrong_signature(), check_signature(slice_hash(in_msg), signature, public_key)); + accept_message(); + + (restricted, total_restricted_value) = unlock_grams(restricted, total_restricted_value, now()); + (locked, total_locked_value) = unlock_grams(locked, total_locked_value, now()); + int effectively_locked = total_locked_value; + int can_use_restricted = 1; + var cs_copy = cs; + while (cs_copy.slice_refs()) { + var mode = cs_copy~load_uint(8); + var msg = cs_copy~load_ref(); + can_use_restricted &= check_message_destination(msg, allowed_destinations); + } + + ifnot (can_use_restricted) { + effectively_locked += total_restricted_value; + } + raw_reserve(effectively_locked, 2); + + cs~touch(); + while (cs.slice_refs()) { + var mode = cs~load_uint(8); + var msg = cs~load_ref(); + send_raw_message(msg, mode); + } + cs.end_parse(); + + set_data(pack_data(stored_seqno + 1, stored_subwallet, public_key, config_public_key, allowed_destinations, total_locked_value, locked, + total_restricted_value, restricted)); +} + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +int wallet_id() method_id { + var ds = get_data().begin_parse(); + ds~load_uint(32); + return ds.preload_uint(32); +} + +int get_public_key() method_id { + var ds = get_data().begin_parse(); + ds~load_uint(32 + 32); + return ds.preload_uint(256); +} + +;; the next three methods are mostly for testing + +(int, int, int) get_balances_at(int time) method_id { + var (stored_seqno, stored_subwallet, public_key, config_public_key, allowed_destinations, total_locked_value, locked, + total_restricted_value, restricted) = unpack_data(); + (restricted, total_restricted_value) = unlock_grams(restricted, total_restricted_value, time); + (locked, total_locked_value) = unlock_grams(locked, total_locked_value, time); + int ton_balance = get_balance().pair_first(); + return ( ton_balance, + total_restricted_value, + total_locked_value ); +} + +(int, int, int) get_balances() method_id { + return get_balances_at(now()); +} + +int check_destination(slice destination) method_id { + var (stored_seqno, stored_subwallet, public_key, config_public_key, allowed_destinations, total_locked_value, locked, + total_restricted_value, restricted) = unpack_data(); + return is_whitelisted?(destination, allowed_destinations); +} diff --git a/crypto/func/auto-tests/legacy_tests/wallet-v4/stdlib.fc b/crypto/func/auto-tests/legacy_tests/wallet-v4/stdlib.fc new file mode 100644 index 00000000..05c3a4c0 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/wallet-v4/stdlib.fc @@ -0,0 +1,215 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +builder store_coins(builder b, int x) asm "STVARUINT16"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDVARUINT16"; + +int equal_slices (slice a, slice b) asm "SDEQ"; +int builder_null?(builder b) asm "ISNULL"; +builder store_builder(builder to, builder from) asm "STBR"; diff --git a/crypto/func/auto-tests/legacy_tests/wallet-v4/wallet-v4-code.fc b/crypto/func/auto-tests/legacy_tests/wallet-v4/wallet-v4-code.fc new file mode 100644 index 00000000..21245a67 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/wallet-v4/wallet-v4-code.fc @@ -0,0 +1,199 @@ +;; Wallet smart contract with plugins + +#include "stdlib.fc"; + +(slice, int) dict_get?(cell dict, int key_len, slice index) asm(index dict key_len) "DICTGET" "NULLSWAPIFNOT"; +(cell, int) dict_add_builder?(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTADDB"; +(cell, int) dict_delete?(cell dict, int key_len, slice index) asm(index dict key_len) "DICTDEL"; + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + var cs = in_msg_cell.begin_parse(); + var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + if (flags & 1) { + ;; ignore all bounced messages + return (); + } + if (in_msg.slice_bits() < 32) { + ;; ignore simple transfers + return (); + } + int op = in_msg~load_uint(32); + if (op != 0x706c7567) & (op != 0x64737472) { ;; "plug" & "dstr" + ;; ignore all messages not related to plugins + return (); + } + slice s_addr = cs~load_msg_addr(); + (int wc, int addr_hash) = parse_std_addr(s_addr); + slice wc_n_address = begin_cell().store_int(wc, 8).store_uint(addr_hash, 256).end_cell().begin_parse(); + var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); + var plugins = ds~load_dict(); + var (_, success?) = plugins.dict_get?(8 + 256, wc_n_address); + if ~(success?) { + ;; it may be a transfer + return (); + } + int query_id = in_msg~load_uint(64); + var msg = begin_cell(); + if (op == 0x706c7567) { ;; request funds + + (int r_toncoins, cell r_extra) = (in_msg~load_grams(), in_msg~load_dict()); + + [int my_balance, _] = get_balance(); + throw_unless(80, my_balance - msg_value >= r_toncoins); + + msg = msg.store_uint(0x18, 6) + .store_slice(s_addr) + .store_grams(r_toncoins) + .store_dict(r_extra) + .store_uint(0, 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0x706c7567 | 0x80000000, 32) + .store_uint(query_id, 64); + send_raw_message(msg.end_cell(), 64); + + } + + if (op == 0x64737472) { ;; remove plugin by its request + + plugins~dict_delete?(8 + 256, wc_n_address); + var ds = get_data().begin_parse().first_bits(32 + 32 + 256); + set_data(begin_cell().store_slice(ds).store_dict(plugins).end_cell()); + ;; return coins only if bounce expected + if (flags & 2) { + msg = msg.store_uint(0x18, 6) + .store_slice(s_addr) + .store_grams(0) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0x64737472 | 0x80000000, 32) + .store_uint(query_id, 64); + send_raw_message(msg.end_cell(), 64); + } + } +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); + throw_if(36, valid_until <= now()); + var ds = get_data().begin_parse(); + var (stored_seqno, stored_subwallet, public_key, plugins) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256), ds~load_dict()); + ds.end_parse(); + throw_unless(33, msg_seqno == stored_seqno); + throw_unless(34, subwallet_id == stored_subwallet); + throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key)); + accept_message(); + set_data(begin_cell() + .store_uint(stored_seqno + 1, 32) + .store_uint(stored_subwallet, 32) + .store_uint(public_key, 256) + .store_dict(plugins) + .end_cell()); + commit(); + cs~touch(); + int op = cs~load_uint(8); + + if (op == 0) { ;; simple send + while (cs.slice_refs()) { + var mode = cs~load_uint(8); + send_raw_message(cs~load_ref(), mode); + } + return (); ;; have already saved the storage + } + + if (op == 1) { ;; deploy and install plugin + int plugin_workchain = cs~load_int(8); + int plugin_balance = cs~load_grams(); + (cell state_init, cell body) = (cs~load_ref(), cs~load_ref()); + int plugin_address = cell_hash(state_init); + slice wc_n_address = begin_cell().store_int(plugin_workchain, 8).store_uint(plugin_address, 256).end_cell().begin_parse(); + var msg = begin_cell() + .store_uint(0x18, 6) + .store_uint(4, 3).store_slice(wc_n_address) + .store_grams(plugin_balance) + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init) + .store_ref(body); + send_raw_message(msg.end_cell(), 3); + (plugins, int success?) = plugins.dict_add_builder?(8 + 256, wc_n_address, begin_cell()); + throw_unless(39, success?); + } + + if (op == 2) { ;; install plugin + slice wc_n_address = cs~load_bits(8 + 256); + int amount = cs~load_grams(); + int query_id = cs~load_uint(64); + + (plugins, int success?) = plugins.dict_add_builder?(8 + 256, wc_n_address, begin_cell()); + throw_unless(39, success?); + + builder msg = begin_cell() + .store_uint(0x18, 6) + .store_uint(4, 3).store_slice(wc_n_address) + .store_grams(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0x6e6f7465, 32) ;; op + .store_uint(query_id, 64); + send_raw_message(msg.end_cell(), 3); + } + + if (op == 3) { ;; remove plugin + slice wc_n_address = cs~load_bits(8 + 256); + int amount = cs~load_grams(); + int query_id = cs~load_uint(64); + + (plugins, int success?) = plugins.dict_delete?(8 + 256, wc_n_address); + throw_unless(39, success?); + + builder msg = begin_cell() + .store_uint(0x18, 6) + .store_uint(4, 3).store_slice(wc_n_address) + .store_grams(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0x64737472, 32) ;; op + .store_uint(query_id, 64); + send_raw_message(msg.end_cell(), 3); + } + + set_data(begin_cell() + .store_uint(stored_seqno + 1, 32) + .store_uint(stored_subwallet, 32) + .store_uint(public_key, 256) + .store_dict(plugins) + .end_cell()); +} + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +int get_subwallet_id() method_id { + return get_data().begin_parse().skip_bits(32).preload_uint(32); +} + +int get_public_key() method_id { + var cs = get_data().begin_parse().skip_bits(64); + return cs.preload_uint(256); +} + +int is_plugin_installed(int wc, int addr_hash) method_id { + var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); + var plugins = ds~load_dict(); + var (_, success?) = plugins.dict_get?(8 + 256, begin_cell().store_int(wc, 8).store_uint(addr_hash, 256).end_cell().begin_parse()); + return success?; +} + +tuple get_plugin_list() method_id { + var list = null(); + var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); + var plugins = ds~load_dict(); + do { + var (wc_n_address, _, f) = plugins~dict::delete_get_min(8 + 256); + if (f) { + (int wc, int addr) = (wc_n_address~load_int(8), wc_n_address~load_uint(256)); + list = cons(pair(wc, addr), list); + } + } until (~ f); + return list; +} diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/LICENSE b/crypto/func/auto-tests/legacy_tests/whales-nominators/LICENSE new file mode 100644 index 00000000..70b5b1cf --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/LICENSE @@ -0,0 +1,676 @@ +AUTHOR: https://github.com/ton-foundation/ton-nominators + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/constants.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/constants.fc new file mode 100644 index 00000000..7187fef0 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/constants.fc @@ -0,0 +1,100 @@ +;; +;; Errors +;; + +int error::invalid_address() asm "70 PUSHINT"; +int error::invalid_message() asm "72 PUSHINT"; +int error::access_denied() asm "73 PUSHINT"; +int error::internal_error() asm "74 PUSHINT"; +int error::already_deployed() asm "75 PUSHINT"; +int error::too_low_value() asm "76 PUSHINT"; +int error::invalid_stake_value() asm "77 PUSHINT"; +int error::unknown_text_command() asm "78 PUSHINT"; + +;; +;; Members +;; + +int op::stake_deposit() asm "2077040623 PUSHINT"; +int op::stake_deposit::response() asm "3326208306 PUSHINT"; +int op::stake_withdraw() asm "3665837821 PUSHINT"; +int op::stake_withdraw::delayed() asm "1958425639 PUSHINT"; +int op::stake_withdraw::response() asm "601104865 PUSHINT"; +int op::donate() asm "1203495973 PUSHINT"; +int op::donate::response() asm "3095625004 PUSHINT"; +;; +;; Owner +;; + +int op::upgrade() asm "3690657815 PUSHINT"; +int op::upgrade::response() asm "1395540087 PUSHINT"; +int op::update() asm "37541164 PUSHINT"; +int op::update::response() asm "839996522 PUSHINT"; + +;; +;; Controller +;; + +int op::stake_send() asm "2718326572 PUSHINT"; +int op::accept_stakes() asm "2577928699 PUSHINT"; +int op::accept_withdraws() asm "2711607604 PUSHINT"; +int op::stake_recover() asm "1699565966 PUSHINT"; +int op::withdraw_unowned() asm "622684824 PUSHINT"; +int op::withdraw_unowned::response() asm "488052159 PUSHINT"; +int op::force_kick() asm "1396625244 PUSHINT"; +int op::force_kick::notification() asm "2060499266 PUSHINT"; + +;; +;; Elector +;; + +int elector::refund::request() asm "0x47657424 PUSHINT"; +int elector::refund::response() asm "0xf96f7324 PUSHINT"; + +int elector::stake::request() asm "0x4e73744b PUSHINT"; +int elector::stake::response() asm "0xf374484c PUSHINT"; +int elector::stake::response::fail() asm "0xee6f454c PUSHINT"; + +;; +;; Send Mode +;; + +int send_mode::default() asm "0 PUSHINT"; +int send_mode::separate_gas() asm "1 PUSHINT"; +int send_mode::ignore_errors() asm "2 PUSHINT"; +int send_mode::carry_remaining_balance() asm "128 PUSHINT"; +int send_mode::carry_remaining_value() asm "64 PUSHINT"; +int send_mode::destroy_if_zero() asm "64 PUSHINT"; + +;; +;; Coins +;; + +int coins::1() asm "1000000000 PUSHINT"; +int coins::100() asm "100000000000 PUSHINT"; + +;; +;; Fees +;; + +int fees::storage_reserve() asm "1000000000 PUSHINT"; ;; 1 TON +int fees::receipt() asm "100000000 PUSHINT"; ;; 0.1 TON +int fees::op() asm "100000000 PUSHINT"; ;; 0.1 TON +int fees::deploy() asm "5000000000 PUSHINT"; ;; 5 TON +int fees::stake_fees() asm "2000000000 PUSHINT"; ;; 2 TON + +;; +;; Parameters +;; + +int params::min_op() asm "100000000 PUSHINT"; ;; 0.1 TON +int params::min_stake() asm "1000000000 PUSHINT"; ;; 1 TON +int params::min_fee() asm "1000000000 PUSHINT"; ;; 1 TON +int params::pending_op() asm "5000000000 PUSHINT"; ;; 5 TON +int params::ppc_precision() asm "10000000000000 PUSHINT"; ;; 10^13 + +;; +;; Members +;; + +int owner_id() asm "0 PUSHINT"; \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/get.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/get.fc new file mode 100644 index 00000000..c9152e1c --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/get.fc @@ -0,0 +1,125 @@ +;; +;; Related contracts +;; + +_ get_proxy() method_id { + load_base_data(); + return ctx_proxy; +} + +_ get_owner() method_id { + load_base_data(); + return ctx_owner; +} + +_ get_controller() method_id { + load_base_data(); + return ctx_controller; +} + +;; +;; Balances for controller +;; + +_ get_unowned() method_id { + load_base_data(); + var [balance, extra] = get_balance(); + return max(balance - owned_balance(), 0); +} + +_ get_available() method_id { + load_base_data(); + return ctx_balance - ctx_balance_sent; +} + +;; +;; Pool and staking status +;; + +_ get_staking_status() method_id { + load_base_data(); + load_validator_data(); + + var querySent = proxy_stored_query_id != 0; + var unlocked = (proxy_stake_until == 0) | (proxy_stake_until < now()); + var until_val = proxy_stake_until; + if ((proxy_stake_at != 0) & (proxy_stake_until != 0)) { + until_val = lockup_lift_time(proxy_stake_at, proxy_stake_until); + unlocked = unlocked & (until_val < now()); + } + return (proxy_stake_at, until_val, proxy_stake_sent, querySent, unlocked, ctx_locked); +} + +_ get_pool_status() method_id { + load_base_data(); + load_member(owner_id()); + return (ctx_balance, ctx_balance_sent, ctx_balance_pending_deposits, ctx_balance_pending_withdraw, ctx_balance_withdraw); +} + +;; +;; Params +;; +_ get_params() method_id { + load_base_data(); + var (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price) = ctx_extras; + return (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price); +} + +;; +;; Members +;; + +_ get_member_balance(slice address) method_id { + load_base_data(); + load_member(parse_work_addr(address)); + + member_update_balance(); + return (ctx_member_balance, ctx_member_pending_deposit, ctx_member_pending_withdraw, ctx_member_withdraw); +} + +_ get_members_raw() method_id { + load_base_data(); + return ctx_nominators; +} + +_ get_members() method_id { + load_base_data(); + + ;; Init with owner + load_member(owner_id()); + member_update_balance(); + var list = nil; + list = cons([ctx_owner, ctx_member_balance, ctx_member_pending_deposit, ctx_member_pending_withdraw, ctx_member_withdraw], list); + + ;; Iterate all members + var id = -1; + do { + (id, var cs, var f) = ctx_nominators.udict_get_next?(256, id); + + ;; NOTE: One line condition doesn't work + if (f) { + if (id != owner_id()) { + ;; For some reason loading member from slice doesn't work + load_member(id); + member_update_balance(); + list = cons([serialize_work_addr(id), ctx_member_balance, ctx_member_pending_deposit, ctx_member_pending_withdraw, ctx_member_withdraw], list); + } + } + } until (~ f); + + return list; +} + +_ get_member(slice address) method_id { + load_base_data(); + load_member(parse_work_addr(address)); + member_update_balance(); + return (ctx_member_balance, ctx_member_pending_deposit, ctx_member_pending_withdraw, ctx_member_withdraw); +} + +_ supported_interfaces() method_id { + return ( + 123515602279859691144772641439386770278, ;; org.ton.introspection.v0 + 256184278959413194623484780286929323492 ;; com.tonwhales.nominators:v0 + ); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/model.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/model.fc new file mode 100644 index 00000000..9c28b42a --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/model.fc @@ -0,0 +1,297 @@ +;; +;; Low level operations +;; + +() add_member_pending_withdraw(int delta) impure inline { + ctx_balance_pending_withdraw = ctx_balance_pending_withdraw + delta; + ctx_member_pending_withdraw = ctx_member_pending_withdraw + delta; +} +() set_member_pending_withdraw(int value) impure inline { + add_member_pending_withdraw(value - ctx_member_pending_withdraw); +} + +() add_member_pending_deposit(int delta) impure inline { + ctx_member_pending_deposit = ctx_member_pending_deposit + delta; + ctx_balance_pending_deposits = ctx_balance_pending_deposits + delta; +} +() set_member_pending_deposit(int value) impure inline { + add_member_pending_deposit(value - ctx_member_pending_deposit); +} + +int compose_profit(int a, int b) { + ;; (a + 1) * (b + 1) - 1 + return (((a + params::ppc_precision()) * (b + params::ppc_precision())) / params::ppc_precision()) - params::ppc_precision(); ;; NOTE: Rounded down +} + +int apply_profit(int value, int value_profit, int profit) { + return ((params::ppc_precision() + profit) * value) / (params::ppc_precision() + value_profit); ;; NOTE: Rounded down +} + +;; +;; Deposit +;; + +() member_update_balance() impure { + + ;; Update profit (for non-owner) + if (ctx_member != owner_id()) { + if (ctx_profit_per_coin != ctx_member_profit_per_coin) { + int new_balance = apply_profit(ctx_member_balance, ctx_member_profit_per_coin, ctx_profit_per_coin); + int delta_balance = new_balance - ctx_member_balance; + ctx_member_balance = ctx_member_balance + delta_balance; + ctx_member_profit_per_coin = ctx_profit_per_coin; + } + } + + ;; Update pending withdraw + if (ctx_member_pending_withdraw_all) { + if (ctx_member_pending_withdraw != ctx_member_balance) { + set_member_pending_withdraw(ctx_member_balance); + } + } else { + if (ctx_member_pending_withdraw > ctx_member_balance) { + set_member_pending_withdraw(ctx_member_balance); + } + } +} + +() member_reset_pending_withdraw() impure { + set_member_pending_withdraw(0); + ctx_member_pending_withdraw_all = false; +} + +() member_stake_deposit(int value) impure { + throw_unless(error::invalid_stake_value(), value > 0); + + ;; Update balances + member_update_balance(); + + ;; Reset pending withdrawal + member_reset_pending_withdraw(); + + ;; Add deposit to pending + ;; NOTE: We are not adding directly deposit to member's balance + ;; and we are always confirming acception of deposit to a pool + ;; via sending accept message. This could be done on- and off-chain. + ;; This could be useful to make private nominator pools or black lists. + ;; Anyone always could withdraw their deposits though. + add_member_pending_deposit(value); +} + +() member_accept_stake() impure { + + ;; Checks if there are pending deposits + throw_unless(error::invalid_message(), ctx_member_pending_deposit > 0); + + ;; Check if not locked + throw_if(error::invalid_message(), ctx_locked); + + ;; Recalculate balance + member_update_balance(); + + ;; Move deposit to member's balance + var amount = ctx_member_pending_deposit; + set_member_pending_deposit(0); + + + ctx_member_balance = ctx_member_balance + amount; + ctx_balance = ctx_balance + amount; +} + +;; +;; Withdraw +;; + +(int, int) member_stake_withdraw(int value) impure { + + ;; Check input + throw_unless(error::invalid_stake_value(), value >= 0); + + ;; Update balances + member_update_balance(); + + ;; Reset pending withdrawal: would be overwritten later + member_reset_pending_withdraw(); + + ;; Pre-flight withdraw check + throw_unless(error::invalid_stake_value(), value >= 0); + throw_unless(error::invalid_stake_value(), ctx_member_balance + ctx_member_withdraw + ctx_member_pending_deposit >= value); + + ;; Check withdraw all + var withdraw_all = false; + if (value == 0) { + withdraw_all = true; + value = ctx_member_pending_deposit + ctx_member_balance + ctx_member_withdraw; + } + + ;; Trying to withdraw immediatelly + var remaining = value; + var withdrawed = 0; + + ;; Try to withdraw from pending deposit + if ((remaining > 0) & (ctx_member_pending_deposit >= 0)) { + int delta = min(ctx_member_pending_deposit, remaining); + add_member_pending_deposit(- delta); + withdrawed = withdrawed + delta; + remaining = remaining - delta; + } + + ;; Try to withdraw from withdraw balance + if ((remaining > 0) & ctx_member_withdraw > 0) { + int delta = min(ctx_member_withdraw, remaining); + ctx_member_withdraw = ctx_member_withdraw - delta; + ctx_balance_withdraw = ctx_balance_withdraw - delta; + withdrawed = withdrawed + delta; + remaining = remaining - delta; + } + + ;; Try to withdraw from balance + if ((remaining > 0) & (~ ctx_locked) & (ctx_member_balance > 0)) { + int delta = min(ctx_member_balance, remaining); + ctx_member_balance = ctx_member_balance - delta; + ctx_balance = ctx_balance - delta; + withdrawed = withdrawed + delta; + remaining = remaining - delta; + } + + ;; Add to pending withdrawals + if (remaining > 0) { + add_member_pending_withdraw(remaining); + ctx_member_pending_withdraw_all = withdraw_all; + } + + ;; Return withdraw result + return (withdrawed, remaining == 0); +} + +() member_accept_withdraw() impure { + + ;; Checks if there are pending withdrawals + throw_unless(error::invalid_message(), ctx_member_pending_withdraw > 0); + + ;; Check if not locked + throw_if(error::invalid_message(), ctx_locked); + + ;; Recalculate balance + member_update_balance(); + + ;; Move deposit to member's balance + var amount = ctx_member_pending_withdraw; + + ctx_member_balance = ctx_member_balance - amount; + ctx_member_withdraw = ctx_member_withdraw + amount; + ctx_balance = ctx_balance - amount; + ctx_balance_withdraw = ctx_balance_withdraw + amount; + ctx_balance_pending_withdraw = ctx_balance_pending_withdraw - amount; + ctx_member_pending_withdraw = 0; + ctx_member_pending_withdraw_all = false; +} + +() distribute_profit(int profit) impure { + + ;; Load extras + var (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price) = ctx_extras; + + ;; Load owner balances + load_member(0); + + ;; Loss + if (profit < 0) { + + ;; Stakes + var owner_stake = ctx_member_balance; + var nominators_stake = ctx_balance - owner_stake; + + ;; Distribute loss to everyone + var cycleProfitPerCoin = profit * params::ppc_precision() / ctx_balance; + var nominators_profit = (nominators_stake * cycleProfitPerCoin) / params::ppc_precision(); + var owner_profit = profit - nominators_profit; + + ;; Update balances + ctx_balance = ctx_balance + profit; + ctx_member_balance = ctx_member_balance + owner_profit; + ctx_profit_per_coin = compose_profit(ctx_profit_per_coin, cycleProfitPerCoin); + + ;; Persist + store_member(); + + return (); + } + + ;; Profit + if (profit > 0) { + + ;; Stakes + var owner_stake = ctx_member_balance; + var nominators_stake = ctx_balance - owner_stake; + + ;; Distribute profit + var cycleProfitPerCoin = profit * params::ppc_precision() * (100 * 100 - pool_fee) / (ctx_balance * 100 * 100); + var nominators_profit = (nominators_stake * cycleProfitPerCoin) / params::ppc_precision(); + var owner_profit = profit - nominators_profit; + + ;; Update balances + ctx_balance = ctx_balance + profit; + ctx_member_balance = ctx_member_balance + owner_profit; + ctx_profit_per_coin = compose_profit(ctx_profit_per_coin, cycleProfitPerCoin); + + ;; Persist + store_member(); + + return (); + } +} + +;; +;; Validator +;; + +() on_locked() impure { + if (~ ctx_locked) { + + ;; Allow locking only on no pending withdrawals + throw_unless(error::invalid_message(), ctx_balance_pending_withdraw == 0); + + ;; Update state + ctx_locked = true; + } +} + +() on_unlocked() impure { + if (ctx_locked) { + + ;; Update state + ctx_locked = false; + } +} + +int available_to_stake() { + return ctx_balance - ctx_balance_sent; +} + +int owned_balance() { + return ctx_balance - ctx_balance_sent + ctx_balance_pending_deposits + ctx_balance_withdraw + fees::storage_reserve(); +} + +() on_stake_sent(int stake) impure { + ctx_balance_sent = ctx_balance_sent + stake; +} + +() on_stake_sent_failed(int stake) impure { + ctx_balance_sent = ctx_balance_sent - stake; +} + +() on_stake_recovered(int stake) impure { + + ;; Calculate profit + ;; NOTE: ctx_locked is true meaning that ctx_balance + ;; have the same value as was when stake was sent + ;; balances are going to be unlocked after profit distribution + var profit = stake - ctx_balance_sent; + + ;; Distribute profit + distribute_profit(profit); + + ;; Reset sent amount + ctx_balance_sent = 0; +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-common.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-common.fc new file mode 100644 index 00000000..b83afdbd --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-common.fc @@ -0,0 +1,175 @@ +() op_deposit(int member, int value) impure { + + ;; Read extras + var (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price) = ctx_extras; + throw_unless(error::invalid_message(), enabled); + + ;; Read stake value + int fee = receipt_price + deposit_fee; + int stake = value - fee; + throw_unless(error::invalid_stake_value(), stake >= min_stake); + + ;; Load nominators + load_member(member); + + ;; Add deposit + member_stake_deposit(stake); + + ;; Resolve address + var address = ctx_owner; + if (member != owner_id()) { + address = serialize_work_addr(member); + } + + ;; Send receipt + if (ctx_query_id == 0) { + send_text_message( + address, + receipt_price, + send_mode::default(), + begin_cell() + .store_accepted_stake(stake) + ); + } else { + send_empty_std_message( + address, + receipt_price, + send_mode::default(), + op::stake_deposit::response(), + ctx_query_id + ); + } + + ;; Persist + store_member(); + store_base_data(); +} + +() op_withdraw(int member, int value, int stake) impure { + + ;; Read extras + var (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price) = ctx_extras; + + ;; Check fee + int fee = receipt_price + withdraw_fee; + + ;; Check value + throw_unless(error::too_low_value(), value == fee); + + ;; Load member + load_member(member); + + ;; Try to withdraw immediatelly + var (withdrawed, all) = member_stake_withdraw(stake); + + ;; Resolve address + var address = ctx_owner; + if (member != owner_id()) { + address = serialize_work_addr(member); + } + + ;; Send receipt + if (ctx_query_id == 0) { + send_text_message( + address, + withdrawed + receipt_price, + send_mode::default(), + all ? begin_cell().store_withdraw_completed() : begin_cell().store_withdraw_delayed() + ); + } else { + send_empty_std_message( + address, + withdrawed + receipt_price, + send_mode::default(), + all ? op::stake_withdraw::response() : op::stake_withdraw::delayed(), + ctx_query_id + ); + } + + ;; Persist + store_member(); + store_base_data(); +} + +() op_donate(int value) impure { + + ;; Check value + throw_unless(error::invalid_message(), value >= 2 * coins::1()); + + ;; Distribute profit to everyone + distribute_profit(value - coins::1()); + + ;; Persist + store_base_data(); +} + +() op_upgrade(int value, slice in_msg) impure { + + ;; Read extras + var (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price) = ctx_extras; + throw_unless(error::invalid_message(), udpates_enabled); + + ;; Check value + throw_unless(error::too_low_value(), value >= fees::deploy()); + + ;; Upgrade code + var code = in_msg~load_ref(); + in_msg.end_parse(); + set_code(code); + + ;; Send receipt + send_empty_std_message( + ctx_owner, + 0, + send_mode::carry_remaining_value(), + op::upgrade::response(), + ctx_query_id + ); +} + +() op_update(int value, slice in_msg) impure { + + ;; Read extras + var (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price) = ctx_extras; + + ;; Check value + throw_unless(error::too_low_value(), value >= fees::deploy()); + + ;; Check extras + var newExtras = in_msg~load_ref(); + var es = newExtras.begin_parse(); + var new_enabled = es~load_int(1); + var new_udpates_enabled = es~load_int(1); + var new_min_stake = es~load_coins(); + var new_deposit_fee = es~load_coins(); + var new_withdraw_fee = es~load_coins(); + var new_pool_fee = es~load_coins(); + var new_receipt_price = es~load_coins(); + es.end_parse(); + + ;; Once upgrades are disabled: prohibit re-enabling + throw_if(error::invalid_message(), (~ udpates_enabled) & new_udpates_enabled); + + ;; At least min_stake + throw_unless(error::invalid_message(), new_min_stake >= params::min_stake()); + ;; At least op fee + throw_unless(error::invalid_message(), new_deposit_fee >= fees::op()); + throw_unless(error::invalid_message(), new_withdraw_fee >= fees::op()); + ;; Must be in 0...10000 + throw_unless(error::invalid_message(), new_pool_fee <= 100 * 100); + ;; At least receipt price + throw_unless(error::invalid_message(), new_receipt_price >= fees::receipt()); + + ;; Persist extras + ctx_extras = (new_enabled, new_udpates_enabled, new_min_stake, new_deposit_fee, new_withdraw_fee, new_pool_fee, new_receipt_price); + store_base_data(); + + ;; Send receipt + send_empty_std_message( + ctx_owner, + 0, + send_mode::carry_remaining_value(), + op::update::response(), + ctx_query_id + ); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-controller.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-controller.fc new file mode 100644 index 00000000..98a8fe59 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-controller.fc @@ -0,0 +1,414 @@ +;; +;; Stake Sending +;; + +() op_controller_stake_send(int value, slice in_msg) impure { + + ;; Parse message + var stake = in_msg~load_coins(); + var validator_pubkey = in_msg~load_uint(256); + var stake_at = in_msg~load_uint(32); + var max_factor = in_msg~load_uint(32); + var adnl_addr = in_msg~load_uint(256); + var signature_ref = in_msg~load_ref(); + var signature = signature_ref.begin_parse().preload_bits(512); + in_msg.end_parse(); + + ;; Check message value + throw_unless(error::invalid_message(), value >= fees::stake_fees()); + + ;; Allow only single request to elector + if (proxy_stored_query_id != 0) { + throw(error::invalid_message()); + } + + ;; Allow update only for current stake + if ((proxy_stake_at != 0) & (proxy_stake_at != stake_at)) { + throw(error::invalid_message()); + } + + ;; Check stake value + var availableStake = available_to_stake(); + throw_unless(error::invalid_stake_value(), availableStake >= stake); + + ;; Parameters + var (electedFor, stakeHeldFor) = get_stake_parameters(); + + ;; Lock stakes + on_locked(); + + ;; Update operation state + proxy_stake_at = stake_at; + proxy_stake_until = stake_at + electedFor + stakeHeldFor; + proxy_stake_sent = proxy_stake_sent + stake; + proxy_stored_query_id = ctx_query_id; + proxy_stored_query_op = elector::stake::request(); + proxy_stored_query_stake = stake; + + ;; Update balances + on_stake_sent(stake); + + ;; Send message to elector + send_std_message( + ctx_proxy, + stake + coins::1(), + send_mode::separate_gas(), + elector::stake::request(), + proxy_stored_query_id, + begin_cell() + .store_uint(validator_pubkey, 256) + .store_uint(stake_at, 32) + .store_uint(max_factor, 32) + .store_uint(adnl_addr, 256) + .store_ref(signature_ref) + ); + + ;; Persist + store_validator_data(); + store_base_data(); +} + +() op_elector_stake_response(int value, slice in_msg) impure { + + ;; Check response + if (ctx_query_id != proxy_stored_query_id) { + ;; How to handle invalid? How it is possible? + return (); + } + if (proxy_stored_query_op != elector::stake::request()) { + ;; How to handle invalid? How it is possible? + return (); + } + + ;; Reset active query + proxy_stored_query_id = 0; + proxy_stored_query_op = 0; + proxy_stored_query_stake = 0; + + ;; Persist + store_validator_data(); + store_base_data(); +} + +() op_elector_stake_response_fail(int value, slice in_msg) impure { + + ;; Load reason + var reason = in_msg~load_uint(32); + + ;; Check response + if (ctx_query_id != proxy_stored_query_id) { + ;; How to handle invalid? How it is possible? + return (); + } + if (proxy_stored_query_op != elector::stake::request()) { + ;; How to handle invalid? How it is possible? + return (); + } + + ;; Update balances + on_stake_sent_failed(proxy_stored_query_stake); + + ;; Update proxy state + proxy_stake_sent = proxy_stake_sent - proxy_stored_query_stake; + + ;; Reset stake at since sent stake became zero + if (proxy_stake_sent == 0) { + proxy_stake_at = 0; + proxy_stake_until = 0; + proxy_stake_sent = 0; + on_unlocked(); + } + + ;; Reset query + proxy_stored_query_id = 0; + proxy_stored_query_op = 0; + proxy_stored_query_stake = 0; + + ;; Persist + store_validator_data(); + store_base_data(); +} + +;; +;; Recover +;; + +() op_stake_recover(int value) impure { + + ;; NOTE: We never block stake recover operation + ;; in case of misbehaviour of something anyone always can get + ;; coins from elector after lockup period is up + + ;; Allow request only if stake is exited lockup period + if ((proxy_stake_until != 0) & (now() < proxy_stake_until)) { + throw(error::invalid_message()); + } + + ;; Double check that validation session and lockup was lifted + if ((proxy_stake_until != 0) & (proxy_stake_at != 0)) { + throw_unless(error::invalid_message(), lockup_lift_time(proxy_stake_at, proxy_stake_until) <= now()); + } + + ;; Check value + throw_unless(error::too_low_value(), value >= fees::stake_fees()); + + ;; Send message to elector + send_empty_std_message( + ctx_proxy, + 0, + send_mode::carry_remaining_value(), + elector::refund::request(), + proxy_stored_query_id + ); + + ;; Persist + store_validator_data(); + store_base_data(); +} + +() op_elector_recover_response(int value, slice in_msg) impure { + + if ((proxy_stake_until != 0) & (now() > proxy_stake_until)) { + + ;; Reset state: all stake is returned + proxy_stake_sent = 0; + proxy_stake_at = 0; + proxy_stake_until = 0; + + ;; Reset query too + proxy_stored_query_id = 0; + proxy_stored_query_op = 0; + proxy_stored_query_stake = 0; + + ;; Handle stake recovered event + ;; NOTE: Any stake recovery outside this condition might be just a noise and + ;; effect of various race condirtions that doesn't carry any substantianal vakue + on_stake_recovered(value - fees::stake_fees()); + + ;; Reset lock state + ;; NOTE: MUST be after on_stake_recovered since it adjusts withdrawals and + ;; modifies global balance + on_unlocked(); + } + + ;; Persist + store_validator_data(); + store_base_data(); +} + +;; +;; Withdraw unowned +;; + +() op_controller_withdraw_unowned(int value, slice in_msg) impure { + + ;; Reserve owned + raw_reserve(owned_balance(), 0); + + ;; Send message to controller + send_empty_std_message( + ctx_controller, + 0, + send_mode::carry_remaining_balance(), + op::withdraw_unowned::response(), + ctx_query_id + ); +} + +;; +;; Process pending +;; + +() op_controller_accept_stakes(int value, slice in_msg) impure { + + ;; Check if enought value + throw_unless(error::invalid_message(), value >= params::pending_op()); + + ;; Check if not locked + throw_if(error::invalid_message(), ctx_locked); + + ;; Parse message + var members = in_msg~load_dict(); + in_msg.end_parse(); + + ;; Process operations + var member = -1; + do { + (member, var cs, var f) = members.udict_get_next?(256, member); + if (f) { + ;; Accept member's stake + load_member(member); + member_accept_stake(); + store_member(); + } + } until (~ f); + + ;; Persist + store_base_data(); +} + +() op_controller_accept_withdraws(int value, slice in_msg) impure { + + ;; Check if enought value + throw_unless(error::invalid_message(), value >= params::pending_op()); + + ;; Check if not locked + throw_if(error::invalid_message(), ctx_locked); + + ;; Parse message + var members = in_msg~load_dict(); + in_msg.end_parse(); + + ;; Process operations + var member = -1; + do { + (member, var cs, var f) = members.udict_get_next?(256, member); + if (f) { + ;; Accept member's stake + load_member(member); + member_accept_withdraw(); + store_member(); + } + } until (~ f); + + ;; Persist + store_base_data(); +} + +() op_controller_force_kick(int value, slice in_msg) impure { + + ;; Check if enought value + throw_unless(error::invalid_message(), value >= params::pending_op()); + + ;; Check if not locked + throw_if(error::invalid_message(), ctx_locked); + + ;; Parse message + var members = in_msg~load_dict(); + in_msg.end_parse(); + + ;; Process operations + var member = -1; + do { + (member, var cs, var f) = members.udict_get_next?(256, member); + if (f) { + + ;; Reject owner kicking + throw_if(error::invalid_message(), member == owner_id()); + + ;; Kick member from a pool + load_member(member); + + ;; Withdraw everything + var (withdrawed, all) = member_stake_withdraw(0); + throw_unless(error::invalid_message(), withdrawed > 0); + throw_unless(error::invalid_message(), all); + + ;; Forced kick + send_empty_std_message( + serialize_work_addr(member), + withdrawed, + send_mode::default(), + op::force_kick::notification(), + ctx_query_id + ); + + ;; Persist membership + store_member(); + } + } until (~ f); + + ;; Persist + store_base_data(); +} + +;; +;; Top Level +;; + +() op_controller(int flags, int value, slice in_msg) impure { + if (flags & 1) { + return (); + } + + ;; Check value + throw_unless(error::invalid_message(), value >= params::min_op()); + + ;; Parse operation + int op = in_msg~load_uint(32); + int query_id = in_msg~load_uint(64); + int gas_limit = in_msg~load_coins(); + set_gas_limit(gas_limit); + ctx_query_id = query_id; + throw_unless(error::invalid_message(), ctx_query_id > 0); + + ;; Send stake + if (op == op::stake_send()) { + op_controller_stake_send(value, in_msg); + return (); + } + + ;; Recover stake + if (op == op::stake_recover()) { + op_stake_recover(value); + return (); + } + + ;; Withdraw unowned + if (op == op::withdraw_unowned()) { + op_controller_withdraw_unowned(value, in_msg); + return (); + } + + ;; Accept stakes + if (op == op::accept_stakes()) { + op_controller_accept_stakes(value, in_msg); + return (); + } + + ;; Accept withdraws + if (op == op::accept_withdraws()) { + op_controller_accept_withdraws(value, in_msg); + return (); + } + + ;; Kick from pool + if (op == op::force_kick()) { + op_controller_force_kick(value, in_msg); + return (); + } + + ;; Unknown message + throw(error::invalid_message()); +} + +() op_elector(int flags, int value, slice in_msg) impure { + int op = in_msg~load_uint(32); + int query_id = in_msg~load_uint(64); + ctx_query_id = query_id; + + ;; Bounced + ;; It seems that handling doesn't make sence sicne there are no throws (?) + ;; in elector contract + if (flags & 1) { + return (); + } + + ;; Stake response + if (op == elector::stake::response()) { + op_elector_stake_response(value, in_msg); + return (); + } + if (op == elector::stake::response::fail()) { + op_elector_stake_response_fail(value, in_msg); + return (); + } + + ;; Refund response + if (op == elector::refund::response()) { + op_elector_recover_response(value, in_msg); + return (); + } + + ;; Ignore +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-nominators.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-nominators.fc new file mode 100644 index 00000000..233f1397 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-nominators.fc @@ -0,0 +1,104 @@ +(slice, (int)) ~parse_text_command(slice in_msg) { + int op = 0; + ;; 3 possible commands deposit, recover, withdraw + int first_char = in_msg~load_uint(8); + + ;; Deposit + if( first_char == 68 ) { ;; D + throw_unless(error::unknown_text_command(), in_msg~load_uint(48) == 111533580577140); ;; eposit + op = op::stake_deposit(); + } + + ;; Withdraw + if( first_char == 87 ) { ;; W + throw_unless(error::unknown_text_command(), in_msg~load_uint(56) == 29682864265257335); ;; ithdraw + op = op::stake_withdraw(); + } + + ;; Recover + if( first_char == 82 ) { ;; R + throw_unless(error::unknown_text_command(), in_msg~load_uint(48) == 111477746197874); ;; ecover + op = op::stake_recover(); + } + + return (in_msg, (op)); +} + +() op_nominators(int member, int flags, int value, slice in_msg) impure { + + ;; Ignore bounced + if (flags & 1) { + return (); + } + + ;; Check value + throw_unless(error::invalid_message(), value >= params::min_op()); + + ;; Parse operation + int op = in_msg~load_uint(32); + + ;; Text operations + if (op == 0) { + ctx_query_id = 0; + op = in_msg~parse_text_command(); + + ;; Deposit stake + if (op == op::stake_deposit()) { + op_deposit(member, value); + return (); + } + + ;; Withdraw stake + if (op == op::stake_withdraw()) { + op_withdraw(member, value, 0); + return (); + } + + ;; Recover stake + if (op == op::stake_recover()) { + load_validator_data(); + op_stake_recover(value); + return (); + } + + ;; Unknown message + throw(error::invalid_message()); + return (); + } + + int query_id = in_msg~load_uint(64); + int gas_limit = in_msg~load_coins(); + set_gas_limit(gas_limit); + ctx_query_id = query_id; + throw_unless(error::invalid_message(), ctx_query_id > 0); + + ;; Deposit stake + if (op == op::stake_deposit()) { + op_deposit(member, value); + return (); + } + + ;; Withdraw stake + if (op == op::stake_withdraw()) { + int stake = in_msg~load_coins(); + in_msg.end_parse(); + op_withdraw(member, value, stake); + return (); + } + + ;; Recover stake + if (op == op::stake_recover()) { + load_validator_data(); + op_stake_recover(value); + return (); + } + + ;; Donate stake + if (op == op::donate()) { + op_donate(value); + return (); + } + + ;; Unknown message + throw(error::invalid_message()); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-owner.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-owner.fc new file mode 100644 index 00000000..231e6889 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-owner.fc @@ -0,0 +1,60 @@ +() op_owner(int flags, int value, slice in_msg) impure { + + ;; Ignore bounced + if (flags & 1) { + return (); + } + + ;; Check value + throw_unless(error::invalid_message(), value >= params::min_op()); + + ;; Parse operation + int op = in_msg~load_uint(32); + int query_id = in_msg~load_uint(64); + int gas_limit = in_msg~load_coins(); + set_gas_limit(gas_limit); + ctx_query_id = query_id; + throw_unless(error::invalid_message(), ctx_query_id > 0); + + ;; Upgrade + if (op == op::upgrade()) { + op_upgrade(value, in_msg); + return (); + } + + ;; Upgrade + if (op == op::update()) { + op_update(value, in_msg); + return (); + } + + ;; Add stake + if (op == op::stake_deposit()) { + op_deposit(owner_id(), value); + return (); + } + + ;; Withdraw stake + if (op == op::stake_withdraw()) { + int stake = in_msg~load_coins(); + in_msg.end_parse(); + op_withdraw(owner_id(), value, stake); + return (); + } + + ;; Recover stake + if (op == op::stake_recover()) { + load_validator_data(); + op_stake_recover(value); + return (); + } + + ;; Donate stake + if (op == op::donate()) { + op_donate(value); + return (); + } + + ;; Unknown message + throw(error::invalid_message()); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-base.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-base.fc new file mode 100644 index 00000000..989c1fd0 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-base.fc @@ -0,0 +1,99 @@ +global int ctx_query_id; + +global int ctx_locked; +global slice ctx_owner; +global slice ctx_controller; +global slice ctx_proxy; +global cell ctx_proxy_state; + +global int ctx_profit_per_coin; +global int ctx_balance; +global int ctx_balance_sent; +global int ctx_balance_withdraw; +global int ctx_balance_pending_withdraw; +global int ctx_balance_pending_deposits; + +global cell ctx_nominators; + +;; var (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price) = ctx_extras; +global (int, int, int, int, int, int, int) ctx_extras; + +() load_base_data() impure { + var ds = get_data().begin_parse(); + ctx_locked = ds~load_int(1); + + ctx_owner = ds~load_msg_addr(); + ctx_controller = ds~load_msg_addr(); + ctx_proxy = ds~load_msg_addr(); + + cell balance_cell = ds~load_ref(); + ctx_nominators = ds~load_dict(); + ctx_proxy_state = ds~load_ref(); + cell extras_cell = null(); + if (ds.slice_refs() > 0) { + extras_cell = ds~load_ref(); + } + ds.end_parse(); + + var bs = balance_cell.begin_parse(); + ctx_profit_per_coin = bs~load_int(128); + ctx_balance = bs~load_coins(); + ctx_balance_sent = bs~load_coins(); + ctx_balance_withdraw = bs~load_coins(); + ctx_balance_pending_withdraw = bs~load_coins(); + ctx_balance_pending_deposits = bs~load_coins(); + bs.end_parse(); + + ;; Parsing extras (enabled, upgrades_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price) + ctx_extras = ( + true, ;; Enabled + true, ;; Upgrades enabled + params::min_stake(), ;; Min Stake + fees::op(), ;; Deposit fee + fees::op(), ;; Withdraw fee + 10 * 100, ;; Pool fee (%), + fees::receipt() + ); + if (~ extras_cell.null?()) { + var ec = extras_cell.begin_parse(); + var enabled = ec~load_int(1); + var udpates_enabled = ec~load_int(1); + var min_stake = ec~load_coins(); + var deposit_fee = ec~load_coins(); + var withdraw_fee = ec~load_coins(); + var pool_fee = ec~load_coins(); + var receipt_price = ec~load_coins(); + ctx_extras = (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price); + ec.end_parse(); + } +} + +() store_base_data() impure { + var (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price) = ctx_extras; + set_data(begin_cell() + .store_int(ctx_locked, 1) + .store_slice(ctx_owner) + .store_slice(ctx_controller) + .store_slice(ctx_proxy) + .store_ref(begin_cell() + .store_int(ctx_profit_per_coin, 128) + .store_coins(ctx_balance) + .store_coins(ctx_balance_sent) + .store_coins(ctx_balance_withdraw) + .store_coins(ctx_balance_pending_withdraw) + .store_coins(ctx_balance_pending_deposits) + .end_cell()) + .store_dict(ctx_nominators) + .store_ref(ctx_proxy_state) + .store_ref(begin_cell() + .store_int(enabled, 1) + .store_int(udpates_enabled, 1) + .store_coins(min_stake) + .store_coins(deposit_fee) + .store_coins(withdraw_fee) + .store_coins(pool_fee) + .store_coins(receipt_price) + .end_cell()) + .end_cell()); + commit(); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-nominators.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-nominators.fc new file mode 100644 index 00000000..aa5ba3c7 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-nominators.fc @@ -0,0 +1,62 @@ +;; +;; Members +;; + +global int ctx_member; +global int ctx_member_balance; +global int ctx_member_pending_withdraw; +global int ctx_member_pending_withdraw_all; +global int ctx_member_pending_deposit; +global int ctx_member_withdraw; +global int ctx_member_profit_per_coin; +global int ctx_member_exist; + +slice load_member_slice(slice cs) impure { + ctx_member_profit_per_coin = cs~load_int(128); + ctx_member_balance = cs~load_coins(); + ctx_member_pending_withdraw = cs~load_coins(); + ctx_member_pending_withdraw_all = cs~load_int(1); + ctx_member_pending_deposit = cs~load_coins(); + ctx_member_withdraw = cs~load_coins(); + ctx_member_exist = true; + return cs; +} + +() load_member(int member) impure { + var (cs, found) = ctx_nominators.udict_get?(256, member); + ctx_member = member; + if (found) { + cs = load_member_slice(cs); + cs.end_parse(); + ctx_member_exist = true; + } else { + ctx_member_balance = 0; + ctx_member_pending_withdraw = 0; + ctx_member_pending_withdraw_all = false; + ctx_member_pending_deposit = 0; + ctx_member_profit_per_coin = 0; + ctx_member_withdraw = 0; + ctx_member_exist = false; + } +} + +() store_member() impure { + var shouldExist = (ctx_member_balance > 0) | (ctx_member_pending_deposit > 0) | (ctx_member_withdraw > 0); + if ((~ shouldExist) & ctx_member_exist) { + ;; Compiler crashes when single lined + var (changed, _) = udict_delete?(ctx_nominators, 256, ctx_member); + ctx_nominators = changed; + } elseif (shouldExist) { + var data = begin_cell() + .store_int(ctx_member_profit_per_coin, 128) + .store_coins(ctx_member_balance) + .store_coins(ctx_member_pending_withdraw) + .store_int(ctx_member_pending_withdraw_all, 1) + .store_coins(ctx_member_pending_deposit) + .store_coins(ctx_member_withdraw); + + ;; Compiler crashes when single lined + var changed = udict_set_builder(ctx_nominators, 256, ctx_member, data); + ctx_nominators = changed; + } +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-validator.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-validator.fc new file mode 100644 index 00000000..a50cb79f --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-validator.fc @@ -0,0 +1,28 @@ +global int proxy_stake_at; +global int proxy_stake_until; +global int proxy_stake_sent; +global int proxy_stored_query_id; +global int proxy_stored_query_op; +global int proxy_stored_query_stake; + +() load_validator_data() impure { + var cs = ctx_proxy_state.begin_parse(); + proxy_stake_at = cs~load_uint(32); + proxy_stake_until = cs~load_uint(32); + proxy_stake_sent = cs~load_coins(); + proxy_stored_query_id = cs~load_uint(64); + proxy_stored_query_op = cs~load_uint(32); + proxy_stored_query_stake = cs~load_coins(); + cs.end_parse(); +} + +() store_validator_data() impure { + ctx_proxy_state = begin_cell() + .store_uint(proxy_stake_at, 32) + .store_uint(proxy_stake_until, 32) + .store_coins(proxy_stake_sent) + .store_uint(proxy_stored_query_id, 64) + .store_uint(proxy_stored_query_op, 32) + .store_coins(proxy_stored_query_stake) + .end_cell(); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils-config-mock.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils-config-mock.fc new file mode 100644 index 00000000..bc464217 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils-config-mock.fc @@ -0,0 +1,3 @@ +(int, int) get_stake_parameters() { + return (1000, 100); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils-config.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils-config.fc new file mode 100644 index 00000000..84e07e01 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils-config.fc @@ -0,0 +1,44 @@ +(int, int) get_stake_parameters() { + var cs = config_param(15).begin_parse(); + int electedFor = cs~load_uint(32); + cs~skip_bits(64); + int stakeHeldFor = cs~load_uint(32); + return (electedFor, stakeHeldFor); +} + +(int, int) get_previous_cycle() { + var cs = config_param(32).begin_parse(); + cs~skip_bits(8); ;; Header + int timeSince = cs~load_uint(32); + int timeUntil = cs~load_uint(32); + return (timeSince, timeUntil); +} + +(int, int) get_current_cycle() { + var cs = config_param(34).begin_parse(); + cs~skip_bits(8); ;; Header + int timeSince = cs~load_uint(32); + int timeUntil = cs~load_uint(32); + return (timeSince, timeUntil); +} + +int lockup_lift_time(int stake_at, int stake_untill) { + + ;; Resolve previous cycle parameters + var (timeSince, timeUntil) = get_previous_cycle(); + + ;; If previous cycle looks as a valid one + if (stake_at <= timeSince) { + return timeSince + (stake_untill - stake_at); + } + + ;; Check current cycle + var (timeSince, timeUntil) = get_current_cycle(); + + ;; If current cycle could be the one we joined validation + if (stake_at <= timeSince) { + return timeSince + (stake_untill - stake_at); + } + + return stake_untill; +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils.fc new file mode 100644 index 00000000..50e4e57c --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils.fc @@ -0,0 +1,185 @@ +;; +;; Basic workchain addresses +;; + +int parse_work_addr(slice cs) { + (int sender_wc, slice sender_addr) = parse_var_addr(cs); + throw_unless(error::invalid_address(), 0 == sender_wc); + return sender_addr~load_uint(256); +} + +(slice) serialize_work_addr(int addr) { + return (begin_cell() + .store_uint(2, 2) ;; Is std address + .store_uint(0, 1) ;; Non-unicast + .store_uint(0, 8) ;; Basic workchain + .store_uint(addr, 256) ;; Address hash + ).end_cell().begin_parse(); +} + +;; +;; Custom Commands +;; + +(int) equal_slices (slice s1, slice s2) asm "SDEQ"; +builder store_builder(builder to, builder what) asm(what to) "STB"; +builder store_builder_ref(builder to, builder what) asm(what to) "STBREFR"; +(slice, cell) load_maybe_cell(slice s) asm( -> 1 0) "LDDICT"; +(int) mod (int x, int y) asm "MOD"; +builder store_coins(builder b, int x) asm "STGRAMS"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDGRAMS"; + + +;; +;; Events +;; + +() send_std_message( + slice to_addr, + int value, + int mode, + int op, + int query_id, + builder content +) impure { + + var body = begin_cell() + .store_uint(op, 32) + .store_uint(query_id, 64) + .store_builder(content) + .end_cell(); + + var msg = begin_cell() + .store_uint(0x10, 6) + .store_slice(to_addr) + .store_coins(value) + .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_ref(body) + .end_cell(); + + send_raw_message(msg, mode); +} + +() send_empty_std_message( + slice to_addr, + int value, + int mode, + int op, + int query_id +) impure { + + var body = begin_cell() + .store_uint(op, 32) + .store_uint(query_id, 64) + .end_cell(); + + var msg = begin_cell() + .store_uint(0x10, 6) + .store_slice(to_addr) + .store_coins(value) + .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_ref(body) + .end_cell(); + + send_raw_message(msg, mode); +} + +() send_text_message( + slice to_addr, + int value, + int mode, + builder content +) impure { + + var body = begin_cell() + .store_uint(0, 32) + .store_builder(content) + .end_cell(); + + var msg = begin_cell() + .store_uint(0x10, 6) + .store_slice(to_addr) + .store_coins(value) + .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_ref(body) + .end_cell(); + + send_raw_message(msg, mode); +} + +;; +;; Generate +;; + +(int) new_query_id() inline { + return now() + mod(cur_lt(), 4294967296); +} + +;; +;; Text Utils +;; + +(int, int) encode_number_to_text(int number) { + int len = 0; + int value = 0; + int mult = 1; + do { + (number, int res) = number.divmod(10); + value = value + (res + 48) * mult; + mult = mult * 256; + len = len + 1; + } until (number == 0); + return (len, value); +} + +builder store_coins_string(builder msg, int amount) { + (int ceil, int res) = divmod(amount, 1000000000); + (int cl, int cv) = encode_number_to_text(ceil); + msg = msg.store_uint(cv, cl * 8 ); + msg = msg.store_uint(46, 8); ;; "." + (int rl, int rv) = encode_number_to_text(res); + ;; repeat( 9 - rl ) { + ;; msg = msg.store_uint(48, 8); ;; " " + ;; } + return msg.store_uint(rv, rl * 8); +} + + +;; 'Stake' +builder store_text_stake(builder b) inline { + return b.store_uint(358434827109, 40); +} + +;; ' ' +builder store_text_space(builder b) inline { + return b.store_uint(32, 8); +} + +;; 'accepted' +builder store_text_accepted(builder b) inline { + return b.store_uint(7017561931702887780, 64); +} + +;; Stake 123.333 accepted +builder store_accepted_stake(builder b, int amount) inline { + return b.store_text_stake() + .store_text_space() + .store_coins_string(amount) + .store_text_space() + .store_text_accepted(); +} + +;; 'Withdraw completed' +builder store_withdraw_completed(builder b) inline { + return b.store_uint(7614653257073527469736132165096662684165476, 144); +} + +;; 'Withdraw requested. Please, retry the command when your balance is ready.' +builder store_withdraw_delayed(builder b) inline { + return b + .store_uint(1949351233810823032252520485584178069312463918, 152) ;; 'Withdraw requested.' + .store_text_space() + .store_uint(555062058613674355757418046597367430905687018487295295368960255172568430, 240) ;; 'Please, retry the command when' + .store_text_space() + .store_uint(45434371896731988359547695118970428857702208118225198, 176); ;; 'your balance is ready.' +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/nominators.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/nominators.fc new file mode 100644 index 00000000..8dee4e5e --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/nominators.fc @@ -0,0 +1,52 @@ +#include "stdlib.fc"; +#include "modules/constants.fc"; +#include "modules/utils-config.fc"; +#include "modules/utils.fc"; +;; #include "modules/utils-config-mock.fc"; +#include "modules/store-base.fc"; +#include "modules/store-nominators.fc"; +#include "modules/store-validator.fc"; +#include "modules/model.fc"; +#include "modules/op-controller.fc"; +#include "modules/op-owner.fc"; +#include "modules/op-common.fc"; +#include "modules/op-nominators.fc"; +#include "modules/get.fc"; + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + + ;; Prepare message context + var cs = in_msg_cell.begin_parse(); + var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + slice s_addr = cs~load_msg_addr(); + load_base_data(); + + ;; Handle controller messages + if (equal_slices(s_addr, ctx_controller)) { + load_validator_data(); + op_controller(flags, msg_value, in_msg); + return (); + } + + ;; Handle elector messages + if (equal_slices(s_addr, ctx_proxy)) { + load_validator_data(); + op_elector(flags, msg_value, in_msg); + return (); + } + + ;; Handle owner messages + if (equal_slices(s_addr, ctx_owner)) { + op_owner(flags, msg_value, in_msg); + return (); + } + + ;; Nominators + var address = parse_work_addr(s_addr); + op_nominators(address, flags, msg_value, in_msg); +} + +() recv_external(slice in_msg) impure { + ;; Do not accept external messages + throw(error::invalid_message()); +} diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/proxy.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/proxy.fc new file mode 100644 index 00000000..805b6098 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/proxy.fc @@ -0,0 +1,67 @@ +(int) equal_slices (slice s1, slice s2) asm "SDEQ"; + +() recv_internal(cell in_msg_cell, slice in_msg) { + + ;; Parse message + var cs = in_msg_cell.begin_parse(); + var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + slice s_addr = cs~load_msg_addr(); + + ;; Parse data + var ds = get_data().begin_parse(); + slice address_0 = ds~load_msg_addr(); + slice address_1 = ds~load_msg_addr(); + ds~skip_bits(64); + ds.end_parse(); + + ;; Resolve addresses address + slice src = null(); + slice dst = null(); + if (equal_slices(s_addr, address_0)) { + src = address_0; + dst = address_1; + } elseif (equal_slices(s_addr, address_1)) { + src = address_1; + dst = address_0; + } + + ;; Bounce while keeping storage fee on unknown + ;; Useful fro deploy + if (null?(src)) { + raw_reserve(1000000000, 2); + var msg = begin_cell() + .store_uint(0x10, 6) + .store_slice(s_addr) + .store_grams(0) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .end_cell(); + send_raw_message(msg, 128); + return (); + } + + ;; Process messages + raw_reserve(1000000000, 2); + var msg = begin_cell() + .store_uint(flags, 4) + .store_uint(0, 2) + .store_slice(dst) + .store_grams(0) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1); + + ;; Content + if(msg.builder_bits() + 1 + in_msg.slice_bits() > 1023) { + msg = msg.store_uint(1,1) + .store_ref(begin_cell().store_slice(in_msg).end_cell()); + } else { + msg = msg.store_uint(0,1) + .store_slice(in_msg); + } + + ;; Send message + send_raw_message(msg.end_cell(), 128); +} + +() recv_external(slice in_msg) impure { + ;; Do not accept external messages + throw(72); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/stdlib.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/stdlib.fc new file mode 100644 index 00000000..dcc5f423 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/stdlib.fc @@ -0,0 +1,212 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; + builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + + +int equal_slices (slice a, slice b) asm "SDEQ"; +int builder_null?(builder b) asm "ISNULL"; From 653c88aa9d658e578c41fd4185c0d98e76d86b36 Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Fri, 13 Jan 2023 12:45:04 +0300 Subject: [PATCH 02/46] Add pragmas to funC for precise control of computation order (#589) * FunC pragmas: allow-post-modification and compute-asm-ltr * Warn if #pragma is enabled only in included files * Add tests for new pragmas * Add special ops for "allow-post-modification" only when needed * Update FunC version to 0.4.1 * Allow empty inlines (#10) Co-authored-by: SpyCheese --- crypto/fift/lib/Asm.fif | 12 +- .../tests/allow_post_modification.fc | 78 ++++++++++++ crypto/func/auto-tests/tests/asm_arg_order.fc | 113 ++++++++++++++++++ crypto/func/func.cpp | 6 +- crypto/func/func.h | 59 ++++++--- crypto/func/gen-abscode.cpp | 106 ++++++++++------ crypto/func/parse-func.cpp | 30 ++++- crypto/parser/srcread.h | 1 + 8 files changed, 344 insertions(+), 61 deletions(-) create mode 100644 crypto/func/auto-tests/tests/allow_post_modification.fc create mode 100644 crypto/func/auto-tests/tests/asm_arg_order.fc diff --git a/crypto/fift/lib/Asm.fif b/crypto/fift/lib/Asm.fif index 5ae44c11..01fb8dd0 100644 --- a/crypto/fift/lib/Asm.fif +++ b/crypto/fift/lib/Asm.fif @@ -875,9 +875,15 @@ x{EDFB} @Defop SAMEALTSAVE } dup : PREPARE : PREPAREDICT // // inline support -{ dup sbits { @addop } { - dup srefs 1- abort"exactly one reference expected in inline" - ref@ CALLREF } cond +{ dup sbits + { @addop } + { + dup srefs // + { ref@ CALLREF } + { drop } + cond + } + cond } : INLINE // // throwing and handling exceptions diff --git a/crypto/func/auto-tests/tests/allow_post_modification.fc b/crypto/func/auto-tests/tests/allow_post_modification.fc new file mode 100644 index 00000000..c6082252 --- /dev/null +++ b/crypto/func/auto-tests/tests/allow_post_modification.fc @@ -0,0 +1,78 @@ +#pragma allow-post-modification; + +forall X -> tuple unsafe_tuple(X x) asm "NOP"; + +(int, int) inc(int x, int y) { + return (x + y, y * 10); +} + +(int, int, int, int, int, int, int) test_return(int x) method_id(11) { + return (x, x~inc(x / 20), x, x = x * 2, x, x += 1, x); +} + +(int, int, int, int, int, int, int) test_assign(int x) method_id(12) { + (int x1, int x2, int x3, int x4, int x5, int x6, int x7) = (x, x~inc(x / 20), x, x = x * 2, x, x += 1, x); + return (x1, x2, x3, x4, x5, x6, x7); +} + +tuple test_tuple(int x) method_id(13) { + tuple t = unsafe_tuple([x, x~inc(x / 20), x, x = x * 2, x, x += 1, x]); + return t; +} + +(int, int, int, int, int, int, int) test_tuple_assign(int x) method_id(14) { + [int x1, int x2, int x3, int x4, int x5, int x6, int x7] = [x, x~inc(x / 20), x, x = x * 2, x, x += 1, x]; + return (x1, x2, x3, x4, x5, x6, x7); +} + +(int, int, int, int, int, int, int) foo1(int x1, int x2, int x3, int x4, int x5, int x6, int x7) { + return (x1, x2, x3, x4, x5, x6, x7); +} + +(int, int, int, int, int, int, int) test_call_1(int x) method_id(15) { + return foo1(x, x~inc(x / 20), x, x = x * 2, x, x += 1, x); +} + +(int, int, int, int, int, int, int) foo2(int x1, int x2, (int, int, int, int) x3456, int x7) { + (int x3, int x4, int x5, int x6) = x3456; + return (x1, x2, x3, x4, x5, x6, x7); +} + +(int, int, int, int, int, int, int) test_call_2(int x) method_id(16) { + return foo2(x, x~inc(x / 20), (x, x = x * 2, x, x += 1), x); +} + +(int, int, int, int, int, int, int) asm_func(int x1, int x2, int x3, int x4, int x5, int x6, int x7) asm + (x4 x5 x6 x7 x1 x2 x3 -> 0 1 2 3 4 5 6) "NOP"; + +(int, int, int, int, int, int, int) test_call_asm_old(int x) method_id(17) { + return asm_func(x, x += 1, x, x, x~inc(x / 20), x, x = x * 2); +} + +#pragma compute-asm-ltr; + +(int, int, int, int, int, int, int) test_call_asm_new(int x) method_id(18) { + return asm_func(x, x~inc(x / 20), x, x = x * 2, x, x += 1, x); +} + +global int xx; +(int, int, int, int, int, int, int) test_global(int x) method_id(19) { + xx = x; + return (xx, xx~inc(xx / 20), xx, xx = xx * 2, xx, xx += 1, xx); +} + +() main() { +} + +{- + method_id | in | out +TESTCASE | 11 | 100 | 100 50 105 210 210 211 211 +TESTCASE | 12 | 100 | 100 50 105 210 210 211 211 +TESTCASE | 13 | 100 | [ 100 50 105 210 210 211 211 ] +TESTCASE | 14 | 100 | 100 50 105 210 210 211 211 +TESTCASE | 15 | 100 | 100 50 105 210 210 211 211 +TESTCASE | 16 | 100 | 100 50 105 210 210 211 211 +TESTCASE | 17 | 100 | 100 50 105 210 210 211 211 +TESTCASE | 18 | 100 | 210 210 211 211 100 50 105 +TESTCASE | 19 | 100 | 100 50 105 210 210 211 211 +-} diff --git a/crypto/func/auto-tests/tests/asm_arg_order.fc b/crypto/func/auto-tests/tests/asm_arg_order.fc new file mode 100644 index 00000000..b53419e3 --- /dev/null +++ b/crypto/func/auto-tests/tests/asm_arg_order.fc @@ -0,0 +1,113 @@ +tuple empty_tuple() asm "NIL"; +forall X -> (tuple, ()) tpush(tuple t, X x) asm "TPUSH"; + +tuple asm_func_1(int x, int y, int z) asm "3 TUPLE"; +tuple asm_func_2(int x, int y, int z) asm (z y x -> 0) "3 TUPLE"; +tuple asm_func_3(int x, int y, int z) asm (y z x -> 0) "3 TUPLE"; +tuple asm_func_4(int a, (int, (int, int)) b, int c) asm (b a c -> 0) "5 TUPLE"; + +(tuple, ()) asm_func_modify(tuple a, int b, int c) asm (c b a -> 0) "SWAP TPUSH SWAP TPUSH"; + +global tuple t; + +int foo(int x) { + t~tpush(x); + return x * 10; +} + +(tuple, tuple) test_old_1() method_id(11) { + t = empty_tuple(); + tuple t2 = asm_func_1(foo(11), foo(22), foo(33)); + return (t, t2); +} + +(tuple, tuple) test_old_2() method_id(12) { + t = empty_tuple(); + tuple t2 = asm_func_2(foo(11), foo(22), foo(33)); + return (t, t2); +} + +(tuple, tuple) test_old_3() method_id(13) { + t = empty_tuple(); + tuple t2 = asm_func_3(foo(11), foo(22), foo(33)); + return (t, t2); +} + +(tuple, tuple) test_old_4() method_id(14) { + t = empty_tuple(); + tuple t2 = empty_tuple(); + ;; This actually computes left-to-right even without compute-asm-ltr + tuple t2 = asm_func_4(foo(11), (foo(22), (foo(33), foo(44))), foo(55)); + return (t, t2); +} + +(tuple, tuple) test_old_modify() method_id(15) { + t = empty_tuple(); + tuple t2 = empty_tuple(); + t2~asm_func_modify(foo(22), foo(33)); + return (t, t2); +} + +(tuple, tuple) test_old_dot() method_id(16) { + t = empty_tuple(); + tuple t2 = foo(11).asm_func_3(foo(22), foo(33)); + return (t, t2); +} + +#pragma compute-asm-ltr; + +(tuple, tuple) test_new_1() method_id(21) { + t = empty_tuple(); + tuple t2 = asm_func_1(foo(11), foo(22), foo(33)); + return (t, t2); +} + +(tuple, tuple) test_new_2() method_id(22) { + t = empty_tuple(); + tuple t2 = asm_func_2(foo(11), foo(22), foo(33)); + return (t, t2); +} + +(tuple, tuple) test_new_3() method_id(23) { + t = empty_tuple(); + tuple t2 = asm_func_3(foo(11), foo(22), foo(33)); + return (t, t2); +} + +(tuple, tuple) test_new_4() method_id(24) { + t = empty_tuple(); + tuple t2 = asm_func_4(foo(11), (foo(22), (foo(33), foo(44))), foo(55)); + return (t, t2); +} + +(tuple, tuple) test_new_modify() method_id(25) { + t = empty_tuple(); + tuple t2 = empty_tuple(); + t2~asm_func_modify(foo(22), foo(33)); + return (t, t2); +} + +(tuple, tuple) test_new_dot() method_id(26) { + t = empty_tuple(); + tuple t2 = foo(11).asm_func_3(foo(22), foo(33)); + return (t, t2); +} + +() main() { +} + +{- + method_id | in | out +TESTCASE | 11 | | [ 11 22 33 ] [ 110 220 330 ] +TESTCASE | 12 | | [ 33 22 11 ] [ 330 220 110 ] +TESTCASE | 13 | | [ 22 33 11 ] [ 220 330 110 ] +TESTCASE | 14 | | [ 11 22 33 44 55 ] [ 220 330 440 110 550 ] +TESTCASE | 15 | | [ 33 22 ] [ 220 330 ] +TESTCASE | 16 | | [ 22 33 11 ] [ 220 330 110 ] +TESTCASE | 21 | | [ 11 22 33 ] [ 110 220 330 ] +TESTCASE | 22 | | [ 11 22 33 ] [ 330 220 110 ] +TESTCASE | 23 | | [ 11 22 33 ] [ 220 330 110 ] +TESTCASE | 24 | | [ 11 22 33 44 55 ] [ 220 330 440 110 550 ] +TESTCASE | 25 | | [ 22 33 ] [ 220 330 ] +TESTCASE | 26 | | [ 11 22 33 ] [ 220 330 110 ] +-} diff --git a/crypto/func/func.cpp b/crypto/func/func.cpp index 3daac5d7..be44d2ea 100644 --- a/crypto/func/func.cpp +++ b/crypto/func/func.cpp @@ -36,6 +36,8 @@ namespace funC { int verbosity, indent, opt_level = 2; bool stack_layout_comments, op_rewrite_comments, program_envelope, asm_preamble; bool interactive = false; +GlobalPragma pragma_allow_post_modification{"allow-post-modification"}; +GlobalPragma pragma_compute_asm_ltr{"compute-asm-ltr"}; std::string generated_from, boc_output_filename; /* @@ -194,7 +196,7 @@ int func_proceed(const std::vector &sources, std::ostream &outs, st int ok = 0, proc = 0; try { for (auto src : sources) { - ok += funC::parse_source_file(src.c_str()); + ok += funC::parse_source_file(src.c_str(), {}, true); proc++; } if (funC::interactive) { @@ -208,6 +210,8 @@ int func_proceed(const std::vector &sources, std::ostream &outs, st if (!proc) { throw src::Fatal{"no source files, no output"}; } + pragma_allow_post_modification.check_enable_in_libs(); + pragma_compute_asm_ltr.check_enable_in_libs(); return funC::generate_output(outs, errs); } catch (src::Fatal& fatal) { errs << "fatal: " << fatal << std::endl; diff --git a/crypto/func/func.h b/crypto/func/func.h index 81911ade..0eeb9557 100644 --- a/crypto/func/func.h +++ b/crypto/func/func.h @@ -39,7 +39,7 @@ extern std::string generated_from; constexpr int optimize_depth = 20; -const std::string func_version{"0.4.0"}; +const std::string func_version{"0.4.1"}; enum Keyword { _Eof = -1, @@ -306,7 +306,7 @@ struct TmpVar { sym_idx_t name; int coord; std::unique_ptr where; - size_t modify_forbidden = 0; + std::vector> on_modification; TmpVar(var_idx_t _idx, int _cls, TypeExpr* _type = 0, SymDef* sym = 0, const SrcLocation* loc = 0); void show(std::ostream& os, int omit_idx = 0) const; void dump(std::ostream& os) const; @@ -681,6 +681,7 @@ typedef std::vector FormalArgList; struct AsmOpList; struct CodeBlob { + enum { _AllowPostModification = 1, _ComputeAsmLtr = 2 }; int var_cnt, in_var_cnt, op_cnt; TypeExpr* ret_type; std::string name; @@ -689,6 +690,7 @@ struct CodeBlob { std::unique_ptr ops; std::unique_ptr* cur_ops; std::stack*> cur_ops_stack; + int flags = 0; CodeBlob(TypeExpr* ret = nullptr) : var_cnt(0), in_var_cnt(0), op_cnt(0), ret_type(ret), cur_ops(&ops) { } template @@ -729,19 +731,9 @@ struct CodeBlob { void generate_code(AsmOpList& out_list, int mode = 0); void generate_code(std::ostream& os, int mode = 0, int indent = 0); - void mark_modify_forbidden(var_idx_t idx) { - ++vars.at(idx).modify_forbidden; - } - - void unmark_modify_forbidden(var_idx_t idx) { - assert(vars.at(idx).modify_forbidden > 0); - --vars.at(idx).modify_forbidden; - } - - void check_modify_forbidden(var_idx_t idx, const SrcLocation& here) const { - if (vars.at(idx).modify_forbidden) { - throw src::ParseError{here, PSTRING() << "Modifying local variable " << vars[idx].to_string() - << " after using it in the same expression"}; + void on_var_modification(var_idx_t idx, const SrcLocation& here) const { + for (auto& f : vars.at(idx).on_modification) { + f(here); } } }; @@ -855,7 +847,7 @@ extern std::vector glob_func, glob_vars; // defined in parse-func.cpp bool parse_source(std::istream* is, const src::FileDescr* fdescr); -bool parse_source_file(const char* filename, src::Lexem lex = {}); +bool parse_source_file(const char* filename, src::Lexem lex = {}, bool is_main = false); bool parse_source_stdin(); extern std::stack inclusion_locations; @@ -1700,6 +1692,41 @@ extern int verbosity, indent, opt_level; extern bool stack_layout_comments, op_rewrite_comments, program_envelope, asm_preamble, interactive; extern std::string generated_from, boc_output_filename; +class GlobalPragma { + public: + explicit GlobalPragma(std::string name) : name_(std::move(name)) { + } + const std::string& name() const { + return name_; + } + bool enabled() const { + return enabled_; + } + void enable(SrcLocation loc) { + enabled_ = true; + locs_.push_back(std::move(loc)); + } + void check_enable_in_libs() { + if (locs_.empty()) { + return; + } + for (const SrcLocation& loc : locs_) { + if (loc.fdescr->is_main) { + return; + } + } + locs_[0].show_warning(PSTRING() << "#pragma " << name_ + << " is enabled in included libraries, it may change the behavior of your code. " + << "Add this #pragma to the main source file to suppress this warning."); + } + + private: + std::string name_; + bool enabled_ = false; + std::vector locs_; +}; +extern GlobalPragma pragma_allow_post_modification, pragma_compute_asm_ltr; + /* * * OUTPUT CODE GENERATOR diff --git a/crypto/func/gen-abscode.cpp b/crypto/func/gen-abscode.cpp index 6fcb1599..cff40379 100644 --- a/crypto/func/gen-abscode.cpp +++ b/crypto/func/gen-abscode.cpp @@ -16,6 +16,7 @@ Copyright 2017-2020 Telegram Systems LLP */ +#include #include "func.h" using namespace std::literals::string_literals; @@ -255,13 +256,75 @@ std::vector Expr::pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rh std::vector> globs; auto left = lhs->pre_compile(code, &globs); for (var_idx_t v : left) { - code.check_modify_forbidden(v, here); + code.on_var_modification(v, here); } code.emplace_back(here, Op::_Let, std::move(left), right); add_set_globs(code, globs, here); return right; } +std::vector pre_compile_tensor(const std::vector args, CodeBlob &code, + std::vector> *lval_globs, + std::vector arg_order) { + if (arg_order.empty()) { + arg_order.resize(args.size()); + std::iota(arg_order.begin(), arg_order.end(), 0); + } + assert(args.size() == arg_order.size()); + std::vector> res_lists(args.size()); + + struct ModifiedVar { + size_t i, j; + Op* op; + }; + auto modified_vars = std::make_shared>(); + for (size_t i : arg_order) { + res_lists[i] = args[i]->pre_compile(code, lval_globs); + for (size_t j = 0; j < res_lists[i].size(); ++j) { + TmpVar& var = code.vars.at(res_lists[i][j]); + if (code.flags & CodeBlob::_AllowPostModification) { + if (!lval_globs && (var.cls & TmpVar::_Named)) { + Op *op = &code.emplace_back(nullptr, Op::_Let, std::vector(), std::vector()); + op->flags |= Op::_Disabled; + var.on_modification.push_back([modified_vars, i, j, op, done = false](const SrcLocation &here) mutable { + if (!done) { + done = true; + modified_vars->push_back({i, j, op}); + } + }); + } else { + var.on_modification.push_back([](const SrcLocation &) { + }); + } + } else { + var.on_modification.push_back([name = var.to_string()](const SrcLocation &here) { + throw src::ParseError{here, PSTRING() << "Modifying local variable " << name + << " after using it in the same expression"}; + }); + } + } + } + for (const auto& list : res_lists) { + for (var_idx_t v : list) { + assert(!code.vars.at(v).on_modification.empty()); + code.vars.at(v).on_modification.pop_back(); + } + } + for (const ModifiedVar &m : *modified_vars) { + var_idx_t& v = res_lists[m.i][m.j]; + var_idx_t v2 = code.create_tmp_var(code.vars[v].v_type, code.vars[v].where.get()); + m.op->left = {v2}; + m.op->right = {v}; + m.op->flags &= ~Op::_Disabled; + v = v2; + } + std::vector res; + for (const auto& list : res_lists) { + res.insert(res.end(), list.cbegin(), list.cend()); + } + return res; +} + std::vector Expr::pre_compile(CodeBlob& code, std::vector>* lval_globs) const { if (lval_globs && !(cls == _Tensor || cls == _Var || cls == _Hole || cls == _TypeApply || cls == _GlobVar)) { std::cerr << "lvalue expression constructor is " << cls << std::endl; @@ -269,46 +332,17 @@ std::vector Expr::pre_compile(CodeBlob& code, std::vector res; - for (const auto& x : args) { - auto add = x->pre_compile(code, lval_globs); - for (var_idx_t v : add) { - code.mark_modify_forbidden(v); - } - res.insert(res.end(), add.cbegin(), add.cend()); - } - for (var_idx_t v : res) { - code.unmark_modify_forbidden(v); - } - return res; + return pre_compile_tensor(args, code, lval_globs, {}); } case _Apply: { assert(sym); - std::vector res; auto func = dynamic_cast(sym->value); - if (func && func->arg_order.size() == args.size()) { + std::vector res; + if (func && func->arg_order.size() == args.size() && !(code.flags & CodeBlob::_ComputeAsmLtr)) { //std::cerr << "!!! reordering " << args.size() << " arguments of " << sym->name() << std::endl; - std::vector> add_list(args.size()); - for (int i : func->arg_order) { - add_list[i] = args[i]->pre_compile(code); - for (var_idx_t v : add_list[i]) { - code.mark_modify_forbidden(v); - } - } - for (const auto& add : add_list) { - res.insert(res.end(), add.cbegin(), add.cend()); - } + res = pre_compile_tensor(args, code, lval_globs, func->arg_order); } else { - for (const auto& x : args) { - auto add = x->pre_compile(code); - for (var_idx_t v : add) { - code.mark_modify_forbidden(v); - } - res.insert(res.end(), add.cbegin(), add.cend()); - } - } - for (var_idx_t v : res) { - code.unmark_modify_forbidden(v); + res = pre_compile_tensor(args, code, lval_globs, {}); } auto rvect = new_tmp_vect(code); auto& op = code.emplace_back(here, Op::_Call, rvect, std::move(res), sym); @@ -371,7 +405,7 @@ std::vector Expr::pre_compile(CodeBlob& code, std::vectorpre_compile(code, lval_globs); left.push_back(rvect[0]); for (var_idx_t v : left) { - code.check_modify_forbidden(v, here); + code.on_var_modification(v, here); } code.emplace_back(here, Op::_Let, std::move(left), std::move(right)); add_set_globs(code, local_globs, here); diff --git a/crypto/func/parse-func.cpp b/crypto/func/parse-func.cpp index fe86bc1e..82462e79 100644 --- a/crypto/func/parse-func.cpp +++ b/crypto/func/parse-func.cpp @@ -261,6 +261,12 @@ void parse_const_decl(Lexer& lex) { } lex.next(); CodeBlob code; + if (pragma_allow_post_modification.enabled()) { + code.flags |= CodeBlob::_AllowPostModification; + } + if (pragma_compute_asm_ltr.enabled()) { + code.flags |= CodeBlob::_ComputeAsmLtr; + } // Handles processing and resolution of literals and consts auto x = parse_expr(lex, code, false); // also does lex.next() ! if (x->flags != Expr::_IsRvalue) { @@ -1210,6 +1216,12 @@ blk_fl::val parse_stmt(Lexer& lex, CodeBlob& code) { CodeBlob* parse_func_body(Lexer& lex, FormalArgList arg_list, TypeExpr* ret_type) { lex.expect('{'); CodeBlob* blob = new CodeBlob{ret_type}; + if (pragma_allow_post_modification.enabled()) { + blob->flags |= CodeBlob::_AllowPostModification; + } + if (pragma_compute_asm_ltr.enabled()) { + blob->flags |= CodeBlob::_ComputeAsmLtr; + } blob->import_params(std::move(arg_list)); blk_fl::val res = blk_fl::init; bool warned = false; @@ -1676,6 +1688,10 @@ void parse_pragma(Lexer& lex) { } func_ver_test = lex.cur().str; lex.next(); + } else if (pragma_name == pragma_allow_post_modification.name()) { + pragma_allow_post_modification.enable(lex.cur().loc); + } else if (pragma_name == pragma_compute_asm_ltr.name()) { + pragma_compute_asm_ltr.enable(lex.cur().loc); } else { lex.cur().error(std::string{"unknown pragma `"} + pragma_name + "`"); } @@ -1684,7 +1700,7 @@ void parse_pragma(Lexer& lex) { std::vector source_fdescr; -std::vector source_files; +std::map source_files; std::stack inclusion_locations; void parse_include(Lexer& lex, const src::FileDescr* fdescr) { @@ -1700,7 +1716,7 @@ void parse_include(Lexer& lex, const src::FileDescr* fdescr) { } lex.next(); lex.expect(';'); - if (!parse_source_file(val.c_str(), include)) { + if (!parse_source_file(val.c_str(), include, false)) { include.error(std::string{"failed parsing included file `"} + val + "`"); } } @@ -1724,7 +1740,7 @@ bool parse_source(std::istream* is, src::FileDescr* fdescr) { return true; } -bool parse_source_file(const char* filename, src::Lexem lex) { +bool parse_source_file(const char* filename, src::Lexem lex, bool is_main) { if (!filename || !*filename) { auto msg = "source file name is an empty string"; if (lex.tp) { @@ -1741,7 +1757,9 @@ bool parse_source_file(const char* filename, src::Lexem lex) { return false; } std::string real_filename = path_res.move_as_ok(); - if (std::count(source_files.begin(), source_files.end(), real_filename)) { + auto it = source_files.find(real_filename); + if (it != source_files.end()) { + it->second->is_main |= is_main; if (verbosity >= 2) { if (lex.tp) { lex.loc.show_warning(std::string{"skipping file "} + real_filename + " because it was already included"); @@ -1755,8 +1773,9 @@ bool parse_source_file(const char* filename, src::Lexem lex) { funC::generated_from += std::string{"incl:"}; } funC::generated_from += std::string{"`"} + filename + "` "; - source_files.push_back(real_filename); src::FileDescr* cur_source = new src::FileDescr{filename}; + source_files[real_filename] = cur_source; + cur_source->is_main = is_main; source_fdescr.push_back(cur_source); std::ifstream ifs{filename}; if (ifs.fail()) { @@ -1775,6 +1794,7 @@ bool parse_source_file(const char* filename, src::Lexem lex) { bool parse_source_stdin() { src::FileDescr* cur_source = new src::FileDescr{"stdin", true}; + cur_source->is_main = true; source_fdescr.push_back(cur_source); return parse_source(&std::cin, cur_source); } diff --git a/crypto/parser/srcread.h b/crypto/parser/srcread.h index 9352a242..61128eff 100644 --- a/crypto/parser/srcread.h +++ b/crypto/parser/srcread.h @@ -35,6 +35,7 @@ struct FileDescr { std::string text; std::vector line_offs; bool is_stdin; + bool is_main = false; FileDescr(std::string _fname, bool _stdin = false) : filename(std::move(_fname)), is_stdin(_stdin) { } const char* push_line(std::string new_line); From c6143715cc29ae23dad202b2580083099d8f61d2 Mon Sep 17 00:00:00 2001 From: SpyCheese Date: Mon, 23 Jan 2023 10:01:40 +0000 Subject: [PATCH 03/46] Fix some error handling in FunC (#599) --- crypto/func/builtins.cpp | 2 +- crypto/func/codegen.cpp | 20 ++++++++++++++------ crypto/func/func.h | 2 +- crypto/func/gen-abscode.cpp | 3 +++ crypto/func/parse-func.cpp | 21 ++++++++++++++------- 5 files changed, 33 insertions(+), 15 deletions(-) diff --git a/crypto/func/builtins.cpp b/crypto/func/builtins.cpp index 2c103d46..36f240d7 100644 --- a/crypto/func/builtins.cpp +++ b/crypto/func/builtins.cpp @@ -266,7 +266,7 @@ int emulate_div(int a, int b) { if ((b & (VarDescr::_NonZero | VarDescr::_Bit)) == (VarDescr::_NonZero | VarDescr::_Bit)) { return a; } else if ((b & (VarDescr::_NonZero | VarDescr::_Bool)) == (VarDescr::_NonZero | VarDescr::_Bool)) { - return emulate_negate(b); + return emulate_negate(a); } if (b & VarDescr::_Zero) { return VarDescr::_Int | VarDescr::_Nan; diff --git a/crypto/func/codegen.cpp b/crypto/func/codegen.cpp index 13b808ea..ac43d12a 100644 --- a/crypto/func/codegen.cpp +++ b/crypto/func/codegen.cpp @@ -663,12 +663,20 @@ bool Op::generate_code_step(Stack& stack) { stack.o << "REPEAT:<{"; stack.o.indent(); stack.forget_const(); - StackLayout layout1 = stack.vars(); - stack.mode &= ~Stack::_InlineFunc; - stack.mode |= Stack::_NeedRetAlt; - block0->generate_code_all(stack); - stack.enforce_state(std::move(layout1)); - stack.opt_show(); + if (block0->noreturn()) { + Stack stack_copy{stack}; + StackLayout layout1 = stack.vars(); + stack_copy.mode &= ~Stack::_InlineFunc; + stack_copy.mode |= Stack::_NeedRetAlt; + block0->generate_code_all(stack_copy); + } else { + StackLayout layout1 = stack.vars(); + stack.mode &= ~Stack::_InlineFunc; + stack.mode |= Stack::_NeedRetAlt; + block0->generate_code_all(stack); + stack.enforce_state(std::move(layout1)); + stack.opt_show(); + } stack.o.undent(); stack.o << "}>"; return true; diff --git a/crypto/func/func.h b/crypto/func/func.h index 0eeb9557..b979afb2 100644 --- a/crypto/func/func.h +++ b/crypto/func/func.h @@ -388,7 +388,7 @@ struct VarDescr { return val & _Const; } bool is_int_const() const { - return (val & (_Int | _Const)) == (_Int | _Const); + return (val & (_Int | _Const)) == (_Int | _Const) && int_const.not_null(); } bool always_nonpos() const { return val & _Neg; diff --git a/crypto/func/gen-abscode.cpp b/crypto/func/gen-abscode.cpp index cff40379..a2b98878 100644 --- a/crypto/func/gen-abscode.cpp +++ b/crypto/func/gen-abscode.cpp @@ -355,6 +355,9 @@ std::vector Expr::pre_compile(CodeBlob& code, std::vectorpre_compile(code, lval_globs); case _Var: case _Hole: + if (val < 0) { + throw src::ParseError{here, "unexpected variable definition"}; + } return {val}; case _VarApply: if (args[0]->cls == _Glob) { diff --git a/crypto/func/parse-func.cpp b/crypto/func/parse-func.cpp index 82462e79..011947d5 100644 --- a/crypto/func/parse-func.cpp +++ b/crypto/func/parse-func.cpp @@ -1372,7 +1372,7 @@ std::vector parse_type_var_list(Lexer& lex) { } auto loc = lex.cur().loc; SymDef* new_sym_def = sym::define_symbol(lex.cur().val, true, loc); - if (new_sym_def->value) { + if (!new_sym_def || new_sym_def->value) { lex.cur().error_at("redefined type variable `", "`"); } auto var = TypeExpr::new_var(idx); @@ -1582,8 +1582,15 @@ void parse_pragma(Lexer& lex) { char op = '='; bool eq = false; int sem_ver[3] = {0, 0, 0}; char segs = 1; + auto stoi = [&](const std::string& s) { + auto R = td::to_integer_safe(s); + if (R.is_error()) { + lex.cur().error("invalid semver format"); + } + return R.move_as_ok(); + }; if (lex.tp() == _Number) { - sem_ver[0] = std::stoi(lex.cur().str); + sem_ver[0] = stoi(lex.cur().str); } else if (lex.tp() == _Ident) { auto id1 = lex.cur().str; char ch1 = id1[0]; @@ -1600,9 +1607,9 @@ void parse_pragma(Lexer& lex) { if (id1.length() < 3) { lex.cur().error("expected number after comparator"); } - sem_ver[0] = std::stoi(id1.substr(2)); + sem_ver[0] = stoi(id1.substr(2)); } else { - sem_ver[0] = std::stoi(id1.substr(1)); + sem_ver[0] = stoi(id1.substr(1)); } } else { lex.cur().error("expected semver with optional comparator"); @@ -1612,7 +1619,7 @@ void parse_pragma(Lexer& lex) { if (lex.tp() != _Ident || lex.cur().str[0] != '.') { lex.cur().error("invalid semver format"); } - sem_ver[1] = std::stoi(lex.cur().str.substr(1)); + sem_ver[1] = stoi(lex.cur().str.substr(1)); segs = 2; lex.next(); } @@ -1620,7 +1627,7 @@ void parse_pragma(Lexer& lex) { if (lex.tp() != _Ident || lex.cur().str[0] != '.') { lex.cur().error("invalid semver format"); } - sem_ver[2] = std::stoi(lex.cur().str.substr(1)); + sem_ver[2] = stoi(lex.cur().str.substr(1)); segs = 3; lex.next(); } @@ -1630,7 +1637,7 @@ void parse_pragma(Lexer& lex) { std::string s; for (int idx = 0; idx < 3; idx++) { std::getline(iss, s, '.'); - func_ver[idx] = std::stoi(s); + func_ver[idx] = stoi(s); } // End parsing embedded semver std::string semver_expr; From 45c0270716bc5cec3d95185b488f880dc3b38366 Mon Sep 17 00:00:00 2001 From: SpyCheese Date: Mon, 23 Jan 2023 11:40:28 +0000 Subject: [PATCH 04/46] Improve handling of absent blocks in archive manager (#600) --- validator/db/archive-manager.cpp | 41 +++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/validator/db/archive-manager.cpp b/validator/db/archive-manager.cpp index 05fb6ca6..6bea5db6 100644 --- a/validator/db/archive-manager.cpp +++ b/validator/db/archive-manager.cpp @@ -749,6 +749,9 @@ ArchiveManager::FileDescription *ArchiveManager::get_file_desc_by_seqno(AccountI return get_file_desc_by_seqno(ShardIdFull{masterchainId}, seqno, key_block); } for (auto it = f.rbegin(); it != f.rend(); it++) { + if (it->second.deleted) { + continue; + } bool found = false; for (int i = 0; i < 60; i++) { auto shard = shard_prefix(account, i); @@ -773,6 +776,9 @@ ArchiveManager::FileDescription *ArchiveManager::get_file_desc_by_unix_time(Acco return get_file_desc_by_unix_time(ShardIdFull{masterchainId}, ts, key_block); } for (auto it = f.rbegin(); it != f.rend(); it++) { + if (it->second.deleted) { + continue; + } bool found = false; for (int i = 0; i < 60; i++) { auto shard = shard_prefix(account, i); @@ -797,6 +803,9 @@ ArchiveManager::FileDescription *ArchiveManager::get_file_desc_by_lt(AccountIdPr return get_file_desc_by_lt(ShardIdFull{masterchainId}, lt, key_block); } for (auto it = f.rbegin(); it != f.rend(); it++) { + if (it->second.deleted) { + continue; + } bool found = false; for (int i = 0; i < 60; i++) { auto shard = shard_prefix(account, i); @@ -818,11 +827,13 @@ ArchiveManager::FileDescription *ArchiveManager::get_next_file_desc(FileDescript auto &m = get_file_map(f->id); auto it = m.find(f->id); CHECK(it != m.end()); - it++; - if (it == m.end()) { - return nullptr; - } else { - return &it->second; + while (true) { + it++; + if (it == m.end()) { + return nullptr; + } else if (!it->second.deleted) { + return &it->second; + } } } @@ -1159,13 +1170,17 @@ void ArchiveManager::truncate(BlockSeqno masterchain_seqno, ConstBlockHandle han auto it = key_files_.begin(); while (it != key_files_.end()) { if (it->first.id <= masterchain_seqno) { - td::actor::send_closure(it->second.file_actor_id(), &ArchiveSlice::truncate, masterchain_seqno, handle, - ig.get_promise()); + if (!it->second.deleted) { + td::actor::send_closure(it->second.file_actor_id(), &ArchiveSlice::truncate, masterchain_seqno, handle, + ig.get_promise()); + } it++; } else { auto it2 = it; it++; - td::actor::send_closure(it2->second.file_actor_id(), &ArchiveSlice::destroy, ig.get_promise()); + if (!it2->second.deleted) { + td::actor::send_closure(it2->second.file_actor_id(), &ArchiveSlice::destroy, ig.get_promise()); + } it2->second.file.release(); index_ ->erase(create_serialize_tl_object(it2->second.id.id, it2->second.id.key, @@ -1180,13 +1195,17 @@ void ArchiveManager::truncate(BlockSeqno masterchain_seqno, ConstBlockHandle han auto it = files_.begin(); while (it != files_.end()) { if (it->first.id <= masterchain_seqno) { - td::actor::send_closure(it->second.file_actor_id(), &ArchiveSlice::truncate, masterchain_seqno, handle, - ig.get_promise()); + if (!it->second.deleted) { + td::actor::send_closure(it->second.file_actor_id(), &ArchiveSlice::truncate, masterchain_seqno, handle, + ig.get_promise()); + } it++; } else { auto it2 = it; it++; - td::actor::send_closure(it2->second.file_actor_id(), &ArchiveSlice::destroy, ig.get_promise()); + if (!it2->second.deleted) { + td::actor::send_closure(it2->second.file_actor_id(), &ArchiveSlice::destroy, ig.get_promise()); + } it2->second.file.release(); index_ ->erase(create_serialize_tl_object(it2->second.id.id, it2->second.id.key, From 9835ac8af2ac9067eca0bc058e9b2bddec8c354c Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Tue, 31 Jan 2023 10:10:00 +0300 Subject: [PATCH 05/46] Add 01.2023 Changelog --- Changelog.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Changelog.md b/Changelog.md index ae131e74..f3c76217 100644 --- a/Changelog.md +++ b/Changelog.md @@ -53,3 +53,10 @@ Node update: 10. TON Storage: added storage-daemon (create, download bag of Files, storage-provider staff), added storage-daemon-cli Besides the work of the core team, this update is based on the efforts of @vtamara (help with abseil-cpp upgrade), @krigga(in-place modification of global variables) and third-party security auditors. + +## 01.2023 Update +1. Added ConfigParam 44: `SuspendedAddressList`. Upon being set this config suspends initialisation of **uninit** addresses from the list for given time. +2. FunC: `v0.4.1` added pragmas for precise control of computation order +3. FunC: fixed compiler crashes for some exotic inputs +4. FunC: added legacy tester, a collection of smart-contracts which is used to check whether compilator update change compilation result +5. Improved archive manager: proper handling of recently garbage-collected blocks From adf67aa869841a6b9646140de8f89cff3bcc8c50 Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Thu, 2 Feb 2023 10:00:58 +0300 Subject: [PATCH 06/46] Reinit ADNL (#586) * Fix dht queries in AdnlPeerPair * Don't use adnl channel if peer does not respond --------- Co-authored-by: SpyCheese --- adnl/adnl-peer.cpp | 16 ++++++++++++---- adnl/adnl-peer.hpp | 3 +++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/adnl/adnl-peer.cpp b/adnl/adnl-peer.cpp index 99356ed5..c84db23c 100644 --- a/adnl/adnl-peer.cpp +++ b/adnl/adnl-peer.cpp @@ -113,6 +113,8 @@ void AdnlPeerPairImpl::discover() { } void AdnlPeerPairImpl::receive_packet_checked(AdnlPacket packet) { + last_received_packet_ = td::Timestamp::now(); + try_reinit_at_ = td::Timestamp::never(); request_reverse_ping_after_ = td::Timestamp::in(15.0); auto d = Adnl::adnl_start_time(); if (packet.dst_reinit_date() > d) { @@ -307,7 +309,9 @@ void AdnlPeerPairImpl::send_messages_in(std::vector message } } - if (!channel_ready_) { + bool try_reinit = try_reinit_at_ && try_reinit_at_.is_in_past(); + bool via_channel = channel_ready_ && !try_reinit; + if (!via_channel) { packet.set_reinit_date(Adnl::adnl_start_time(), reinit_date_); packet.set_source(local_id_); } @@ -330,7 +334,7 @@ void AdnlPeerPairImpl::send_messages_in(std::vector message packet.run_basic_checks().ensure(); auto P = td::PromiseCreator::lambda([SelfId = actor_id(this), conn, id = print_id(), - via_channel = channel_ready_](td::Result res) { + via_channel](td::Result res) { if (res.is_error()) { LOG(ERROR) << id << ": dropping OUT message: error while creating packet: " << res.move_as_error(); } else { @@ -339,9 +343,9 @@ void AdnlPeerPairImpl::send_messages_in(std::vector message }); td::actor::send_closure(local_actor_, &AdnlLocalId::update_packet, std::move(packet), - !channel_ready_ && ack_seqno_ == 0 && in_seqno_ == 0, !channel_ready_, + (!channel_ready_ && ack_seqno_ == 0 && in_seqno_ == 0) || try_reinit, !via_channel, (first || s + addr_list_max_size() <= AdnlNetworkManager::get_mtu()) - ? peer_recv_addr_list_version_ + ? (try_reinit ? 0 : peer_recv_addr_list_version_) : 0x7fffffff, (first || s + 2 * addr_list_max_size() <= AdnlNetworkManager::get_mtu()) ? peer_recv_priority_addr_list_version_ @@ -388,6 +392,9 @@ void AdnlPeerPairImpl::send_messages(std::vector messages) void AdnlPeerPairImpl::send_packet_continue(AdnlPacket packet, td::actor::ActorId conn, bool via_channel) { + if (!try_reinit_at_ && last_received_packet_ < td::Timestamp::in(-5.0)) { + try_reinit_at_ = td::Timestamp::in(10.0); + } packet.run_basic_checks().ensure(); auto B = serialize_tl_object(packet.tl(), true); if (via_channel) { @@ -933,6 +940,7 @@ void AdnlPeerPairImpl::got_data_from_dht(td::Result R) { CHECK(dht_query_active_); dht_query_active_ = false; next_dht_query_at_ = td::Timestamp::in(td::Random::fast(60.0, 120.0)); + alarm_timestamp().relax(next_dht_query_at_); if (R.is_error()) { VLOG(ADNL_INFO) << this << ": dht query failed: " << R.move_as_error(); return; diff --git a/adnl/adnl-peer.hpp b/adnl/adnl-peer.hpp index 0efe827d..12ee01c6 100644 --- a/adnl/adnl-peer.hpp +++ b/adnl/adnl-peer.hpp @@ -254,6 +254,9 @@ class AdnlPeerPairImpl : public AdnlPeerPair { td::Timestamp next_db_update_at_ = td::Timestamp::never(); td::Timestamp retry_send_at_ = td::Timestamp::never(); + td::Timestamp last_received_packet_ = td::Timestamp::never(); + td::Timestamp try_reinit_at_ = td::Timestamp::never(); + bool has_reverse_addr_ = false; td::Timestamp request_reverse_ping_after_ = td::Timestamp::now(); bool request_reverse_ping_active_ = false; From 3b3c25b654ade3fcea1546d7b91454673038ed4e Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Thu, 2 Feb 2023 10:03:45 +0300 Subject: [PATCH 07/46] Add account state by transaction and emulator (extended) (#592) * account_state_by_transaction * Correct time calculation * Bug fixes * Refactor * namespace block::transaction * smc.forget * RunEmulator: remove wallet_id * Refactor & fixes * AccountStateByTransaction: use shardchain block instead of masterchain block * transaction emulator core * refactor * tx emulator major functionality * small format changes * readme * clean * return json, add support for init messages * tx emulator readme * refactor getConfigParam and getConfigAll * add shardchain_libs_boc parameter * option to change verbosity level of transaction emulator * fix deserializing ShardAccount with account_none * add mode needSpecialSmc when unpack config * emulator: block::Transaction -> block::transaction::Transaction * Refactor * emulator: Fix bug * emulator: Support for emulator-extern * emulator: Refactor * Return vm log and vm exit code. * fix build on macos, emulator_static added * adjust documentation * ignore_chksig for external messages * tvm emulator, run get method * Added more params for transaction emulator * Added setters for optional transaction emulator params, moved libs to a new setter * Added actions cell output to transaction emulator * fix tonlib build * refactoring, rand seed as hex size 64, tvm emulator send message * tvm send message, small refactoring * fix config decoding, rename * improve documentation * macos export symbols * Added run_get_method to transaction emulator emscipten wrapper * Fixed empty action list serialization * Changed actions list cell to serialize as json null instead of empty string in transaction emulator * stack as boc * log gas remaining * Fix prev_block_id * fix build errors * Refactor fetch_config_params * fix failing unwrap of optional rand_seed * lookup correct shard, choose prev_block based on account shard * fix tonlib android jni build --------- Co-authored-by: legaii Co-authored-by: ms Co-authored-by: krigga --- CMakeLists.txt | 1 + crypto/CMakeLists.txt | 4 + crypto/block/block-parse.cpp | 2 +- crypto/block/block-parse.h | 4 +- crypto/block/block.tlb | 2 +- crypto/block/mc-config.h | 1 - crypto/block/transaction.cpp | 97 ++++- crypto/block/transaction.h | 23 +- crypto/smc-envelope/SmartContract.cpp | 30 +- crypto/smc-envelope/SmartContract.h | 11 + crypto/vm/log.h | 2 +- crypto/vm/vm.cpp | 13 +- emulator/CMakeLists.txt | 55 +++ emulator/README.md | 32 ++ emulator/StringLog.h | 27 ++ emulator/emulator-emscripten.cpp | 189 ++++++++++ emulator/emulator-extern.cpp | 435 ++++++++++++++++++++++ emulator/emulator-extern.h | 216 +++++++++++ emulator/emulator_export_list | 17 + emulator/transaction-emulator.cpp | 254 +++++++++++++ emulator/transaction-emulator.h | 83 +++++ emulator/tvm-emulator.hpp | 46 +++ tl/generate/scheme/tonlib_api.tl | 8 +- tl/generate/scheme/tonlib_api.tlo | Bin 31016 -> 31844 bytes tonlib/CMakeLists.txt | 4 +- tonlib/tonlib/TonlibClient.cpp | 495 +++++++++++++++++++++++++- tonlib/tonlib/TonlibClient.h | 18 + tonlib/tonlib/tonlib-cli.cpp | 27 ++ validator/impl/collator-impl.h | 16 +- validator/impl/collator.cpp | 108 ++---- validator/impl/external-message.cpp | 13 +- validator/impl/validate-query.cpp | 20 +- 32 files changed, 2095 insertions(+), 158 deletions(-) create mode 100644 emulator/CMakeLists.txt create mode 100644 emulator/README.md create mode 100644 emulator/StringLog.h create mode 100644 emulator/emulator-emscripten.cpp create mode 100644 emulator/emulator-extern.cpp create mode 100644 emulator/emulator-extern.h create mode 100644 emulator/emulator_export_list create mode 100644 emulator/transaction-emulator.cpp create mode 100644 emulator/transaction-emulator.h create mode 100644 emulator/tvm-emulator.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e52f969..4ac5e85a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -383,6 +383,7 @@ add_subdirectory(tl-utils) add_subdirectory(adnl) add_subdirectory(crypto) add_subdirectory(lite-client) +add_subdirectory(emulator) #BEGIN tonlib add_subdirectory(tonlib) diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index fab75bfc..202e420b 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -274,6 +274,10 @@ add_library(ton_crypto STATIC ${TON_CRYPTO_SOURCE}) target_include_directories(ton_crypto PUBLIC $ $) target_link_libraries(ton_crypto PUBLIC ${OPENSSL_CRYPTO_LIBRARY} tdutils tddb_utils) +if (USE_EMSCRIPTEN) + target_link_options(ton_crypto PRIVATE -fexceptions) + target_compile_options(ton_crypto PRIVATE -fexceptions) +endif() if (NOT WIN32) find_library(DL dl) if (DL) diff --git a/crypto/block/block-parse.cpp b/crypto/block/block-parse.cpp index c62854d4..e9eb8209 100644 --- a/crypto/block/block-parse.cpp +++ b/crypto/block/block-parse.cpp @@ -1000,7 +1000,7 @@ bool Account::skip_copy_depth_balance(vm::CellBuilder& cb, vm::CellSlice& cs) co } const Account t_Account, t_AccountE{true}; -const RefTo t_Ref_Account; +const RefTo t_Ref_AccountE{true}; bool ShardAccount::extract_account_state(Ref cs_ref, Ref& acc_state) { if (cs_ref.is_null()) { diff --git a/crypto/block/block-parse.h b/crypto/block/block-parse.h index 25476a64..ad4faec0 100644 --- a/crypto/block/block-parse.h +++ b/crypto/block/block-parse.h @@ -536,7 +536,7 @@ struct Account final : TLB_Complex { }; extern const Account t_Account, t_AccountE; -extern const RefTo t_Ref_Account; +extern const RefTo t_Ref_AccountE; struct AccountStatus final : TLB { enum { acc_state_uninit, acc_state_frozen, acc_state_active, acc_state_nonexist }; @@ -572,7 +572,7 @@ struct ShardAccount final : TLB_Complex { return cs.advance_ext(0x140, 1); } bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override { - return cs.advance(0x140) && t_Ref_Account.validate_skip(ops, cs, weak); + return cs.advance(0x140) && t_Ref_AccountE.validate_skip(ops, cs, weak); } static bool unpack(vm::CellSlice& cs, Record& info) { return info.unpack(cs); diff --git a/crypto/block/block.tlb b/crypto/block/block.tlb index 8d98197f..8662e243 100644 --- a/crypto/block/block.tlb +++ b/crypto/block/block.tlb @@ -366,7 +366,7 @@ trans_merge_install$0111 split_info:SplitMergeInfo smc_info#076ef1ea actions:uint16 msgs_sent:uint16 unixtime:uint32 block_lt:uint64 trans_lt:uint64 rand_seed:bits256 balance_remaining:CurrencyCollection - myself:MsgAddressInt = SmartContractInfo; + myself:MsgAddressInt global_config:(Maybe Cell) = SmartContractInfo; // // out_list_empty$_ = OutList 0; diff --git a/crypto/block/mc-config.h b/crypto/block/mc-config.h index ad5999e5..aca21475 100644 --- a/crypto/block/mc-config.h +++ b/crypto/block/mc-config.h @@ -632,7 +632,6 @@ class Config { static td::Result> unpack_param_dict(vm::Dictionary& dict); static td::Result> unpack_param_dict(Ref dict_root); - protected: Config(int _mode) : mode(_mode) { config_addr.set_zero(); } diff --git a/crypto/block/transaction.cpp b/crypto/block/transaction.cpp index adda48a5..7607ef81 100644 --- a/crypto/block/transaction.cpp +++ b/crypto/block/transaction.cpp @@ -20,6 +20,7 @@ #include "block/block.h" #include "block/block-parse.h" #include "block/block-auto.h" +#include "crypto/openssl/rand.hpp" #include "td/utils/bits.h" #include "td/utils/uint128.h" #include "ton/ton-shard.h" @@ -513,6 +514,7 @@ td::RefInt256 Account::compute_storage_fees(ton::UnixTime now, const std::vector return StoragePrices::compute_storage_fees(now, pricing, storage_stat, last_paid, is_special, is_masterchain()); } +namespace transaction { Transaction::Transaction(const Account& _account, int ttype, ton::LogicalTime req_start_lt, ton::UnixTime _now, Ref _inmsg) : trans_type(ttype) @@ -745,6 +747,7 @@ bool Transaction::prepare_credit_phase() { total_fees += std::move(collected); return true; } +} // namespace transaction bool ComputePhaseConfig::parse_GasLimitsPrices(Ref cell, td::RefInt256& freeze_due_limit, td::RefInt256& delete_due_limit) { @@ -837,6 +840,7 @@ td::RefInt256 ComputePhaseConfig::compute_gas_price(td::uint64 gas_used) const { : td::rshift(gas_price256 * (gas_used - flat_gas_limit), 16, 1) + flat_gas_price; } +namespace transaction { bool Transaction::compute_gas_limits(ComputePhase& cp, const ComputePhaseConfig& cfg) { // Compute gas limits if (account.is_special) { @@ -1057,13 +1061,21 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) { std::unique_ptr logger; auto vm_log = vm::VmLog(); if (cfg.with_vm_log) { - logger = std::make_unique(); + size_t log_max_size = cfg.vm_log_verbosity > 0 ? 1024 * 1024 : 256; + logger = std::make_unique(log_max_size); vm_log.log_interface = logger.get(); vm_log.log_options = td::LogOptions(VERBOSITY_NAME(DEBUG), true, false); + if (cfg.vm_log_verbosity > 1) { + vm_log.log_mask |= vm::VmLog::ExecLocation; + if (cfg.vm_log_verbosity > 2) { + vm_log.log_mask |= vm::VmLog::DumpStack | vm::VmLog::GasRemaining; + } + } } vm::VmState vm{new_code, std::move(stack), gas, 1, new_data, vm_log, compute_vm_libraries(cfg)}; vm.set_max_data_depth(cfg.max_vm_data_depth); vm.set_c7(prepare_vm_c7(cfg)); // tuple with SmartContractInfo + vm.set_chksig_always_succeed(cfg.ignore_chksig); // vm.incr_stack_trace(1); // enable stack dump after each step LOG(DEBUG) << "starting VM"; @@ -1338,6 +1350,7 @@ int Transaction::try_action_change_library(vm::CellSlice& cs, ActionPhase& ap, c ap.spec_actions++; return 0; } +} // namespace transaction // msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms // ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor)/2^16) nanograms @@ -1372,6 +1385,7 @@ td::RefInt256 MsgPrices::get_next_part(td::RefInt256 total) const { return (std::move(total) * next_frac) >> 16; } +namespace transaction { bool Transaction::check_replace_src_addr(Ref& src_addr) const { int t = (int)src_addr->prefetch_ulong(2); if (!t && src_addr->size_ext() == 2) { @@ -1978,6 +1992,7 @@ bool Transaction::prepare_bounce_phase(const ActionPhaseConfig& cfg) { bp.ok = true; return true; } +} // namespace transaction /* * @@ -2033,6 +2048,7 @@ static td::optional try_update_storage_stat(const vm::CellS return new_stat; } +namespace transaction { bool Transaction::compute_state() { if (new_total_state.not_null()) { return true; @@ -2460,6 +2476,7 @@ void Transaction::extract_out_msgs(std::vector& list) { list.emplace_back(start_lt + i + 1, std::move(out_msgs[i])); } } +} // namespace transaction void Account::push_transaction(Ref trans_root, ton::LogicalTime trans_lt) { transactions.emplace_back(trans_lt, std::move(trans_root)); @@ -2503,4 +2520,82 @@ bool Account::libraries_changed() const { } } +td::Status FetchConfigParams::fetch_config_params(const block::Config& config, + Ref* old_mparams, + std::vector* storage_prices, + block::StoragePhaseConfig* storage_phase_cfg, + td::BitArray<256>* rand_seed, + block::ComputePhaseConfig* compute_phase_cfg, + block::ActionPhaseConfig* action_phase_cfg, + td::RefInt256* masterchain_create_fee, + td::RefInt256* basechain_create_fee, + ton::WorkchainId wc, + ton::UnixTime now) { + *old_mparams = config.get_config_param(9); + { + auto res = config.get_storage_prices(); + if (res.is_error()) { + return res.move_as_error(); + } + *storage_prices = res.move_as_ok(); + } + if (rand_seed->is_zero()) { + // generate rand seed + prng::rand_gen().strong_rand_bytes(rand_seed->data(), 32); + LOG(DEBUG) << "block random seed set to " << rand_seed->to_hex(); + } + TRY_RESULT(size_limits, config.get_size_limits_config()); + { + // compute compute_phase_cfg / storage_phase_cfg + auto cell = config.get_config_param(wc == ton::masterchainId ? 20 : 21); + if (cell.is_null()) { + return td::Status::Error(-668, "cannot fetch current gas prices and limits from masterchain configuration"); + } + if (!compute_phase_cfg->parse_GasLimitsPrices(std::move(cell), storage_phase_cfg->freeze_due_limit, + storage_phase_cfg->delete_due_limit)) { + return td::Status::Error(-668, "cannot unpack current gas prices and limits from masterchain configuration"); + } + compute_phase_cfg->block_rand_seed = *rand_seed; + compute_phase_cfg->max_vm_data_depth = size_limits.max_vm_data_depth; + compute_phase_cfg->global_config = config.get_root_cell(); + compute_phase_cfg->suspended_addresses = config.get_suspended_addresses(now); + } + { + // compute action_phase_cfg + block::gen::MsgForwardPrices::Record rec; + auto cell = config.get_config_param(24); + if (cell.is_null() || !tlb::unpack_cell(std::move(cell), rec)) { + return td::Status::Error(-668, "cannot fetch masterchain message transfer prices from masterchain configuration"); + } + action_phase_cfg->fwd_mc = + block::MsgPrices{rec.lump_price, rec.bit_price, rec.cell_price, rec.ihr_price_factor, + (unsigned)rec.first_frac, (unsigned)rec.next_frac}; + cell = config.get_config_param(25); + if (cell.is_null() || !tlb::unpack_cell(std::move(cell), rec)) { + return td::Status::Error(-668, "cannot fetch standard message transfer prices from masterchain configuration"); + } + action_phase_cfg->fwd_std = + block::MsgPrices{rec.lump_price, rec.bit_price, rec.cell_price, rec.ihr_price_factor, + (unsigned)rec.first_frac, (unsigned)rec.next_frac}; + action_phase_cfg->workchains = &config.get_workchain_list(); + action_phase_cfg->bounce_msg_body = (config.has_capability(ton::capBounceMsgBody) ? 256 : 0); + action_phase_cfg->size_limits = size_limits; + } + { + // fetch block_grams_created + auto cell = config.get_config_param(14); + if (cell.is_null()) { + *basechain_create_fee = *masterchain_create_fee = td::zero_refint(); + } else { + block::gen::BlockCreateFees::Record create_fees; + if (!(tlb::unpack_cell(cell, create_fees) && + block::tlb::t_Grams.as_integer_to(create_fees.masterchain_block_fee, *masterchain_create_fee) && + block::tlb::t_Grams.as_integer_to(create_fees.basechain_block_fee, *basechain_create_fee))) { + return td::Status::Error(-668, "cannot unpack BlockCreateFees from configuration parameter #14"); + } + } + } + return td::Status::OK(); +} + } // namespace block diff --git a/crypto/block/transaction.h b/crypto/block/transaction.h index 2560c010..2e4463bd 100644 --- a/crypto/block/transaction.h +++ b/crypto/block/transaction.h @@ -35,7 +35,10 @@ using td::Ref; using LtCellRef = std::pair>; struct Account; + +namespace transaction { struct Transaction; +} // namespace transaction struct CollatorError { std::string msg; @@ -106,9 +109,11 @@ struct ComputePhaseConfig { std::unique_ptr libraries; Ref global_config; td::BitArray<256> block_rand_seed; + bool ignore_chksig{false}; bool with_vm_log{false}; td::uint16 max_vm_data_depth = 512; std::unique_ptr suspended_addresses; + int vm_log_verbosity = 0; ComputePhaseConfig(td::uint64 _gas_price = 0, td::uint64 _gas_limit = 0, td::uint64 _gas_credit = 0) : gas_price(_gas_price), gas_limit(_gas_limit), special_gas_limit(_gas_limit), gas_credit(_gas_credit) { compute_threshold(); @@ -273,7 +278,7 @@ struct Account { bool create_account_block(vm::CellBuilder& cb); // stores an AccountBlock with all transactions protected: - friend struct Transaction; + friend struct transaction::Transaction; bool set_split_depth(int split_depth); bool check_split_depth(int split_depth) const; bool forget_split_depth(); @@ -288,6 +293,7 @@ struct Account { bool compute_my_addr(bool force = false); }; +namespace transaction { struct Transaction { enum { tr_none, @@ -390,5 +396,20 @@ struct Transaction { bool serialize_bounce_phase(vm::CellBuilder& cb); bool unpack_msg_state(bool lib_only = false); }; +} // namespace transaction + +struct FetchConfigParams { +static td::Status fetch_config_params(const block::Config& config, + Ref* old_mparams, + std::vector* storage_prices, + StoragePhaseConfig* storage_phase_cfg, + td::BitArray<256>* rand_seed, + ComputePhaseConfig* compute_phase_cfg, + ActionPhaseConfig* action_phase_cfg, + td::RefInt256* masterchain_create_fee, + td::RefInt256* basechain_create_fee, + ton::WorkchainId wc, + ton::UnixTime now); +}; } // namespace block diff --git a/crypto/smc-envelope/SmartContract.cpp b/crypto/smc-envelope/SmartContract.cpp index 9a273a08..713caf9c 100644 --- a/crypto/smc-envelope/SmartContract.cpp +++ b/crypto/smc-envelope/SmartContract.cpp @@ -54,7 +54,11 @@ td::Ref prepare_vm_stack(td::RefInt256 amount, td::Ref td::Ref prepare_vm_c7(SmartContract::Args args) { td::BitArray<256> rand_seed; - rand_seed.as_slice().fill(0); + if (args.rand_seed) { + rand_seed = args.rand_seed.unwrap(); + } else { + rand_seed.as_slice().fill(0); + } td::RefInt256 rand_seed_int{true}; rand_seed_int.unique_write().import_bits(rand_seed.cbits(), 256, false); @@ -96,7 +100,7 @@ td::Ref prepare_vm_c7(SmartContract::Args args) { } SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref stack, td::Ref c7, - vm::GasLimits gas, bool ignore_chksig, td::Ref libraries) { + vm::GasLimits gas, bool ignore_chksig, td::Ref libraries, int vm_log_verbosity) { auto gas_credit = gas.gas_credit; vm::init_op_cp0(); vm::DictionaryBase::get_empty_dictionary(); @@ -109,15 +113,12 @@ SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref= VERBOSITY_NAME(DEBUG)) { - log.log_options.level = 4; - log.log_options.fix_newlines = true; - log.log_mask |= vm::VmLog::DumpStack; - } else { - log.log_options.level = 0; - log.log_mask = 0; + vm::VmLog log{&logger, td::LogOptions(VERBOSITY_NAME(DEBUG), true, false)}; + if (vm_log_verbosity > 1) { + log.log_mask |= vm::VmLog::ExecLocation; + if (vm_log_verbosity > 2) { + log.log_mask |= vm::VmLog::DumpStack | vm::VmLog::GasRemaining; + } } SmartContract::Answer res; @@ -137,13 +138,13 @@ SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref= VERBOSITY_NAME(DEBUG)) { LOG(DEBUG) << "VM log\n" << logger.res; std::ostringstream os; @@ -153,6 +154,7 @@ SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref{}); + args.libraries ? args.libraries.unwrap().get_root_cell() : td::Ref{}, args.vm_log_verbosity_level); state_ = res.new_state; return res; } @@ -237,7 +239,7 @@ SmartContract::Answer SmartContract::run_get_method(Args args) const { CHECK(args.method_id); args.stack.value().write().push_smallint(args.method_id.unwrap()); return run_smartcont(get_state(), args.stack.unwrap(), args.c7.unwrap(), args.limits.unwrap(), args.ignore_chksig, - args.libraries ? args.libraries.unwrap().get_root_cell() : td::Ref{}); + args.libraries ? args.libraries.unwrap().get_root_cell() : td::Ref{}, args.vm_log_verbosity_level); } SmartContract::Answer SmartContract::run_get_method(td::Slice method, Args args) const { diff --git a/crypto/smc-envelope/SmartContract.h b/crypto/smc-envelope/SmartContract.h index 95dd2e84..6da74bc6 100644 --- a/crypto/smc-envelope/SmartContract.h +++ b/crypto/smc-envelope/SmartContract.h @@ -50,6 +50,7 @@ class SmartContract : public td::CntObject { td::int32 code; td::int64 gas_used; td::ConstBitPtr missing_library{0}; + std::string vm_log; static int output_actions_count(td::Ref list); }; @@ -59,9 +60,11 @@ class SmartContract : public td::CntObject { td::optional> c7; td::optional> stack; td::optional now; + td::optional> rand_seed; bool ignore_chksig{false}; td::uint64 amount{0}; td::uint64 balance{0}; + int vm_log_verbosity_level{0}; td::optional address; td::optional> config; @@ -100,6 +103,10 @@ class SmartContract : public td::CntObject { this->stack = std::move(stack); return std::move(*this); } + Args&& set_rand_seed(td::BitArray<256> rand_seed) { + this->rand_seed = std::move(rand_seed); + return std::move(*this); + } Args&& set_ignore_chksig(bool ignore_chksig) { this->ignore_chksig = ignore_chksig; return std::move(*this); @@ -124,6 +131,10 @@ class SmartContract : public td::CntObject { this->libraries = libraries; return std::move(*this); } + Args&& set_vm_verbosity_level(int vm_log_verbosity_level) { + this->vm_log_verbosity_level = vm_log_verbosity_level; + return std::move(*this); + } td::Result get_method_id() const { if (!method_id) { diff --git a/crypto/vm/log.h b/crypto/vm/log.h index 30ace2c4..b62ada5e 100644 --- a/crypto/vm/log.h +++ b/crypto/vm/log.h @@ -31,7 +31,7 @@ namespace vm { struct VmLog { td::LogInterface *log_interface{td::log_interface}; td::LogOptions log_options{td::log_options}; - enum { DumpStack = 2 }; + enum { DumpStack = 2, ExecLocation = 4, GasRemaining = 8 }; int log_mask{1}; static VmLog Null() { VmLog res; diff --git a/crypto/vm/vm.cpp b/crypto/vm/vm.cpp index 6668c6d5..3baba279 100644 --- a/crypto/vm/vm.cpp +++ b/crypto/vm/vm.cpp @@ -433,18 +433,24 @@ void VmState::change_gas_limit(long long new_limit) { int VmState::step() { CHECK(code.not_null() && stack.not_null()); - //VM_LOG(st) << "stack:"; stack->dump(VM_LOG(st)); - //VM_LOG(st) << "; cr0.refcnt = " << get_c0()->get_refcnt() - 1 << std::endl; + if (log.log_mask & vm::VmLog::DumpStack) { + std::stringstream ss; + stack->dump(ss, 3); + VM_LOG(this) << "stack:" << ss.str(); + } if (stack_trace) { stack->dump(std::cerr, 3); } ++steps; if (code->size()) { + VM_LOG_MASK(this, vm::VmLog::ExecLocation) << "code cell hash: " << code->get_base_cell()->get_hash().to_hex() << " offset: " << code->cur_pos(); return dispatch->dispatch(this, code.write()); } else if (code->size_refs()) { VM_LOG(this) << "execute implicit JMPREF"; + auto ref_cell = code->prefetch_ref(); + VM_LOG_MASK(this, vm::VmLog::ExecLocation) << "code cell hash: " << ref_cell->get_hash().to_hex() << " offset: 0"; gas.consume_chk(implicit_jmpref_gas_price); - Ref cont = Ref{true, load_cell_slice_ref(code->prefetch_ref()), get_cp()}; + Ref cont = Ref{true, load_cell_slice_ref(std::move(ref_cell)), get_cp()}; return jump(std::move(cont)); } else { VM_LOG(this) << "execute implicit RET"; @@ -465,6 +471,7 @@ int VmState::run() { try { try { res = step(); + VM_LOG_MASK(this, vm::VmLog::GasRemaining) << "gas remaining: " << gas.gas_remaining; gas.check(); } catch (vm::CellBuilder::CellWriteError) { throw VmError{Excno::cell_ov}; diff --git a/emulator/CMakeLists.txt b/emulator/CMakeLists.txt new file mode 100644 index 00000000..43881c7d --- /dev/null +++ b/emulator/CMakeLists.txt @@ -0,0 +1,55 @@ +cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) + +if (NOT OPENSSL_FOUND) + find_package(OpenSSL REQUIRED) +endif() + +set(EMULATOR_STATIC_SOURCE + transaction-emulator.cpp + tvm-emulator.hpp +) + +set(EMULATOR_HEADERS + transaction-emulator.h + emulator-extern.h +) + +set(EMULATOR_SOURCE + emulator-extern.cpp +) + +set(EMULATOR_EMSCRIPTEN_SOURCE + transaction-emscripten.cpp +) + +include(GenerateExportHeader) + +add_library(emulator_static STATIC ${EMULATOR_STATIC_SOURCE}) +target_link_libraries(emulator_static PUBLIC ton_crypto ton_block smc-envelope) + +add_library(emulator SHARED ${EMULATOR_SOURCE} ${EMULATOR_HEADERS}) +target_link_libraries(emulator PUBLIC emulator_static) +generate_export_header(emulator EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/emulator_export.h) +target_include_directories(emulator PUBLIC + $ + $) +if (APPLE) + set_target_properties(emulator PROPERTIES LINK_FLAGS "-Wl,-exported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/emulator_export_list") +endif() + +if (USE_EMSCRIPTEN) + add_executable(emulator-emscripten ${EMULATOR_EMSCRIPTEN_SOURCE}) + target_link_libraries(emulator-emscripten PUBLIC emulator) + target_link_options(emulator-emscripten PRIVATE -sEXPORTED_RUNTIME_METHODS=_malloc,free,UTF8ToString,stringToUTF8,allocate,ALLOC_NORMAL,lengthBytesUTF8) + target_link_options(emulator-emscripten PRIVATE -sEXPORTED_FUNCTIONS=_emulate,_free,_run_get_method) + target_link_options(emulator-emscripten PRIVATE -sEXPORT_NAME=EmulatorModule) + target_link_options(emulator-emscripten PRIVATE -sERROR_ON_UNDEFINED_SYMBOLS=0) + target_link_options(emulator-emscripten PRIVATE -Oz) + target_link_options(emulator-emscripten PRIVATE -sIGNORE_MISSING_MAIN=1) + target_link_options(emulator-emscripten PRIVATE -sAUTO_NATIVE_LIBRARIES=0) + target_link_options(emulator-emscripten PRIVATE -sMODULARIZE=1) + target_link_options(emulator-emscripten PRIVATE -sENVIRONMENT=web) + target_link_options(emulator-emscripten PRIVATE -sFILESYSTEM=0) + target_link_options(emulator-emscripten PRIVATE -fexceptions) + target_compile_options(emulator-emscripten PRIVATE -fexceptions) +endif() diff --git a/emulator/README.md b/emulator/README.md new file mode 100644 index 00000000..d4e2244e --- /dev/null +++ b/emulator/README.md @@ -0,0 +1,32 @@ +# Emulator + +Emulator is a shared library containing the following functionality: +- Emulating blockchain transactions +- Emulating TVM - get methods and sending external and internal messages. + +## Transaction Emulator + +To emulate transaction you need the following data: + +- Account state of type *ShardAccount*. +- Global config of type *(Hashmap 32 ^Cell)*. +- Inbound message of type *MessageAny*. + +Optionally you can set emulation parameters: +- *ignore_chksig* - whether CHKSIG instructions are set to always succeed. Default: *false* +- *lt* - logical time of emulation. Default: next block's lt after the account's last transaction block. +- *unixtime* - unix time of emulation. Default: current system time +- *rand_seed* - random seed. Default: generated randomly +- *libs* - shared libraries. If your smart contract uses shared libraries (located in masterchain), you should set this parameter. + +Emulator output contains: +- Transaction object (*Transaction*) +- New account state (*ShardAccount*) +- Actions cell (*OutList n*) +- TVM log + +## TVM Emulator + +TVM emulator is intended to run get methods or emulate sending message on TVM level. It is initialized with smart contract code and data cells. +- To run get method you pass *initial stack* and *method id* (as integer). +- To emulate sending message you pass *message body* and in case of internal message *amount* in nanograms. diff --git a/emulator/StringLog.h b/emulator/StringLog.h new file mode 100644 index 00000000..724182c4 --- /dev/null +++ b/emulator/StringLog.h @@ -0,0 +1,27 @@ +#ifndef TON_STRINGLOG_H +#define TON_STRINGLOG_H + +#include "td/utils/logging.h" +#include + +class StringLog : public td::LogInterface { + public: + StringLog() { + } + + void append(td::CSlice new_slice, int log_level) override { + str.append(new_slice.str()); + } + + void rotate() override { + } + + std::string get_string() const { + return str; + } + + private: + std::string str; +}; + +#endif //TON_STRINGLOG_H \ No newline at end of file diff --git a/emulator/emulator-emscripten.cpp b/emulator/emulator-emscripten.cpp new file mode 100644 index 00000000..f0f2c903 --- /dev/null +++ b/emulator/emulator-emscripten.cpp @@ -0,0 +1,189 @@ +#include "emulator-extern.h" +#include "td/utils/logging.h" +#include "td/utils/JsonBuilder.h" +#include "td/utils/misc.h" +#include "td/utils/optional.h" +#include "StringLog.h" +#include +#include "crypto/common/bitstring.h" + +struct TransactionEmulationParams { + uint32_t utime; + uint64_t lt; + td::optional rand_seed_hex; + bool ignore_chksig; +}; + +td::Result decode_transaction_emulation_params(const char* json) { + TransactionEmulationParams params; + + std::string json_str(json); + TRY_RESULT(input_json, td::json_decode(td::MutableSlice(json_str))); + auto &obj = input_json.get_object(); + + TRY_RESULT(utime_field, td::get_json_object_field(obj, "utime", td::JsonValue::Type::Number, false)); + TRY_RESULT(utime, td::to_integer_safe(utime_field.get_number())); + params.utime = utime; + + TRY_RESULT(lt_field, td::get_json_object_field(obj, "lt", td::JsonValue::Type::String, false)); + TRY_RESULT(lt, td::to_integer_safe(lt_field.get_string())); + params.lt = lt; + + TRY_RESULT(rand_seed_str, td::get_json_object_string_field(obj, "rand_seed", true)); + if (rand_seed_str.size() > 0) { + params.rand_seed_hex = rand_seed_str; + } + + TRY_RESULT(ignore_chksig, td::get_json_object_bool_field(obj, "ignore_chksig", false)); + params.ignore_chksig = ignore_chksig; + + return params; +} + +struct GetMethodParams { + std::string code; + std::string data; + int verbosity; + td::optional libs; + std::string address; + uint32_t unixtime; + uint64_t balance; + std::string rand_seed_hex; + int64_t gas_limit; + int method_id; +}; + +td::Result decode_get_method_params(const char* json) { + GetMethodParams params; + + std::string json_str(json); + TRY_RESULT(input_json, td::json_decode(td::MutableSlice(json_str))); + auto &obj = input_json.get_object(); + + TRY_RESULT(code, td::get_json_object_string_field(obj, "code", false)); + params.code = code; + + TRY_RESULT(data, td::get_json_object_string_field(obj, "data", false)); + params.data = data; + + TRY_RESULT(verbosity, td::get_json_object_int_field(obj, "verbosity", false)); + params.verbosity = verbosity; + + TRY_RESULT(libs, td::get_json_object_string_field(obj, "libs", true)); + if (libs.size() > 0) { + params.libs = libs; + } + + TRY_RESULT(address, td::get_json_object_string_field(obj, "address", false)); + params.address = address; + + TRY_RESULT(unixtime_field, td::get_json_object_field(obj, "unixtime", td::JsonValue::Type::Number, false)); + TRY_RESULT(unixtime, td::to_integer_safe(unixtime_field.get_number())); + params.unixtime = unixtime; + + TRY_RESULT(balance_field, td::get_json_object_field(obj, "balance", td::JsonValue::Type::String, false)); + TRY_RESULT(balance, td::to_integer_safe(balance_field.get_string())); + params.balance = balance; + + TRY_RESULT(rand_seed_str, td::get_json_object_string_field(obj, "rand_seed", false)); + params.rand_seed_hex = rand_seed_str; + + TRY_RESULT(gas_limit_field, td::get_json_object_field(obj, "gas_limit", td::JsonValue::Type::String, false)); + TRY_RESULT(gas_limit, td::to_integer_safe(gas_limit_field.get_string())); + params.gas_limit = gas_limit; + + TRY_RESULT(method_id, td::get_json_object_int_field(obj, "method_id", false)); + params.method_id = method_id; + + return params; +} + +extern "C" { + +const char *emulate(const char *config, const char* libs, int verbosity, const char* account, const char* message, const char* params) { + StringLog logger; + + td::log_interface = &logger; + SET_VERBOSITY_LEVEL(verbosity_DEBUG); + + auto decoded_params_res = decode_transaction_emulation_params(params); + if (decoded_params_res.is_error()) { + return strdup(R"({"fail":true,"message":"Can't decode other params"})"); + } + auto decoded_params = decoded_params_res.move_as_ok(); + + auto em = transaction_emulator_create(config, verbosity); + + bool rand_seed_set = true; + if (decoded_params.rand_seed_hex) { + rand_seed_set = transaction_emulator_set_rand_seed(em, decoded_params.rand_seed_hex.unwrap().c_str()); + } + + if (!transaction_emulator_set_libs(em, libs) || + !transaction_emulator_set_lt(em, decoded_params.lt) || + !transaction_emulator_set_unixtime(em, decoded_params.utime) || + !transaction_emulator_set_ignore_chksig(em, decoded_params.ignore_chksig) || + !rand_seed_set) { + transaction_emulator_destroy(em); + return strdup(R"({"fail":true,"message":"Can't set params"})"); + } + + auto tx = transaction_emulator_emulate_transaction(em, account, message); + + transaction_emulator_destroy(em); + + const char* output = nullptr; + { + td::JsonBuilder jb; + auto json_obj = jb.enter_object(); + json_obj("output", td::JsonRaw(td::Slice(tx))); + json_obj("logs", logger.get_string()); + json_obj.leave(); + output = strdup(jb.string_builder().as_cslice().c_str()); + } + free((void*) tx); + + return output; +} + +const char *run_get_method(const char *params, const char* stack, const char* config) { + StringLog logger; + + td::log_interface = &logger; + SET_VERBOSITY_LEVEL(verbosity_DEBUG); + + auto decoded_params_res = decode_get_method_params(params); + if (decoded_params_res.is_error()) { + return strdup(R"({"fail":true,"message":"Can't decode params"})"); + } + auto decoded_params = decoded_params_res.move_as_ok(); + + auto tvm = tvm_emulator_create(decoded_params.code.c_str(), decoded_params.data.c_str(), decoded_params.verbosity); + + if ((decoded_params.libs && !tvm_emulator_set_libraries(tvm, decoded_params.libs.value().c_str())) || + !tvm_emulator_set_c7(tvm, decoded_params.address.c_str(), decoded_params.unixtime, + decoded_params.balance, decoded_params.rand_seed_hex.c_str(), config) || + (decoded_params.gas_limit > 0 && !tvm_emulator_set_gas_limit(tvm, decoded_params.gas_limit))) { + tvm_emulator_destroy(tvm); + return strdup(R"({"fail":true,"message":"Can't set params"})"); + } + + auto res = tvm_emulator_run_get_method(tvm, decoded_params.method_id, stack); + + tvm_emulator_destroy(tvm); + + const char* output = nullptr; + { + td::JsonBuilder jb; + auto json_obj = jb.enter_object(); + json_obj("output", td::JsonRaw(td::Slice(res))); + json_obj("logs", logger.get_string()); + json_obj.leave(); + output = strdup(jb.string_builder().as_cslice().c_str()); + } + free((void*) res); + + return output; +} + +} \ No newline at end of file diff --git a/emulator/emulator-extern.cpp b/emulator/emulator-extern.cpp new file mode 100644 index 00000000..01b9d5c7 --- /dev/null +++ b/emulator/emulator-extern.cpp @@ -0,0 +1,435 @@ +#include "emulator-extern.h" +#include "td/utils/base64.h" +#include "td/utils/Status.h" +#include "td/utils/JsonBuilder.h" +#include "td/utils/logging.h" +#include "td/utils/Variant.h" +#include "td/utils/overloaded.h" +#include "transaction-emulator.h" +#include "tvm-emulator.hpp" +#include "crypto/vm/stack.hpp" + +td::Result> boc_b64_to_cell(const char *boc) { + TRY_RESULT_PREFIX(boc_decoded, td::base64_decode(td::Slice(boc)), "Can't decode base64 boc: "); + return vm::std_boc_deserialize(boc_decoded); +} + +td::Result cell_to_boc_b64(td::Ref cell) { + TRY_RESULT_PREFIX(boc, vm::std_boc_serialize(std::move(cell), vm::BagOfCells::Mode::WithCRC32C), "Can't serialize cell: "); + return td::base64_encode(boc.as_slice()); +} + +const char *success_response(std::string&& transaction, std::string&& new_shard_account, std::string&& vm_log, td::optional&& actions) { + td::JsonBuilder jb; + auto json_obj = jb.enter_object(); + json_obj("success", td::JsonTrue()); + json_obj("transaction", std::move(transaction)); + json_obj("shard_account", std::move(new_shard_account)); + json_obj("vm_log", std::move(vm_log)); + if (actions) { + json_obj("actions", actions.unwrap()); + } else { + json_obj("actions", td::JsonNull()); + } + json_obj.leave(); + return strdup(jb.string_builder().as_cslice().c_str()); +} + +const char *error_response(std::string&& error) { + td::JsonBuilder jb; + auto json_obj = jb.enter_object(); + json_obj("success", td::JsonFalse()); + json_obj("error", std::move(error)); + json_obj.leave(); + return strdup(jb.string_builder().as_cslice().c_str()); +} + +const char *external_not_accepted_response(std::string&& vm_log, int vm_exit_code) { + td::JsonBuilder jb; + auto json_obj = jb.enter_object(); + json_obj("success", td::JsonFalse()); + json_obj("error", "External message not accepted by smart contract"); + json_obj("vm_log", std::move(vm_log)); + json_obj("vm_exit_code", vm_exit_code); + json_obj.leave(); + return strdup(jb.string_builder().as_cslice().c_str()); +} + +#define ERROR_RESPONSE(error) return error_response(error) + +td::Result decode_config(const char* config_boc) { + TRY_RESULT_PREFIX(config_params_cell, boc_b64_to_cell(config_boc), "Can't deserialize config params boc: "); + auto global_config = block::Config(config_params_cell, td::Bits256::zero(), block::Config::needWorkchainInfo | block::Config::needSpecialSmc); + TRY_STATUS_PREFIX(global_config.unpack(), "Can't unpack config params: "); + return global_config; +} + +void *transaction_emulator_create(const char *config_params_boc, int vm_log_verbosity) { + auto global_config_res = decode_config(config_params_boc); + if (global_config_res.is_error()) { + LOG(ERROR) << global_config_res.move_as_error().message(); + return nullptr; + } + + return new emulator::TransactionEmulator(global_config_res.move_as_ok(), vm_log_verbosity); +} + +const char *transaction_emulator_emulate_transaction(void *transaction_emulator, const char *shard_account_boc, const char *message_boc) { + auto emulator = static_cast(transaction_emulator); + + auto message_cell_r = boc_b64_to_cell(message_boc); + if (message_cell_r.is_error()) { + ERROR_RESPONSE(PSTRING() << "Can't deserialize message boc: " << message_cell_r.move_as_error()); + } + auto message_cell = message_cell_r.move_as_ok(); + auto message_cs = vm::load_cell_slice(message_cell); + int msg_tag = block::gen::t_CommonMsgInfo.get_tag(message_cs); + + auto shard_account_cell = boc_b64_to_cell(shard_account_boc); + if (shard_account_cell.is_error()) { + ERROR_RESPONSE(PSTRING() << "Can't deserialize shard account boc: " << shard_account_cell.move_as_error()); + } + auto shard_account_slice = vm::load_cell_slice(shard_account_cell.ok_ref()); + block::gen::ShardAccount::Record shard_account; + if (!tlb::unpack(shard_account_slice, shard_account)) { + ERROR_RESPONSE(PSTRING() << "Can't unpack shard account cell"); + } + + td::Ref addr_slice; + auto account_slice = vm::load_cell_slice(shard_account.account); + if (block::gen::t_Account.get_tag(account_slice) == block::gen::Account::account_none) { + if (msg_tag == block::gen::CommonMsgInfo::ext_in_msg_info) { + block::gen::CommonMsgInfo::Record_ext_in_msg_info info; + if (!tlb::unpack(message_cs, info)) { + ERROR_RESPONSE(PSTRING() << "Can't unpack inbound external message"); + } + addr_slice = std::move(info.dest); + } + else if (msg_tag == block::gen::CommonMsgInfo::int_msg_info) { + block::gen::CommonMsgInfo::Record_int_msg_info info; + if (!tlb::unpack(message_cs, info)) { + ERROR_RESPONSE(PSTRING() << "Can't unpack inbound internal message"); + } + addr_slice = std::move(info.dest); + } else { + ERROR_RESPONSE(PSTRING() << "Only ext in and int message are supported"); + } + } else { + block::gen::Account::Record_account account_record; + if (!tlb::unpack(account_slice, account_record)) { + ERROR_RESPONSE(PSTRING() << "Can't unpack account cell"); + } + addr_slice = std::move(account_record.addr); + } + ton::WorkchainId wc; + ton::StdSmcAddress addr; + if (!block::tlb::t_MsgAddressInt.extract_std_address(addr_slice, wc, addr)) { + ERROR_RESPONSE(PSTRING() << "Can't extract account address"); + } + + auto account = block::Account(wc, addr.bits()); + ton::UnixTime now = (unsigned)std::time(nullptr); + bool is_special = wc == ton::masterchainId && emulator->get_config().is_special_smartcontract(addr); + if (!account.unpack(vm::load_cell_slice_ref(shard_account_cell.move_as_ok()), td::Ref(), now, is_special)) { + ERROR_RESPONSE(PSTRING() << "Can't unpack shard account"); + } + + auto result = emulator->emulate_transaction(std::move(account), message_cell, 0, 0, block::transaction::Transaction::tr_ord); + if (result.is_error()) { + ERROR_RESPONSE(PSTRING() << "Emulate transaction failed: " << result.move_as_error()); + } + auto emulation_result = result.move_as_ok(); + + auto external_not_accepted = dynamic_cast(emulation_result.get()); + if (external_not_accepted) { + return external_not_accepted_response(std::move(external_not_accepted->vm_log), external_not_accepted->vm_exit_code); + } + + auto emulation_success = dynamic_cast(*emulation_result); + auto trans_boc_b64 = cell_to_boc_b64(std::move(emulation_success.transaction)); + if (trans_boc_b64.is_error()) { + ERROR_RESPONSE(PSTRING() << "Can't serialize Transaction to boc " << trans_boc_b64.move_as_error()); + } + + auto new_shard_account_cell = vm::CellBuilder().store_ref(emulation_success.account.total_state) + .store_bits(emulation_success.account.last_trans_hash_.as_bitslice()) + .store_long(emulation_success.account.last_trans_lt_).finalize(); + auto new_shard_account_boc_b64 = cell_to_boc_b64(std::move(new_shard_account_cell)); + if (new_shard_account_boc_b64.is_error()) { + ERROR_RESPONSE(PSTRING() << "Can't serialize ShardAccount to boc " << new_shard_account_boc_b64.move_as_error()); + } + + td::optional actions_boc_b64; + if (emulation_success.actions.not_null()) { + auto actions_boc_b64_result = cell_to_boc_b64(std::move(emulation_success.actions)); + if (actions_boc_b64_result.is_error()) { + ERROR_RESPONSE(PSTRING() << "Can't serialize actions list cell to boc " << actions_boc_b64_result.move_as_error()); + } + actions_boc_b64 = actions_boc_b64_result.move_as_ok(); + } + + return success_response(trans_boc_b64.move_as_ok(), new_shard_account_boc_b64.move_as_ok(), std::move(emulation_success.vm_log), std::move(actions_boc_b64)); +} + +bool transaction_emulator_set_unixtime(void *transaction_emulator, uint32_t unixtime) { + auto emulator = static_cast(transaction_emulator); + + emulator->set_unixtime(unixtime); + + return true; +} + +bool transaction_emulator_set_lt(void *transaction_emulator, uint64_t lt) { + auto emulator = static_cast(transaction_emulator); + + emulator->set_lt(lt); + + return true; +} + +bool transaction_emulator_set_rand_seed(void *transaction_emulator, const char* rand_seed_hex) { + auto emulator = static_cast(transaction_emulator); + + auto rand_seed_hex_slice = td::Slice(rand_seed_hex); + if (rand_seed_hex_slice.size() != 64) { + LOG(ERROR) << "Rand seed expected as 64 characters hex string"; + return false; + } + auto rand_seed_bytes = td::hex_decode(rand_seed_hex_slice); + if (rand_seed_bytes.is_error()) { + LOG(ERROR) << "Can't decode hex rand seed"; + return false; + } + td::BitArray<256> rand_seed; + rand_seed.as_slice().copy_from(rand_seed_bytes.move_as_ok()); + + emulator->set_rand_seed(rand_seed); + return true; +} + +bool transaction_emulator_set_ignore_chksig(void *transaction_emulator, bool ignore_chksig) { + auto emulator = static_cast(transaction_emulator); + + emulator->set_ignore_chksig(ignore_chksig); + + return true; +} + +bool transaction_emulator_set_config(void *transaction_emulator, const char* config_boc) { + auto emulator = static_cast(transaction_emulator); + + auto global_config_res = decode_config(config_boc); + if (global_config_res.is_error()) { + LOG(ERROR) << global_config_res.move_as_error().message(); + return false; + } + + emulator->set_config(global_config_res.move_as_ok()); + + return true; +} + +bool transaction_emulator_set_libs(void *transaction_emulator, const char* shardchain_libs_boc) { + auto emulator = static_cast(transaction_emulator); + + if (shardchain_libs_boc != nullptr) { + auto shardchain_libs_cell = boc_b64_to_cell(shardchain_libs_boc); + if (shardchain_libs_cell.is_error()) { + LOG(ERROR) << "Can't deserialize shardchain libraries boc: " << shardchain_libs_cell.move_as_error(); + return false; + } + emulator->set_libs(vm::Dictionary(shardchain_libs_cell.move_as_ok(), 256)); + } + + return true; +} + +void transaction_emulator_destroy(void *transaction_emulator) { + delete static_cast(transaction_emulator); +} + +bool emulator_set_verbosity_level(int verbosity_level) { + if (0 <= verbosity_level && verbosity_level <= VERBOSITY_NAME(NEVER)) { + SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL) + verbosity_level); + return true; + } + return false; +} + +void *tvm_emulator_create(const char *code, const char *data, int vm_log_verbosity) { + auto code_cell = boc_b64_to_cell(code); + if (code_cell.is_error()) { + LOG(ERROR) << "Can't deserialize code boc: " << code_cell.move_as_error(); + return nullptr; + } + auto data_cell = boc_b64_to_cell(data); + if (data_cell.is_error()) { + LOG(ERROR) << "Can't deserialize code boc: " << data_cell.move_as_error(); + return nullptr; + } + + auto emulator = new emulator::TvmEmulator(code_cell.move_as_ok(), data_cell.move_as_ok()); + emulator->set_vm_verbosity_level(vm_log_verbosity); + return emulator; +} + +bool tvm_emulator_set_libraries(void *tvm_emulator, const char *libs_boc) { + vm::Dictionary libs{256}; + auto libs_cell = boc_b64_to_cell(libs_boc); + if (libs_cell.is_error()) { + LOG(ERROR) << "Can't deserialize libraries boc: " << libs_cell.move_as_error(); + return false; + } + libs = vm::Dictionary(libs_cell.move_as_ok(), 256); + + auto emulator = static_cast(tvm_emulator); + emulator->set_libraries(std::move(libs)); + + return true; +} + +bool tvm_emulator_set_c7(void *tvm_emulator, const char *address, uint32_t unixtime, uint64_t balance, const char *rand_seed_hex, const char *config_boc) { + auto emulator = static_cast(tvm_emulator); + auto std_address = block::StdAddress::parse(td::Slice(address)); + if (std_address.is_error()) { + LOG(ERROR) << "Can't parse address: " << std_address.move_as_error(); + return false; + } + + auto config_params_cell = boc_b64_to_cell(config_boc); + if (config_params_cell.is_error()) { + LOG(ERROR) << "Can't deserialize config params boc: " << config_params_cell.move_as_error(); + return false; + } + auto global_config = std::make_shared(config_params_cell.move_as_ok(), td::Bits256::zero(), block::Config::needWorkchainInfo | block::Config::needSpecialSmc); + auto unpack_res = global_config->unpack(); + if (unpack_res.is_error()) { + LOG(ERROR) << "Can't unpack config params"; + return false; + } + + auto rand_seed_hex_slice = td::Slice(rand_seed_hex); + if (rand_seed_hex_slice.size() != 64) { + LOG(ERROR) << "Rand seed expected as 64 characters hex string"; + return false; + } + auto rand_seed_bytes = td::hex_decode(rand_seed_hex_slice); + if (rand_seed_bytes.is_error()) { + LOG(ERROR) << "Can't decode hex rand seed"; + return false; + } + td::BitArray<256> rand_seed; + rand_seed.as_slice().copy_from(rand_seed_bytes.move_as_ok()); + + emulator->set_c7(std_address.move_as_ok(), unixtime, balance, rand_seed, std::const_pointer_cast(global_config)); + + return true; +} + +bool tvm_emulator_set_gas_limit(void *tvm_emulator, int64_t gas_limit) { + auto emulator = static_cast(tvm_emulator); + emulator->set_gas_limit(gas_limit); + return true; +} + +const char *tvm_emulator_run_get_method(void *tvm_emulator, int method_id, const char *stack_boc) { + auto stack_cell = boc_b64_to_cell(stack_boc); + if (stack_cell.is_error()) { + ERROR_RESPONSE(PSTRING() << "Couldn't deserialize stack cell: " << stack_cell.move_as_error().to_string()); + } + auto stack_cs = vm::load_cell_slice(stack_cell.move_as_ok()); + td::Ref stack; + if (!vm::Stack::deserialize_to(stack_cs, stack)) { + ERROR_RESPONSE(PSTRING() << "Couldn't deserialize stack"); + } + + auto emulator = static_cast(tvm_emulator); + auto result = emulator->run_get_method(method_id, stack); + + vm::CellBuilder stack_cb; + if (!result.stack->serialize(stack_cb)) { + ERROR_RESPONSE(PSTRING() << "Couldn't serialize stack"); + } + auto result_stack_boc = cell_to_boc_b64(stack_cb.finalize()); + if (result_stack_boc.is_error()) { + ERROR_RESPONSE(PSTRING() << "Couldn't serialize stack cell: " << result_stack_boc.move_as_error().to_string()); + } + + td::JsonBuilder jb; + auto json_obj = jb.enter_object(); + json_obj("success", td::JsonTrue()); + json_obj("stack", result_stack_boc.move_as_ok()); + json_obj("gas_used", std::to_string(result.gas_used)); + json_obj("vm_exit_code", result.code); + json_obj("vm_log", result.vm_log); + if (result.missing_library.is_null()) { + json_obj("missing_library", td::JsonNull()); + } else { + json_obj("missing_library", td::Bits256(result.missing_library).to_hex()); + } + json_obj.leave(); + + return strdup(jb.string_builder().as_cslice().c_str()); +} + +const char *tvm_emulator_send_external_message(void *tvm_emulator, const char *message_body_boc) { + auto message_body_cell = boc_b64_to_cell(message_body_boc); + if (message_body_cell.is_error()) { + ERROR_RESPONSE(PSTRING() << "Can't deserialize message body boc: " << message_body_cell.move_as_error()); + } + + auto emulator = static_cast(tvm_emulator); + auto result = emulator->send_external_message(message_body_cell.move_as_ok()); + + td::JsonBuilder jb; + auto json_obj = jb.enter_object(); + json_obj("success", td::JsonTrue()); + json_obj("gas_used", std::to_string(result.gas_used)); + json_obj("vm_exit_code", result.code); + json_obj("accepted", td::JsonBool(result.accepted)); + json_obj("vm_log", result.vm_log); + if (result.missing_library.is_null()) { + json_obj("missing_library", td::JsonNull()); + } else { + json_obj("missing_library", td::Bits256(result.missing_library).to_hex()); + } + json_obj("actions", cell_to_boc_b64(result.actions).move_as_ok()); + json_obj("new_code", cell_to_boc_b64(result.new_state.code).move_as_ok()); + json_obj("new_data", cell_to_boc_b64(result.new_state.data).move_as_ok()); + json_obj.leave(); + + return strdup(jb.string_builder().as_cslice().c_str()); +} + +const char *tvm_emulator_send_internal_message(void *tvm_emulator, const char *message_body_boc, uint64_t amount) { + auto message_body_cell = boc_b64_to_cell(message_body_boc); + if (message_body_cell.is_error()) { + ERROR_RESPONSE(PSTRING() << "Can't deserialize message body boc: " << message_body_cell.move_as_error()); + } + + auto emulator = static_cast(tvm_emulator); + auto result = emulator->send_internal_message(message_body_cell.move_as_ok(), amount); + + td::JsonBuilder jb; + auto json_obj = jb.enter_object(); + json_obj("success", td::JsonTrue()); + json_obj("gas_used", std::to_string(result.gas_used)); + json_obj("vm_exit_code", result.code); + json_obj("accepted", td::JsonBool(result.accepted)); + json_obj("vm_log", result.vm_log); + if (result.missing_library.is_null()) { + json_obj("missing_library", td::JsonNull()); + } else { + json_obj("missing_library", td::Bits256(result.missing_library).to_hex()); + } + json_obj("actions", cell_to_boc_b64(result.actions).move_as_ok()); + json_obj("new_code", cell_to_boc_b64(result.new_state.code).move_as_ok()); + json_obj("new_data", cell_to_boc_b64(result.new_state.data).move_as_ok()); + json_obj.leave(); + + return strdup(jb.string_builder().as_cslice().c_str()); +} + +void tvm_emulator_destroy(void *tvm_emulator) { + delete static_cast(tvm_emulator); +} diff --git a/emulator/emulator-extern.h b/emulator/emulator-extern.h new file mode 100644 index 00000000..ad5972de --- /dev/null +++ b/emulator/emulator-extern.h @@ -0,0 +1,216 @@ +#pragma once + +#include +#include "emulator_export.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Creates TransactionEmulator object + * @param config_params_boc Base64 encoded BoC serialized Config dictionary (Hashmap 32 ^Cell) + * @param vm_log_verbosity Verbosity level of VM log. 0 - log truncated to last 256 characters. 1 - unlimited length log. + * 2 - for each command prints its cell hash and offset. 3 - for each command log prints all stack values. + * @return Pointer to TransactionEmulator or nullptr in case of error + */ +EMULATOR_EXPORT void *transaction_emulator_create(const char *config_params_boc, int vm_log_verbosity); + +/** + * @brief Set unixtime for emulation + * @param transaction_emulator Pointer to TransactionEmulator object + * @param unixtime Unix timestamp + * @return true in case of success, false in case of error + */ +EMULATOR_EXPORT bool transaction_emulator_set_unixtime(void *transaction_emulator, uint32_t unixtime); + +/** + * @brief Set lt for emulation + * @param transaction_emulator Pointer to TransactionEmulator object + * @param lt Logical time + * @return true in case of success, false in case of error + */ +EMULATOR_EXPORT bool transaction_emulator_set_lt(void *transaction_emulator, uint64_t lt); + +/** + * @brief Set rand seed for emulation + * @param transaction_emulator Pointer to TransactionEmulator object + * @param rand_seed_hex Hex string of length 64 + * @return true in case of success, false in case of error + */ +EMULATOR_EXPORT bool transaction_emulator_set_rand_seed(void *transaction_emulator, const char* rand_seed_hex); + +/** + * @brief Set ignore_chksig flag for emulation + * @param transaction_emulator Pointer to TransactionEmulator object + * @param ignore_chksig Whether emulation should always succeed on CHKSIG operation + * @return true in case of success, false in case of error + */ +EMULATOR_EXPORT bool transaction_emulator_set_ignore_chksig(void *transaction_emulator, bool ignore_chksig); + +/** + * @brief Set unixtime for emulation + * @param transaction_emulator Pointer to TransactionEmulator object + * @param config_boc Base64 encoded BoC serialized Config dictionary (Hashmap 32 ^Cell) + * @return true in case of success, false in case of error + */ +EMULATOR_EXPORT bool transaction_emulator_set_config(void *transaction_emulator, const char* config_boc); + +/** + * @brief Set unixtime for emulation + * @param transaction_emulator Pointer to TransactionEmulator object + * @param libs_boc Base64 encoded BoC serialized shared libraries dictionary (HashmapE 256 ^Cell). + * @return true in case of success, false in case of error + */ +EMULATOR_EXPORT bool transaction_emulator_set_libs(void *transaction_emulator, const char* libs_boc); + +/** + * @brief Emulate transaction + * @param transaction_emulator Pointer to TransactionEmulator object + * @param shard_account_boc Base64 encoded BoC serialized ShardAccount + * @param message_boc Base64 encoded BoC serialized inbound Message (internal or external) + * @return Json object with error: + * { + * "success": false, + * "error": "Error description" + * // and optional fields "vm_exit_code" and "vm_log" in case external message was not accepted. + * } + * Or success: + * { + * "success": true, + * "transaction": "Base64 encoded Transaction boc", + * "shard_account": "Base64 encoded new ShardAccount boc", + * "vm_log": "execute DUP...", + * "actions": "Base64 encoded compute phase actions boc (OutList n)" + * } + */ +EMULATOR_EXPORT const char *transaction_emulator_emulate_transaction(void *transaction_emulator, const char *shard_account_boc, const char *message_boc); + +/** + * @brief Destroy TransactionEmulator object + * @param transaction_emulator Pointer to TransactionEmulator object + */ +EMULATOR_EXPORT void transaction_emulator_destroy(void *transaction_emulator); + +/** + * @brief Set global verbosity level of the library + * @param verbosity_level New verbosity level (0 - never, 1 - error, 2 - warning, 3 - info, 4 - debug) + */ +EMULATOR_EXPORT bool emulator_set_verbosity_level(int verbosity_level); + +/** + * @brief Create TVM emulator + * @param code_boc Base64 encoded BoC serialized smart contract code cell + * @param data_boc Base64 encoded BoC serialized smart contract data cell + * @param vm_log_verbosity Verbosity level of VM log + * @return Pointer to TVM emulator object + */ +EMULATOR_EXPORT void *tvm_emulator_create(const char *code_boc, const char *data_boc, int vm_log_verbosity); + +/** + * @brief Set libraries for TVM emulator + * @param libs_boc Base64 encoded BoC serialized libraries dictionary (HashmapE 256 ^Cell). + * @return true in case of success, false in case of error + */ +EMULATOR_EXPORT bool tvm_emulator_set_libraries(void *tvm_emulator, const char *libs_boc); + +/** + * @brief Set c7 parameters + * @param tvm_emulator Pointer to TVM emulator + * @param address Adress of smart contract + * @param unixtime Unix timestamp + * @param balance Smart contract balance + * @param rand_seed_hex Random seed as hex string of length 64 + * @param config Base64 encoded BoC serialized Config dictionary (Hashmap 32 ^Cell) + * @return true in case of success, false in case of error + */ +EMULATOR_EXPORT bool tvm_emulator_set_c7(void *tvm_emulator, const char *address, uint32_t unixtime, uint64_t balance, const char *rand_seed_hex, const char *config); + +/** + * @brief Set TVM gas limit + * @param tvm_emulator Pointer to TVM emulator + * @param gas_limit Gas limit + * @return true in case of success, false in case of error + */ +EMULATOR_EXPORT bool tvm_emulator_set_gas_limit(void *tvm_emulator, int64_t gas_limit); + +/** + * @brief Run get method + * @param tvm_emulator Pointer to TVM emulator + * @param method_id Integer method id + * @param stack_boc Base64 encoded BoC serialized stack (VmStack) + * @return Json object with error: + * { + * "success": false, + * "error": "Error description" + * } + * Or success: + * { + * "success": true + * "vm_log": "...", + * "vm_exit_code": 0, + * "stack": "Base64 encoded BoC serialized stack (VmStack)", + * "missing_library": null, + * "gas_used": 1212 + * } + */ +EMULATOR_EXPORT const char *tvm_emulator_run_get_method(void *tvm_emulator, int method_id, const char *stack_boc); + +/** + * @brief Send external message + * @param tvm_emulator Pointer to TVM emulator + * @param message_body_boc Base64 encoded BoC serialized message body cell. + * @return Json object with error: + * { + * "success": false, + * "error": "Error description" + * } + * Or success: + * { + * "success": true, + * "new_code": "Base64 boc decoded new code cell", + * "new_data": "Base64 boc decoded new data cell", + * "accepted": true, + * "vm_exit_code": 0, + * "vm_log": "...", + * "missing_library": null, + * "gas_used": 1212, + * "actions": "Base64 boc decoded actions cell of type (OutList n)" + * } + */ +EMULATOR_EXPORT const char *tvm_emulator_send_external_message(void *tvm_emulator, const char *message_body_boc); + +/** + * @brief Send internal message + * @param tvm_emulator Pointer to TVM emulator + * @param message_body_boc Base64 encoded BoC serialized message body cell. + * @param amount Amount of nanograms attached with internal message. + * @return Json object with error: + * { + * "success": false, + * "error": "Error description" + * } + * Or success: + * { + * "success": true, + * "new_code": "Base64 boc decoded new code cell", + * "new_data": "Base64 boc decoded new data cell", + * "accepted": true, + * "vm_exit_code": 0, + * "vm_log": "...", + * "missing_library": null, + * "gas_used": 1212, + * "actions": "Base64 boc decoded actions cell of type (OutList n)" + * } + */ +EMULATOR_EXPORT const char *tvm_emulator_send_internal_message(void *tvm_emulator, const char *message_body_boc, uint64_t amount); + +/** + * @brief Destroy TVM emulator object + * @param tvm_emulator Pointer to TVM emulator object + */ +EMULATOR_EXPORT void tvm_emulator_destroy(void *tvm_emulator); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/emulator/emulator_export_list b/emulator/emulator_export_list new file mode 100644 index 00000000..b53a4114 --- /dev/null +++ b/emulator/emulator_export_list @@ -0,0 +1,17 @@ +_transaction_emulator_create +_transaction_emulator_set_lt +_transaction_emulator_set_rand_seed +_transaction_emulator_set_ignore_chksig +_transaction_emulator_set_config +_transaction_emulator_set_libs +_transaction_emulator_emulate_transaction +_transaction_emulator_destroy +_emulator_set_verbosity_level +_tvm_emulator_create +_tvm_emulator_set_libraries +_tvm_emulator_set_c7 +_tvm_emulator_set_gas_limit +_tvm_emulator_run_get_method +_tvm_emulator_send_external_message +_tvm_emulator_send_internal_message +_tvm_emulator_destroy diff --git a/emulator/transaction-emulator.cpp b/emulator/transaction-emulator.cpp new file mode 100644 index 00000000..8611378a --- /dev/null +++ b/emulator/transaction-emulator.cpp @@ -0,0 +1,254 @@ +#include +#include "transaction-emulator.h" +#include "crypto/common/refcnt.hpp" +#include "vm/cp0.h" + +using td::Ref; +using namespace std::string_literals; + +namespace emulator { +td::Result> TransactionEmulator::emulate_transaction( + block::Account&& account, td::Ref msg_root, ton::UnixTime utime, ton::LogicalTime lt, int trans_type) { + + td::Ref old_mparams; + std::vector storage_prices; + block::StoragePhaseConfig storage_phase_cfg{&storage_prices}; + block::ComputePhaseConfig compute_phase_cfg; + block::ActionPhaseConfig action_phase_cfg; + td::RefInt256 masterchain_create_fee, basechain_create_fee; + + if (!utime) { + utime = unixtime_; + } + if (!utime) { + utime = (unsigned)std::time(nullptr); + } + + auto fetch_res = block::FetchConfigParams::fetch_config_params(config_, &old_mparams, + &storage_prices, &storage_phase_cfg, + &rand_seed_, &compute_phase_cfg, + &action_phase_cfg, &masterchain_create_fee, + &basechain_create_fee, account.workchain, utime); + if(fetch_res.is_error()) { + return fetch_res.move_as_error_prefix("cannot fetch config params "); + } + + vm::init_op_cp0(); + + if (!lt) { + lt = lt_; + } + if (!lt) { + lt = (account.last_trans_lt_ / block::ConfigInfo::get_lt_align() + 1) * block::ConfigInfo::get_lt_align(); // next block after account_.last_trans_lt_ + } + + compute_phase_cfg.libraries = std::make_unique(libraries_); + compute_phase_cfg.ignore_chksig = ignore_chksig_; + compute_phase_cfg.with_vm_log = true; + compute_phase_cfg.vm_log_verbosity = vm_log_verbosity_; + + auto res = create_transaction(msg_root, &account, utime, lt, trans_type, + &storage_phase_cfg, &compute_phase_cfg, + &action_phase_cfg); + if(res.is_error()) { + return res.move_as_error_prefix("cannot run message on account "); + } + std::unique_ptr trans = res.move_as_ok(); + + if (!trans->compute_phase->accepted && trans->in_msg_extern) { + auto vm_log = trans->compute_phase->vm_log; + auto vm_exit_code = trans->compute_phase->exit_code; + return std::make_unique(std::move(vm_log), vm_exit_code); + } + + if (!trans->serialize()) { + return td::Status::Error(-669,"cannot serialize new transaction for smart contract "s + trans->account.addr.to_hex()); + } + + auto trans_root = trans->commit(account); + if (trans_root.is_null()) { + return td::Status::Error(PSLICE() << "cannot commit new transaction for smart contract"); + } + + return std::make_unique(std::move(trans_root), std::move(account), std::move(trans->compute_phase->vm_log), std::move(trans->compute_phase->actions)); +} + +td::Result TransactionEmulator::emulate_transaction(block::Account&& account, td::Ref original_trans) { + + block::gen::Transaction::Record record_trans; + if (!tlb::unpack_cell(original_trans, record_trans)) { + return td::Status::Error("Failed to unpack Transaction"); + } + + ton::LogicalTime lt = record_trans.lt; + ton::UnixTime utime = record_trans.now; + account.now_ = utime; + td::Ref msg_root = record_trans.r1.in_msg->prefetch_ref(); + int tag = block::gen::t_TransactionDescr.get_tag(vm::load_cell_slice(record_trans.description)); + + int trans_type = block::transaction::Transaction::tr_none; + switch (tag) { + case block::gen::TransactionDescr::trans_ord: { + trans_type = block::transaction::Transaction::tr_ord; + break; + } + case block::gen::TransactionDescr::trans_storage: { + trans_type = block::transaction::Transaction::tr_storage; + break; + } + case block::gen::TransactionDescr::trans_tick_tock: { + block::gen::TransactionDescr::Record_trans_tick_tock tick_tock; + if (!tlb::unpack_cell(record_trans.description, tick_tock)) { + return td::Status::Error("Failed to unpack tick tock transaction description"); + } + trans_type = tick_tock.is_tock ? block::transaction::Transaction::tr_tock : block::transaction::Transaction::tr_tick; + break; + } + case block::gen::TransactionDescr::trans_split_prepare: { + trans_type = block::transaction::Transaction::tr_split_prepare; + break; + } + case block::gen::TransactionDescr::trans_split_install: { + trans_type = block::transaction::Transaction::tr_split_install; + break; + } + case block::gen::TransactionDescr::trans_merge_prepare: { + trans_type = block::transaction::Transaction::tr_merge_prepare; + break; + } + case block::gen::TransactionDescr::trans_merge_install: { + trans_type = block::transaction::Transaction::tr_merge_install; + break; + } + } + + TRY_RESULT(emulation, emulate_transaction(std::move(account), msg_root, utime, lt, trans_type)); + + auto emulation_result = dynamic_cast(*emulation); + if (td::Bits256(emulation_result.transaction->get_hash().bits()) != td::Bits256(original_trans->get_hash().bits())) { + return td::Status::Error("transaction hash mismatch"); + } + + if (!check_state_update(emulation_result.account, record_trans)) { + return td::Status::Error("account hash mismatch"); + } + + return emulation_result; +} + +td::Result TransactionEmulator::emulate_transactions_chain(block::Account&& account, std::vector>&& original_transactions) { + + std::vector> emulated_transactions; + for (const auto& original_trans : original_transactions) { + if (original_trans.is_null()) { + continue; + } + + TRY_RESULT(emulation_result, emulate_transaction(std::move(account), original_trans)); + emulated_transactions.push_back(std::move(emulation_result.transaction)); + account = std::move(emulation_result.account); + } + + return TransactionEmulator::EmulationChain{ std::move(emulated_transactions), std::move(account) }; +} + +bool TransactionEmulator::check_state_update(const block::Account& account, const block::gen::Transaction::Record& trans) { + block::gen::HASH_UPDATE::Record hash_update; + return tlb::type_unpack_cell(trans.state_update, block::gen::t_HASH_UPDATE_Account, hash_update) && + hash_update.new_hash == account.total_state->get_hash().bits(); +} + +td::Result> TransactionEmulator::create_transaction( + td::Ref msg_root, block::Account* acc, + ton::UnixTime utime, ton::LogicalTime lt, int trans_type, + block::StoragePhaseConfig* storage_phase_cfg, + block::ComputePhaseConfig* compute_phase_cfg, + block::ActionPhaseConfig* action_phase_cfg) { + bool external{false}, ihr_delivered{false}, need_credit_phase{false}; + + if (msg_root.not_null()) { + auto cs = vm::load_cell_slice(msg_root); + external = block::gen::t_CommonMsgInfo.get_tag(cs); + } + + if (trans_type == block::transaction::Transaction::tr_ord) { + need_credit_phase = !external; + } else if (trans_type == block::transaction::Transaction::tr_merge_install) { + need_credit_phase = true; + } + + std::unique_ptr trans = + std::make_unique(*acc, trans_type, lt, utime, msg_root); + + if (msg_root.not_null() && !trans->unpack_input_msg(ihr_delivered, action_phase_cfg)) { + if (external) { + // inbound external message was not accepted + return td::Status::Error(-701,"inbound external message rejected by account "s + acc->addr.to_hex() + + " before smart-contract execution"); + } + return td::Status::Error(-669,"cannot unpack input message for a new transaction"); + } + + if (trans->bounce_enabled) { + if (!trans->prepare_storage_phase(*storage_phase_cfg, true)) { + return td::Status::Error(-669,"cannot create storage phase of a new transaction for smart contract "s + acc->addr.to_hex()); + } + if (need_credit_phase && !trans->prepare_credit_phase()) { + return td::Status::Error(-669,"cannot create credit phase of a new transaction for smart contract "s + acc->addr.to_hex()); + } + } else { + if (need_credit_phase && !trans->prepare_credit_phase()) { + return td::Status::Error(-669,"cannot create credit phase of a new transaction for smart contract "s + acc->addr.to_hex()); + } + if (!trans->prepare_storage_phase(*storage_phase_cfg, true, need_credit_phase)) { + return td::Status::Error(-669,"cannot create storage phase of a new transaction for smart contract "s + acc->addr.to_hex()); + } + } + + if (!trans->prepare_compute_phase(*compute_phase_cfg)) { + return td::Status::Error(-669,"cannot create compute phase of a new transaction for smart contract "s + acc->addr.to_hex()); + } + + if (!trans->compute_phase->accepted) { + if (!external && trans->compute_phase->skip_reason == block::ComputePhase::sk_none) { + return td::Status::Error(-669,"new ordinary transaction for smart contract "s + acc->addr.to_hex() + + " has not been accepted by the smart contract (?)"); + } + } + + if (trans->compute_phase->success && !trans->prepare_action_phase(*action_phase_cfg)) { + return td::Status::Error(-669,"cannot create action phase of a new transaction for smart contract "s + acc->addr.to_hex()); + } + + if (trans->bounce_enabled && !trans->compute_phase->success && !trans->prepare_bounce_phase(*action_phase_cfg)) { + return td::Status::Error(-669,"cannot create bounce phase of a new transaction for smart contract "s + acc->addr.to_hex()); + } + + return trans; +} + +void TransactionEmulator::set_unixtime(ton::UnixTime unixtime) { + unixtime_ = unixtime; +} + +void TransactionEmulator::set_lt(ton::LogicalTime lt) { + lt_ = lt; +} + +void TransactionEmulator::set_rand_seed(td::BitArray<256>& rand_seed) { + rand_seed_ = rand_seed; +} + +void TransactionEmulator::set_ignore_chksig(bool ignore_chksig) { + ignore_chksig_ = ignore_chksig; +} + +void TransactionEmulator::set_config(block::Config &&config) { + config_ = std::forward(config); +} + +void TransactionEmulator::set_libs(vm::Dictionary &&libs) { + libraries_ = std::forward(libs); +} + +} // namespace emulator diff --git a/emulator/transaction-emulator.h b/emulator/transaction-emulator.h new file mode 100644 index 00000000..fe0e22cb --- /dev/null +++ b/emulator/transaction-emulator.h @@ -0,0 +1,83 @@ +#pragma once +#include "crypto/common/refcnt.hpp" +#include "ton/ton-types.h" +#include "crypto/vm/cells.h" +#include "block/transaction.h" +#include "block/block-auto.h" +#include "block/block-parse.h" +#include "block/mc-config.h" + +namespace emulator { +class TransactionEmulator { + block::Config config_; + vm::Dictionary libraries_; + int vm_log_verbosity_; + ton::UnixTime unixtime_; + ton::LogicalTime lt_; + td::BitArray<256> rand_seed_; + bool ignore_chksig_; + +public: + TransactionEmulator(block::Config&& config, int vm_log_verbosity = 0) : + config_(std::move(config)), libraries_(256), vm_log_verbosity_(vm_log_verbosity), + unixtime_(0), lt_(0), rand_seed_(0), ignore_chksig_(false) { + } + + struct EmulationResult { + std::string vm_log; + + EmulationResult(std::string vm_log_) : vm_log(vm_log_) {} + virtual ~EmulationResult() = default; + }; + + struct EmulationSuccess: EmulationResult { + td::Ref transaction; + block::Account account; + td::Ref actions; + + EmulationSuccess(td::Ref transaction_, block::Account account_, std::string vm_log_, td::Ref actions_) : + EmulationResult(vm_log_), transaction(transaction_), account(account_) , actions(actions_) + {} + }; + + struct EmulationExternalNotAccepted: EmulationResult { + int vm_exit_code; + + EmulationExternalNotAccepted(std::string vm_log_, int vm_exit_code_) : + EmulationResult(vm_log_), vm_exit_code(vm_exit_code_) + {} + }; + + struct EmulationChain { + std::vector> transactions; + block::Account account; + }; + + const block::Config& get_config() { + return config_; + } + + td::Result> emulate_transaction( + block::Account&& account, td::Ref msg_root, ton::UnixTime utime, ton::LogicalTime lt, int trans_type); + + td::Result emulate_transaction(block::Account&& account, td::Ref original_trans); + td::Result emulate_transactions_chain(block::Account&& account, std::vector>&& original_transactions); + + void set_unixtime(ton::UnixTime unixtime); + void set_lt(ton::LogicalTime lt); + void set_rand_seed(td::BitArray<256>& rand_seed); + void set_ignore_chksig(bool ignore_chksig); + void set_config(block::Config &&config); + void set_libs(vm::Dictionary &&libs); + +private: + bool check_state_update(const block::Account& account, const block::gen::Transaction::Record& trans); + + td::Result> create_transaction( + td::Ref msg_root, block::Account* acc, + ton::UnixTime utime, ton::LogicalTime lt, int trans_type, + block::StoragePhaseConfig* storage_phase_cfg, + block::ComputePhaseConfig* compute_phase_cfg, + block::ActionPhaseConfig* action_phase_cfg); +}; +} // namespace emulator diff --git a/emulator/tvm-emulator.hpp b/emulator/tvm-emulator.hpp new file mode 100644 index 00000000..dfbc20b1 --- /dev/null +++ b/emulator/tvm-emulator.hpp @@ -0,0 +1,46 @@ +#pragma once +#include "smc-envelope/SmartContract.h" + +namespace emulator { +class TvmEmulator { + ton::SmartContract smc_; + ton::SmartContract::Args args_; +public: + using Answer = ton::SmartContract::Answer; + + TvmEmulator(td::Ref code, td::Ref data): smc_({code, data}) { + } + + void set_vm_verbosity_level(int vm_log_verbosity) { + args_.set_vm_verbosity_level(vm_log_verbosity); + } + + void set_libraries(vm::Dictionary&& libraries) { + args_.set_libraries(libraries); + } + + void set_gas_limit(int64_t limit) { + args_.set_limits(vm::GasLimits(limit)); + } + + void set_c7(block::StdAddress address, uint32_t unixtime, uint64_t balance, td::BitArray<256> rand_seed, std::shared_ptr config) { + args_.set_address(address); + args_.set_now(unixtime); + args_.set_balance(balance); + args_.set_rand_seed(rand_seed); + args_.set_config(config); + } + + Answer run_get_method(int method_id, td::Ref stack) { + return smc_.run_get_method(args_.set_stack(stack).set_method_id(method_id)); + } + + Answer send_external_message(td::Ref message_body) { + return smc_.send_external_message(message_body, args_); + } + + Answer send_internal_message(td::Ref message_body, uint64_t amount) { + return smc_.send_internal_message(message_body, args_.set_amount(amount)); + } +}; +} \ No newline at end of file diff --git a/tl/generate/scheme/tonlib_api.tl b/tl/generate/scheme/tonlib_api.tl index 10a5896c..c9a7df3d 100644 --- a/tl/generate/scheme/tonlib_api.tl +++ b/tl/generate/scheme/tonlib_api.tl @@ -257,6 +257,7 @@ getBip39Hints prefix:string = Bip39Hints; //raw.init initial_account_state:raw.initialAccountState = Ok; raw.getAccountState account_address:accountAddress = raw.FullAccountState; +raw.getAccountStateByTransaction account_address:accountAddress transaction_id:internal.transactionId = raw.FullAccountState; raw.getTransactions private_key:InputKey account_address:accountAddress from_transaction_id:internal.transactionId = raw.Transactions; raw.getTransactionsV2 private_key:InputKey account_address:accountAddress from_transaction_id:internal.transactionId count:# try_decode_messages:Bool = raw.Transactions; raw.sendMessage body:bytes = Ok; @@ -278,9 +279,13 @@ guessAccountRevision initial_account_state:InitialAccountState workchain_id:int3 guessAccount public_key:string rwallet_init_public_key:string = AccountRevisionList; getAccountState account_address:accountAddress = FullAccountState; +getAccountStateByTransaction account_address:accountAddress transaction_id:internal.transactionId = FullAccountState; +getShardAccountCell account_address:accountAddress = tvm.Cell; +getShardAccountCellByTransaction account_address:accountAddress transaction_id:internal.transactionId = tvm.Cell; createQuery private_key:InputKey address:accountAddress timeout:int32 action:Action initial_account_state:InitialAccountState = query.Info; -getConfigParam mode:# id:ton.blockIdExt param:# = ConfigInfo; +getConfigParam mode:# param:# = ConfigInfo; +getConfigAll mode:# = ConfigInfo; msg.decrypt input_key:InputKey data:msg.dataEncryptedArray = msg.DataDecryptedArray; msg.decryptWithProof proof:bytes data:msg.dataEncrypted = msg.Data; @@ -292,6 +297,7 @@ query.estimateFees id:int53 ignore_chksig:Bool = query.Fees; query.getInfo id:int53 = query.Info; smc.load account_address:accountAddress = smc.Info; +smc.loadByTransaction account_address:accountAddress transaction_id:internal.transactionId = smc.Info; smc.forget id:int53 = Ok; smc.getCode id:int53 = tvm.Cell; smc.getData id:int53 = tvm.Cell; diff --git a/tl/generate/scheme/tonlib_api.tlo b/tl/generate/scheme/tonlib_api.tlo index 075c5a3abdf705679b48dd79a799a1f70a9d67a9..a9570f6acb5e5d4ea44438365eda5fab9ebfcb59 100644 GIT binary patch delta 371 zcmZ4SiSfw~#tlot7y~vh4QuAsJ;yJjE0dmD;+UMAUz%4ET#{Il>Qot0l$ckXm|T*X zpU1$!;AwR!?B+2cAEwEUMf#I9d{icH@a32+k(428?7Hv~4^X#reqLH;x?@fbQ1$;% z22F_S#t7}nK2bcA8+3UlKQQ2!%#$pzxglu@qm1;Fc{7B8+JZ9@i&CIAJE!L4EL*?)TZ3QS`P ryaYD86dJIp8hS5n7cI_B*2~FHOhI#k&9vFhm|AoE1vZzIZ(#%gM|h1| delta 40 ycmV+@0N4NI_yMT$0kEW20ZOx_R)Y(Zz+zalkYl6)vovNP4YTlbFb1=*db9zJaS~bp diff --git a/tonlib/CMakeLists.txt b/tonlib/CMakeLists.txt index f27e00f2..69b5318a 100644 --- a/tonlib/CMakeLists.txt +++ b/tonlib/CMakeLists.txt @@ -60,7 +60,7 @@ target_include_directories(tonlib PUBLIC $/.. $ ) -target_link_libraries(tonlib PRIVATE tdactor adnllite tl_lite_api tl-lite-utils ton_crypto ton_block lite-client-common smc-envelope) +target_link_libraries(tonlib PRIVATE tdactor adnllite tl_lite_api tl-lite-utils ton_crypto ton_block lite-client-common smc-envelope emulator_static) target_link_libraries(tonlib PUBLIC tdutils tl_tonlib_api) if (TONLIB_ENABLE_JNI AND NOT ANDROID) # jni is available by default on Android @@ -136,7 +136,7 @@ if (WIN32) set(WINGETOPT_TARGET wingetopt) endif() install(TARGETS tdnet keys crc32c tdactor adnllite tl_api tl-utils tl_lite_api tl-lite-utils ton_crypto ton_block smc-envelope ${WINGETOPT_TARGET} - tdutils tl_tonlib_api tonlib lite-client-common tddb_utils Tonlib EXPORT Tonlib + tdutils tl_tonlib_api tonlib lite-client-common tddb_utils emulator_static Tonlib EXPORT Tonlib LIBRARY DESTINATION lib ARCHIVE DESTINATION lib RUNTIME DESTINATION bin diff --git a/tonlib/tonlib/TonlibClient.cpp b/tonlib/tonlib/TonlibClient.cpp index bdfea9f5..c5e47fca 100644 --- a/tonlib/tonlib/TonlibClient.cpp +++ b/tonlib/tonlib/TonlibClient.cpp @@ -36,6 +36,8 @@ #include "smc-envelope/PaymentChannel.h" #include "smc-envelope/SmartContractCode.h" +#include "emulator/transaction-emulator.h" + #include "auto/tl/tonlib_api.hpp" #include "block/block-auto.h" #include "block/check-proof.h" @@ -74,6 +76,14 @@ struct GetAccountState { using ReturnType = td::unique_ptr; }; +struct GetAccountStateByTransaction { + block::StdAddress address; + std::int64_t lt; + td::Bits256 hash; + //td::optional public_key; + using ReturnType = td::unique_ptr; +}; + struct RemoteRunSmcMethod { block::StdAddress address; td::optional block_id; @@ -416,6 +426,19 @@ class AccountState { get_wallet_revision()); } + td::Result> to_shardAccountCell() const { + auto account_root = raw_.info.root; + if (account_root.is_null()) { + block::gen::Account().cell_pack_account_none(account_root); + } + auto cell = vm::CellBuilder().store_ref(account_root).store_bits(raw_.info.last_trans_hash.as_bitslice()).store_long(raw_.info.last_trans_lt).finalize(); + return tonlib_api::make_object(to_bytes(cell)); + } + + td::Result> to_shardAccountCellSlice() const { + return vm::CellBuilder().store_ref(raw_.info.root).store_bits(raw_.info.last_trans_hash.as_bitslice()).store_long(raw_.info.last_trans_lt).as_cellslice_ref(); + } + //NB: Order is important! Used during guessAccountRevision enum WalletType { Empty, @@ -940,7 +963,7 @@ class Query { } while (list.not_null()); return i; } -}; // namespace tonlib +}; td::Result to_balance_or_throw(td::Ref balance_ref) { vm::CellSlice balance_slice = *balance_ref; @@ -1633,6 +1656,305 @@ class GetShardBlockProof : public td::actor::Actor { std::vector> links_; }; +auto to_lite_api(const tonlib_api::ton_blockIdExt& blk) -> td::Result>; +auto to_tonlib_api(const ton::lite_api::liteServer_transactionId& txid) -> tonlib_api_ptr; + +class RunEmulator : public td::actor::Actor { + public: + RunEmulator(ExtClientRef ext_client_ref, int_api::GetAccountStateByTransaction request, + td::actor::ActorShared<> parent, td::Promise>&& promise) + : request_(std::move(request)), parent_(std::move(parent)), promise_(std::move(promise)) { + client_.set_client(ext_client_ref); + } + + private: + struct FullBlockId { + ton::BlockIdExt id; + ton::BlockIdExt mc; + ton::BlockIdExt prev; + ton::Bits256 rand_seed; + }; + + ExtClient client_; + int_api::GetAccountStateByTransaction request_; + td::actor::ActorShared<> parent_; + td::Promise> promise_; + + std::map> actors_; + td::int64 actor_id_{1}; + + FullBlockId block_id_; + td::Ref mc_state_root_; // ^ShardStateUnsplit + td::unique_ptr account_state_; + std::vector> transactions_; // std::vector<^Transaction> + + size_t count_{0}; + size_t count_transactions_{0}; + bool incomplete_{true}; + bool stopped_{false}; + + void get_block_id(td::Promise&& promise) { + auto shard_id = ton::shard_prefix(request_.address.addr, 60); + auto query = ton::lite_api::liteServer_lookupBlock(0b111111010, ton::create_tl_lite_block_id_simple({request_.address.workchain, shard_id, 0}), request_.lt, 0); + client_.send_query(std::move(query), promise.wrap([self = this, shard_id](td::Result> header_r) -> td::Result { + + TRY_RESULT(header, std::move(header_r)); + ton::BlockIdExt block_id = ton::create_block_id(header->id_); + TRY_RESULT(root, vm::std_boc_deserialize(std::move(header->header_proof_))); + + try { + auto virt_root = vm::MerkleProof::virtualize(root, 1); + if (virt_root.is_null()) { + return td::Status::Error("block header proof is not a valid Merkle proof"); + } + + ton::RootHash vhash{virt_root->get_hash().bits()}; + if (ton::RootHash{virt_root->get_hash().bits()} != block_id.root_hash) { + return td::Status::Error("block header has incorrect root hash"); + } + + std::vector prev_blocks; + ton::BlockIdExt mc_block_id; + bool after_split; + td::Status status = block::unpack_block_prev_blk_ext(virt_root, block_id, prev_blocks, mc_block_id, after_split); + if (status.is_error()) { + return status.move_as_error(); + } + + ton::BlockIdExt prev_block; + if (prev_blocks.size() == 1 || ton::shard_is_ancestor(prev_blocks[0].id.shard, shard_id)) { + prev_block = std::move(prev_blocks[0]); + } else { + prev_block = std::move(prev_blocks[1]); + } + + block::gen::Block::Record block; + block::gen::BlockExtra::Record extra; + if (!tlb::unpack_cell(virt_root, block) || !tlb::unpack_cell(block.extra, extra)) { + return td::Status::Error("cannot unpack block header"); + } + + return FullBlockId{std::move(block_id), std::move(mc_block_id), std::move(prev_block), std::move(extra.rand_seed)}; + } catch (vm::VmError& err) { + return err.as_status("error processing header"); + } catch (vm::VmVirtError& err) { + return err.as_status("error processing header"); + } + })); + } + + void get_mc_state_root(td::Promise>&& promise) { + TRY_RESULT_PROMISE(promise, lite_block, to_lite_api(*to_tonlib_api(block_id_.mc))); + auto block = ton::create_block_id(lite_block); + client_.send_query(ton::lite_api::liteServer_getConfigAll(0b11'11111111, std::move(lite_block)), promise.wrap([self = this, block](auto r_config) -> td::Result> { + + TRY_RESULT(state, block::check_extract_state_proof(block, r_config->state_proof_.as_slice(), r_config->config_proof_.as_slice())); + + return std::move(state); + })); + } + + void get_account_state(td::Promise>&& promise) { + auto actor_id = actor_id_++; + actors_[actor_id] = td::actor::create_actor( + "GetAccountState", client_.get_client(), request_.address, block_id_.prev, + actor_shared(this, actor_id), + promise.wrap([address = request_.address](auto&& state) { + return td::make_unique(std::move(address), std::move(state), 0); + })); + } + + td::Status get_transactions(std::int64_t lt) { + TRY_RESULT(lite_block, to_lite_api(*to_tonlib_api(block_id_.id))); + auto after = ton::lite_api::make_object(request_.address.addr, lt); + auto query = ton::lite_api::liteServer_listBlockTransactions(std::move(lite_block), 0b10100111, 256, std::move(after), false, false); + + client_.send_query(std::move(query), [self = this](lite_api_ptr&& bTxes) { + if (!bTxes) { + self->check(td::Status::Error("liteServer.blockTransactions is null")); + return; + } + + std::int64_t last_lt = 0; + for (auto& id : bTxes->ids_) { + last_lt = id->lt_; + if (id->account_ != self->request_.address.addr) { + continue; + } + + if (id->lt_ == self->request_.lt && id->hash_ == self->request_.hash) { + self->incomplete_ = false; + } + + self->transactions_.push_back({}); + self->get_transaction(id->lt_, id->hash_, [self, i = self->transactions_.size() - 1](auto transaction) { self->set_transaction(i, std::move(transaction)); }); + + if (!self->incomplete_) { + return; + } + } + + if (bTxes->incomplete_) { + self->check(self->get_transactions(last_lt)); + } + }); + return td::Status::OK(); + } + + void get_transaction(std::int64_t lt, td::Bits256 hash, td::Promise>&& promise) { + auto actor_id = actor_id_++; + actors_[actor_id] = td::actor::create_actor( + "GetTransactionHistory", client_.get_client(), request_.address, lt, hash, 1, actor_shared(this, actor_id), + promise.wrap([](auto&& transactions) mutable { + return std::move(transactions.transactions.front().transaction); + })); + } + + void start_up() override { + if (stopped_) { + return; + } + get_block_id([self = this](td::Result&& block_id) { self->set_block_id(std::move(block_id)); }); + } + + void set_block_id(td::Result&& block_id) { + if (block_id.is_error()) { + check(block_id.move_as_error()); + } else { + block_id_ = block_id.move_as_ok(); + + get_mc_state_root([self = this](td::Result>&& mc_state_root) { self->set_mc_state_root(std::move(mc_state_root)); }); + get_account_state([self = this](td::Result>&& state) { self->set_account_state(std::move(state)); }); + check(get_transactions(0)); + + inc(); + } + } + + void set_mc_state_root(td::Result>&& mc_state_root) { + if (mc_state_root.is_error()) { + check(mc_state_root.move_as_error()); + } else { + mc_state_root_ = mc_state_root.move_as_ok(); + inc(); + } + } + + void set_account_state(td::Result>&& account_state) { + if (account_state.is_error()) { + check(account_state.move_as_error()); + } else { + account_state_ = account_state.move_as_ok(); + inc(); + } + } + + void set_transaction(size_t i, td::Result>&& transaction) { + if (transaction.is_error()) { + check(transaction.move_as_error()); + } else { + transactions_[i] = transaction.move_as_ok(); + inc_transactions(); + } + } + + void inc_transactions() { + if (stopped_ || ++count_transactions_ != transactions_.size() || incomplete_) { + return; + } + inc(); + } + + void inc() { + if (stopped_ || ++count_ != 4) { // 4 -- block_id + mc_state_root + account_state + transactions + return; + } + + auto r_config = block::Config::extract_from_state(mc_state_root_, 0b11'11111111); + if (r_config.is_error()) { + check(r_config.move_as_error()); + return; + } + std::unique_ptr config = r_config.move_as_ok(); + + block::gen::ShardStateUnsplit::Record shard_state; + if (!tlb::unpack_cell(mc_state_root_, shard_state)) { + check(td::Status::Error("Failed to unpack masterchain state")); + return; + } + vm::Dictionary libraries(shard_state.r1.libraries->prefetch_ref(), 256); + + auto r_shard_account = account_state_->to_shardAccountCellSlice(); + if (r_shard_account.is_error()) { + check(r_shard_account.move_as_error()); + return; + } + td::Ref shard_account = r_shard_account.move_as_ok(); + + const block::StdAddress& address = account_state_->get_address(); + ton::UnixTime now = account_state_->get_sync_time(); + bool is_special = address.workchain == ton::masterchainId && config->is_special_smartcontract(address.addr); + block::Account account(address.workchain, address.addr.bits()); + if (!account.unpack(std::move(shard_account), td::Ref(), now, is_special)) { + check(td::Status::Error("Can't unpack shard account")); + return; + } + + emulator::TransactionEmulator trans_emulator(std::move(*config)); + trans_emulator.set_libs(std::move(libraries)); + trans_emulator.set_rand_seed(block_id_.rand_seed); + td::Result emulation_result = trans_emulator.emulate_transactions_chain(std::move(account), std::move(transactions_)); + + if (emulation_result.is_error()) { + promise_.set_error(emulation_result.move_as_error()); + } else { + account = std::move(emulation_result.move_as_ok().account); + RawAccountState raw = std::move(account_state_->raw()); + raw.block_id = block_id_.id; + raw.balance = account.get_balance().grams->to_long(); + raw.storage_last_paid = std::move(account.last_paid); + raw.storage_stat = std::move(account.storage_stat); + raw.code = std::move(account.code); + raw.data = std::move(account.data); + raw.state = std::move(account.total_state); + raw.info.last_trans_lt = account.last_trans_lt_; + raw.info.last_trans_hash = account.last_trans_hash_; + raw.info.gen_utime = account.now_; + + if (account.status == block::Account::acc_frozen) { + raw.frozen_hash = (char*)account.state_hash.data(); + } + + promise_.set_value(td::make_unique(address, std::move(raw), 0)); + } + stopped_ = true; + try_stop(); + } + + void check(td::Status status) { + if (status.is_error()) { + promise_.set_error(std::move(status)); + stopped_ = true; + try_stop(); + } + } + + void try_stop() { + if (stopped_ && actors_.empty()) { + stop(); + } + } + + void hangup_shared() override { + actors_.erase(get_link_token()); + try_stop(); + } + + void hangup() override { + check(TonlibError::Cancelled()); + } +}; + TonlibClient::TonlibClient(td::unique_ptr callback) : callback_(std::move(callback)) { } TonlibClient::~TonlibClient() = default; @@ -2765,6 +3087,27 @@ td::Status TonlibClient::do_request(tonlib_api::raw_getAccountState& request, return td::Status::OK(); } +td::Status TonlibClient::do_request(tonlib_api::raw_getAccountStateByTransaction& request, + td::Promise>&& promise) { + if (!request.account_address_) { + return TonlibError::EmptyField("account_address"); + } + if (!request.transaction_id_) { + return TonlibError::EmptyField("transaction_id"); + } + TRY_RESULT(account_address, get_account_address(request.account_address_->account_address_)); + auto lt = request.transaction_id_->lt_; + auto hash_str = request.transaction_id_->hash_; + if (hash_str.size() != 32) { + return td::Status::Error(400, "Invalid transaction id hash size"); + } + td::Bits256 hash; + hash.as_slice().copy_from(hash_str); + make_request(int_api::GetAccountStateByTransaction{account_address, lt, hash}, + promise.wrap([](auto&& res) { return res->to_raw_fullAccountState(); })); + return td::Status::OK(); +} + td::Result from_tonlib(tonlib_api::inputKeyRegular& input_key) { if (!input_key.key_) { return TonlibError::EmptyField("key"); @@ -2877,6 +3220,59 @@ td::Status TonlibClient::do_request(const tonlib_api::getAccountState& request, return td::Status::OK(); } +td::Status TonlibClient::do_request(const tonlib_api::getAccountStateByTransaction& request, + td::Promise>&& promise) { + if (!request.account_address_) { + return TonlibError::EmptyField("account_address"); + } + if (!request.transaction_id_) { + return TonlibError::EmptyField("transaction_id"); + } + TRY_RESULT(account_address, get_account_address(request.account_address_->account_address_)); + auto lt = request.transaction_id_->lt_; + auto hash_str = request.transaction_id_->hash_; + if (hash_str.size() != 32) { + return td::Status::Error(400, "Invalid transaction id hash size"); + } + td::Bits256 hash; + hash.as_slice().copy_from(hash_str); + make_request(int_api::GetAccountStateByTransaction{account_address, lt, hash}, + promise.wrap([](auto&& res) { return res->to_fullAccountState(); })); + return td::Status::OK(); +} + +td::Status TonlibClient::do_request(const tonlib_api::getShardAccountCell& request, + td::Promise>&& promise) { + if (!request.account_address_) { + return TonlibError::EmptyField("account_address"); + } + TRY_RESULT(account_address, get_account_address(request.account_address_->account_address_)); + make_request(int_api::GetAccountState{std::move(account_address), query_context_.block_id.copy(), {}}, + promise.wrap([](auto&& res) { return res->to_shardAccountCell(); })); + return td::Status::OK(); +} + +td::Status TonlibClient::do_request(const tonlib_api::getShardAccountCellByTransaction& request, + td::Promise>&& promise) { + if (!request.account_address_) { + return TonlibError::EmptyField("account_address"); + } + if (!request.transaction_id_) { + return TonlibError::EmptyField("transaction_id"); + } + TRY_RESULT(account_address, get_account_address(request.account_address_->account_address_)); + auto lt = request.transaction_id_->lt_; + auto hash_str = request.transaction_id_->hash_; + if (hash_str.size() != 32) { + return td::Status::Error(400, "Invalid transaction id hash size"); + } + td::Bits256 hash; + hash.as_slice().copy_from(hash_str); + make_request(int_api::GetAccountStateByTransaction{account_address, lt, hash}, + promise.wrap([](auto&& res) { return res->to_shardAccountCell(); })); + return td::Status::OK(); +} + td::Result to_dns_entry_data(tonlib_api::dns_EntryData& entry_data) { using R = td::Result; return downcast_call2( @@ -3502,7 +3898,7 @@ class GenericCreateSendGrams : public TonlibQueryActor { return with_wallet(*source_->get_wallet()); } -}; // namespace tonlib +}; td::int64 TonlibClient::register_query(td::unique_ptr query) { auto query_id = ++next_query_id_; @@ -3713,6 +4109,27 @@ td::Status TonlibClient::do_request(const tonlib_api::smc_load& request, return td::Status::OK(); } +td::Status TonlibClient::do_request(const tonlib_api::smc_loadByTransaction& request, + td::Promise>&& promise) { + if (!request.account_address_) { + return TonlibError::EmptyField("account_address"); + } + if (!request.transaction_id_) { + return TonlibError::EmptyField("transaction_id"); + } + TRY_RESULT(account_address, get_account_address(request.account_address_->account_address_)); + auto lt = request.transaction_id_->lt_; + auto hash_str = request.transaction_id_->hash_; + if (hash_str.size() != 32) { + return td::Status::Error(400, "Invalid transaction id hash size"); + } + td::Bits256 hash; + hash.as_slice().copy_from(hash_str); + make_request(int_api::GetAccountStateByTransaction{account_address, lt, hash}, + promise.send_closure(actor_id(this), &TonlibClient::finish_load_smc)); + return td::Status::OK(); +} + td::Status TonlibClient::do_request(const tonlib_api::smc_forget& request, td::Promise>&& promise) { auto it = smcs_.find(request.id_); @@ -4555,6 +4972,17 @@ td::Status TonlibClient::do_request(int_api::GetAccountState request, return td::Status::OK(); } +td::Status TonlibClient::do_request(int_api::GetAccountStateByTransaction request, + td::Promise>&& promise) { + auto actor_id = actor_id_++; + actors_[actor_id] = td::actor::create_actor( + "RunEmulator", client_.get_client(), request, actor_shared(this, actor_id), + promise.wrap([](auto&& state) { + return std::move(state); + })); + return td::Status::OK(); +} + td::Status TonlibClient::do_request(int_api::RemoteRunSmcMethod request, td::Promise&& promise) { auto actor_id = actor_id_++; @@ -4641,29 +5069,72 @@ td::Result to_block_id(const tonlib_api::ton_blockIdExt& blk) { return ton::BlockIdExt(blk.workchain_, blk.shard_, blk.seqno_, root_hash, file_hash); } -td::Status TonlibClient::do_request(const tonlib_api::getConfigParam& request, - td::Promise>&& promise) { - TRY_RESULT(lite_block, to_lite_api(*request.id_)) - auto block = create_block_id(std::move(lite_block)); - auto param = request.param_; +void TonlibClient::get_config_param(int32_t param, int32_t mode, ton::BlockIdExt block, td::Promise>&& promise) { std::vector params = { param }; - - client_.send_query(ton::lite_api::liteServer_getConfigParams(0, std::move(lite_block), std::move(params)), - promise.wrap([block, param](auto r_config) { + client_.send_query(ton::lite_api::liteServer_getConfigParams(mode, ton::create_tl_lite_block_id(block), std::move(params)), + promise.wrap([param, block](auto r_config) -> td::Result> { auto state = block::check_extract_state_proof(block, r_config->state_proof_.as_slice(), r_config->config_proof_.as_slice()); if (state.is_error()) { - LOG(ERROR) << "block::check_extract_state_proof failed: " << state.error(); + return state.move_as_error_prefix(TonlibError::ValidateConfig()); } auto config = block::Config::extract_from_state(std::move(state.move_as_ok()), 0); if (config.is_error()) { - LOG(ERROR) << "block::Config::extract_from_state failed: " << config.error(); + return config.move_as_error_prefix(TonlibError::ValidateConfig()); } tonlib_api::configInfo config_result; config_result.config_ = tonlib_api::make_object(to_bytes(config.move_as_ok()->get_config_param(param))); return tonlib_api::make_object(std::move(config_result)); })); +} +td::Status TonlibClient::do_request(const tonlib_api::getConfigParam& request, + td::Promise>&& promise) { + if (query_context_.block_id) { + get_config_param(request.param_, request.mode_, query_context_.block_id.value(), std::move(promise)); + } else { + client_.with_last_block([this, promise = std::move(promise), param = request.param_, mode = request.mode_](td::Result r_last_block) mutable { + if (r_last_block.is_error()) { + promise.set_error(r_last_block.move_as_error_prefix(TonlibError::Internal("get last block failed "))); + } else { + this->get_config_param(param, mode, r_last_block.move_as_ok().last_block_id, std::move(promise)); + } + }); + } + return td::Status::OK(); +} + +void TonlibClient::get_config_all(int32_t mode, ton::BlockIdExt block, td::Promise>&& promise) { + client_.send_query(ton::lite_api::liteServer_getConfigAll(mode, ton::create_tl_lite_block_id(block)), + promise.wrap([block](auto r_config) -> td::Result> { + auto state = block::check_extract_state_proof(block, r_config->state_proof_.as_slice(), + r_config->config_proof_.as_slice()); + if (state.is_error()) { + return state.move_as_error_prefix(TonlibError::ValidateConfig()); + } + auto config = block::Config::extract_from_state(std::move(state.move_as_ok()), 0); + if (config.is_error()) { + return config.move_as_error_prefix(TonlibError::ValidateConfig()); + } + tonlib_api::configInfo config_result; + config_result.config_ = tonlib_api::make_object(to_bytes(config.move_as_ok()->get_root_cell())); + return tonlib_api::make_object(std::move(config_result)); + })); +} + +td::Status TonlibClient::do_request(const tonlib_api::getConfigAll& request, + td::Promise>&& promise) { + if (query_context_.block_id) { + get_config_all(request.mode_, query_context_.block_id.value(), std::move(promise)); + } else { + client_.with_last_block([this, promise = std::move(promise), mode = request.mode_](td::Result r_last_block) mutable { + if (r_last_block.is_error()) { + promise.set_error(r_last_block.move_as_error_prefix(TonlibError::Internal("get last block failed "))); + } else { + this->get_config_all(mode, r_last_block.move_as_ok().last_block_id, std::move(promise)); + } + }); + } return td::Status::OK(); } diff --git a/tonlib/tonlib/TonlibClient.h b/tonlib/tonlib/TonlibClient.h index 0bb7aadc..dbbb62a2 100644 --- a/tonlib/tonlib/TonlibClient.h +++ b/tonlib/tonlib/TonlibClient.h @@ -39,6 +39,7 @@ namespace tonlib { namespace int_api { struct GetAccountState; +struct GetAccountStateByTransaction; struct GetPrivateKey; struct GetDnsResolver; struct SendMessage; @@ -51,6 +52,7 @@ inline std::string to_string(const int_api::SendMessage&) { } // namespace int_api class AccountState; class Query; +class RunEmulator; td::Result> to_tonlib_api( const ton::ManualDns::EntryData& entry_data); @@ -234,6 +236,8 @@ class TonlibClient : public td::actor::Actor { td::Status do_request(tonlib_api::raw_getAccountState& request, td::Promise>&& promise); + td::Status do_request(tonlib_api::raw_getAccountStateByTransaction& request, + td::Promise>&& promise); td::Status do_request(tonlib_api::raw_getTransactions& request, td::Promise>&& promise); td::Status do_request(tonlib_api::raw_getTransactionsV2& request, @@ -241,6 +245,12 @@ class TonlibClient : public td::actor::Actor { td::Status do_request(const tonlib_api::getAccountState& request, td::Promise>&& promise); + td::Status do_request(const tonlib_api::getAccountStateByTransaction& request, + td::Promise>&& promise); + td::Status do_request(const tonlib_api::getShardAccountCell& request, + td::Promise>&& promise); + td::Status do_request(const tonlib_api::getShardAccountCellByTransaction& request, + td::Promise>&& promise); td::Status do_request(tonlib_api::guessAccountRevision& request, td::Promise>&& promise); td::Status do_request(tonlib_api::guessAccount& request, @@ -305,6 +315,7 @@ class TonlibClient : public td::actor::Actor { td::Result> get_smc_info(td::int64 id); void finish_load_smc(td::unique_ptr query, td::Promise>&& promise); td::Status do_request(const tonlib_api::smc_load& request, td::Promise>&& promise); + td::Status do_request(const tonlib_api::smc_loadByTransaction& request, td::Promise>&& promise); td::Status do_request(const tonlib_api::smc_forget& request, td::Promise>&& promise); td::Status do_request(const tonlib_api::smc_getCode& request, td::Promise>&& promise); @@ -344,6 +355,7 @@ class TonlibClient : public td::actor::Actor { td::Promise>&& promise); td::Status do_request(int_api::GetAccountState request, td::Promise>&&); + td::Status do_request(int_api::GetAccountStateByTransaction request, td::Promise>&&); td::Status do_request(int_api::GetPrivateKey request, td::Promise&&); td::Status do_request(int_api::GetDnsResolver request, td::Promise&&); td::Status do_request(int_api::RemoteRunSmcMethod request, @@ -370,8 +382,14 @@ class TonlibClient : public td::actor::Actor { td::Status do_request(const tonlib_api::blocks_getShardBlockProof& request, td::Promise>&& promise); + void get_config_param(int32_t param, int32_t mode, ton::BlockIdExt block, + td::Promise>&& promise); td::Status do_request(const tonlib_api::getConfigParam& request, td::Promise>&& promise); + void get_config_all(int32_t mode, ton::BlockIdExt block, + td::Promise>&& promise); + td::Status do_request(const tonlib_api::getConfigAll& request, + td::Promise>&& promise); void proxy_request(td::int64 query_id, std::string data); diff --git a/tonlib/tonlib/tonlib-cli.cpp b/tonlib/tonlib/tonlib-cli.cpp index db7497b0..ba476599 100644 --- a/tonlib/tonlib/tonlib-cli.cpp +++ b/tonlib/tonlib/tonlib-cli.cpp @@ -389,6 +389,7 @@ class TonlibCli : public td::actor::Actor { td::TerminalIO::out() << "runmethod ...\tRuns GET method of account " " with specified parameters\n"; td::TerminalIO::out() << "getstate \tget state of wallet with requested key\n"; + td::TerminalIO::out() << "getstatebytransaction \tget state of wallet with requested key after transaction with local time and hash (base64url)\n"; td::TerminalIO::out() << "guessrevision \tsearch of existing accounts corresponding to the given key\n"; td::TerminalIO::out() << "guessaccount \tsearch of existing accounts corresponding to the given key\n"; td::TerminalIO::out() << "getaddress \tget address of wallet with requested key\n"; @@ -489,6 +490,8 @@ class TonlibCli : public td::actor::Actor { transfer(parser, cmd, std::move(cmd_promise)); } else if (cmd == "getstate") { get_state(parser.read_word(), std::move(cmd_promise)); + } else if (cmd == "getstatebytransaction") { + get_state_by_transaction(parser, std::move(cmd_promise)); } else if (cmd == "getaddress") { get_address(parser.read_word(), std::move(cmd_promise)); } else if (cmd == "importkeypem") { @@ -2067,6 +2070,30 @@ class TonlibCli : public td::actor::Actor { })); } + void get_state_by_transaction(td::ConstParser& parser, td::Promise promise) { + TRY_RESULT_PROMISE(promise, address, to_account_address(parser.read_word(), false)); + TRY_RESULT_PROMISE(promise, lt, td::to_integer_safe(parser.read_word())); + TRY_RESULT_PROMISE(promise, hash, td::base64url_decode(parser.read_word())); + + auto address_str = address.address->account_address_; + auto transaction_id = std::make_unique(lt, std::move(hash)); + send_query(make_object( + ton::move_tl_object_as(std::move(address.address)), + ton::move_tl_object_as(std::move(transaction_id))), + promise.wrap([address_str](auto&& state) { + td::TerminalIO::out() << "Address: " << address_str << "\n"; + td::TerminalIO::out() << "Balance: " + << Grams{td::narrow_cast(state->balance_ * (state->balance_ > 0))} + << "\n"; + td::TerminalIO::out() << "Sync utime: " << state->sync_utime_ << "\n"; + td::TerminalIO::out() << "transaction.LT: " << state->last_transaction_id_->lt_ << "\n"; + td::TerminalIO::out() << "transaction.Hash: " << td::base64_encode(state->last_transaction_id_->hash_) + << "\n"; + td::TerminalIO::out() << to_string(state->account_state_); + return td::Unit(); + })); + } + void get_address(td::Slice key, td::Promise promise) { TRY_RESULT_PROMISE(promise, address, to_account_address(key, false)); promise.set_value(td::Unit()); diff --git a/validator/impl/collator-impl.h b/validator/impl/collator-impl.h index 8fd8dc0c..dfe844a3 100644 --- a/validator/impl/collator-impl.h +++ b/validator/impl/collator-impl.h @@ -104,19 +104,7 @@ class Collator final : public td::actor::Actor { return 2; } - static td::Result> - impl_fetch_config_params(std::unique_ptr config, - Ref* old_mparams, - std::vector* storage_prices, - block::StoragePhaseConfig* storage_phase_cfg, - td::BitArray<256>* rand_seed, - block::ComputePhaseConfig* compute_phase_cfg, - block::ActionPhaseConfig* action_phase_cfg, - td::RefInt256* masterchain_create_fee, - td::RefInt256* basechain_create_fee, - WorkchainId wc, UnixTime now); - - static td::Result> + static td::Result> impl_create_ordinary_transaction(Ref msg_root, block::Account* acc, UnixTime utime, LogicalTime lt, @@ -285,7 +273,7 @@ class Collator final : public td::actor::Actor { td::Result register_shard_signatures_cell(Ref shard_blk_signatures); td::Result register_shard_signatures(td::Slice shard_blk_signatures_boc); void register_new_msg(block::NewOutMsg msg); - void register_new_msgs(block::Transaction& trans); + void register_new_msgs(block::transaction::Transaction& trans); bool process_new_messages(bool enqueue_only = false); int process_one_new_message(block::NewOutMsg msg, bool enqueue_only = false, Ref* is_special = nullptr); bool process_inbound_internal_messages(); diff --git a/validator/impl/collator.cpp b/validator/impl/collator.cpp index fb18d8be..8eab9103 100644 --- a/validator/impl/collator.cpp +++ b/validator/impl/collator.cpp @@ -1561,90 +1561,19 @@ bool Collator::init_lt() { } bool Collator::fetch_config_params() { - auto res = impl_fetch_config_params(std::move(config_), &old_mparams_, &storage_prices_, &storage_phase_cfg_, - &rand_seed_, &compute_phase_cfg_, &action_phase_cfg_, &masterchain_create_fee_, - &basechain_create_fee_, workchain(), now_); + auto res = block::FetchConfigParams::fetch_config_params(*config_, + &old_mparams_, &storage_prices_, &storage_phase_cfg_, + &rand_seed_, &compute_phase_cfg_, &action_phase_cfg_, + &masterchain_create_fee_, &basechain_create_fee_, + workchain(), now_ + ); if (res.is_error()) { return fatal_error(res.move_as_error()); } - config_ = res.move_as_ok(); + compute_phase_cfg_.libraries = std::make_unique(config_->get_libraries_root(), 256); return true; } -td::Result> Collator::impl_fetch_config_params( - std::unique_ptr config, Ref* old_mparams, - std::vector* storage_prices, block::StoragePhaseConfig* storage_phase_cfg, - td::BitArray<256>* rand_seed, block::ComputePhaseConfig* compute_phase_cfg, - block::ActionPhaseConfig* action_phase_cfg, td::RefInt256* masterchain_create_fee, - td::RefInt256* basechain_create_fee, WorkchainId wc, UnixTime now) { - *old_mparams = config->get_config_param(9); - { - auto res = config->get_storage_prices(); - if (res.is_error()) { - return res.move_as_error(); - } - *storage_prices = res.move_as_ok(); - } - { - // generate rand seed - prng::rand_gen().strong_rand_bytes(rand_seed->data(), 32); - LOG(DEBUG) << "block random seed set to " << rand_seed->to_hex(); - } - TRY_RESULT(size_limits, config->get_size_limits_config()); - { - // compute compute_phase_cfg / storage_phase_cfg - auto cell = config->get_config_param(wc == ton::masterchainId ? 20 : 21); - if (cell.is_null()) { - return td::Status::Error(-668, "cannot fetch current gas prices and limits from masterchain configuration"); - } - if (!compute_phase_cfg->parse_GasLimitsPrices(std::move(cell), storage_phase_cfg->freeze_due_limit, - storage_phase_cfg->delete_due_limit)) { - return td::Status::Error(-668, "cannot unpack current gas prices and limits from masterchain configuration"); - } - compute_phase_cfg->block_rand_seed = *rand_seed; - compute_phase_cfg->libraries = std::make_unique(config->get_libraries_root(), 256); - compute_phase_cfg->max_vm_data_depth = size_limits.max_vm_data_depth; - compute_phase_cfg->global_config = config->get_root_cell(); - compute_phase_cfg->suspended_addresses = config->get_suspended_addresses(now); - } - { - // compute action_phase_cfg - block::gen::MsgForwardPrices::Record rec; - auto cell = config->get_config_param(24); - if (cell.is_null() || !tlb::unpack_cell(std::move(cell), rec)) { - return td::Status::Error(-668, "cannot fetch masterchain message transfer prices from masterchain configuration"); - } - action_phase_cfg->fwd_mc = - block::MsgPrices{rec.lump_price, rec.bit_price, rec.cell_price, rec.ihr_price_factor, - (unsigned)rec.first_frac, (unsigned)rec.next_frac}; - cell = config->get_config_param(25); - if (cell.is_null() || !tlb::unpack_cell(std::move(cell), rec)) { - return td::Status::Error(-668, "cannot fetch standard message transfer prices from masterchain configuration"); - } - action_phase_cfg->fwd_std = - block::MsgPrices{rec.lump_price, rec.bit_price, rec.cell_price, rec.ihr_price_factor, - (unsigned)rec.first_frac, (unsigned)rec.next_frac}; - action_phase_cfg->workchains = &config->get_workchain_list(); - action_phase_cfg->bounce_msg_body = (config->has_capability(ton::capBounceMsgBody) ? 256 : 0); - action_phase_cfg->size_limits = size_limits; - } - { - // fetch block_grams_created - auto cell = config->get_config_param(14); - if (cell.is_null()) { - *basechain_create_fee = *masterchain_create_fee = td::zero_refint(); - } else { - block::gen::BlockCreateFees::Record create_fees; - if (!(tlb::unpack_cell(cell, create_fees) && - block::tlb::t_Grams.as_integer_to(create_fees.masterchain_block_fee, *masterchain_create_fee) && - block::tlb::t_Grams.as_integer_to(create_fees.basechain_block_fee, *basechain_create_fee))) { - return td::Status::Error(-668, "cannot unpack BlockCreateFees from configuration parameter #14"); - } - } - } - return std::move(config); -} - bool Collator::compute_minted_amount(block::CurrencyCollection& to_mint) { if (!is_masterchain()) { return to_mint.set_zero(); @@ -2167,8 +2096,8 @@ bool Collator::create_ticktock_transaction(const ton::StdSmcAddress& smc_addr, t << "last transaction time in the state of account " << workchain() << ":" << smc_addr.to_hex() << " is too large")); } - std::unique_ptr trans = std::make_unique( - *acc, mask == 2 ? block::Transaction::tr_tick : block::Transaction::tr_tock, req_start_lt, now_); + std::unique_ptr trans = std::make_unique( + *acc, mask == 2 ? block::transaction::Transaction::tr_tick : block::transaction::Transaction::tr_tock, req_start_lt, now_); if (!trans->prepare_storage_phase(storage_phase_cfg_, true)) { return fatal_error(td::Status::Error( -666, std::string{"cannot create storage phase of a new transaction for smart contract "} + smc_addr.to_hex())); @@ -2258,7 +2187,7 @@ Ref Collator::create_ordinary_transaction(Ref msg_root) { fatal_error(std::move(error)); return {}; } - std::unique_ptr trans = res.move_as_ok(); + std::unique_ptr trans = res.move_as_ok(); if (!trans->update_limits(*block_limit_status_)) { fatal_error("cannot update block limit status to include the new transaction"); @@ -2277,10 +2206,13 @@ Ref Collator::create_ordinary_transaction(Ref msg_root) { // If td::status::error_code == 669 - Fatal Error block can not be produced // if td::status::error_code == 701 - Transaction can not be included into block, but it's ok (external or too early internal) -td::Result> Collator::impl_create_ordinary_transaction( - Ref msg_root, block::Account* acc, UnixTime utime, LogicalTime lt, - block::StoragePhaseConfig* storage_phase_cfg, block::ComputePhaseConfig* compute_phase_cfg, - block::ActionPhaseConfig* action_phase_cfg, bool external, LogicalTime after_lt) { +td::Result> Collator::impl_create_ordinary_transaction(Ref msg_root, + block::Account* acc, + UnixTime utime, LogicalTime lt, + block::StoragePhaseConfig* storage_phase_cfg, + block::ComputePhaseConfig* compute_phase_cfg, + block::ActionPhaseConfig* action_phase_cfg, + bool external, LogicalTime after_lt) { if (acc->last_trans_end_lt_ >= lt && acc->transactions.empty()) { return td::Status::Error(-669, PSTRING() << "last transaction time in the state of account " << acc->workchain << ":" << acc->addr.to_hex() << " is too large"); @@ -2291,8 +2223,8 @@ td::Result> Collator::impl_create_ordinary_t trans_min_lt = std::max(trans_min_lt, after_lt); } - std::unique_ptr trans = - std::make_unique(*acc, block::Transaction::tr_ord, trans_min_lt + 1, utime, msg_root); + std::unique_ptr trans = + std::make_unique(*acc, block::transaction::Transaction::tr_ord, trans_min_lt + 1, utime, msg_root); bool ihr_delivered = false; // FIXME if (!trans->unpack_input_msg(ihr_delivered, action_phase_cfg)) { if (external) { @@ -3041,7 +2973,7 @@ void Collator::register_new_msg(block::NewOutMsg new_msg) { new_msgs.push(std::move(new_msg)); } -void Collator::register_new_msgs(block::Transaction& trans) { +void Collator::register_new_msgs(block::transaction::Transaction& trans) { CHECK(trans.root.not_null()); for (unsigned i = 0; i < trans.out_msgs.size(); i++) { register_new_msg(trans.extract_out_msg_ext(i)); diff --git a/validator/impl/external-message.cpp b/validator/impl/external-message.cpp index 5d3028db..9383e734 100644 --- a/validator/impl/external-message.cpp +++ b/validator/impl/external-message.cpp @@ -143,16 +143,17 @@ td::Status ExtMessageQ::run_message_on_account(ton::WorkchainId wc, block::ActionPhaseConfig action_phase_cfg_; td::RefInt256 masterchain_create_fee, basechain_create_fee; - auto fetch_res = Collator::impl_fetch_config_params(std::move(config), &old_mparams, - &storage_prices_, &storage_phase_cfg_, - &rand_seed_, &compute_phase_cfg_, - &action_phase_cfg_, &masterchain_create_fee, - &basechain_create_fee, wc, utime); + auto fetch_res = block::FetchConfigParams::fetch_config_params(*config, &old_mparams, + &storage_prices_, &storage_phase_cfg_, + &rand_seed_, &compute_phase_cfg_, + &action_phase_cfg_, &masterchain_create_fee, + &basechain_create_fee, wc, utime); if(fetch_res.is_error()) { auto error = fetch_res.move_as_error(); LOG(DEBUG) << "Cannot fetch config params: " << error.message(); return error.move_as_error_prefix("Cannot fetch config params: "); } + compute_phase_cfg_.libraries = std::make_unique(config->get_libraries_root(), 256); compute_phase_cfg_.with_vm_log = true; auto res = Collator::impl_create_ordinary_transaction(msg_root, acc, utime, lt, @@ -164,7 +165,7 @@ td::Status ExtMessageQ::run_message_on_account(ton::WorkchainId wc, LOG(DEBUG) << "Cannot run message on account: " << error.message(); return error.move_as_error_prefix("Cannot run message on account: "); } - std::unique_ptr trans = res.move_as_ok(); + std::unique_ptr trans = res.move_as_ok(); auto trans_root = trans->commit(*acc); if (trans_root.is_null()) { diff --git a/validator/impl/validate-query.cpp b/validator/impl/validate-query.cpp index 4d751f33..50a3e591 100644 --- a/validator/impl/validate-query.cpp +++ b/validator/impl/validate-query.cpp @@ -4359,7 +4359,7 @@ bool ValidateQuery::check_one_transaction(block::Account& account, ton::LogicalT << account.total_state->get_hash().to_hex()); } // some type-specific checks - int trans_type = block::Transaction::tr_none; + int trans_type = block::transaction::Transaction::tr_none; switch (tag) { case block::gen::TransactionDescr::trans_ord: { if (!block_limit_status_->fits(block::ParamLimits::cl_medium)) { @@ -4369,7 +4369,7 @@ bool ValidateQuery::check_one_transaction(block::Account& account, ton::LogicalT << "lt_delta=" << block_limit_status_->cur_lt - block_limits_->start_lt << "(limit=" << block_limits_->lt_delta.hard() << ")"); } - trans_type = block::Transaction::tr_ord; + trans_type = block::transaction::Transaction::tr_ord; if (in_msg_root.is_null()) { return reject_query(PSTRING() << "ordinary transaction " << lt << " of account " << addr.to_hex() << " has no inbound message"); @@ -4378,7 +4378,7 @@ bool ValidateQuery::check_one_transaction(block::Account& account, ton::LogicalT break; } case block::gen::TransactionDescr::trans_storage: { - trans_type = block::Transaction::tr_storage; + trans_type = block::transaction::Transaction::tr_storage; if (in_msg_root.not_null()) { return reject_query(PSTRING() << "storage transaction " << lt << " of account " << addr.to_hex() << " has an inbound message"); @@ -4394,7 +4394,7 @@ bool ValidateQuery::check_one_transaction(block::Account& account, ton::LogicalT } case block::gen::TransactionDescr::trans_tick_tock: { bool is_tock = (td_cs.prefetch_ulong(4) & 1); - trans_type = is_tock ? block::Transaction::tr_tock : block::Transaction::tr_tick; + trans_type = is_tock ? block::transaction::Transaction::tr_tock : block::transaction::Transaction::tr_tick; if (in_msg_root.not_null()) { return reject_query(PSTRING() << (is_tock ? "tock" : "tick") << " transaction " << lt << " of account " << addr.to_hex() << " has an inbound message"); @@ -4402,7 +4402,7 @@ bool ValidateQuery::check_one_transaction(block::Account& account, ton::LogicalT break; } case block::gen::TransactionDescr::trans_merge_prepare: { - trans_type = block::Transaction::tr_merge_prepare; + trans_type = block::transaction::Transaction::tr_merge_prepare; if (in_msg_root.not_null()) { return reject_query(PSTRING() << "merge prepare transaction " << lt << " of account " << addr.to_hex() << " has an inbound message"); @@ -4417,7 +4417,7 @@ bool ValidateQuery::check_one_transaction(block::Account& account, ton::LogicalT break; } case block::gen::TransactionDescr::trans_merge_install: { - trans_type = block::Transaction::tr_merge_install; + trans_type = block::transaction::Transaction::tr_merge_install; if (in_msg_root.is_null()) { return reject_query(PSTRING() << "merge install transaction " << lt << " of account " << addr.to_hex() << " has no inbound message"); @@ -4429,7 +4429,7 @@ bool ValidateQuery::check_one_transaction(block::Account& account, ton::LogicalT break; } case block::gen::TransactionDescr::trans_split_prepare: { - trans_type = block::Transaction::tr_split_prepare; + trans_type = block::transaction::Transaction::tr_split_prepare; if (in_msg_root.not_null()) { return reject_query(PSTRING() << "split prepare transaction " << lt << " of account " << addr.to_hex() << " has an inbound message"); @@ -4444,7 +4444,7 @@ bool ValidateQuery::check_one_transaction(block::Account& account, ton::LogicalT break; } case block::gen::TransactionDescr::trans_split_install: { - trans_type = block::Transaction::tr_split_install; + trans_type = block::transaction::Transaction::tr_split_install; if (in_msg_root.is_null()) { return reject_query(PSTRING() << "split install transaction " << lt << " of account " << addr.to_hex() << " has no inbound message"); @@ -4459,8 +4459,8 @@ bool ValidateQuery::check_one_transaction(block::Account& account, ton::LogicalT // check transaction computation by re-doing it // similar to Collator::create_ordinary_transaction() and Collator::create_ticktock_transaction() // .... - std::unique_ptr trs = - std::make_unique(account, trans_type, lt, now_, in_msg_root); + std::unique_ptr trs = + std::make_unique(account, trans_type, lt, now_, in_msg_root); if (in_msg_root.not_null()) { if (!trs->unpack_input_msg(ihr_delivered, &action_phase_cfg_)) { // inbound external message was not accepted From 1e4eecfdb0a31e1bf69a85818fb841174fe4824a Mon Sep 17 00:00:00 2001 From: SpyCheese Date: Thu, 2 Feb 2023 07:04:19 +0000 Subject: [PATCH 08/46] Logs and size limits for incoming queries in FullNodeShard (#601) * Log incoming queries in FullNodeShard * Limit size for some queries in FullNodeShard --- tdutils/td/utils/filesystem.cpp | 2 +- validator/full-node-shard.cpp | 110 ++++++++++++++++++++++---------- 2 files changed, 78 insertions(+), 34 deletions(-) diff --git a/tdutils/td/utils/filesystem.cpp b/tdutils/td/utils/filesystem.cpp index 18fb5717..562a4281 100644 --- a/tdutils/td/utils/filesystem.cpp +++ b/tdutils/td/utils/filesystem.cpp @@ -60,7 +60,7 @@ Result read_file_impl(CSlice path, int64 size, int64 offset) { if (size == -1) { size = file_size - offset; } else if (size >= 0) { - if (size + offset > file_size) { + if (size > file_size - offset) { size = file_size - offset; } } diff --git a/validator/full-node-shard.cpp b/validator/full-node-shard.cpp index 6ed0ae85..4cb5627d 100644 --- a/validator/full-node-shard.cpp +++ b/validator/full-node-shard.cpp @@ -221,8 +221,9 @@ void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNod } } }); - td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_next_block, - create_block_id(query.prev_block_), std::move(P)); + BlockIdExt block_id = create_block_id(query.prev_block_); + VLOG(FULL_NODE_DEBUG) << "Got query getNextBlockDescription " << block_id.to_str() << " from " << src; + td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_next_block, block_id, std::move(P)); } void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNode_prepareBlock &query, @@ -242,8 +243,10 @@ void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNod } } }); - td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_block_handle, - create_block_id(query.block_), false, std::move(P)); + BlockIdExt block_id = create_block_id(query.block_); + VLOG(FULL_NODE_DEBUG) << "Got query prepareBlock " << block_id.to_str() << " from " << src; + td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_block_handle, block_id, false, + std::move(P)); } void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNode_downloadBlock &query, @@ -261,22 +264,24 @@ void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNod } } }); - td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_block_handle, - create_block_id(query.block_), false, std::move(P)); + BlockIdExt block_id = create_block_id(query.block_); + VLOG(FULL_NODE_DEBUG) << "Got query downloadBlock " << block_id.to_str() << " from " << src; + td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_block_handle, block_id, false, + std::move(P)); } void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNode_downloadBlockFull &query, td::Promise promise) { - td::actor::create_actor("sender", ton::create_block_id(query.block_), false, validator_manager_, - std::move(promise)) - .release(); + BlockIdExt block_id = create_block_id(query.block_); + VLOG(FULL_NODE_DEBUG) << "Got query downloadBlockFull " << block_id.to_str() << " from " << src; + td::actor::create_actor("sender", block_id, false, validator_manager_, std::move(promise)).release(); } void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNode_downloadNextBlockFull &query, td::Promise promise) { - td::actor::create_actor("sender", ton::create_block_id(query.prev_block_), true, validator_manager_, - std::move(promise)) - .release(); + BlockIdExt block_id = create_block_id(query.prev_block_); + VLOG(FULL_NODE_DEBUG) << "Got query downloadNextBlockFull " << block_id.to_str() << " from " << src; + td::actor::create_actor("sender", block_id, true, validator_manager_, std::move(promise)).release(); } void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNode_prepareBlockProof &query, @@ -308,8 +313,10 @@ void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNod } }); - td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_block_handle, - create_block_id(query.block_), false, std::move(P)); + BlockIdExt block_id = create_block_id(query.block_); + VLOG(FULL_NODE_DEBUG) << "Got query prepareBlockProof " << block_id.to_str() << " from " << src; + td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_block_handle, block_id, false, + std::move(P)); } void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNode_prepareKeyBlockProof &query, @@ -332,12 +339,15 @@ void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNod } }); + BlockIdExt block_id = create_block_id(query.block_); + VLOG(FULL_NODE_DEBUG) << "Got query prepareKeyBlockProof " << block_id.to_str() << " " << query.allow_partial_ + << " from " << src; if (query.allow_partial_) { - td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_key_block_proof_link, - create_block_id(query.block_), std::move(P)); + td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_key_block_proof_link, block_id, + std::move(P)); } else { - td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_key_block_proof, - create_block_id(query.block_), std::move(P)); + td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_key_block_proof, block_id, + std::move(P)); } } @@ -360,8 +370,10 @@ void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNod } }); - td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_block_handle, - create_block_id(query.block_), false, std::move(P)); + BlockIdExt block_id = create_block_id(query.block_); + VLOG(FULL_NODE_DEBUG) << "Got query downloadBlockProof " << block_id.to_str() << " from " << src; + td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_block_handle, block_id, false, + std::move(P)); } void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNode_downloadBlockProofLink &query, @@ -383,8 +395,10 @@ void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNod } }); - td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_block_handle, - create_block_id(query.block_), false, std::move(P)); + BlockIdExt block_id = create_block_id(query.block_); + VLOG(FULL_NODE_DEBUG) << "Got query downloadBlockProofLink " << block_id.to_str() << " from " << src; + td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_block_handle, block_id, false, + std::move(P)); } void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNode_downloadKeyBlockProof &query, @@ -401,8 +415,9 @@ void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNod } }); - td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_key_block_proof, - create_block_id(query.block_), std::move(P)); + BlockIdExt block_id = create_block_id(query.block_); + VLOG(FULL_NODE_DEBUG) << "Got query downloadKeyBlockProof " << block_id.to_str() << " from " << src; + td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_key_block_proof, block_id, std::move(P)); } void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNode_downloadKeyBlockProofLink &query, @@ -419,8 +434,10 @@ void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNod } }); - td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_key_block_proof_link, - create_block_id(query.block_), std::move(P)); + BlockIdExt block_id = create_block_id(query.block_); + VLOG(FULL_NODE_DEBUG) << "Got query downloadKeyBlockProofLink " << block_id.to_str() << " from " << src; + td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_key_block_proof_link, block_id, + std::move(P)); } void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNode_prepareZeroState &query, @@ -437,6 +454,7 @@ void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNod promise.set_value(std::move(x)); }); auto block_id = create_block_id(query.block_); + VLOG(FULL_NODE_DEBUG) << "Got query prepareZeroState " << block_id.to_str() << " from " << src; td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::check_zero_state_exists, block_id, std::move(P)); } @@ -456,6 +474,8 @@ void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNod }); auto block_id = create_block_id(query.block_); auto masterchain_block_id = create_block_id(query.masterchain_block_); + VLOG(FULL_NODE_DEBUG) << "Got query preparePersistentState " << block_id.to_str() << " " + << masterchain_block_id.to_str() << " from " << src; td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::check_persistent_state_exists, block_id, masterchain_block_id, std::move(P)); } @@ -484,6 +504,7 @@ void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNod promise.set_value(std::move(x)); }); auto block_id = create_block_id(query.block_); + VLOG(FULL_NODE_DEBUG) << "Got query getNextKeyBlockIds " << block_id.to_str() << " " << cnt << " from " << src; td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_next_key_blocks, block_id, cnt, std::move(P)); } @@ -500,28 +521,45 @@ void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNod promise.set_value(R.move_as_ok()); }); auto block_id = create_block_id(query.block_); + VLOG(FULL_NODE_DEBUG) << "Got query downloadZeroState " << block_id.to_str() << " from " << src; td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_zero_state, block_id, std::move(P)); } void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNode_downloadPersistentState &query, td::Promise promise) { + td::uint64 max_size = 1 << 24; auto P = td::PromiseCreator::lambda( - [SelfId = actor_id(this), promise = std::move(promise)](td::Result R) mutable { + [SelfId = actor_id(this), promise = std::move(promise), max_size](td::Result R) mutable { if (R.is_error()) { promise.set_error(R.move_as_error_prefix("failed to get state from db: ")); return; } - - promise.set_value(R.move_as_ok()); + td::BufferSlice s = R.move_as_ok(); + if (s.size() > max_size) { + promise.set_error(td::Status::Error("state is too big")); + return; + } + promise.set_value(std::move(s)); }); auto block_id = create_block_id(query.block_); auto masterchain_block_id = create_block_id(query.masterchain_block_); - td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_persistent_state, block_id, - masterchain_block_id, std::move(P)); + VLOG(FULL_NODE_DEBUG) << "Got query downloadPersistentState " << block_id.to_str() << " " + << masterchain_block_id.to_str() << " from " << src; + td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_persistent_state_slice, block_id, + masterchain_block_id, 0, max_size + 1, std::move(P)); } void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNode_downloadPersistentStateSlice &query, td::Promise promise) { + auto block_id = create_block_id(query.block_); + auto masterchain_block_id = create_block_id(query.masterchain_block_); + VLOG(FULL_NODE_DEBUG) << "Got query downloadPersistentStateSlice " << block_id.to_str() << " " + << masterchain_block_id.to_str() << " " << query.offset_ << " " << query.max_size_ << " from " + << src; + if (query.max_size_ < 0 || query.max_size_ > (1 << 24)) { + promise.set_error(td::Status::Error(ErrorCode::protoviolation, "invalid max_size")); + return; + } auto P = td::PromiseCreator::lambda( [SelfId = actor_id(this), promise = std::move(promise)](td::Result R) mutable { if (R.is_error()) { @@ -531,14 +569,13 @@ void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNod promise.set_value(R.move_as_ok()); }); - auto block_id = create_block_id(query.block_); - auto masterchain_block_id = create_block_id(query.masterchain_block_); td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_persistent_state_slice, block_id, masterchain_block_id, query.offset_, query.max_size_, std::move(P)); } void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNode_getCapabilities &query, td::Promise promise) { + VLOG(FULL_NODE_DEBUG) << "Got query getCapabilities from " << src; promise.set_value(create_serialize_tl_object(proto_version(), proto_capabilities())); } @@ -552,12 +589,19 @@ void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNod promise.set_value(create_serialize_tl_object(R.move_as_ok())); } }); + VLOG(FULL_NODE_DEBUG) << "Got query getArchiveInfo " << query.masterchain_seqno_ << " from " << src; td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_archive_id, query.masterchain_seqno_, std::move(P)); } void FullNodeShardImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::tonNode_getArchiveSlice &query, td::Promise promise) { + VLOG(FULL_NODE_DEBUG) << "Got query getArchiveSlice " << query.archive_id_ << " " << query.offset_ << " " + << query.max_size_ << " from " << src; + if (query.max_size_ < 0 || query.max_size_ > (1 << 24)) { + promise.set_error(td::Status::Error(ErrorCode::protoviolation, "invalid max_size")); + return; + } td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::get_archive_slice, query.archive_id_, query.offset_, query.max_size_, std::move(promise)); } From 9ea6b5bf3033b9a8e62be1909674d0af632df56d Mon Sep 17 00:00:00 2001 From: Andrey Pfau Date: Thu, 2 Feb 2023 10:04:39 +0300 Subject: [PATCH 09/46] Fix error message for unpacking block header (#595) Co-authored-by: EmelyanenkoK --- crypto/block/check-proof.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/block/check-proof.cpp b/crypto/block/check-proof.cpp index 6720ad40..4abba64c 100644 --- a/crypto/block/check-proof.cpp +++ b/crypto/block/check-proof.cpp @@ -362,7 +362,7 @@ td::Status BlockProofLink::validate(td::uint32* save_utime) const { if (to.seqno()) { TRY_STATUS(check_block_header(vd_root, to)); if (!(tlb::unpack_cell(vd_root, blk) && tlb::unpack_cell(blk.info, info))) { - return td::Status::Error("cannot unpack header for block "s + from.to_str()); + return td::Status::Error("cannot unpack header for block "s + to.to_str()); } if (info.key_block != is_key) { return td::Status::Error(PSTRING() << "incorrect is_key_block value " << is_key << " for destination block " From 426879cd822d0c3add0f9824d5c5824604a9ca58 Mon Sep 17 00:00:00 2001 From: Andrey Pfau Date: Thu, 2 Feb 2023 10:04:54 +0300 Subject: [PATCH 10/46] `ErrorCode::notready` for "block is not applied": (#594) --- validator/impl/liteserver.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/validator/impl/liteserver.cpp b/validator/impl/liteserver.cpp index 7f6811fb..ac2932bc 100644 --- a/validator/impl/liteserver.cpp +++ b/validator/impl/liteserver.cpp @@ -509,7 +509,7 @@ void LiteQuery::get_block_handle_checked(BlockIdExt blkid, td::Promiseis_applied()) { promise.set_result(std::move(handle)); } else { - promise.set_error(td::Status::Error("block is not applied")); + promise.set_error(td::Status::Error(ErrorCode::notready, "block is not applied")); } } }); @@ -1471,7 +1471,7 @@ void LiteQuery::continue_getTransactions(unsigned remaining, bool exact) { } else { auto handle = res.move_as_ok(); if (!handle->is_applied()) { - td::actor::send_closure(Self, &LiteQuery::abort_getTransactions, td::Status::Error("block is not applied"), + td::actor::send_closure(Self, &LiteQuery::abort_getTransactions, td::Status::Error(ErrorCode::notready, "block is not applied"), ton::BlockIdExt{}); return; } @@ -1801,7 +1801,7 @@ void LiteQuery::perform_lookupBlock(BlockId blkid, int mode, LogicalTime lt, Uni } else { auto handle = res.move_as_ok(); if (!handle->is_applied()) { - td::actor::send_closure(Self, &LiteQuery::abort_query, td::Status::Error("block is not applied")); + td::actor::send_closure(Self, &LiteQuery::abort_query, td::Status::Error(ErrorCode::notready, "block is not applied")); return; } LOG(DEBUG) << "requesting data for block " << handle->id().to_str(); From 681b494410f53450dc78afea4b2eaa42f4463acf Mon Sep 17 00:00:00 2001 From: neodiX42 Date: Thu, 2 Feb 2023 08:06:17 +0100 Subject: [PATCH 11/46] Include blockchain-explorer to artifacts (#593) * Update create-release.yml minor test * Update macos-12.6-compile.yml Add blockchain-explorer * Update macos-11.7-compile.yml add blockchain-explorer * Update win-2019-compile.yml add blockchain-explorer * Update macos-12.6-compile.yml * Update macos-11.7-compile.yml * Update macos-12.6-compile.yml * Update macos-12.6-compile.yml * Update macos-12.6-compile.yml * Update macos-11.7-compile.yml --------- Co-authored-by: EmelyanenkoK --- .github/workflows/create-release.yml | 2 +- .github/workflows/macos-11.7-compile.yml | 5 +++-- .github/workflows/macos-12.6-compile.yml | 5 +++-- .github/workflows/win-2019-compile.yml | 4 ++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 089af583..3319bc70 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -164,4 +164,4 @@ jobs: repo_token: ${{ secrets.GITHUB_TOKEN }} file: artifacts/ton-ubuntu-22.04-arm64.zip asset_name: ton-ubuntu-22.04-arm64.zip - tag: v${{ steps.date.outputs.date }} \ No newline at end of file + tag: v${{ steps.date.outputs.date }} diff --git a/.github/workflows/macos-11.7-compile.yml b/.github/workflows/macos-11.7-compile.yml index 80a57e22..2d73c558 100644 --- a/.github/workflows/macos-11.7-compile.yml +++ b/.github/workflows/macos-11.7-compile.yml @@ -23,18 +23,19 @@ jobs: - name: Build all run: | export NONINTERACTIVE=1 - brew install ninja + brew install ninja libmicrohttpd rootPath=`pwd` mkdir build cd build cmake -GNinja -DOPENSSL_FOUND=1 -DOPENSSL_INCLUDE_DIR=$rootPath/openssl_1_1_1/include -DOPENSSL_CRYPTO_LIBRARY=$rootPath/openssl_1_1_1/libcrypto.a -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=11.7 -DCMAKE_CXX_FLAGS="-stdlib=libc++" -DCMAKE_BUILD_TYPE=Release .. - ninja storage-daemon storage-daemon-cli fift func tonlib tonlibjson tonlib-cli validator-engine lite-client pow-miner validator-engine-console generate-random-id json2tlo dht-server http-proxy rldp-http-proxy adnl-proxy create-state create-hardfork tlbc + ninja storage-daemon storage-daemon-cli blockchain-explorer fift func tonlib tonlibjson tonlib-cli validator-engine lite-client pow-miner validator-engine-console generate-random-id json2tlo dht-server http-proxy rldp-http-proxy adnl-proxy create-state create-hardfork tlbc - name: Find & copy binaries run: | mkdir artifacts cp build/storage/storage-daemon/storage-daemon artifacts/ cp build/storage/storage-daemon/storage-daemon-cli artifacts/ + cp build/blockchain-explorer/blockchain-explorer artifacts/ cp build/crypto/fift artifacts/ cp build/crypto/func artifacts/ cp build/crypto/create-state artifacts/ diff --git a/.github/workflows/macos-12.6-compile.yml b/.github/workflows/macos-12.6-compile.yml index 2656de0a..5a47dbbb 100644 --- a/.github/workflows/macos-12.6-compile.yml +++ b/.github/workflows/macos-12.6-compile.yml @@ -23,18 +23,19 @@ jobs: - name: Build all run: | export NONINTERACTIVE=1 - brew install ninja + brew install ninja libmicrohttpd rootPath=`pwd` mkdir build cd build cmake -GNinja -DOPENSSL_FOUND=1 -DOPENSSL_INCLUDE_DIR=$rootPath/openssl_1_1_1/include -DOPENSSL_CRYPTO_LIBRARY=$rootPath/openssl_1_1_1/libcrypto.a -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=12.6 -DCMAKE_CXX_FLAGS="-stdlib=libc++" -DCMAKE_BUILD_TYPE=Release .. - ninja storage-daemon storage-daemon-cli fift func tonlib tonlibjson tonlib-cli validator-engine lite-client pow-miner validator-engine-console generate-random-id json2tlo dht-server http-proxy rldp-http-proxy adnl-proxy create-state create-hardfork tlbc + ninja storage-daemon storage-daemon-cli blockchain-explorer fift func tonlib tonlibjson tonlib-cli validator-engine lite-client pow-miner validator-engine-console generate-random-id json2tlo dht-server http-proxy rldp-http-proxy adnl-proxy create-state create-hardfork tlbc - name: Find & copy binaries run: | mkdir artifacts cp build/storage/storage-daemon/storage-daemon artifacts/ cp build/storage/storage-daemon/storage-daemon-cli artifacts/ + cp build/blockchain-explorer/blockchain-explorer artifacts/ cp build/crypto/fift artifacts/ cp build/crypto/func artifacts/ cp build/crypto/create-state artifacts/ diff --git a/.github/workflows/win-2019-compile.yml b/.github/workflows/win-2019-compile.yml index e648dbb7..9164014c 100644 --- a/.github/workflows/win-2019-compile.yml +++ b/.github/workflows/win-2019-compile.yml @@ -57,7 +57,7 @@ jobs: mkdir build cd build cmake -DREADLINE_INCLUDE_DIR=%root%\readline-5.0-1-lib\include\readline -DREADLINE_LIBRARY=%root%\readline-5.0-1-lib\lib\readline.lib -DZLIB_FOUND=1 -DMHD_FOUND=1 -DMHD_LIBRARY=%root%\libmicrohttpd-0.9.75-w32-bin\x86_64\VS2019\Release-static\libmicrohttpd.lib -DMHD_INCLUDE_DIR=%root%\libmicrohttpd-0.9.75-w32-bin\x86_64\VS2019\Release-static -DZLIB_INCLUDE_DIR=%root%\zlib -DZLIB_LIBRARY=%root%\zlib\contrib\vstudio\vc14\x64\ZlibStatReleaseWithoutAsm\zlibstat.lib -DOPENSSL_FOUND=1 -DOPENSSL_INCLUDE_DIR=%root%/openssl-1.1/x64/include -DOPENSSL_CRYPTO_LIBRARY=%root%/openssl-1.1/x64/lib/libcrypto.lib -DCMAKE_CXX_FLAGS="/DTD_WINDOWS=1 /EHsc /bigobj /W0" .. - cmake --build . --target storage-daemon storage-daemon-cli fift func tonlib tonlibjson tonlib-cli validator-engine lite-client pow-miner validator-engine-console generate-random-id json2tlo dht-server http-proxy rldp-http-proxy adnl-proxy create-state create-hardfork --config Release + cmake --build . --target storage-daemon storage-daemon-cli blockchain-explorer fift func tonlib tonlibjson tonlib-cli validator-engine lite-client pow-miner validator-engine-console generate-random-id json2tlo dht-server http-proxy rldp-http-proxy adnl-proxy create-state create-hardfork --config Release - name: Show executables run: | @@ -77,7 +77,7 @@ jobs: mkdir artifacts\smartcont mkdir artifacts\lib - for %%I in (build\storage\storage-daemon\Release\storage-daemon.exe build\storage\storage-daemon\Release\storage-daemon-cli.exe build\crypto\Release\fift.exe build\crypto\Release\tlbc.exe build\crypto\Release\func.exe build\crypto\Release\create-state.exe build\validator-engine-console\Release\validator-engine-console.exe build\tonlib\Release\tonlib-cli.exe build\tonlib\Release\tonlibjson.dll build\http\Release\http-proxy.exe build\rldp-http-proxy\Release\rldp-http-proxy.exe build\dht-server\Release\dht-server.exe build\lite-client\Release\lite-client.exe build\validator-engine\Release\validator-engine.exe build\utils\Release\generate-random-id.exe build\utils\Release\json2tlo.exe build\adnl\Release\adnl-proxy.exe) do copy %%I artifacts\ + for %%I in (build\storage\storage-daemon\Release\storage-daemon.exe build\storage\storage-daemon\Release\storage-daemon-cli.exe build\blockchain-explorer\Release\blockchain-explorer.exe build\crypto\Release\fift.exe build\crypto\Release\tlbc.exe build\crypto\Release\func.exe build\crypto\Release\create-state.exe build\validator-engine-console\Release\validator-engine-console.exe build\tonlib\Release\tonlib-cli.exe build\tonlib\Release\tonlibjson.dll build\http\Release\http-proxy.exe build\rldp-http-proxy\Release\rldp-http-proxy.exe build\dht-server\Release\dht-server.exe build\lite-client\Release\lite-client.exe build\validator-engine\Release\validator-engine.exe build\utils\Release\generate-random-id.exe build\utils\Release\json2tlo.exe build\adnl\Release\adnl-proxy.exe) do copy %%I artifacts\ xcopy /e /k /h /i crypto\smartcont artifacts\smartcont xcopy /e /k /h /i crypto\fift\lib artifacts\lib From 7a78ea33b7861bd0487a49512f7eb48798e1850f Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Thu, 2 Feb 2023 12:28:41 +0300 Subject: [PATCH 12/46] Update op-code for provider/contract interaction (#602) * Update provider->contract deploy_contract op-code * Switch to B{} representation of child contracts --- storage/storage-daemon/StorageProvider.cpp | 10 ++++-- storage/storage-daemon/smartcont/constants.fc | 1 + .../smartcont/storage-contract-code.boc | Bin 777 -> 777 bytes .../smartcont/storage-contract.fc | 4 +-- .../smartcont/storage-contract.fif | 2 +- .../smartcont/storage-provider-code.boc | Bin 1378 -> 1380 bytes .../smartcont/storage-provider.fc | 7 +++-- .../smartcont/storage-provider.fif | 29 +++++++++--------- 8 files changed, 31 insertions(+), 22 deletions(-) diff --git a/storage/storage-daemon/StorageProvider.cpp b/storage/storage-daemon/StorageProvider.cpp index d09b66cd..da4b5042 100644 --- a/storage/storage-daemon/StorageProvider.cpp +++ b/storage/storage-daemon/StorageProvider.cpp @@ -258,9 +258,13 @@ void StorageProvider::process_transaction(tl_object_ptr body = r_body.move_as_ok(); vm::CellSlice cs = vm::load_cell_slice(body); - // const op::offer_storage_contract = 0x107c49ef; - if (cs.size() >= 32 && cs.prefetch_long(32) == 0x107c49ef) { - new_contract_address = message->destination_->account_address_; + if (cs.size() >= 32) { + long long op_code = cs.prefetch_long(32); + // const op::offer_storage_contract = 0x107c49ef; -- old versions + // const op::deploy_storage_contract = 0xe4748df1; -- new versions + if((op_code == 0x107c49ef) || (op_code == 0xe4748df1)) { + new_contract_address = message->destination_->account_address_; + } } } if (!new_contract_address.empty()) { diff --git a/storage/storage-daemon/smartcont/constants.fc b/storage/storage-daemon/smartcont/constants.fc index 479cdfa3..19cc28b1 100644 --- a/storage/storage-daemon/smartcont/constants.fc +++ b/storage/storage-daemon/smartcont/constants.fc @@ -1,4 +1,5 @@ const op::offer_storage_contract = 0x107c49ef; +const op::deploy_storage_contract = 0xe4748df1; const op::close_contract = 0x79f937ea; const op::contract_deployed = 0xbf7bd0c1; const op::storage_contract_confirmed = 0xd4caedcd; diff --git a/storage/storage-daemon/smartcont/storage-contract-code.boc b/storage/storage-daemon/smartcont/storage-contract-code.boc index 03dd18eaa761e8c2b94dd32ca8a4f11dad352320..647b3a798e607f538895e22242f47e4c3b9652c4 100644 GIT binary patch delta 16 XcmeBV>tvgd%xWpCT$8skV<8g&DkTLr delta 16 XcmeBV>tvgd%*rA!RQ+ya#zH0lDq96{ diff --git a/storage/storage-daemon/smartcont/storage-contract.fc b/storage/storage-daemon/smartcont/storage-contract.fc index 41e3ee5b..b9087f41 100644 --- a/storage/storage-daemon/smartcont/storage-contract.fc +++ b/storage/storage-daemon/smartcont/storage-contract.fc @@ -76,7 +76,7 @@ int check_proof(int merkle_hash, int byte_to_proof, int file_size, cell file_dic int query_id = in_msg_body~load_uint(64); - if(op == op::offer_storage_contract) { + if(op == op::deploy_storage_contract) { add_to_balance(msg_value - 2 * fee::receipt_value); var (client, torrent_hash) = get_client_data(get_data().begin_parse()); var msg = begin_cell() @@ -263,4 +263,4 @@ _ get_next_proof_info() method_id { next_proof, rate_per_mb_day, max_span, last_proof_time, client, torrent_hash) = get_storage_contract_data(); return (next_proof, last_proof_time, max_span); -} \ No newline at end of file +} diff --git a/storage/storage-daemon/smartcont/storage-contract.fif b/storage/storage-daemon/smartcont/storage-contract.fif index 5dd86983..05641521 100644 --- a/storage/storage-daemon/smartcont/storage-contract.fif +++ b/storage/storage-daemon/smartcont/storage-contract.fif @@ -111,7 +111,7 @@ PROGRAM{ }> // msg_value sender_address op in_msg_body 64 LDU // msg_value sender_address op query_id in_msg_body s2 PUSH - 276580847 PUSHINT // msg_value sender_address op query_id in_msg_body op _27=276580847 + 3832843761 PUSHINT // msg_value sender_address op query_id in_msg_body op _27=3832843761 EQUAL // msg_value sender_address op query_id in_msg_body _28 IF:<{ // msg_value sender_address op query_id in_msg_body s0 s4 XCHG diff --git a/storage/storage-daemon/smartcont/storage-provider-code.boc b/storage/storage-daemon/smartcont/storage-provider-code.boc index e7cee4ff737214b55a2e5d4870f1c82979f7ea57..b2f96a42272fd52ab71607dd44e9da3375364340 100644 GIT binary patch delta 49 zcmaFF^@NLi>$^Ecj7*x046G3ox$9UyT4(Iu*d4~mYALH+6Fd1CV-c8@w>g%Hl^Fo6 CL=WZw delta 47 zcmaFD^@xjm>$^Ecj7*x046I=jx$BtTT5s$PV`OEK7plHH`2}MUnDuURJQFK30D>G3 AHvj+t diff --git a/storage/storage-daemon/smartcont/storage-provider.fc b/storage/storage-daemon/smartcont/storage-provider.fc index 17223833..dce2ea6b 100644 --- a/storage/storage-daemon/smartcont/storage-provider.fc +++ b/storage/storage-daemon/smartcont/storage-provider.fc @@ -4,7 +4,10 @@ const min_deploy_amount = 50000000; -cell storage_contract_code() asm """ "storage-contract-code.boc" file>B B>boc PUSHREF """; +;; cell storage_contract_code() asm """ "storage-contract-code.boc" file>B B>boc PUSHREF """; +;; the same constant but more "compiler" friendly +cell storage_contract_code() asm " B{B5EE9C72010213010002FD000114FF00F4A413F4BCF2C80B0102016202030202CE04050201200D0E020120060700115D74CD0FA40D3FF30804E1007434C0C05C6C2497C0F83E900C0871C023A0D6F6CF380074C7C8700023A117C0F6CF3834CFC8A084391D237C6EA3AD4120829896802876CF3B51343C00E0842FDEF4305C20063232C1540133C5A0824C4B403E8084F2DA84B2C7D48832CFF2FFF25C3EC0244D388860841E8D85A22EA008080809006F35CE6CE4D7C11C3834C1C070C0E4D7C11C3834FFC12F64D7C0DC3800B50C1C25A010086B092E64693A0CC06AC140BD039BE84C645FF81C20002CED44D0D200FA0003A001C8CA0001FA0201CF16C9ED5403E68E4FED44D0D200FA00FA4003B3F2E3EF5350C705F2E1917FC8CA0058FA0201CF1621CF16C9ED54F003F8258210D4CAEDCD708018C8CB055005CF168209312D00FA0214CB6A13CB1F12CB3FCBFFC970FB00DE21821079F937EABAE30221821046ED2E94BA9130E30D8210419D5D4DBA915BE30D0A0B0C00EA10235F03ED44D0D200FA00FA40F0035352C7055162C70516B1F2E191F8258210B6236D63708018C8CB055004CF165005FA0212CB6A13CB1F12CB3F5230CBFFC902B39730318100A0FB00E0018040FB00F8258210B6236D63708018C8CB055004CF1623FA0213CB6A12CB1FCB3FCBFFC98100A0FB000092ED44D0D200FA00FA403002F2E3EB5341C705F2E19120C200998208989680A072FB029130E28210A91BAF56708018C8CB055003CF168209312D00FA0212CB6ACB1FCB3FC98100A0FB0000FA01D430ED44D0D200FA00FA40D3FFD33FD33FFA00D31FD31FD43009F2E3EB53A6C705F2E191544540525BF001F2E3EA22F811F8235003A128B6085331A8018102A3AA1AA984067007A116B609F8237FC8CA0058FA025005CF1613CBFFCB3FCB3F58FA0213CB1F12CB1FCCC9ED54708018C8CB0558CF16CB6EC98042FB000201200F100011BEE6576A2686B8500402012011120033B9241ED44D0D200FA00FA40D3FFD33FD33FFA00D31FD31FF00380017B6E4F0402A483DA87B0D9430001BB7CA50402A483DA87B0B664D8A70} B>boc PUSHREF "; + slice calculate_address_by_stateinit(cell state_init) { return begin_cell().store_uint(4, 3) @@ -67,7 +70,7 @@ cell build_storage_contract_stateinit(int merkle_hash, int file_size, int rate_p .store_coins(0) .store_uint(4 + 2, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) .store_ref(state_init) - .store_uint(op::offer_storage_contract, 32) + .store_uint(op::deploy_storage_contract, 32) .store_uint(query_id, 64) .end_cell(); send_raw_message(msg, 64); diff --git a/storage/storage-daemon/smartcont/storage-provider.fif b/storage/storage-daemon/smartcont/storage-provider.fif index 28dddc7c..c4503485 100644 --- a/storage/storage-daemon/smartcont/storage-provider.fif +++ b/storage/storage-daemon/smartcont/storage-provider.fif @@ -52,7 +52,7 @@ PROGRAM{ STREF // _33 ENDC // data 0 PUSHINT // data _36=0 - "storage-contract-code.boc" file>B B>boc PUSHREF // data _36=0 _37 + B{B5EE9C72010213010002FD000114FF00F4A413F4BCF2C80B0102016202030202CE04050201200D0E020120060700115D74CD0FA40D3FF30804E1007434C0C05C6C2497C0F83E900C0871C023A0D6F6CF380074C7C8700023A117C0F6CF3834CFC8A084391D237C6EA3AD4120829896802876CF3B51343C00E0842FDEF4305C20063232C1540133C5A0824C4B403E8084F2DA84B2C7D48832CFF2FFF25C3EC0244D388860841E8D85A22EA008080809006F35CE6CE4D7C11C3834C1C070C0E4D7C11C3834FFC12F64D7C0DC3800B50C1C25A010086B092E64693A0CC06AC140BD039BE84C645FF81C20002CED44D0D200FA0003A001C8CA0001FA0201CF16C9ED5403E68E4FED44D0D200FA00FA4003B3F2E3EF5350C705F2E1917FC8CA0058FA0201CF1621CF16C9ED54F003F8258210D4CAEDCD708018C8CB055005CF168209312D00FA0214CB6A13CB1F12CB3FCBFFC970FB00DE21821079F937EABAE30221821046ED2E94BA9130E30D8210419D5D4DBA915BE30D0A0B0C00EA10235F03ED44D0D200FA00FA40F0035352C7055162C70516B1F2E191F8258210B6236D63708018C8CB055004CF165005FA0212CB6A13CB1F12CB3F5230CBFFC902B39730318100A0FB00E0018040FB00F8258210B6236D63708018C8CB055004CF1623FA0213CB6A12CB1FCB3FCBFFC98100A0FB000092ED44D0D200FA00FA403002F2E3EB5341C705F2E19120C200998208989680A072FB029130E28210A91BAF56708018C8CB055003CF168209312D00FA0212CB6ACB1FCB3FC98100A0FB0000FA01D430ED44D0D200FA00FA40D3FFD33FD33FFA00D31FD31FD43009F2E3EB53A6C705F2E191544540525BF001F2E3EA22F811F8235003A128B6085331A8018102A3AA1AA984067007A116B609F8237FC8CA0058FA025005CF1613CBFFCB3FCB3F58FA0213CB1F12CB1FCCC9ED54708018C8CB0558CF16CB6EC98042FB000201200F100011BEE6576A2686B8500402012011120033B9241ED44D0D200FA00FA40D3FFD33FD33FFA00D31FD31FF00380017B6E4F0402A483DA87B0D9430001BB7CA50402A483DA87B0B664D8A70} B>boc PUSHREF // data _36=0 _37 OVER // data _36=0 _37 _38=0 NEWC // data _36=0 _37 _38=0 _39 2 STU // data _36=0 _37 _41 @@ -93,19 +93,19 @@ PROGRAM{ s1 s5 XCHG s1 s6 XCHG // query_id merkle_hash file_size rate_per_mb_day max_span client torrent_hash build_storage_contract_stateinit CALLDICT // query_id state_init - 276580847 PUSHINT // query_id state_init _54=276580847 - 6 PUSHINT // query_id state_init _54=276580847 _57 - 24 PUSHINT // query_id state_init _54=276580847 _57 _58=24 - NEWC // query_id state_init _54=276580847 _57 _58=24 _59 - 6 STU // query_id state_init _54=276580847 _57 _61 - s3 PUSH // query_id state_init _54=276580847 _57 _61 state_init - calculate_address_by_stateinit CALLDICT // query_id state_init _54=276580847 _57 _61 _62 - STSLICER // query_id state_init _54=276580847 _57 _63 - 0 PUSHINT // query_id state_init _54=276580847 _57 _63 _64=0 - STGRAMS // query_id state_init _54=276580847 _57 _65 - 108 STU // query_id state_init _54=276580847 _81 - s1 s2 XCHG // query_id _54=276580847 state_init _81 - STREF // query_id _54=276580847 _82 + 3832843761 PUSHINT // query_id state_init _54=3832843761 + 6 PUSHINT // query_id state_init _54=3832843761 _57 + 24 PUSHINT // query_id state_init _54=3832843761 _57 _58=24 + NEWC // query_id state_init _54=3832843761 _57 _58=24 _59 + 6 STU // query_id state_init _54=3832843761 _57 _61 + s3 PUSH // query_id state_init _54=3832843761 _57 _61 state_init + calculate_address_by_stateinit CALLDICT // query_id state_init _54=3832843761 _57 _61 _62 + STSLICER // query_id state_init _54=3832843761 _57 _63 + 0 PUSHINT // query_id state_init _54=3832843761 _57 _63 _64=0 + STGRAMS // query_id state_init _54=3832843761 _57 _65 + 108 STU // query_id state_init _54=3832843761 _81 + s1 s2 XCHG // query_id _54=3832843761 state_init _81 + STREF // query_id _54=3832843761 _82 32 STU // query_id _84 64 STU // _86 ENDC // msg @@ -113,6 +113,7 @@ PROGRAM{ SENDRAWMSG }> recv_internal PROC:<{ + c2 SAVE SAMEALTSAVE // msg_value in_msg_full in_msg_body SWAP // msg_value in_msg_body in_msg_full CTOS // msg_value in_msg_body cs From c369127ae0b92c482887e5b1f1c7676f0cb06fea Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Fri, 3 Feb 2023 09:50:44 +0300 Subject: [PATCH 13/46] rldp2 support in rldp-http-proxy (#608) Co-authored-by: SpyCheese --- rldp-http-proxy/CMakeLists.txt | 2 +- rldp-http-proxy/rldp-http-proxy.cpp | 261 +++++++++++++++++++++------- rldp2/rldp-in.hpp | 4 + rldp2/rldp.cpp | 13 ++ rldp2/rldp.h | 2 + tl/generate/scheme/ton_api.tl | 2 + tl/generate/scheme/ton_api.tlo | Bin 83196 -> 83460 bytes tl/generate/scheme/tonlib_api.tlo | Bin 31844 -> 31788 bytes 8 files changed, 220 insertions(+), 64 deletions(-) diff --git a/rldp-http-proxy/CMakeLists.txt b/rldp-http-proxy/CMakeLists.txt index eefd1dd8..44f19a24 100644 --- a/rldp-http-proxy/CMakeLists.txt +++ b/rldp-http-proxy/CMakeLists.txt @@ -2,4 +2,4 @@ cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) add_executable(rldp-http-proxy rldp-http-proxy.cpp DNSResolver.h DNSResolver.cpp) target_include_directories(rldp-http-proxy PUBLIC $) -target_link_libraries(rldp-http-proxy PRIVATE tonhttp rldp dht tonlib git) +target_link_libraries(rldp-http-proxy PRIVATE tonhttp rldp rldp2 dht tonlib git) diff --git a/rldp-http-proxy/rldp-http-proxy.cpp b/rldp-http-proxy/rldp-http-proxy.cpp index eb04ef9e..124168cc 100644 --- a/rldp-http-proxy/rldp-http-proxy.cpp +++ b/rldp-http-proxy/rldp-http-proxy.cpp @@ -45,6 +45,7 @@ #include "adnl/adnl.h" #include "rldp/rldp.h" +#include "rldp2/rldp.h" #include "dht/dht.h" #include @@ -63,6 +64,53 @@ class RldpHttpProxy; +class RldpDispatcher : public ton::adnl::AdnlSenderInterface { + public: + RldpDispatcher(td::actor::ActorId rldp, td::actor::ActorId rldp2) + : rldp_(std::move(rldp)), rldp2_(std::move(rldp2)) { + } + + void send_message(ton::adnl::AdnlNodeIdShort src, ton::adnl::AdnlNodeIdShort dst, td::BufferSlice data) override { + td::actor::send_closure(dispatch(dst), &ton::adnl::AdnlSenderInterface::send_message, src, dst, std::move(data)); + } + + void send_query(ton::adnl::AdnlNodeIdShort src, ton::adnl::AdnlNodeIdShort dst, std::string name, + td::Promise promise, td::Timestamp timeout, td::BufferSlice data) override { + td::actor::send_closure(dispatch(dst), &ton::adnl::AdnlSenderInterface::send_query, src, dst, std::move(name), + std::move(promise), timeout, std::move(data)); + } + void send_query_ex(ton::adnl::AdnlNodeIdShort src, ton::adnl::AdnlNodeIdShort dst, std::string name, + td::Promise promise, td::Timestamp timeout, td::BufferSlice data, + td::uint64 max_answer_size) override { + td::actor::send_closure(dispatch(dst), &ton::adnl::AdnlSenderInterface::send_query_ex, src, dst, std::move(name), + std::move(promise), timeout, std::move(data), max_answer_size); + } + void get_conn_ip_str(ton::adnl::AdnlNodeIdShort l_id, ton::adnl::AdnlNodeIdShort p_id, + td::Promise promise) override { + td::actor::send_closure(rldp_, &ton::adnl::AdnlSenderInterface::get_conn_ip_str, l_id, p_id, std::move(promise)); + } + + void set_supports_rldp2(ton::adnl::AdnlNodeIdShort dst, bool supports) { + if (supports) { + supports_rldp2_.insert(dst); + } else { + supports_rldp2_.erase(dst); + } + } + + private: + td::actor::ActorId rldp_; + td::actor::ActorId rldp2_; + std::set supports_rldp2_; + + td::actor::ActorId dispatch(ton::adnl::AdnlNodeIdShort dst) const { + if (supports_rldp2_.count(dst)) { + return rldp2_; + } + return rldp_; + } +}; + class HttpRemote : public td::actor::Actor { public: struct Query { @@ -137,6 +185,8 @@ const std::string PROXY_SITE_VERISON_HEADER_NAME = "Ton-Proxy-Site-Version"; const std::string PROXY_ENTRY_VERISON_HEADER_NAME = "Ton-Proxy-Entry-Version"; const std::string PROXY_VERSION_HEADER = PSTRING() << "Commit: " << GitMetadata::CommitSHA1() << ", Date: " << GitMetadata::CommitDate(); +const td::uint64 CAPABILITY_RLDP2 = 1; +const td::uint64 CAPABILITIES = 1; using RegisteredPayloadSenderGuard = std::unique_ptr, td::Bits256>, @@ -146,8 +196,8 @@ class HttpRldpPayloadReceiver : public td::actor::Actor { public: HttpRldpPayloadReceiver(std::shared_ptr payload, td::Bits256 transfer_id, ton::adnl::AdnlNodeIdShort src, ton::adnl::AdnlNodeIdShort local_id, - td::actor::ActorId adnl, td::actor::ActorId rldp, - bool is_tunnel = false) + td::actor::ActorId adnl, + td::actor::ActorId rldp, bool is_tunnel = false) : payload_(std::move(payload)) , id_(transfer_id) , src_(src) @@ -204,8 +254,8 @@ class HttpRldpPayloadReceiver : public td::actor::Actor { auto f = ton::create_serialize_tl_object( id_, seqno_++, static_cast(chunk_size())); auto timeout = td::Timestamp::in(is_tunnel_ ? 60.0 : 15.0); - td::actor::send_closure(rldp_, &ton::rldp::Rldp::send_query_ex, local_id_, src_, "payload part", std::move(P), - timeout, std::move(f), 2 * chunk_size() + 1024); + td::actor::send_closure(rldp_, &ton::adnl::AdnlSenderInterface::send_query_ex, local_id_, src_, "payload part", + std::move(P), timeout, std::move(f), 2 * chunk_size() + 1024); } void add_data(td::BufferSlice data) { @@ -265,7 +315,7 @@ class HttpRldpPayloadReceiver : public td::actor::Actor { ton::adnl::AdnlNodeIdShort src_; ton::adnl::AdnlNodeIdShort local_id_; td::actor::ActorId adnl_; - td::actor::ActorId rldp_; + td::actor::ActorId rldp_; bool sent_ = false; td::int32 seqno_ = 0; @@ -276,8 +326,8 @@ class HttpRldpPayloadSender : public td::actor::Actor { public: HttpRldpPayloadSender(std::shared_ptr payload, td::Bits256 transfer_id, ton::adnl::AdnlNodeIdShort local_id, td::actor::ActorId adnl, - td::actor::ActorId rldp, td::actor::ActorId proxy, - bool is_tunnel = false) + td::actor::ActorId rldp, + td::actor::ActorId proxy, bool is_tunnel = false) : payload_(std::move(payload)) , id_(transfer_id) , local_id_(local_id) @@ -407,7 +457,7 @@ class HttpRldpPayloadSender : public td::actor::Actor { ton::adnl::AdnlNodeIdShort local_id_; td::actor::ActorId adnl_; - td::actor::ActorId rldp_; + td::actor::ActorId rldp_; td::actor::ActorId proxy_; size_t cur_query_size_; @@ -424,9 +474,8 @@ class TcpToRldpRequestSender : public td::actor::Actor { std::shared_ptr request_payload, td::Promise, std::shared_ptr>> promise, td::actor::ActorId adnl, td::actor::ActorId dht, - td::actor::ActorId rldp, td::actor::ActorId proxy, - td::actor::ActorId dns_resolver, - ton::adnl::AdnlNodeIdShort storage_gateway) + td::actor::ActorId rldp, td::actor::ActorId proxy, + td::actor::ActorId dns_resolver, ton::adnl::AdnlNodeIdShort storage_gateway) : local_id_(local_id) , host_(std::move(host)) , request_(std::move(request)) @@ -447,26 +496,7 @@ class TcpToRldpRequestSender : public td::actor::Actor { } void resolve(std::string host); - - void resolved(ton::adnl::AdnlNodeIdShort id) { - dst_ = id; - - auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) { - if (R.is_error()) { - td::actor::send_closure(SelfId, &TcpToRldpRequestSender::abort_query, R.move_as_error()); - } else { - td::actor::send_closure(SelfId, &TcpToRldpRequestSender::got_result, R.move_as_ok()); - } - }); - - td::actor::create_actor("HttpPayloadSender", request_payload_, id_, local_id_, adnl_, rldp_, - proxy_, is_tunnel()) - .release(); - - auto f = ton::serialize_tl_object(request_tl_, true); - td::actor::send_closure(rldp_, &ton::rldp::Rldp::send_query_ex, local_id_, dst_, "http request over rldp", - std::move(P), td::Timestamp::in(30.0), std::move(f), 16 << 10); - } + void resolved(ton::adnl::AdnlNodeIdShort id); void got_result(td::BufferSlice data) { auto F = ton::fetch_tl_object(data, true); @@ -548,7 +578,7 @@ class TcpToRldpRequestSender : public td::actor::Actor { td::actor::ActorId adnl_; td::actor::ActorId dht_; - td::actor::ActorId rldp_; + td::actor::ActorId rldp_; td::actor::ActorId proxy_; td::actor::ActorId dns_resolver_; ton::adnl::AdnlNodeIdShort storage_gateway_ = ton::adnl::AdnlNodeIdShort::zero(); @@ -562,7 +592,7 @@ class TcpToRldpRequestSender : public td::actor::Actor { class RldpTcpTunnel : public td::actor::Actor, private td::ObserverBase { public: RldpTcpTunnel(td::Bits256 transfer_id, ton::adnl::AdnlNodeIdShort src, ton::adnl::AdnlNodeIdShort local_id, - td::actor::ActorId adnl, td::actor::ActorId rldp, + td::actor::ActorId adnl, td::actor::ActorId rldp, td::actor::ActorId proxy, td::SocketFd fd) : id_(transfer_id) , src_(src) @@ -599,8 +629,8 @@ class RldpTcpTunnel : public td::actor::Actor, private td::ObserverBase { auto f = ton::create_serialize_tl_object(id_, out_seqno_++, (1 << 21) - (1 << 11)); - td::actor::send_closure(rldp_, &ton::rldp::Rldp::send_query_ex, local_id_, src_, "payload part", std::move(P), - td::Timestamp::in(60.0), std::move(f), (1 << 21) + 1024); + td::actor::send_closure(rldp_, &ton::adnl::AdnlSenderInterface::send_query_ex, local_id_, src_, "payload part", + std::move(P), td::Timestamp::in(60.0), std::move(f), (1 << 21) + 1024); } void receive_query(ton::tl_object_ptr f, @@ -727,7 +757,7 @@ class RldpTcpTunnel : public td::actor::Actor, private td::ObserverBase { ton::adnl::AdnlNodeIdShort src_; ton::adnl::AdnlNodeIdShort local_id_; td::actor::ActorId adnl_; - td::actor::ActorId rldp_; + td::actor::ActorId rldp_; td::actor::ActorId proxy_; td::BufferedFd fd_; @@ -746,7 +776,8 @@ class RldpToTcpRequestSender : public td::actor::Actor { RldpToTcpRequestSender(td::Bits256 id, ton::adnl::AdnlNodeIdShort local_id, ton::adnl::AdnlNodeIdShort dst, std::unique_ptr request, std::shared_ptr request_payload, td::Promise promise, - td::actor::ActorId adnl, td::actor::ActorId rldp, + td::actor::ActorId adnl, + td::actor::ActorId rldp, td::actor::ActorId proxy, td::actor::ActorId remote) : id_(id) , local_id_(local_id) @@ -806,7 +837,7 @@ class RldpToTcpRequestSender : public td::actor::Actor { td::Promise promise_; td::actor::ActorId adnl_; - td::actor::ActorId rldp_; + td::actor::ActorId rldp_; td::actor::ActorId proxy_; td::actor::ActorId remote_; @@ -1032,35 +1063,58 @@ class RldpHttpProxy : public td::actor::Actor { private: td::actor::ActorId self_id_; }; - for (auto &serv_id : server_ids_) { - class AdnlCb : public ton::adnl::Adnl::Callback { - public: - AdnlCb(td::actor::ActorId id) : self_id_(id) { - } - void receive_message(ton::adnl::AdnlNodeIdShort src, ton::adnl::AdnlNodeIdShort dst, - td::BufferSlice data) override { - } - void receive_query(ton::adnl::AdnlNodeIdShort src, ton::adnl::AdnlNodeIdShort dst, td::BufferSlice data, - td::Promise promise) override { - td::actor::send_closure(self_id_, &RldpHttpProxy::receive_rldp_request, src, dst, std::move(data), - std::move(promise)); - } + class AdnlCapabilitiesCb : public ton::adnl::Adnl::Callback { + public: + AdnlCapabilitiesCb(td::actor::ActorId id) : self_id_(id) { + } + void receive_message(ton::adnl::AdnlNodeIdShort src, ton::adnl::AdnlNodeIdShort dst, + td::BufferSlice data) override { + } + void receive_query(ton::adnl::AdnlNodeIdShort src, ton::adnl::AdnlNodeIdShort dst, td::BufferSlice data, + td::Promise promise) override { + TRY_RESULT_PROMISE(promise, query, ton::fetch_tl_object(data, true)); + promise.set_result(ton::create_serialize_tl_object(CAPABILITIES)); + td::actor::send_closure(self_id_, &RldpHttpProxy::update_peer_capabilities, src, query->capabilities_); + } - private: - td::actor::ActorId self_id_; - }; + private: + td::actor::ActorId self_id_; + }; + class AdnlServerCb : public ton::adnl::Adnl::Callback { + public: + AdnlServerCb(td::actor::ActorId id) : self_id_(id) { + } + void receive_message(ton::adnl::AdnlNodeIdShort src, ton::adnl::AdnlNodeIdShort dst, + td::BufferSlice data) override { + } + void receive_query(ton::adnl::AdnlNodeIdShort src, ton::adnl::AdnlNodeIdShort dst, td::BufferSlice data, + td::Promise promise) override { + td::actor::send_closure(self_id_, &RldpHttpProxy::receive_rldp_request, src, dst, std::move(data), + std::move(promise)); + } + + private: + td::actor::ActorId self_id_; + }; + for (auto &serv_id : server_ids_) { td::actor::send_closure(adnl_, &ton::adnl::Adnl::subscribe, serv_id, ton::adnl::Adnl::int_to_bytestring(ton::ton_api::http_request::ID), - std::make_unique(actor_id(this))); + std::make_unique(actor_id(this))); if (local_id_ != serv_id) { td::actor::send_closure(adnl_, &ton::adnl::Adnl::subscribe, serv_id, ton::adnl::Adnl::int_to_bytestring(ton::ton_api::http_getNextPayloadPart::ID), std::make_unique(actor_id(this))); + td::actor::send_closure(adnl_, &ton::adnl::Adnl::subscribe, serv_id, + ton::adnl::Adnl::int_to_bytestring(ton::ton_api::http_proxy_getCapabilities::ID), + std::make_unique(actor_id(this))); } } td::actor::send_closure(adnl_, &ton::adnl::Adnl::subscribe, local_id_, ton::adnl::Adnl::int_to_bytestring(ton::ton_api::http_getNextPayloadPart::ID), std::make_unique(actor_id(this))); + td::actor::send_closure(adnl_, &ton::adnl::Adnl::subscribe, local_id_, + ton::adnl::Adnl::int_to_bytestring(ton::ton_api::http_proxy_getCapabilities::ID), + std::make_unique(actor_id(this))); rldp_ = ton::rldp::Rldp::create(adnl_.get()); td::actor::send_closure(rldp_, &ton::rldp::Rldp::set_default_mtu, 16 << 10); @@ -1069,6 +1123,15 @@ class RldpHttpProxy : public td::actor::Actor { td::actor::send_closure(rldp_, &ton::rldp::Rldp::add_id, serv_id); } + rldp2_ = ton::rldp2::Rldp::create(adnl_.get()); + td::actor::send_closure(rldp2_, &ton::rldp2::Rldp::set_default_mtu, 16 << 10); + td::actor::send_closure(rldp2_, &ton::rldp2::Rldp::add_id, local_id_); + for (auto &serv_id : server_ids_) { + td::actor::send_closure(rldp2_, &ton::rldp2::Rldp::add_id, serv_id); + } + + rldp_dispatcher_ = td::actor::create_actor("RldpDispatcher", rldp_.get(), rldp2_.get()); + store_dht(); } @@ -1107,7 +1170,7 @@ class RldpHttpProxy : public td::actor::Actor { } std::transform(host.begin(), host.end(), host.begin(), [](unsigned char c) { return std::tolower(c); }); bool allow = proxy_all_; - for (const char* suffix : {".adnl", ".ton", ".bag"}) { + for (const char *suffix : {".adnl", ".ton", ".bag"}) { if (td::ends_with(host, td::Slice(suffix))) { allow = true; } @@ -1117,9 +1180,9 @@ class RldpHttpProxy : public td::actor::Actor { return; } - td::actor::create_actor("outboundreq", local_id_, host, std::move(request), - std::move(payload), std::move(promise), adnl_.get(), dht_.get(), - rldp_.get(), actor_id(this), dns_resolver_.get(), storage_gateway_) + td::actor::create_actor( + "outboundreq", local_id_, host, std::move(request), std::move(payload), std::move(promise), adnl_.get(), + dht_.get(), rldp_dispatcher_.get(), actor_id(this), dns_resolver_.get(), storage_gateway_) .release(); } @@ -1127,6 +1190,7 @@ class RldpHttpProxy : public td::actor::Actor { td::Promise promise) { LOG(INFO) << "got HTTP request over rldp from " << src; TRY_RESULT_PROMISE(promise, f, ton::fetch_tl_object(data, true)); + ask_peer_capabilities(src); std::unique_ptr request; auto S = [&]() { TRY_RESULT_ASSIGN(request, ton::http::HttpRequest::create(f->method_, f->url_, f->http_version_)); @@ -1214,8 +1278,8 @@ class RldpHttpProxy : public td::actor::Actor { LOG(INFO) << "starting HTTP over RLDP request"; td::actor::create_actor("inboundreq", f->id_, dst, src, std::move(request), - payload.move_as_ok(), std::move(promise), adnl_.get(), rldp_.get(), - actor_id(this), server.http_remote_.get()) + payload.move_as_ok(), std::move(promise), adnl_.get(), + rldp_dispatcher_.get(), actor_id(this), server.http_remote_.get()) .release(); } @@ -1227,7 +1291,7 @@ class RldpHttpProxy : public td::actor::Actor { return; } td::actor::create_actor(td::actor::ActorOptions().with_name("tunnel").with_poll(), id, src, local_id, - adnl_.get(), rldp_.get(), actor_id(this), fd.move_as_ok()) + adnl_.get(), rldp_dispatcher_.get(), actor_id(this), fd.move_as_ok()) .release(); std::vector> headers; headers.push_back( @@ -1291,6 +1355,47 @@ class RldpHttpProxy : public td::actor::Actor { storage_gateway_ = id; } + void update_peer_capabilities(ton::adnl::AdnlNodeIdShort peer, td::uint64 capabilities) { + auto &c = peer_capabilities_[peer]; + if (c.capabilities != capabilities) { + LOG(DEBUG) << "Update capabilities of peer " << peer << " : " << capabilities; + } + c.capabilities = capabilities; + c.received = true; + td::actor::send_closure(rldp_dispatcher_, &RldpDispatcher::set_supports_rldp2, peer, + capabilities & CAPABILITY_RLDP2); + } + + void ask_peer_capabilities(ton::adnl::AdnlNodeIdShort peer) { + auto &c = peer_capabilities_[peer]; + if (!c.received && c.retry_at.is_in_past()) { + c.retry_at = td::Timestamp::in(30.0); + auto send_query = [&](const ton::adnl::AdnlNodeIdShort &local_id) { + td::actor::send_closure( + adnl_, &ton::adnl::Adnl::send_query, local_id, peer, "q", + [SelfId = actor_id(this), peer](td::Result R) { + if (R.is_error()) { + return; + } + auto r_obj = ton::fetch_tl_object(R.move_as_ok(), true); + if (r_obj.is_error()) { + return; + } + td::actor::send_closure(SelfId, &RldpHttpProxy::update_peer_capabilities, peer, + r_obj.ok()->capabilities_); + }, + td::Timestamp::in(3.0), + ton::create_serialize_tl_object(CAPABILITIES)); + }; + for (const ton::adnl::AdnlNodeIdShort &local_id : server_ids_) { + if (local_id != local_id_) { + send_query(local_id); + } + } + send_query(local_id_); + } + } + private: struct Host { struct Server { @@ -1320,6 +1425,8 @@ class RldpHttpProxy : public td::actor::Actor { td::actor::ActorOwn adnl_; td::actor::ActorOwn dht_; td::actor::ActorOwn rldp_; + td::actor::ActorOwn rldp2_; + td::actor::ActorOwn rldp_dispatcher_; std::shared_ptr dht_config_; @@ -1333,6 +1440,13 @@ class RldpHttpProxy : public td::actor::Actor { std::map, td::Promise)>> payload_senders_; + + struct PeerCapabilities { + td::uint64 capabilities = 0; + bool received = false; + td::Timestamp retry_at = td::Timestamp::now(); + }; + std::map peer_capabilities_; }; void TcpToRldpRequestSender::resolve(std::string host) { @@ -1355,7 +1469,7 @@ void TcpToRldpRequestSender::resolve(std::string host) { } request_tl_->url_ = (PSTRING() << "/gateway/" << bag_id << url); host = storage_gateway_.serialize() + ".adnl"; - for (auto& header : request_tl_->headers_) { + for (auto &header : request_tl_->headers_) { if (td::to_lower(header->name_) == "host") { header->value_ = host; break; @@ -1390,6 +1504,27 @@ void TcpToRldpRequestSender::resolve(std::string host) { }); } +void TcpToRldpRequestSender::resolved(ton::adnl::AdnlNodeIdShort id) { + dst_ = id; + td::actor::send_closure(proxy_, &RldpHttpProxy::ask_peer_capabilities, id); + + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) { + if (R.is_error()) { + td::actor::send_closure(SelfId, &TcpToRldpRequestSender::abort_query, R.move_as_error()); + } else { + td::actor::send_closure(SelfId, &TcpToRldpRequestSender::got_result, R.move_as_ok()); + } + }); + + td::actor::create_actor("HttpPayloadSender", request_payload_, id_, local_id_, adnl_, rldp_, + proxy_, is_tunnel()) + .release(); + + auto f = ton::serialize_tl_object(request_tl_, true); + td::actor::send_closure(rldp_, &ton::adnl::AdnlSenderInterface::send_query_ex, local_id_, dst_, + "http request over rldp", std::move(P), td::Timestamp::in(30.0), std::move(f), 16 << 10); +} + void HttpRldpPayloadSender::start_up() { td::actor::send_closure( proxy_, &RldpHttpProxy::register_payload_sender, id_, diff --git a/rldp2/rldp-in.hpp b/rldp2/rldp-in.hpp index c2e46d2a..095c1479 100644 --- a/rldp2/rldp-in.hpp +++ b/rldp2/rldp-in.hpp @@ -92,6 +92,8 @@ class RldpIn : public RldpImpl { void get_conn_ip_str(adnl::AdnlNodeIdShort l_id, adnl::AdnlNodeIdShort p_id, td::Promise promise) override; + void set_default_mtu(td::uint64 mtu) override; + RldpIn(td::actor::ActorId adnl) : adnl_(adnl) { } @@ -107,6 +109,8 @@ class RldpIn : public RldpImpl { std::set local_ids_; + td::optional custom_default_mtu_; + td::actor::ActorId create_connection(adnl::AdnlNodeIdShort src, adnl::AdnlNodeIdShort dst); }; diff --git a/rldp2/rldp.cpp b/rldp2/rldp.cpp index 765e38a5..95780b23 100644 --- a/rldp2/rldp.cpp +++ b/rldp2/rldp.cpp @@ -44,6 +44,9 @@ class RldpConnectionActor : public td::actor::Actor, private ConnectionCallback connection_.receive_raw(std::move(data)); yield(); } + void set_default_mtu(td::uint64 mtu) { + connection_.set_default_mtu(mtu); + } private: td::actor::ActorId rldp_; @@ -129,6 +132,9 @@ td::actor::ActorId RldpIn::create_connection(adnl::AdnlNode return it->second.get(); } auto connection = td::actor::create_actor("RldpConnection", actor_id(this), src, dst, adnl_); + if (custom_default_mtu_) { + td::actor::send_closure(connection, &RldpConnectionActor::set_default_mtu, custom_default_mtu_.value()); + } auto res = connection.get(); connections_[std::make_pair(src, dst)] = std::move(connection); return res; @@ -221,6 +227,13 @@ void RldpIn::get_conn_ip_str(adnl::AdnlNodeIdShort l_id, adnl::AdnlNodeIdShort p td::actor::send_closure(adnl_, &adnl::AdnlPeerTable::get_conn_ip_str, l_id, p_id, std::move(promise)); } +void RldpIn::set_default_mtu(td::uint64 mtu) { + custom_default_mtu_ = mtu; + for (auto &connection : connections_) { + td::actor::send_closure(connection.second, &RldpConnectionActor::set_default_mtu, mtu); + } +} + std::unique_ptr RldpIn::make_adnl_callback() { class Callback : public adnl::Adnl::Callback { private: diff --git a/rldp2/rldp.h b/rldp2/rldp.h index f4b26985..97fc126a 100644 --- a/rldp2/rldp.h +++ b/rldp2/rldp.h @@ -37,6 +37,8 @@ class Rldp : public adnl::AdnlSenderInterface { virtual void send_message_ex(adnl::AdnlNodeIdShort src, adnl::AdnlNodeIdShort dst, td::Timestamp timeout, td::BufferSlice data) = 0; + virtual void set_default_mtu(td::uint64 mtu) = 0; + static td::actor::ActorOwn create(td::actor::ActorId adnl); }; diff --git a/tl/generate/scheme/ton_api.tl b/tl/generate/scheme/ton_api.tl index 07aab3da..643825ea 100644 --- a/tl/generate/scheme/ton_api.tl +++ b/tl/generate/scheme/ton_api.tl @@ -720,11 +720,13 @@ storage.getPiece piece_id:int = storage.Piece; http.header name:string value:string = http.Header; http.payloadPart data:bytes trailer:(vector http.header) last:Bool = http.PayloadPart; http.response http_version:string status_code:int reason:string headers:(vector http.header) no_payload:Bool = http.Response; +http.proxy.capabilities capabilities:long = http.proxy.Capabilities; ---functions--- http.request id:int256 method:string url:string http_version:string headers:(vector http.header) = http.Response; http.getNextPayloadPart id:int256 seqno:int max_chunk_size:int = http.PayloadPart; +http.proxy.getCapabilities capabilities:long = http.proxy.Capabilities; ---types--- diff --git a/tl/generate/scheme/ton_api.tlo b/tl/generate/scheme/ton_api.tlo index 6a2e18f1640f473c854f3eb2dabdf8f076537438..3c81ca06d4678fc20baf53591156c6a347034762 100644 GIT binary patch delta 186 zcmey<$=cGx%KK=xz7+!${Mg9rEGZ+HGs#dqqokxjub?QuqEgQ}u^=%iGbghoGqrf~ z17?ZM0g@}k7)v+*)Hg0vMOU1Rq!=W3^O%qi7W Z7u}XZvM4>Z1lcYIO#R!>C^8x-0|3_(Lo)yX delta 55 zcmZqaVg1v|%KK=xz7+!$eBa3HEV;Qwa)lUU$!1*x+!&!f*(Zu;a)U0<ElSQNiWV^nIGx4RT jmN@6 Date: Sun, 26 Feb 2023 11:38:25 +0300 Subject: [PATCH 14/46] fix getAccountStateByTransaction (#623) Co-authored-by: ms --- emulator/transaction-emulator.h | 2 +- tonlib/tonlib/TonlibClient.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/emulator/transaction-emulator.h b/emulator/transaction-emulator.h index fe0e22cb..025bfa93 100644 --- a/emulator/transaction-emulator.h +++ b/emulator/transaction-emulator.h @@ -20,7 +20,7 @@ class TransactionEmulator { public: TransactionEmulator(block::Config&& config, int vm_log_verbosity = 0) : config_(std::move(config)), libraries_(256), vm_log_verbosity_(vm_log_verbosity), - unixtime_(0), lt_(0), rand_seed_(0), ignore_chksig_(false) { + unixtime_(0), lt_(0), rand_seed_(td::BitArray<256>::zero()), ignore_chksig_(false) { } struct EmulationResult { diff --git a/tonlib/tonlib/TonlibClient.cpp b/tonlib/tonlib/TonlibClient.cpp index c5e47fca..d56bcd1a 100644 --- a/tonlib/tonlib/TonlibClient.cpp +++ b/tonlib/tonlib/TonlibClient.cpp @@ -436,7 +436,11 @@ class AccountState { } td::Result> to_shardAccountCellSlice() const { - return vm::CellBuilder().store_ref(raw_.info.root).store_bits(raw_.info.last_trans_hash.as_bitslice()).store_long(raw_.info.last_trans_lt).as_cellslice_ref(); + auto account_root = raw_.info.root; + if (account_root.is_null()) { + block::gen::Account().cell_pack_account_none(account_root); + } + return vm::CellBuilder().store_ref(account_root).store_bits(raw_.info.last_trans_hash.as_bitslice()).store_long(raw_.info.last_trans_lt).as_cellslice_ref(); } //NB: Order is important! Used during guessAccountRevision @@ -1311,7 +1315,7 @@ class GetRawAccountState : public td::actor::Actor { ton::lite_api::liteServer_getAccountState( ton::create_tl_lite_block_id(block_id_.value()), ton::create_tl_object(address_.workchain, address_.addr)), - [self = this](auto r_state) { self->with_account_state(std::move(r_state)); }, block_id_.value().id.seqno); + [self = this](auto r_state) { self->with_account_state(std::move(r_state)); }); } td::Status do_with_last_block(td::Result r_last_block) { From d3b622a5273c071568202ded14928520c4142e1c Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Mon, 27 Feb 2023 08:49:03 +0300 Subject: [PATCH 15/46] Fix block_lt in account state by transaction (#624) * fix getAccountStateByTransaction * set correct block_lt --------- Co-authored-by: ms --- emulator/transaction-emulator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/emulator/transaction-emulator.cpp b/emulator/transaction-emulator.cpp index 8611378a..4a616418 100644 --- a/emulator/transaction-emulator.cpp +++ b/emulator/transaction-emulator.cpp @@ -83,6 +83,7 @@ td::Result TransactionEmulator::emulate_t ton::LogicalTime lt = record_trans.lt; ton::UnixTime utime = record_trans.now; account.now_ = utime; + account.block_lt = record_trans.lt - record_trans.lt % block::ConfigInfo::get_lt_align(); td::Ref msg_root = record_trans.r1.in_msg->prefetch_ref(); int tag = block::gen::t_TransactionDescr.get_tag(vm::load_cell_slice(record_trans.description)); From 470b97fa2c591c43dbc6dfabe8d344b9a4861b9c Mon Sep 17 00:00:00 2001 From: krigga Date: Mon, 27 Feb 2023 12:26:51 +0300 Subject: [PATCH 16/46] fix: parse capabilities in emulator config to have bounced message bodies (#620) --- emulator/emulator-extern.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emulator/emulator-extern.cpp b/emulator/emulator-extern.cpp index 01b9d5c7..811316db 100644 --- a/emulator/emulator-extern.cpp +++ b/emulator/emulator-extern.cpp @@ -59,7 +59,7 @@ const char *external_not_accepted_response(std::string&& vm_log, int vm_exit_cod td::Result decode_config(const char* config_boc) { TRY_RESULT_PREFIX(config_params_cell, boc_b64_to_cell(config_boc), "Can't deserialize config params boc: "); - auto global_config = block::Config(config_params_cell, td::Bits256::zero(), block::Config::needWorkchainInfo | block::Config::needSpecialSmc); + auto global_config = block::Config(config_params_cell, td::Bits256::zero(), block::Config::needWorkchainInfo | block::Config::needSpecialSmc | block::Config::needCapabilities); TRY_STATUS_PREFIX(global_config.unpack(), "Can't unpack config params: "); return global_config; } From 5a47495d876a2768b426cc72e284b8d1ad2d8b19 Mon Sep 17 00:00:00 2001 From: neodiX42 Date: Mon, 27 Feb 2023 10:32:41 +0100 Subject: [PATCH 17/46] Add cross-platform Linux and macOS binaries (statically compiled with NixPkgs) + wasm artifacts (#621) * fix build * nix flake * nix package * static musl build env * make all builds in static env * GH Actions nightly and static workflows * deb package * cmake install {adnl-proxy,blockchain-explorer,create-state,http-proxy,rldp-http-proxy,storage-cli} * nix flake: add static glibc build variant * GH Actions: deb-nightly-{musl,glibc_static}, tests-rolling jobs * rpm package * build blockchain-explorer * GH Actions: deb_rpm-nightly: ubuntu-{latest=>22.04} * crypto/pow-miner: skip install * ghactions: tests-rolling: show tests run, progress * ghactions: deb_rpm-nightly: add manual trigger [skip ci] * nix flake refactor * find_package=>pkg-config(zlib) FindZLIB.cmake can't find static zlib prior to CMake 3.24, so use pkg-config. * nix old glibc build * nix aarch64 support * packages: Populate APT and RPM repos at ton-repo - {deb,rpm}.sh: Separate build and install dirs - rpm.sh: Conditionally include lib/ - Accomodate local CI runs w/act * [skip ci] README packages * fix aarch64 build -Wnoerror=address * [skip ci] rpm set releasever * [skip ci] document local packages upload * m1 build: gate cpu=apple-m1 by clang version * packages: Ship musl binaries + old glibc dylibs * packages: macos build * nix: bump nixpkgs * fix windows CI build * [skip ci] nix: static aarch64 builds * packages: deb,rpm multiarch * ghactions: aarch64 musl deb,rpm build * [skip ci] deb build: deref source links, -x * [skip ci] nix darwin static build * [skip ci] nix common hostPkgs * [skip ci] brew: move formula over to homebrew-ton-repo * [skip ci] nix fix aarch64-linux build * [skip ci] ghactions: nix use GITHUB_TOKEN * [skip ci] Move from ton-repo to gh releases * [skip ci] ghactions aarch64-darwin self-hosted runner * [skip ci] ghactions deb,rpm nightly 10h timeout * [skip ci] fix brew install fixes Errno::EACCES: Permission denied @ dir_s_mkdir - /private/tmp/ton-XXX/bin/.brew_home * [skip ci] ghactions deb,rpm nightly: don't upload ton-packages as we gh release them later * [skip ci] README: brew instructions * [skip ci] nightly linux binaries release * [skip ci] packages: ship macos dylib * [skip ci] ghactions: Run Windows build nightly, upload to gh releases * nix: remove defaultPackage, switch to different oldglibc build method We used to rebuild nixpkgs-stable with old glibc, which broke on aarch64 due to its particular bootstrap toolchain. This just takes nixos 19.09's version of GCC but new dependencies, sidestepping the issue. * fix rpm release, add aur release * fix local (act) ci run * ghactions: linux-nightly: Print out SSH public keys * ghactions: bump cachix actions * nix: default devShell * [skip ci] rpm,aur: Ship lib * [skip ci] packages: windows: Remove CMake files from out * [skip ci] packages: Import chocolatey package * fixup! fix rpm release, add aur release * [skip ci] packages: aarch64-linux: build dylib as well * [skip ci] ghactions: run on self-hosted * [skip ci] ghactions: windows-nightly: Bump nodejs actions * [skip ci] nix: Only add Linux packaging tools on Linux * [skip ci] doc: document direct download binaries in README * fix tonlib android jni ci * fixup! fix tonlib android jni ci * [skip ci] ghactions: Update GH release dates Errata: doesn't update tags. * [skip ci] ghactions: Fix racy brew gh release by splitting arch * initiali commit - binaries only * fixes * fixes * fixes * fixes * remove packages dir for now * add storage-daemon storage-daemon-cli * fix emulator * try macos aarch64 * try macos aarch64 * try macos aarch64 * try macos aarch64 * try macos aarch64 * try macos aarch64 * try macos aarch64 * try macos aarch64 * try macos aarch64 * try macos aarch64 * try macos aarch64 sh * try macos aarch64 sh * try macos aarch64 bash * try macos aarch64 bash * try macos aarch64 bash * try macos aarch64 bash * try macos aarch64 bash * try macos aarch64 bash * try macos aarch64 bash * fix funcfiftlib compilation with emscripten * fix funcfiftlib compilation with emscripten * add github action to compile TON with emscripten * add github action to compile TON with emscripten * add github action to compile TON with emscripten * add github action to compile TON with emscripten * add github action to compile TON with emscripten * add github action to compile TON with emscripten * try macos aarch64 * fix funcfiftlib compilation with emscripten * fix funcfiftlib compilation with emscripten * add github action to compile TON with emscripten * disable aarch64 github actions for now * disable aarch64 github actions for now * trigger all GH actions * trigger all GH actions 2 * trigger all GH actions 3 * trigger all GH actions 4 * trigger all GH actions 5 * put back rldp-http-proxy to win build * put back rldp-http-proxy to win build * dont use pkgConfig for zlib * fix zlib_library * use BUILD_SHARED_LIBS flag for static compilation * test 1 * test 2 * add wasm binaries to release. test 3 * add simple binaries' execution test * build emulator-emscripten * build and add into artifacts wasm tlbc and emulator-emscripten * build and add into artifacts wasm tlbc and emulator-emscripten, 2 * build and add into artifacts wasm tlbc and emulator-emscripten, 3 * build and add into artifacts wasm tlbc and emulator-emscripten, 4 * build emulator-emscripten with static libs * minor nix mac aarch64 fix * add single artifacts to release * bypass $repo to Dockerfile * add wasm artifacts to release * add wasm artifacts to release * add wasm artifacts to release * add wasm artifacts to release * add more artifacts to release; remove compilation against Ubuntu 18.04. * retrieve GITHUB_TOKEN for ton-blockchain/ton * remove binary check for arm64 --------- Co-authored-by: tonthemoon --- .github/script/amd64-18.04.Dockerfile | 19 -- .github/script/amd64-20.04.Dockerfile | 5 +- .github/script/amd64-22.04.Dockerfile | 5 +- .github/script/arm64-18.04.Dockerfile | 19 -- .github/script/arm64-20.04.Dockerfile | 5 +- .github/script/arm64-22.04.Dockerfile | 5 +- .github/script/fift-func-wasm-build-ubuntu.sh | 56 ++-- .github/workflows/create-release.yml | 310 +++++++++++++++--- .github/workflows/docker-compile-ubuntu.yml | 4 +- .github/workflows/macos-11.7-compile.yml | 10 +- .github/workflows/macos-12.6-compile.yml | 10 +- .github/workflows/ton-aarch64-linux.yml | 48 +++ .github/workflows/ton-aarch64-macos.yml | 45 +++ ...8.04-ton-ccpcheck.yml => ton-ccpcheck.yml} | 2 +- .github/workflows/ton-wasm-emscripten.yml | 40 +++ .github/workflows/ton-x86-64-linux.yml | 47 +++ .github/workflows/ton-x86-64-macos.yml | 43 +++ ...4-compile.yml => ubuntu-22.04-compile.yml} | 12 +- .github/workflows/ubuntu-compile.yml | 11 +- .github/workflows/win-2019-compile.yml | 12 +- CMake/FindMHD.cmake | 15 +- CMakeLists.txt | 15 +- README.md | 2 +- adnl/CMakeLists.txt | 2 + blockchain-explorer/CMakeLists.txt | 26 +- crypto/CMakeLists.txt | 2 +- emulator/CMakeLists.txt | 4 +- example/android/CMakeLists.txt | 2 +- example/android/build.sh | 2 +- example/android/export.sh | 2 +- flake.lock | 76 +++++ flake.nix | 137 ++++++++ http/CMakeLists.txt | 2 + rldp-http-proxy/CMakeLists.txt | 2 + shell.nix | 10 + storage/CMakeLists.txt | 4 +- tdutils/CMakeLists.txt | 3 +- tonlib/CMakeLists.txt | 22 +- validator/validator-group.hpp | 2 + 39 files changed, 858 insertions(+), 180 deletions(-) delete mode 100644 .github/script/amd64-18.04.Dockerfile delete mode 100644 .github/script/arm64-18.04.Dockerfile create mode 100644 .github/workflows/ton-aarch64-linux.yml create mode 100644 .github/workflows/ton-aarch64-macos.yml rename .github/workflows/{ubuntu-18.04-ton-ccpcheck.yml => ton-ccpcheck.yml} (95%) create mode 100644 .github/workflows/ton-wasm-emscripten.yml create mode 100644 .github/workflows/ton-x86-64-linux.yml create mode 100644 .github/workflows/ton-x86-64-macos.yml rename .github/workflows/{ubuntu-18.04-compile.yml => ubuntu-22.04-compile.yml} (88%) create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 shell.nix diff --git a/.github/script/amd64-18.04.Dockerfile b/.github/script/amd64-18.04.Dockerfile deleted file mode 100644 index 3e98b026..00000000 --- a/.github/script/amd64-18.04.Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM ubuntu:18.04 - -RUN apt update -RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata -RUN apt install -y build-essential cmake clang openssl libssl-dev zlib1g-dev gperf wget git curl libreadline-dev ccache libmicrohttpd-dev ninja-build - -WORKDIR / - -ARG BRANCH -RUN git clone --recurse-submodules https://github.com/ton-blockchain/ton.git && cd ton && git checkout $BRANCH - -WORKDIR /ton -RUN mkdir /ton/build -WORKDIR /ton/build -ENV CC clang -ENV CXX clang++ -ENV CCACHE_DISABLE 1 -RUN cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DPORTABLE=1 -DTON_ARCH= -DCMAKE_CXX_FLAGS="-mavx2" .. -RUN ninja storage-daemon storage-daemon-cli tonlibjson blockchain-explorer fift func validator-engine validator-engine-console create-state generate-random-id create-hardfork dht-server lite-client \ No newline at end of file diff --git a/.github/script/amd64-20.04.Dockerfile b/.github/script/amd64-20.04.Dockerfile index ab71bbf2..cab9caa5 100644 --- a/.github/script/amd64-20.04.Dockerfile +++ b/.github/script/amd64-20.04.Dockerfile @@ -2,12 +2,13 @@ FROM ubuntu:20.04 RUN apt update RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata -RUN apt install -y build-essential cmake clang openssl libssl-dev zlib1g-dev gperf wget git curl libreadline-dev ccache libmicrohttpd-dev ninja-build +RUN apt install -y build-essential cmake clang openssl libssl-dev zlib1g-dev gperf wget git curl libreadline-dev ccache libmicrohttpd-dev ninja-build pkg-config WORKDIR / ARG BRANCH -RUN git clone --recurse-submodules https://github.com/ton-blockchain/ton.git && cd ton && git checkout $BRANCH +ARG REPO +RUN git clone --recurse-submodules https://github.com/$REPO && cd ton && git checkout $BRANCH WORKDIR /ton RUN mkdir /ton/build diff --git a/.github/script/amd64-22.04.Dockerfile b/.github/script/amd64-22.04.Dockerfile index 0479aa0b..1ed30e77 100644 --- a/.github/script/amd64-22.04.Dockerfile +++ b/.github/script/amd64-22.04.Dockerfile @@ -2,12 +2,13 @@ FROM ubuntu:22.04 RUN apt update RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata -RUN apt install -y build-essential cmake clang openssl libssl-dev zlib1g-dev gperf wget git curl libreadline-dev ccache libmicrohttpd-dev ninja-build +RUN apt install -y build-essential cmake clang openssl libssl-dev zlib1g-dev gperf wget git curl libreadline-dev ccache libmicrohttpd-dev ninja-build pkg-config WORKDIR / ARG BRANCH -RUN git clone --recurse-submodules https://github.com/ton-blockchain/ton.git && cd ton && git checkout $BRANCH +ARG REPO +RUN git clone --recurse-submodules https://github.com/$REPO && cd ton && git checkout $BRANCH WORKDIR /ton RUN mkdir /ton/build diff --git a/.github/script/arm64-18.04.Dockerfile b/.github/script/arm64-18.04.Dockerfile deleted file mode 100644 index 6c527a45..00000000 --- a/.github/script/arm64-18.04.Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM ubuntu:18.04 - -RUN apt update -RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata -RUN apt install -y build-essential cmake clang openssl libssl-dev zlib1g-dev gperf wget git curl libreadline-dev ccache libmicrohttpd-dev ninja-build - -WORKDIR / - -ARG BRANCH -RUN git clone --recurse-submodules https://github.com/ton-blockchain/ton.git && cd ton && git checkout $BRANCH - -WORKDIR /ton -RUN mkdir /ton/build -WORKDIR /ton/build -ENV CC clang -ENV CXX clang++ -ENV CCACHE_DISABLE 1 -RUN cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DPORTABLE=1 -DTON_ARCH= .. -RUN ninja storage-daemon storage-daemon-cli tonlibjson blockchain-explorer fift func validator-engine validator-engine-console create-state generate-random-id dht-server lite-client \ No newline at end of file diff --git a/.github/script/arm64-20.04.Dockerfile b/.github/script/arm64-20.04.Dockerfile index 7b2348fd..9066c0f4 100644 --- a/.github/script/arm64-20.04.Dockerfile +++ b/.github/script/arm64-20.04.Dockerfile @@ -2,12 +2,13 @@ FROM ubuntu:20.04 RUN apt update RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata -RUN apt install -y build-essential cmake clang openssl libssl-dev zlib1g-dev gperf wget git curl libreadline-dev ccache libmicrohttpd-dev ninja-build +RUN apt install -y build-essential cmake clang openssl libssl-dev zlib1g-dev gperf wget git curl libreadline-dev ccache libmicrohttpd-dev ninja-build pkg-config WORKDIR / ARG BRANCH -RUN git clone --recurse-submodules https://github.com/ton-blockchain/ton.git && cd ton && git checkout $BRANCH +ARG REPO +RUN git clone --recurse-submodules https://github.com/$REPO && cd ton && git checkout $BRANCH WORKDIR /ton RUN mkdir /ton/build diff --git a/.github/script/arm64-22.04.Dockerfile b/.github/script/arm64-22.04.Dockerfile index d0ea491b..e2cbfadc 100644 --- a/.github/script/arm64-22.04.Dockerfile +++ b/.github/script/arm64-22.04.Dockerfile @@ -2,12 +2,13 @@ FROM ubuntu:22.04 RUN apt update RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata -RUN apt install -y build-essential cmake clang openssl libssl-dev zlib1g-dev gperf wget git curl libreadline-dev ccache libmicrohttpd-dev ninja-build +RUN apt install -y build-essential cmake clang openssl libssl-dev zlib1g-dev gperf wget git curl libreadline-dev ccache libmicrohttpd-dev ninja-build pkg-config WORKDIR / ARG BRANCH -RUN git clone --recurse-submodules https://github.com/ton-blockchain/ton.git && cd ton && git checkout $BRANCH +ARG REPO +RUN git clone --recurse-submodules https://github.com/$REPO && cd ton && git checkout $BRANCH WORKDIR /ton RUN mkdir /ton/build diff --git a/.github/script/fift-func-wasm-build-ubuntu.sh b/.github/script/fift-func-wasm-build-ubuntu.sh index 505ce137..b02fe3ee 100755 --- a/.github/script/fift-func-wasm-build-ubuntu.sh +++ b/.github/script/fift-func-wasm-build-ubuntu.sh @@ -1,50 +1,48 @@ -# The script build funcfift compiler to WASM +# The script builds funcfift compiler to WASM # dependencies: -#sudo apt-get install -y build-essential git make cmake clang libgflags-dev zlib1g-dev libssl-dev libreadline-dev libmicrohttpd-dev pkg-config libgsl-dev python3 python3-dev python3-pip nodejs +#sudo apt-get install -y build-essential git make cmake clang libgflags-dev zlib1g-dev libssl-dev libreadline-dev libmicrohttpd-dev pkg-config libgsl-dev python3 python3-dev python3-pip nodejs libevent-dev export CC=$(which clang) export CXX=$(which clang++) export CCACHE_DISABLE=1 +cd ../.. +rm -rf openssl zlib emsdk build +echo `pwd` + git clone https://github.com/openssl/openssl.git cd openssl git checkout OpenSSL_1_1_1j - ./config -make -j4 - +make -j16 OPENSSL_DIR=`pwd` - cd .. git clone https://github.com/madler/zlib.git cd zlib ZLIB_DIR=`pwd` - cd .. -# clone ton repo -git clone --recursive https://github.com/the-ton-tech/ton-blockchain.git - -# only to generate auto-block.cpp - -cd ton-blockchain -git pull -git checkout 1566a23b2bece49fd1de9ab2f35e88297d22829f mkdir build cd build -cmake -DCMAKE_BUILD_TYPE=Release -DZLIB_LIBRARY=/usr/lib/x86_64-linux-gnu/libz.so -DZLIB_INCLUDE_DIR=$ZLIB_DIR -DOPENSSL_ROOT_DIR=$OPENSSL_DIR -DOPENSSL_INCLUDE_DIR=$OPENSSL_DIR/include -DOPENSSL_CRYPTO_LIBRARY=$OPENSSL_DIR/libcrypto.so -DOPENSSL_SSL_LIBRARY=$OPENSSL_DIR/libssl.so .. -make -j4 fift +cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DZLIB_LIBRARY=/usr/lib/x86_64-linux-gnu/libz.so -DZLIB_INCLUDE_DIR=$ZLIB_DIR -DOPENSSL_ROOT_DIR=$OPENSSL_DIR -DOPENSSL_INCLUDE_DIR=$OPENSSL_DIR/include -DOPENSSL_CRYPTO_LIBRARY=$OPENSSL_DIR/libcrypto.so -DOPENSSL_SSL_LIBRARY=$OPENSSL_DIR/libssl.so -DTON_USE_ABSEIL=OFF .. + +test $? -eq 0 || { echo "Can't configure TON build"; exit 1; } + + +ninja fift smc-envelope + +test $? -eq 0 || { echo "Can't compile fift "; exit 1; } rm -rf * -cd ../.. +cd .. git clone https://github.com/emscripten-core/emsdk.git cd emsdk -./emsdk install latest -./emsdk activate latest +./emsdk install 3.1.19 +./emsdk activate 3.1.19 EMSDK_DIR=`pwd` source $EMSDK_DIR/emsdk_env.sh @@ -55,7 +53,8 @@ export CCACHE_DISABLE=1 cd ../zlib emconfigure ./configure --static -emmake make -j4 +emmake make -j16 +test $? -eq 0 || { echo "Can't compile zlib with emmake "; exit 1; } ZLIB_DIR=`pwd` cd ../openssl @@ -66,14 +65,13 @@ sed -i 's/CROSS_COMPILE=.*/CROSS_COMPILE=/g' Makefile sed -i 's/-ldl//g' Makefile sed -i 's/-O3/-Os/g' Makefile emmake make depend -emmake make -j4 - -cd ../ton-blockchain - -cd build - -emcmake cmake -DUSE_EMSCRIPTEN=ON -DCMAKE_BUILD_TYPE=Release -DZLIB_LIBRARY=$ZLIB_DIR/libz.a -DZLIB_INCLUDE_DIR=$ZLIB_DIR -DOPENSSL_ROOT_DIR=$OPENSSL_DIR -DOPENSSL_INCLUDE_DIR=$OPENSSL_DIR/include -DOPENSSL_CRYPTO_LIBRARY=$OPENSSL_DIR/libcrypto.a -DOPENSSL_SSL_LIBRARY=$OPENSSL_DIR/libssl.a -DCMAKE_TOOLCHAIN_FILE=$EMSDK_DIR/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_CXX_FLAGS="-pthread -sUSE_ZLIB=1" .. +emmake make -j16 +test $? -eq 0 || { echo "Can't compile OpenSSL with emmake "; exit 1; } +cd ../build +emcmake cmake -DUSE_EMSCRIPTEN=ON -DCMAKE_BUILD_TYPE=Release -DZLIB_LIBRARY=$ZLIB_DIR/libz.a -DZLIB_INCLUDE_DIR=$ZLIB_DIR -DOPENSSL_ROOT_DIR=$OPENSSL_DIR -DOPENSSL_INCLUDE_DIR=$OPENSSL_DIR/include -DOPENSSL_CRYPTO_LIBRARY=$OPENSSL_DIR/libcrypto.a -DOPENSSL_SSL_LIBRARY=$OPENSSL_DIR/libssl.a -DCMAKE_TOOLCHAIN_FILE=$EMSDK_DIR/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_CXX_FLAGS="-sUSE_ZLIB=1" .. +test $? -eq 0 || { echo "Can't configure TON with with emmake "; exit 1; } cp -R ../crypto/smartcont ../crypto/fift/lib crypto -emmake make -j4 funcfiftlib +emmake make -j16 funcfiftlib func fift tlbc emulator-emscripten + diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 3319bc70..a9ff3c4c 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -11,42 +11,69 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Show all artifacts - run: | - mkdir artifacts - ls -lart artifacts - - - name: Download Ubuntu x86-64 artifacts + - name: Download Linux x86-64 artifacts uses: dawidd6/action-download-artifact@v2 with: - workflow: ubuntu-compile.yml + workflow: ton-x86-64-linux.yml path: artifacts workflow_conclusion: success skip_unpack: true - - name: Download Ubuntu arm64 artifacts + - name: Download and unzip Linux x86-64 artifacts uses: dawidd6/action-download-artifact@v2 with: - workflow: docker-compile-ubuntu.yml + workflow: ton-x86-64-linux.yml + path: artifacts + workflow_conclusion: success + skip_unpack: false + +# - name: Download Linux arm64 artifacts +# uses: dawidd6/action-download-artifact@v2 +# with: +# workflow: ton-aarch64-linux.yml +# path: artifacts +# workflow_conclusion: success +# skip_unpack: true +# +# - name: Download and unzip Linux arm64 artifacts +# uses: dawidd6/action-download-artifact@v2 +# with: +# workflow: ton-aarch64-linux.yml +# path: artifacts +# workflow_conclusion: success +# skip_unpack: false + + - name: Download Mac x86-64 artifacts + uses: dawidd6/action-download-artifact@v2 + with: + workflow: ton-x86-64-macos.yml path: artifacts workflow_conclusion: success skip_unpack: true - - name: Download MacOS 11.7 artifacts + - name: Download and unzip Mac x86-64 artifacts uses: dawidd6/action-download-artifact@v2 with: - workflow: macos-11.7-compile.yml + workflow: ton-x86-64-macos.yml path: artifacts workflow_conclusion: success - skip_unpack: true + skip_unpack: false - - name: Download MacOS 12.6 artifacts - uses: dawidd6/action-download-artifact@v2 - with: - workflow: macos-12.6-compile.yml - path: artifacts - workflow_conclusion: success - skip_unpack: true +# - name: Download Mac arm64 artifacts +# uses: dawidd6/action-download-artifact@v2 +# with: +# workflow: ton-aarch64-macos.yml +# path: artifacts +# workflow_conclusion: success +# skip_unpack: true +# +# - name: Download and unzip Mac arm64 artifacts +# uses: dawidd6/action-download-artifact@v2 +# with: +# workflow: ton-aarch64-macos.yml +# path: artifacts +# workflow_conclusion: success +# skip_unpack: false - name: Download Windows artifacts uses: dawidd6/action-download-artifact@v2 @@ -56,9 +83,26 @@ jobs: workflow_conclusion: success skip_unpack: true + - name: Download and unzip Windows artifacts + uses: dawidd6/action-download-artifact@v2 + with: + workflow: win-2019-compile.yml + path: artifacts + workflow_conclusion: success + skip_unpack: false + + - name: Download WASM artifacts + uses: dawidd6/action-download-artifact@v2 + with: + workflow: ton-wasm-emscripten.yml + path: artifacts + workflow_conclusion: success + skip_unpack: true + - name: Show all artifacts run: | tree artifacts + # create release @@ -79,7 +123,7 @@ jobs: - name: Get registration token id: getRegToken run: | - curl -X POST -H \"Accept: application/vnd.github+json\" -H 'Authorization: token ${{ secrets.GITHUB_TOKEN }}' https://api.github.com/repos/neodix42/HardTestDevelopment/actions/runners/registration-token + curl -X POST -H \"Accept: application/vnd.github+json\" -H 'Authorization: token ${{ secrets.GITHUB_TOKEN }}' https://api.github.com/repos/ton-blockchain/ton/actions/runners/registration-token - name: Create release id: create_release @@ -94,74 +138,250 @@ jobs: draft: false prerelease: false +# upload + +# win + - name: Upload Windows 2019 artifacts uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: artifacts/ton-win-binaries.zip - asset_name: ton-windows-2019-x86-64.zip + asset_name: ton-win-x86-64.zip tag: v${{ steps.date.outputs.date }} - - name: Upload MacOS 11.7 x86-64 artifacts + - name: Upload Windows 2019 single artifact - fift uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} - file: artifacts/ton-macos-11.7.zip - asset_name: ton-macos-11.7-x86-64.zip + file: artifacts/ton-win-binaries/fift.exe + asset_name: fift.exe tag: v${{ steps.date.outputs.date }} - - name: Upload MacOS 12.6 x86-64 artifacts + - name: Upload Windows 2019 single artifact - func uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} - file: artifacts/ton-macos-12.6.zip - asset_name: ton-macos-12.6-x86-64.zip + file: artifacts/ton-win-binaries/func.exe + asset_name: func.exe tag: v${{ steps.date.outputs.date }} - - name: Upload Ubuntu 18.04 x86-64 artifacts + - name: Upload Windows 2019 single artifact - lite-client uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} - file: artifacts/ton-binaries-ubuntu-18.04.zip - asset_name: ton-ubuntu-18.04-x86-64.zip + file: artifacts/ton-win-binaries/lite-client.exe + asset_name: lite-client.exe tag: v${{ steps.date.outputs.date }} - - name: Upload Ubuntu 20.04 x86-64 artifacts + - name: Upload Windows 2019 single artifact - rldp-http-proxy uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} - file: artifacts/ton-binaries-ubuntu-20.04.zip - asset_name: ton-ubuntu-20.04-x86-64.zip + file: artifacts/ton-win-binaries/rldp-http-proxy.exe + asset_name: rldp-http-proxy.exe tag: v${{ steps.date.outputs.date }} - - name: Upload Ubuntu 22.04 x86-64 artifacts + - name: Upload Windows 2019 single artifact - http-proxy uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} - file: artifacts/ton-binaries-ubuntu-22.04.zip - asset_name: ton-ubuntu-22.04-x86-64.zip + file: artifacts/ton-win-binaries/http-proxy.exe + asset_name: http-proxy.exe tag: v${{ steps.date.outputs.date }} - - name: Upload Ubuntu 18.04 arm64 artifacts + - name: Upload Windows 2019 single artifact - storage-daemon-cli uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} - file: artifacts/ton-ubuntu-18.04-arm64.zip - asset_name: ton-ubuntu-18.04-arm64.zip + file: artifacts/ton-win-binaries/storage-daemon-cli.exe + asset_name: storage-daemon-cli.exe tag: v${{ steps.date.outputs.date }} - - name: Upload Ubuntu 20.04 arm64 artifacts + - name: Upload Windows 2019 single artifact - tonlibjson uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} - file: artifacts/ton-ubuntu-20.04-arm64.zip - asset_name: ton-ubuntu-20.04-arm64.zip + file: artifacts/ton-win-binaries/tonlibjson.dll + asset_name: tonlibjson.dll tag: v${{ steps.date.outputs.date }} - - name: Upload Ubuntu 22.04 arm64 artifacts + - name: Upload Windows 2019 single artifact - tonlib-cli uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} - file: artifacts/ton-ubuntu-22.04-arm64.zip - asset_name: ton-ubuntu-22.04-arm64.zip + file: artifacts/ton-win-binaries/tonlib-cli.exe + asset_name: tonlib-cli.exe tag: v${{ steps.date.outputs.date }} + +# mac x86-64 + + - name: Upload Mac x86-64 artifacts + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-macos-binaries.zip + asset_name: ton-mac-x86-64.zip + tag: v${{ steps.date.outputs.date }} + + - name: Upload Mac x86-64 single artifact - fift + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-macos-binaries/fift + asset_name: fift-mac-x86-64 + tag: v${{ steps.date.outputs.date }} + + - name: Upload Mac x86-64 single artifact - func + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-macos-binaries/func + asset_name: func-mac-x86-64 + tag: v${{ steps.date.outputs.date }} + + - name: Upload Mac x86-64 single artifact - lite-client + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-macos-binaries/lite-client + asset_name: lite-client-mac-x86-64 + tag: v${{ steps.date.outputs.date }} + + - name: Upload Mac x86-64 single artifact - rldp-http-proxy + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-macos-binaries/rldp-http-proxy + asset_name: rldp-http-proxy-mac-x86-64 + tag: v${{ steps.date.outputs.date }} + + - name: Upload Mac x86-64 single artifact - http-proxy + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-macos-binaries/http-proxy + asset_name: http-proxy-mac-x86-64 + tag: v${{ steps.date.outputs.date }} + + - name: Upload Mac x86-64 single artifact - storage-daemon-cli + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-macos-binaries/storage-daemon-cli + asset_name: storage-daemon-cli-mac-x86-64 + tag: v${{ steps.date.outputs.date }} + + - name: Upload Mac x86-64 single artifact - tonlibjson + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-macos-binaries/libtonlibjson.dylib + asset_name: tonlibjson-mac-x86-64.dylib + tag: v${{ steps.date.outputs.date }} + + - name: Upload Mac x86-64 single artifact - tonlib-cli + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-macos-binaries/tonlib-cli + asset_name: tonlib-cli-mac-x86-64 + tag: v${{ steps.date.outputs.date }} + +# linux x86-64 + + - name: Upload Linux x86-64 artifacts + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-linux-binaries.zip + asset_name: ton-linux-x86_64.zip + tag: v${{ steps.date.outputs.date }} + + - name: Upload Linux x86-64 single artifact - fift + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-linux-binaries/fift + asset_name: fift-linux-x86_64 + tag: v${{ steps.date.outputs.date }} + + - name: Upload Linux x86-64 single artifact - func + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-linux-binaries/func + asset_name: func-linux-x86_64 + tag: v${{ steps.date.outputs.date }} + + - name: Upload Linux x86-64 single artifact - lite-client + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-linux-binaries/lite-client + asset_name: lite-client-linux-x86_64 + tag: v${{ steps.date.outputs.date }} + + - name: Upload Linux x86-64 single artifact - rldp-http-proxy + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-linux-binaries/rldp-http-proxy + asset_name: rldp-http-proxy-linux-x86_64 + tag: v${{ steps.date.outputs.date }} + + - name: Upload Linux x86-64 single artifact - http-proxy + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-linux-binaries/http-proxy + asset_name: http-proxy-linux-x86_64 + tag: v${{ steps.date.outputs.date }} + + - name: Upload Linux x86-64 single artifact - storage-daemon-cli + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-linux-binaries/storage-daemon-cli + asset_name: storage-daemon-cli-linux-x86_64 + tag: v${{ steps.date.outputs.date }} + + - name: Upload Linux x86-64 single artifact - tonlibjson + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-linux-binaries/libtonlibjson.so.0.5 + asset_name: tonlibjson-linux-x86_64.so + tag: v${{ steps.date.outputs.date }} + + - name: Upload Linux x86-64 single artifact - tonlib-cli + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-linux-binaries/tonlib-cli + asset_name: tonlib-cli-linux-x86_64 + tag: v${{ steps.date.outputs.date }} + +# - name: Upload Linux arm64 artifacts +# uses: svenstaro/upload-release-action@v2 +# with: +# repo_token: ${{ secrets.GITHUB_TOKEN }} +# file: artifacts/ton-aarch64-linux-binaries.zip +# asset_name: ton-linux-arm64.zip +# tag: v${{ steps.date.outputs.date }} +# +# - name: Upload Mac arm64 artifacts +# uses: svenstaro/upload-release-action@v2 +# with: +# repo_token: ${{ secrets.GITHUB_TOKEN }} +# file: artifacts/ton-aarch64-macos-binaries +# asset_name: ton-mac-arm64.zip +# tag: v${{ steps.date.outputs.date }} + + - name: Upload WASM artifacts + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-wasm-binaries.zip + asset_name: ton-wasm-binaries.zip + tag: v${{ steps.date.outputs.date }} \ No newline at end of file diff --git a/.github/workflows/docker-compile-ubuntu.yml b/.github/workflows/docker-compile-ubuntu.yml index 08caab23..41fbb8e0 100644 --- a/.github/workflows/docker-compile-ubuntu.yml +++ b/.github/workflows/docker-compile-ubuntu.yml @@ -9,7 +9,7 @@ jobs: max-parallel: 3 matrix: arch: [arm64] - ver: [22.04, 18.04, 20.04 ] + ver: [22.04, 20.04 ] runs-on: ubuntu-22.04 steps: @@ -33,7 +33,7 @@ jobs: run: | mkdir build-${{matrix.ver}}-${{matrix.arch}} - docker buildx build --build-arg BRANCH=${{ steps.vars.outputs.short_ref }} --platform=linux/${{matrix.arch}} --progress=plain --load . -t build-${{matrix.ver}}-${{matrix.arch}} -f .github/script/${{matrix.arch}}-${{matrix.ver}}.Dockerfile + docker buildx build --build-arg REPO=${{ github.repository }} --build-arg BRANCH=${{ steps.vars.outputs.short_ref }} --platform=linux/${{matrix.arch}} --progress=plain --load . -t build-${{matrix.ver}}-${{matrix.arch}} -f .github/script/${{matrix.arch}}-${{matrix.ver}}.Dockerfile container_id=$(docker create --platform=linux/${{matrix.arch}} build-${{matrix.ver}}-${{matrix.arch}}) docker cp $container_id:/ton/build/dht-server/dht-server build-${{matrix.ver}}-${{matrix.arch}}/ docker cp -a $container_id:/ton/build/validator-engine/validator-engine build-${{matrix.ver}}-${{matrix.arch}}/ diff --git a/.github/workflows/macos-11.7-compile.yml b/.github/workflows/macos-11.7-compile.yml index 2d73c558..b1c97d3d 100644 --- a/.github/workflows/macos-11.7-compile.yml +++ b/.github/workflows/macos-11.7-compile.yml @@ -23,7 +23,7 @@ jobs: - name: Build all run: | export NONINTERACTIVE=1 - brew install ninja libmicrohttpd + brew install ninja libmicrohttpd pkg-config rootPath=`pwd` mkdir build cd build @@ -51,10 +51,18 @@ jobs: cp build/utils/generate-random-id artifacts/ cp build/utils/json2tlo artifacts/ cp build/adnl/adnl-proxy artifacts/ + chmod +x artifacts/* rsync -r crypto/smartcont artifacts/ rsync -r crypto/fift/lib artifacts/ ls -laRt artifacts + - name: Simple binaries test + run: | + artifacts/validator-engine -V + artifacts/lite-client -V + artifacts/fift -V + artifacts/func -V + - name: Upload artifacts uses: actions/upload-artifact@master with: diff --git a/.github/workflows/macos-12.6-compile.yml b/.github/workflows/macos-12.6-compile.yml index 5a47dbbb..c7e2e99b 100644 --- a/.github/workflows/macos-12.6-compile.yml +++ b/.github/workflows/macos-12.6-compile.yml @@ -23,7 +23,7 @@ jobs: - name: Build all run: | export NONINTERACTIVE=1 - brew install ninja libmicrohttpd + brew install ninja libmicrohttpd pkg-config rootPath=`pwd` mkdir build cd build @@ -51,10 +51,18 @@ jobs: cp build/utils/generate-random-id artifacts/ cp build/utils/json2tlo artifacts/ cp build/adnl/adnl-proxy artifacts/ + chmod +x artifacts/* rsync -r crypto/smartcont artifacts/ rsync -r crypto/fift/lib artifacts/ ls -laRt artifacts + - name: Simple binaries test + run: | + artifacts/validator-engine -V + artifacts/lite-client -V + artifacts/fift -V + artifacts/func -V + - name: Upload artifacts uses: actions/upload-artifact@master with: diff --git a/.github/workflows/ton-aarch64-linux.yml b/.github/workflows/ton-aarch64-linux.yml new file mode 100644 index 00000000..83ad8694 --- /dev/null +++ b/.github/workflows/ton-aarch64-linux.yml @@ -0,0 +1,48 @@ +name: "TON aarch64 Linux binaries" + +on: [workflow_dispatch,workflow_call] + +jobs: + build: + runs-on: ubuntu-22.04 + + steps: + - run: | + sudo apt update + sudo apt install -y apt-utils + sudo apt install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static + + - uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - uses: cachix/install-nix-action@v18 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + + - name: Compile + run: nix build .?submodules=1#packages.aarch64-linux.ton-oldglibc_staticbinaries --print-build-logs --system aarch64-linux -o result-aarch64 + + - name: Copy binaries + run: | + ls -lart + mkdir artifacts + cp $PWD/result-aarch64-linux/bin/* artifacts/ + chmod +x artifacts/* + cp $PWD/result-aarch64-linux/lib/libtonlibjson.so.0.5 artifacts/ + cp -R crypto/smartcont artifacts/ + cp -R crypto/fift/lib artifacts/ + + - name: Simple binaries test + run: | + artifacts/validator-engine -V + artifacts/lite-client -V + artifacts/fift -V + artifacts/func -V + + - name: Upload artifacts + uses: actions/upload-artifact@master + with: + name: ton-aarch64-linux-binaries + path: artifacts \ No newline at end of file diff --git a/.github/workflows/ton-aarch64-macos.yml b/.github/workflows/ton-aarch64-macos.yml new file mode 100644 index 00000000..855ffa6c --- /dev/null +++ b/.github/workflows/ton-aarch64-macos.yml @@ -0,0 +1,45 @@ +name: "TON aarch64 macOS binaries" + +on: [workflow_dispatch,workflow_call] + +jobs: + build: + runs-on: macos-12 + + steps: + - run: brew install qemu + + - uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - uses: cachix/install-nix-action@v18 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + + - name: Compile + run: nix build .?submodules=1#packages.aarch64-darwin.ton-staticbin-dylib --print-build-logs -o result-aarch64-darwin + + - name: Copy binaries + run: | + ls -lart + mkdir artifacts + cp $PWD/result-aarch64-darwin/bin/* artifacts/ + chmod +x artifacts/* + cp $PWD/result-aarch64-darwin/lib/libtonlibjson* artifacts/ + cp -R crypto/smartcont artifacts/ + cp -R crypto/fift/lib artifacts/ + + - name: Simple binaries test + run: | + artifacts/validator-engine -V + artifacts/lite-client -V + artifacts/fift -V + artifacts/func -V + + - name: Upload artifacts + uses: actions/upload-artifact@master + with: + name: ton-aarch64-macos-binaries + path: artifacts diff --git a/.github/workflows/ubuntu-18.04-ton-ccpcheck.yml b/.github/workflows/ton-ccpcheck.yml similarity index 95% rename from .github/workflows/ubuntu-18.04-ton-ccpcheck.yml rename to .github/workflows/ton-ccpcheck.yml index f440d7a5..c2295638 100644 --- a/.github/workflows/ubuntu-18.04-ton-ccpcheck.yml +++ b/.github/workflows/ton-ccpcheck.yml @@ -5,7 +5,7 @@ on: [push,workflow_dispatch,workflow_call] jobs: build: - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 steps: - name: Check out repository diff --git a/.github/workflows/ton-wasm-emscripten.yml b/.github/workflows/ton-wasm-emscripten.yml new file mode 100644 index 00000000..1ce83ccc --- /dev/null +++ b/.github/workflows/ton-wasm-emscripten.yml @@ -0,0 +1,40 @@ +name: TON WASM Compile + +on: [push,workflow_dispatch,workflow_call] + +jobs: + build: + runs-on: ubuntu-22.04 + + steps: + - name: Check out repository + uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - name: Install libraries + run: | + sudo apt update + sudo apt install -y build-essential git make cmake ninja-build clang libgflags-dev zlib1g-dev libssl-dev libreadline-dev libmicrohttpd-dev pkg-config libgsl-dev python3 python3-dev python3-pip nodejs + + - name: Configure & Build + run: | + cd .github/script + ./fift-func-wasm-build-ubuntu.sh + + - name: Find & copy binaries + run: | + mkdir artifacts + ls build/crypto + cp build/crypto/fift* artifacts + cp build/crypto/func* artifacts + cp build/crypto/tlbc* artifacts + cp build/emulator/emulator-emscripten* artifacts + cp -R crypto/smartcont artifacts + cp -R crypto/fift/lib artifacts + + - name: Upload artifacts + uses: actions/upload-artifact@master + with: + name: ton-wasm-binaries + path: artifacts \ No newline at end of file diff --git a/.github/workflows/ton-x86-64-linux.yml b/.github/workflows/ton-x86-64-linux.yml new file mode 100644 index 00000000..0af0051b --- /dev/null +++ b/.github/workflows/ton-x86-64-linux.yml @@ -0,0 +1,47 @@ +name: "TON x86_64 Linux binaries" + +on: [push,workflow_dispatch,workflow_call] + +jobs: + build: + runs-on: ubuntu-22.04 + + steps: + - run: | + sudo apt update + sudo apt install -y apt-utils + + - uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - uses: cachix/install-nix-action@v18 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + + - name: Compile + run: nix build .?submodules=1#packages.x86_64-linux.ton-oldglibc_staticbinaries --print-build-logs --system x86_64-linux -o result-x86_64 + + - name: Copy binaries + run: | + ls -lart + mkdir artifacts + cp $PWD/result-x86_64/bin/* artifacts/ + chmod +x artifacts/* + cp $PWD/result-x86_64/lib/libtonlibjson.so.0.5 artifacts/ + cp -R crypto/smartcont artifacts/ + cp -R crypto/fift/lib artifacts/ + + - name: Simple binaries test + run: | + artifacts/validator-engine -V + artifacts/lite-client -V + artifacts/fift -V + artifacts/func -V + + - name: Upload artifacts + uses: actions/upload-artifact@master + with: + name: ton-x86_64-linux-binaries + path: artifacts \ No newline at end of file diff --git a/.github/workflows/ton-x86-64-macos.yml b/.github/workflows/ton-x86-64-macos.yml new file mode 100644 index 00000000..9d490997 --- /dev/null +++ b/.github/workflows/ton-x86-64-macos.yml @@ -0,0 +1,43 @@ +name: "TON x86_64 macOS binaries" + +on: [push,workflow_dispatch,workflow_call] + +jobs: + build: + runs-on: macos-12 + + steps: + - uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - uses: cachix/install-nix-action@v18 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + + - name: Compile + run: nix build .?submodules=1#packages.x86_64-darwin.ton-staticbin-dylib --print-build-logs -o result-x86_64-darwin + + - name: Copy binaries + run: | + ls -lart + mkdir artifacts + cp $PWD/result-x86_64-darwin/bin/* artifacts/ + chmod +x artifacts/* + cp $PWD/result-x86_64-darwin/lib/libtonlibjson.dylib artifacts/ + cp -R crypto/smartcont artifacts/ + cp -R crypto/fift/lib artifacts/ + + - name: Simple binaries test + run: | + artifacts/validator-engine -V + artifacts/lite-client -V + artifacts/fift -V + artifacts/func -V + + - name: Upload artifacts + uses: actions/upload-artifact@master + with: + name: ton-x86_64-macos-binaries + path: artifacts \ No newline at end of file diff --git a/.github/workflows/ubuntu-18.04-compile.yml b/.github/workflows/ubuntu-22.04-compile.yml similarity index 88% rename from .github/workflows/ubuntu-18.04-compile.yml rename to .github/workflows/ubuntu-22.04-compile.yml index c661a683..d27d1b4d 100644 --- a/.github/workflows/ubuntu-18.04-compile.yml +++ b/.github/workflows/ubuntu-22.04-compile.yml @@ -1,11 +1,11 @@ -name: Ubuntu 18.04 Compile +name: Ubuntu 22.04 Compile on: [push,workflow_dispatch,workflow_call] jobs: build: - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 steps: - name: Check out repository @@ -36,9 +36,17 @@ jobs: run: | mkdir artifacts cp build/storage/storage-daemon/storage-daemon build/storage/storage-daemon/storage-daemon-cli build/crypto/fift build/crypto/tlbc build/crypto/func build/crypto/create-state build/validator-engine-console/validator-engine-console build/tonlib/tonlib-cli build/tonlib/libtonlibjson.so.0.5 build/http/http-proxy build/rldp-http-proxy/rldp-http-proxy build/dht-server/dht-server build/lite-client/lite-client build/validator-engine/validator-engine build/utils/generate-random-id build/utils/json2tlo build/adnl/adnl-proxy artifacts + chmod +x artifacts/* cp -R crypto/smartcont artifacts/ cp -R crypto/fift/lib artifacts/ + - name: Simple binaries test + run: | + artifacts/validator-engine -V + artifacts/lite-client -V + artifacts/fift -V + artifacts/func -V + - name: Upload artifacts uses: actions/upload-artifact@master with: diff --git a/.github/workflows/ubuntu-compile.yml b/.github/workflows/ubuntu-compile.yml index a91e7128..dd246a39 100644 --- a/.github/workflows/ubuntu-compile.yml +++ b/.github/workflows/ubuntu-compile.yml @@ -7,7 +7,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-18.04, ubuntu-20.04, ubuntu-22.04] + os: [ubuntu-20.04, ubuntu-22.04] runs-on: ${{ matrix.os }} steps: @@ -39,8 +39,17 @@ jobs: run: | mkdir artifacts-${{ matrix.os }} cp build-${{ matrix.os }}/storage/storage-daemon/storage-daemon build-${{ matrix.os }}/storage/storage-daemon/storage-daemon-cli build-${{ matrix.os }}/crypto/fift build-${{ matrix.os }}/crypto/tlbc build-${{ matrix.os }}/crypto/func build-${{ matrix.os }}/crypto/create-state build-${{ matrix.os }}/validator-engine-console/validator-engine-console build-${{ matrix.os }}/tonlib/tonlib-cli build-${{ matrix.os }}/tonlib/libtonlibjson.so.0.5 build-${{ matrix.os }}/http/http-proxy build-${{ matrix.os }}/rldp-http-proxy/rldp-http-proxy build-${{ matrix.os }}/dht-server/dht-server build-${{ matrix.os }}/lite-client/lite-client build-${{ matrix.os }}/validator-engine/validator-engine build-${{ matrix.os }}/utils/generate-random-id build-${{ matrix.os }}/utils/json2tlo build-${{ matrix.os }}/adnl/adnl-proxy artifacts-${{ matrix.os }} + chmod +x artifacts-${{ matrix.os }}/* cp -R crypto/smartcont artifacts-${{ matrix.os }} cp -R crypto/fift/lib artifacts-${{ matrix.os }} + + - name: Simple binaries test + run: | + artifacts-${{ matrix.os }}/validator-engine -V + artifacts-${{ matrix.os }}/lite-client -V + artifacts-${{ matrix.os }}/fift -V + artifacts-${{ matrix.os }}/func -V + - name: Upload artifacts uses: actions/upload-artifact@master with: diff --git a/.github/workflows/win-2019-compile.yml b/.github/workflows/win-2019-compile.yml index 9164014c..e3d0aa1e 100644 --- a/.github/workflows/win-2019-compile.yml +++ b/.github/workflows/win-2019-compile.yml @@ -28,7 +28,10 @@ jobs: path: zlib - name: Setup msbuild.exe - uses: microsoft/setup-msbuild@v1.0.2 + uses: microsoft/setup-msbuild@v1.1 + + - name: Install Pkg-config Lite + run: choco install pkgconfiglite - name: Compile zlib Win64 run: | @@ -68,8 +71,7 @@ jobs: - name: Check if validator-engine.exe exists run: | - set root=%cd% - copy %root%\build\validator-engine\Release\validator-engine.exe test + copy %cd%\build\validator-engine\Release\validator-engine.exe test - name: Find & copy binaries run: | @@ -77,9 +79,9 @@ jobs: mkdir artifacts\smartcont mkdir artifacts\lib - for %%I in (build\storage\storage-daemon\Release\storage-daemon.exe build\storage\storage-daemon\Release\storage-daemon-cli.exe build\blockchain-explorer\Release\blockchain-explorer.exe build\crypto\Release\fift.exe build\crypto\Release\tlbc.exe build\crypto\Release\func.exe build\crypto\Release\create-state.exe build\validator-engine-console\Release\validator-engine-console.exe build\tonlib\Release\tonlib-cli.exe build\tonlib\Release\tonlibjson.dll build\http\Release\http-proxy.exe build\rldp-http-proxy\Release\rldp-http-proxy.exe build\dht-server\Release\dht-server.exe build\lite-client\Release\lite-client.exe build\validator-engine\Release\validator-engine.exe build\utils\Release\generate-random-id.exe build\utils\Release\json2tlo.exe build\adnl\Release\adnl-proxy.exe) do copy %%I artifacts\ + for %%I in (build\storage\storage-daemon\storage-daemon.exe build\storage\storage-daemon\storage-daemon-cli.exe build\blockchain-explorer\blockchain-explorer.exe build\crypto\Release\fift.exe build\crypto\Release\tlbc.exe build\crypto\Release\func.exe build\crypto\Release\create-state.exe build\validator-engine-console\Release\validator-engine-console.exe build\tonlib\Release\tonlib-cli.exe build\tonlib\Release\tonlibjson.dll build\http\Release\http-proxy.exe build\rldp-http-proxy\Release\rldp-http-proxy.exe build\dht-server\Release\dht-server.exe build\lite-client\Release\lite-client.exe build\validator-engine\Release\validator-engine.exe build\utils\Release\generate-random-id.exe build\utils\Release\json2tlo.exe build\adnl\Release\adnl-proxy.exe) do copy %%I artifacts\ xcopy /e /k /h /i crypto\smartcont artifacts\smartcont - xcopy /e /k /h /i crypto\fift\lib artifacts\lib + xcopy /e /k /h /i crypto\fift\lib artifacts\lib - name: Upload artifacts uses: actions/upload-artifact@master diff --git a/CMake/FindMHD.cmake b/CMake/FindMHD.cmake index 822714a2..c4b94c0e 100644 --- a/CMake/FindMHD.cmake +++ b/CMake/FindMHD.cmake @@ -20,19 +20,6 @@ find_library( set(MHD_INCLUDE_DIRS ${MHD_INCLUDE_DIR}) set(MHD_LIBRARIES ${MHD_LIBRARY}) -# debug library on windows -# same naming convention as in qt (appending debug library with d) -# boost is using the same "hack" as us with "optimized" and "debug" -# official MHD project actually uses _d suffix -if (MSVC) - find_library( - MHD_LIBRARY_DEBUG - NAMES microhttpd_d microhttpd-10_d libmicrohttpd_d libmicrohttpd-dll_d - DOC "mhd debug library" - ) - set(MHD_LIBRARIES optimized ${MHD_LIBRARIES} debug ${MHD_LIBRARY_DEBUG}) -endif() - include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(mhd DEFAULT_MSG MHD_INCLUDE_DIR MHD_LIBRARY) +find_package_handle_standard_args(MHD DEFAULT_MSG MHD_INCLUDE_DIR MHD_LIBRARY) mark_as_advanced(MHD_INCLUDE_DIR MHD_LIBRARY) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ac5e85a..aafc79b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,7 +108,8 @@ set(TON_ARCH "native" CACHE STRING "Architecture, will be passed to -march=") #BEGIN M1 support EXECUTE_PROCESS( COMMAND uname -m COMMAND tr -d '\n' OUTPUT_VARIABLE ARCHITECTURE ) -if ((ARCHITECTURE MATCHES "arm64") AND (CMAKE_SYSTEM_NAME STREQUAL "Darwin")) +if ((ARCHITECTURE MATCHES "arm64") AND (CMAKE_SYSTEM_NAME STREQUAL "Darwin") AND + (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 13.0)) # only clang 13+ supports cpu=apple-m1 set(TON_ARCH "apple-m1") endif() #END M1 support @@ -135,7 +136,16 @@ set(CRC32C_BUILD_BENCHMARKS OFF CACHE BOOL "Build CRC32C's benchmarks") set(CRC32C_USE_GLOG OFF CACHE BOOL "Build CRC32C's tests with Google Logging") set(CRC32C_INSTALL OFF CACHE BOOL "Install CRC32C's header and library") message("Add crc32c") -add_subdirectory(third-party/crc32c EXCLUDE_FROM_ALL) +if (NOT MSVC) + set(OLD_CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) + # fix aarch64 build @ crc32c/src/crc32c_arm64_linux_check.h + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=address") + add_subdirectory(third-party/crc32c EXCLUDE_FROM_ALL) + set(CMAKE_CXX_FLAGS ${OLD_CMAKE_CXX_FLAGS}) + unset(OLD_CMAKE_CXX_FLAGS) +else() + add_subdirectory(third-party/crc32c EXCLUDE_FROM_ALL) +endif() set(CRC32C_FOUND 1) if (TON_USE_ROCKSDB) @@ -198,6 +208,7 @@ include(CheckCXXCompilerFlag) set(CMAKE_THREAD_PREFER_PTHREAD ON) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) +find_package(PkgConfig REQUIRED) find_package(ZLIB REQUIRED) if (TON_ARCH AND NOT MSVC) diff --git a/README.md b/README.md index 893717ba..0b1205d1 100644 --- a/README.md +++ b/README.md @@ -49,4 +49,4 @@ Usually, the response to your pull request will indicate which section it falls If a CI workflow fails not because of your changes but workflow issues, try to fix it yourself or contact one of the persons listed below via Telegram messenger: * **C/C++ CI (ccpp-linux.yml)**: TBD -* **C/C++ CI Win64 Compile (ccpp-win64.yml)**: TBD +* **C/C++ CI Win64 Compile (ccpp-win64.yml)**: TBD \ No newline at end of file diff --git a/adnl/CMakeLists.txt b/adnl/CMakeLists.txt index 954db27b..b287cba0 100644 --- a/adnl/CMakeLists.txt +++ b/adnl/CMakeLists.txt @@ -99,6 +99,8 @@ target_link_libraries(adnl-pong PUBLIC tdactor ton_crypto tl_api tdnet common add_library(adnltest STATIC ${ADNL_TEST_SOURCE}) target_include_directories(adnltest PUBLIC $) target_link_libraries(adnltest PUBLIC adnl ) + +install(TARGETS adnl-proxy RUNTIME DESTINATION bin) endif() #END internal diff --git a/blockchain-explorer/CMakeLists.txt b/blockchain-explorer/CMakeLists.txt index 0d02f01f..11328a7a 100644 --- a/blockchain-explorer/CMakeLists.txt +++ b/blockchain-explorer/CMakeLists.txt @@ -1,22 +1,28 @@ cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) +option(NIX "Use \"ON\" for a static build." OFF) -find_package(MHD) - -if (MHD_FOUND) - - set(BLOCHAIN_EXPLORER_SOURCE +set(BLOCHAIN_EXPLORER_SOURCE blockchain-explorer.cpp blockchain-explorer.hpp blockchain-explorer-http.cpp blockchain-explorer-http.hpp blockchain-explorer-query.cpp blockchain-explorer-query.hpp - ) +) - add_executable(blockchain-explorer ${BLOCHAIN_EXPLORER_SOURCE}) +add_executable(blockchain-explorer ${BLOCHAIN_EXPLORER_SOURCE}) + +if (NIX) + find_package(PkgConfig REQUIRED) + pkg_check_modules(MHD libmicrohttpd) + target_include_directories(blockchain-explorer PUBLIC ${MHD_INCLUDE_DIRS} ${MHD_STATIC_INCLUDE_DIRS}) + target_link_libraries(blockchain-explorer tdutils tdactor adnllite tl_lite_api tl-lite-utils ton_crypto ton_block ${MHD_LIBRARIES} ${MHD_STATIC_LIBRARIES}) +else() + find_package(MHD) target_include_directories(blockchain-explorer PUBLIC ${MHD_INCLUDE_DIRS}) - target_link_libraries(blockchain-explorer tdutils tdactor adnllite tl_lite_api tl-lite-utils - ton_crypto ton_block ${MHD_LIBRARY}) - + target_link_libraries(blockchain-explorer tdutils tdactor adnllite tl_lite_api tl-lite-utils ton_crypto ton_block ${MHD_LIBRARIES}) endif() + +install(TARGETS blockchain-explorer RUNTIME DESTINATION bin) + diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index 202e420b..a1b05b81 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -488,6 +488,6 @@ if (WINGETOPT_FOUND) target_link_libraries_system(test-weight-distr wingetopt) endif() -install(TARGETS fift func pow-miner RUNTIME DESTINATION bin) +install(TARGETS fift func create-state tlbc RUNTIME DESTINATION bin) install(DIRECTORY fift/lib/ DESTINATION lib/fift) install(DIRECTORY smartcont DESTINATION share/ton) diff --git a/emulator/CMakeLists.txt b/emulator/CMakeLists.txt index 43881c7d..2fb27519 100644 --- a/emulator/CMakeLists.txt +++ b/emulator/CMakeLists.txt @@ -19,7 +19,7 @@ set(EMULATOR_SOURCE ) set(EMULATOR_EMSCRIPTEN_SOURCE - transaction-emscripten.cpp + emulator-emscripten.cpp ) include(GenerateExportHeader) @@ -27,7 +27,7 @@ include(GenerateExportHeader) add_library(emulator_static STATIC ${EMULATOR_STATIC_SOURCE}) target_link_libraries(emulator_static PUBLIC ton_crypto ton_block smc-envelope) -add_library(emulator SHARED ${EMULATOR_SOURCE} ${EMULATOR_HEADERS}) +add_library(emulator STATIC ${EMULATOR_SOURCE} ${EMULATOR_HEADERS}) target_link_libraries(emulator PUBLIC emulator_static) generate_export_header(emulator EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/emulator_export.h) target_include_directories(emulator PUBLIC diff --git a/example/android/CMakeLists.txt b/example/android/CMakeLists.txt index 55eda220..fc8067dd 100644 --- a/example/android/CMakeLists.txt +++ b/example/android/CMakeLists.txt @@ -34,7 +34,7 @@ add_library( # Sets the name of the library. list(APPEND CMAKE_FIND_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/third_party/crypto/${ANDROID_ARCH_NAME}") set(TON_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../..) add_subdirectory(${TON_DIR} ton EXCLUDE_FROM_ALL) -target_link_libraries(native-lib tonlibjson_static) +target_link_libraries(native-lib tonlibjson) target_link_libraries(native-lib tonlib) #target_sources(native-lib PRIVATE ${ALL_TEST_SOURCE}) diff --git a/example/android/build.sh b/example/android/build.sh index b16a9df9..61681da2 100755 --- a/example/android/build.sh +++ b/example/android/build.sh @@ -33,7 +33,7 @@ mkdir -p build-$ARCH cd build-$ARCH -cmake .. -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -GNinja -DANDROID_ABI=${ABI} -DOPENSSL_ROOT_DIR=${OPENSSL_DIR}/${ARCH} -DTON_ARCH="" -DTON_ONLY_TONLIB=ON || exit 1 +cmake .. -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -GNinja -DANDROID_ABI=${ABI} -DOPENSSL_ROOT_DIR=${OPENSSL_DIR}/${ARCH} -DTON_ARCH="" -DTON_ONLY_TONLIB=ON -DBUILD_SHARED_LIBS=OFF || exit 1 ninja native-lib || exit 1 popd diff --git a/example/android/export.sh b/example/android/export.sh index 26367b9c..8f4e9162 100755 --- a/example/android/export.sh +++ b/example/android/export.sh @@ -2,7 +2,7 @@ pushd . mkdir -p build_native cd build_native -cmake -DTON_ONLY_TONLIB=ON .. || exit 1 +cmake -DTON_ONLY_TONLIB=ON -DBUILD_SHARED_LIBS=OFF .. || exit 1 cmake --build . --target prepare_cross_compiling || exit 2 #cmake --build . --target tl_generate_java || exit 1 popd diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..9a8b67eb --- /dev/null +++ b/flake.lock @@ -0,0 +1,76 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1650374568, + "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "b4a34015c698c7793d592d66adbab377907a2be8", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1652776076, + "narHash": "sha256-gzTw/v1vj4dOVbpBSJX4J0DwUR6LIyXo7/SuuTJp1kM=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "04c1b180862888302ddfb2e3ad9eaa63afc60cf8", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1659445012, + "narHash": "sha256-n8/7npmp3hLbPSTRHPW8EPO8qh9vJ10RgkRM3Ve4vfc=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "a9f66ae640146ac16b6e33d2359e9171b27b0993", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-22.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-trunk": { + "locked": { + "lastModified": 1659597264, + "narHash": "sha256-aI/r4XEZwMJnuDjIMnSiDm34vVXP6TPaWgqPIF/65SI=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "2b22614150a727a8aeedc10395dbd2ff6430841b", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "nixpkgs-stable": "nixpkgs-stable", + "nixpkgs-trunk": "nixpkgs-trunk" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..e7b0422e --- /dev/null +++ b/flake.nix @@ -0,0 +1,137 @@ +{ + inputs = { + nixpkgs-stable.url = "github:nixos/nixpkgs/nixos-22.05"; + nixpkgs-trunk.url = "github:nixos/nixpkgs"; + flake-compat = { + url = "github:edolstra/flake-compat"; + flake = false; + }; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs-stable, nixpkgs-trunk, flake-compat, flake-utils }: + let + ton = { host, pkgs ? host, stdenv ? pkgs.stdenv, staticGlibc ? false + , staticMusl ? false, staticExternalDeps ? staticGlibc }: + with host.lib; + stdenv.mkDerivation { + pname = "ton"; + version = "dev"; + + src = ./.; + + nativeBuildInputs = with host; + [ cmake ninja pkg-config git ] ++ + optionals stdenv.isLinux [ dpkg rpm createrepo_c pacman ]; + buildInputs = with pkgs; + # at some point nixpkgs' pkgsStatic will build with static glibc + # then we can skip these manual overrides + # and switch between pkgsStatic and pkgsStatic.pkgsMusl for static glibc and musl builds + if !staticExternalDeps then [ + openssl_1_1 + zlib + libmicrohttpd + ] else + [ + (openssl_1_1.override { static = true; }).dev + (zlib.override { shared = false; }).dev + pkgsStatic.libmicrohttpd.dev + ] ++ optional staticGlibc glibc.static; + + cmakeFlags = [ "-DTON_USE_ABSEIL=OFF" "-DNIX=ON" ] ++ optionals staticMusl [ + "-DCMAKE_CROSSCOMPILING=OFF" # pkgsStatic sets cross + ] ++ optionals (staticGlibc || staticMusl) [ + "-DCMAKE_LINK_SEARCH_START_STATIC=ON" + "-DCMAKE_LINK_SEARCH_END_STATIC=ON" + ]; + + LDFLAGS = optional staticExternalDeps (concatStringsSep " " [ + (optionalString stdenv.cc.isGNU "-static-libgcc") + "-static-libstdc++" + ]); + + postInstall = '' + moveToOutput bin "$bin" + ''; + + outputs = [ "bin" "out" ]; + }; + hostPkgs = system: + import nixpkgs-stable { + inherit system; + overlays = [ + (self: super: { + zchunk = nixpkgs-trunk.legacyPackages.${system}.zchunk; + }) + ]; + }; + in with flake-utils.lib; + (nixpkgs-stable.lib.recursiveUpdate + (eachSystem (with system; [ x86_64-linux aarch64-linux ]) (system: + let + host = hostPkgs system; + # look out for https://github.com/NixOS/nixpkgs/issues/129595 for progress on better infra for this + # + # nixos 19.09 ships with glibc 2.27 + # we could also just override glibc source to a particular release + # but then we'd need to port patches as well + nixos1909 = (import (builtins.fetchTarball { + url = "https://channels.nixos.org/nixos-19.09/nixexprs.tar.xz"; + sha256 = "1vp1h2gkkrckp8dzkqnpcc6xx5lph5d2z46sg2cwzccpr8ay58zy"; + }) { inherit system; }); + glibc227 = nixos1909.glibc // { pname = "glibc"; }; + stdenv227 = let + cc = host.wrapCCWith { + cc = nixos1909.buildPackages.gcc-unwrapped; + libc = glibc227; + bintools = host.binutils.override { libc = glibc227; }; + }; + in (host.overrideCC host.stdenv cc); + in rec { + packages = rec { + ton-normal = ton { inherit host; }; + ton-static = ton { + inherit host; + stdenv = host.makeStatic host.stdenv; + staticGlibc = true; + }; + ton-musl = + let pkgs = nixpkgs-stable.legacyPackages.${system}.pkgsStatic; + in ton { + inherit host; + inherit pkgs; + stdenv = + pkgs.gcc12Stdenv; # doesn't build on aarch64-linux w/default GCC 9 + staticMusl = true; + }; + ton-oldglibc = (ton { + inherit host; + stdenv = stdenv227; + staticExternalDeps = true; + }); + ton-oldglibc_staticbinaries = host.symlinkJoin { + name = "ton"; + paths = [ ton-musl.bin ton-oldglibc.out ]; + }; + }; + devShells.default = + host.mkShell { inputsFrom = [ packages.ton-normal ]; }; + })) (eachSystem (with system; [ x86_64-darwin aarch64-darwin ]) (system: + let host = hostPkgs system; + in rec { + packages = rec { + ton-normal = ton { inherit host; }; + ton-static = ton { + inherit host; + stdenv = host.makeStatic host.stdenv; + staticExternalDeps = true; + }; + ton-staticbin-dylib = host.symlinkJoin { + name = "ton"; + paths = [ ton-static.bin ton-normal.out ]; + }; + }; + devShells.default = + host.mkShell { inputsFrom = [ packages.ton-normal ]; }; + }))); +} diff --git a/http/CMakeLists.txt b/http/CMakeLists.txt index 9437f244..dbc57ec2 100644 --- a/http/CMakeLists.txt +++ b/http/CMakeLists.txt @@ -23,3 +23,5 @@ target_link_libraries(tonhttp PUBLIC tdactor ton_crypto tl_api tdnet ) add_executable(http-proxy http-proxy.cpp) target_include_directories(http-proxy PUBLIC $) target_link_libraries(http-proxy PRIVATE tonhttp git) + +install(TARGETS http-proxy RUNTIME DESTINATION bin) diff --git a/rldp-http-proxy/CMakeLists.txt b/rldp-http-proxy/CMakeLists.txt index 44f19a24..92cb01ac 100644 --- a/rldp-http-proxy/CMakeLists.txt +++ b/rldp-http-proxy/CMakeLists.txt @@ -3,3 +3,5 @@ cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) add_executable(rldp-http-proxy rldp-http-proxy.cpp DNSResolver.h DNSResolver.cpp) target_include_directories(rldp-http-proxy PUBLIC $) target_link_libraries(rldp-http-proxy PRIVATE tonhttp rldp rldp2 dht tonlib git) + +install(TARGETS rldp-http-proxy RUNTIME DESTINATION bin) diff --git a/shell.nix b/shell.nix new file mode 100644 index 00000000..6234bb4d --- /dev/null +++ b/shell.nix @@ -0,0 +1,10 @@ +(import + ( + let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) + { src = ./.; } +).shellNix diff --git a/storage/CMakeLists.txt b/storage/CMakeLists.txt index a53a42b8..840dfd46 100644 --- a/storage/CMakeLists.txt +++ b/storage/CMakeLists.txt @@ -36,7 +36,7 @@ set(STORAGE_CLI_SOURCE storage-cli.cpp ) -add_library(storage ${STORAGE_SOURCE}) +add_library(storage STATIC ${STORAGE_SOURCE}) target_link_libraries(storage tdutils tdactor tddb ton_crypto tl_api ${JEMALLOC_LIBRARIES}) target_include_directories(storage PUBLIC $ @@ -54,4 +54,4 @@ set(STORAGE_TEST_SOURCE add_subdirectory(storage-daemon) # Do not install it yet -#install(TARGETS storage-cli RUNTIME DESTINATION bin) +install(TARGETS storage-cli storage-daemon storage-daemon-cli RUNTIME DESTINATION bin) diff --git a/tdutils/CMakeLists.txt b/tdutils/CMakeLists.txt index fbcc74fd..7b577e4f 100644 --- a/tdutils/CMakeLists.txt +++ b/tdutils/CMakeLists.txt @@ -14,8 +14,9 @@ if (NOT DEFINED CMAKE_INSTALL_LIBDIR) set(CMAKE_INSTALL_LIBDIR "lib") endif() +find_package(PkgConfig REQUIRED) if (NOT ZLIB_FOUND) - find_package(ZLIB) + pkg_check_modules(ZLIB zlib) endif() if (ZLIB_FOUND) set(TD_HAVE_ZLIB 1) diff --git a/tonlib/CMakeLists.txt b/tonlib/CMakeLists.txt index 69b5318a..ae5bbe86 100644 --- a/tonlib/CMakeLists.txt +++ b/tonlib/CMakeLists.txt @@ -1,5 +1,7 @@ cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) +option(BUILD_SHARED_LIBS "Use \"OFF\" for a static build." ON) + if (NOT OPENSSL_FOUND) find_package(OpenSSL REQUIRED) endif() @@ -92,7 +94,7 @@ set(TONLIB_JSON_HEADERS tonlib/tonlib_client_json.h) set(TONLIB_JSON_SOURCE tonlib/tonlib_client_json.cpp) include(GenerateExportHeader) -if (NOT USE_EMSCRIPTEN) +if (NOT USE_EMSCRIPTEN AND BUILD_SHARED_LIBS) add_library(tonlibjson SHARED ${TONLIB_JSON_SOURCE} ${TONLIB_JSON_HEADERS}) else() add_library(tonlibjson STATIC ${TONLIB_JSON_SOURCE} ${TONLIB_JSON_HEADERS}) @@ -100,6 +102,9 @@ endif() target_link_libraries(tonlibjson PRIVATE tonlibjson_private) generate_export_header(tonlibjson EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/tonlib/tonlibjson_export.h) +if (!BUILD_SHARED_LIBS) + target_compile_definitions(tonlibjson PUBLIC TONLIBJSON_STATIC_DEFINE) +endif() target_include_directories(tonlibjson PUBLIC $ $) @@ -107,13 +112,6 @@ if (APPLE) set_target_properties(tonlibjson PROPERTIES LINK_FLAGS "-Wl,-exported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/tonlibclientjson_export_list") endif() -add_library(tonlibjson_static STATIC ${TONLIB_JSON_SOURCE} ${TONLIB_JSON_HEADERS}) -target_link_libraries(tonlibjson_static PRIVATE tonlibjson_private) -target_compile_definitions(tonlibjson_static PUBLIC TONLIBJSON_STATIC_DEFINE) -target_include_directories(tonlibjson_static PUBLIC - $ - $) - add_library(TonlibJson INTERFACE) target_link_libraries(TonlibJson INTERFACE tonlibjson) @@ -158,13 +156,17 @@ endif() install(FILES ${TONLIB_JSON_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/tonlib/tonlibjson_export.h DESTINATION include/tonlib/) -if (NOT USE_EMSCRIPTEN) +if (NOT USE_EMSCRIPTEN AND BUILD_SHARED_LIBS) install(EXPORT Tonlib FILE TonlibTargets.cmake NAMESPACE Tonlib:: DESTINATION lib/cmake/Tonlib ) + + # Add SOVERSION to shared libraries + set_property(TARGET tonlibjson PROPERTY SOVERSION ${TON_VERSION}) endif() + include(CMakePackageConfigHelpers) write_basic_package_version_file("TonlibConfigVersion.cmake" VERSION ${TON_VERSION} @@ -174,6 +176,4 @@ install(FILES "TonlibConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/TonlibConfigVers DESTINATION lib/cmake/Tonlib ) -# Add SOVERSION to shared libraries -set_property(TARGET tonlibjson PROPERTY SOVERSION ${TON_VERSION}) install(TARGETS tonlib-cli RUNTIME DESTINATION bin) diff --git a/validator/validator-group.hpp b/validator/validator-group.hpp index 6bba5887..da9193ba 100644 --- a/validator/validator-group.hpp +++ b/validator/validator-group.hpp @@ -24,6 +24,8 @@ #include "rldp/rldp.h" +#include + namespace ton { namespace validator { From 0578cb4a4285cf16e613129b85da21729fab7453 Mon Sep 17 00:00:00 2001 From: SpyCheese Date: Tue, 28 Feb 2023 09:06:09 +0000 Subject: [PATCH 18/46] Fix typos, UBs and warnings (#625) --- adnl/adnl-peer.cpp | 8 +- crypto/block/block-db.cpp | 8 ++ crypto/fift/words.cpp | 6 +- crypto/func/abscode.cpp | 6 +- crypto/func/analyzer.cpp | 2 +- crypto/func/builtins.cpp | 164 ++++++++++++++++++---------------- crypto/func/codegen.cpp | 15 ++-- crypto/func/func-main.cpp | 2 +- crypto/func/func.h | 8 +- crypto/func/parse-func.cpp | 2 +- crypto/test/modbigint.cpp | 4 +- crypto/test/test-bigint.cpp | 4 +- dht/dht-query.hpp | 14 +-- tdutils/test/List.cpp | 2 +- tl/generate/tl_writer_hpp.cpp | 69 +++++++++----- tl/tl/tl_json.h | 3 +- validator/db/celldb.cpp | 4 +- validator/impl/liteserver.cpp | 6 +- validator/token-manager.cpp | 5 +- 19 files changed, 193 insertions(+), 139 deletions(-) diff --git a/adnl/adnl-peer.cpp b/adnl/adnl-peer.cpp index c84db23c..108d5211 100644 --- a/adnl/adnl-peer.cpp +++ b/adnl/adnl-peer.cpp @@ -68,7 +68,9 @@ void AdnlPeerPairImpl::alarm() { } if (retry_send_at_ && retry_send_at_.is_in_past()) { retry_send_at_ = td::Timestamp::never(); - send_messages_in(std::move(pending_messages_), false); + auto messages = std::move(pending_messages_); + pending_messages_.clear(); + send_messages_in(std::move(messages), true); } alarm_timestamp().relax(next_dht_query_at_); alarm_timestamp().relax(next_db_update_at_); @@ -791,7 +793,9 @@ void AdnlPeerPairImpl::Conn::create_conn(td::actor::ActorId pe void AdnlPeerPairImpl::conn_change_state(AdnlConnectionIdShort id, bool ready) { if (ready) { if (pending_messages_.size() > 0) { - send_messages_in(std::move(pending_messages_), true); + auto messages = std::move(pending_messages_); + pending_messages_.clear(); + send_messages_in(std::move(messages), true); } } } diff --git a/crypto/block/block-db.cpp b/crypto/block/block-db.cpp index 2be1f580..21c7c0a0 100644 --- a/crypto/block/block-db.cpp +++ b/crypto/block/block-db.cpp @@ -624,6 +624,7 @@ void BlockDbImpl::get_block_by_id(ton::BlockId blk_id, bool need_data, td::Promi } } promise(it->second); + return; } promise(td::Status::Error(-666, "block not found in database")); } @@ -642,6 +643,7 @@ void BlockDbImpl::get_state_by_id(ton::BlockId blk_id, bool need_data, td::Promi } } promise(it->second); + return; } if (zerostate.not_null() && blk_id == zerostate->blk.id) { LOG(DEBUG) << "get_state_by_id(): zerostate requested"; @@ -666,6 +668,7 @@ void BlockDbImpl::get_out_queue_info_by_id(ton::BlockId blk_id, td::Promisesecond->data.is_null()) { LOG(DEBUG) << "loading data for state " << blk_id.to_str(); @@ -679,6 +682,7 @@ void BlockDbImpl::get_out_queue_info_by_id(ton::BlockId blk_id, td::Promisesecond->data.clone(), options); @@ -707,10 +711,12 @@ void BlockDbImpl::get_out_queue_info_by_id(ton::BlockId blk_id, td::Promisesecond->blk.root_hash != state_root->get_hash().bits()) { promise(td::Status::Error( -668, std::string{"state for block "} + blk_id.to_str() + " is invalid : state root hash mismatch")); + return; } vm::CellSlice cs = vm::load_cell_slice(state_root); if (!cs.have(64, 1) || cs.prefetch_ulong(32) != 0x9023afde) { promise(td::Status::Error(-668, std::string{"state for block "} + blk_id.to_str() + " is invalid")); + return; } auto out_queue_info = cs.prefetch_ref(); promise(Ref{true, blk_id, it2->second->blk.root_hash.cbits(), state_root->get_hash().bits(), @@ -758,6 +764,7 @@ void BlockDbImpl::save_new_block(ton::BlockIdExt id, td::BufferSlice data, int a auto save_res = save_db_file(id.file_hash, data, FMode::chk_if_exists | FMode::overwrite | FMode::chk_file_hash); if (save_res.is_error()) { promise(std::move(save_res)); + return; } auto sz = data.size(); auto lev = bb.alloc(id.id, id.root_hash, id.file_hash, data.size(), authority & 0xff); @@ -780,6 +787,7 @@ void BlockDbImpl::save_new_state(ton::BlockIdExt id, td::BufferSlice data, int a auto save_res = save_db_file(id.file_hash, data, FMode::chk_if_exists | FMode::overwrite | FMode::chk_file_hash); if (save_res.is_error()) { promise(std::move(save_res)); + return; } auto sz = data.size(); auto lev = bb.alloc(id.id, id.root_hash, id.file_hash, data.size(), authority & 0xff); diff --git a/crypto/fift/words.cpp b/crypto/fift/words.cpp index cc1595dc..7556a127 100644 --- a/crypto/fift/words.cpp +++ b/crypto/fift/words.cpp @@ -486,7 +486,7 @@ void interpret_make_pop(vm::Stack& stack) { } void interpret_is_string(vm::Stack& stack) { - stack.push_bool(stack.pop().type() == vm::StackEntry::t_string); + stack.push_bool(stack.pop_chk().type() == vm::StackEntry::t_string); } int make_utf8_char(char buffer[4], int x) { @@ -1285,7 +1285,7 @@ void interpret_atom_anon(vm::Stack& stack) { } void interpret_is_atom(vm::Stack& stack) { - stack.push_bool(stack.pop().is_atom()); + stack.push_bool(stack.pop_chk().is_atom()); } bool are_eqv(vm::StackEntry x, vm::StackEntry y) { @@ -1307,11 +1307,13 @@ bool are_eqv(vm::StackEntry x, vm::StackEntry y) { } void interpret_is_eqv(vm::Stack& stack) { + stack.check_underflow(2); auto y = stack.pop(), x = stack.pop(); stack.push_bool(are_eqv(std::move(x), std::move(y))); } void interpret_is_eq(vm::Stack& stack) { + stack.check_underflow(2); auto y = stack.pop(), x = stack.pop(); stack.push_bool(x == y); } diff --git a/crypto/func/abscode.cpp b/crypto/func/abscode.cpp index 4c893f35..21d172cf 100644 --- a/crypto/func/abscode.cpp +++ b/crypto/func/abscode.cpp @@ -158,9 +158,9 @@ void VarDescr::set_const(td::RefInt256 value) { } else if (s > 0) { val |= _NonZero | _Pos | _Finite; } else if (!s) { - if (*int_const == 1) { - val |= _Bit; - } + //if (*int_const == 1) { + // val |= _Bit; + //} val |= _Zero | _Neg | _Pos | _Finite | _Bool | _Bit; } if (val & _Finite) { diff --git a/crypto/func/analyzer.cpp b/crypto/func/analyzer.cpp index c001e3e8..5d9b3991 100644 --- a/crypto/func/analyzer.cpp +++ b/crypto/func/analyzer.cpp @@ -735,7 +735,7 @@ VarDescrList Op::fwd_analyze(VarDescrList values) { res.emplace_back(i); } AsmOpList tmp; - func->compile(tmp, res, args); // abstract interpretation of res := f (args) + func->compile(tmp, res, args, where); // abstract interpretation of res := f (args) int j = 0; for (var_idx_t i : left) { values.add_newval(i).set_value(res[j++]); diff --git a/crypto/func/builtins.cpp b/crypto/func/builtins.cpp index 36f240d7..b8524bb9 100644 --- a/crypto/func/builtins.cpp +++ b/crypto/func/builtins.cpp @@ -82,9 +82,10 @@ SymDef* define_builtin_const(std::string name, TypeExpr* const_type, Args&&... a define_builtin_func(name, TypeExpr::new_map(TypeExpr::new_unit(), const_type), std::forward(args)...)); } -bool SymValAsmFunc::compile(AsmOpList& dest, std::vector& out, std::vector& in) const { +bool SymValAsmFunc::compile(AsmOpList& dest, std::vector& out, std::vector& in, + const SrcLocation& where) const { if (simple_compile) { - return dest.append(simple_compile(out, in)); + return dest.append(simple_compile(out, in, where)); } else if (ext_compile) { return ext_compile(dest, out, in); } else { @@ -423,11 +424,14 @@ AsmOp push_const(td::RefInt256 x) { return AsmOp::IntConst(std::move(x)); } -AsmOp compile_add(std::vector& res, std::vector& args) { +AsmOp compile_add(std::vector& res, std::vector& args, const SrcLocation& where) { assert(res.size() == 1 && args.size() == 2); VarDescr &r = res[0], &x = args[0], &y = args[1]; if (x.is_int_const() && y.is_int_const()) { r.set_const(x.int_const + y.int_const); + if (!r.int_const->is_valid()) { + throw src::ParseError(where, "integer overflow"); + } x.unused(); y.unused(); return push_const(r.int_const); @@ -462,11 +466,14 @@ AsmOp compile_add(std::vector& res, std::vector& args) { return exec_op("ADD", 2); } -AsmOp compile_sub(std::vector& res, std::vector& args) { +AsmOp compile_sub(std::vector& res, std::vector& args, const SrcLocation& where) { assert(res.size() == 1 && args.size() == 2); VarDescr &r = res[0], &x = args[0], &y = args[1]; if (x.is_int_const() && y.is_int_const()) { r.set_const(x.int_const - y.int_const); + if (!r.int_const->is_valid()) { + throw src::ParseError(where, "integer overflow"); + } x.unused(); y.unused(); return push_const(r.int_const); @@ -492,11 +499,14 @@ AsmOp compile_sub(std::vector& res, std::vector& args) { return exec_op("SUB", 2); } -AsmOp compile_negate(std::vector& res, std::vector& args) { +AsmOp compile_negate(std::vector& res, std::vector& args, const SrcLocation& where) { assert(res.size() == 1 && args.size() == 1); VarDescr &r = res[0], &x = args[0]; if (x.is_int_const()) { r.set_const(-x.int_const); + if (!r.int_const->is_valid()) { + throw src::ParseError(where, "integer overflow"); + } x.unused(); return push_const(r.int_const); } @@ -504,7 +514,7 @@ AsmOp compile_negate(std::vector& res, std::vector& args) { return exec_op("NEGATE", 1); } -AsmOp compile_and(std::vector& res, std::vector& args) { +AsmOp compile_and(std::vector& res, std::vector& args, const SrcLocation& where) { assert(res.size() == 1 && args.size() == 2); VarDescr &r = res[0], &x = args[0], &y = args[1]; if (x.is_int_const() && y.is_int_const()) { @@ -517,7 +527,7 @@ AsmOp compile_and(std::vector& res, std::vector& args) { return exec_op("AND", 2); } -AsmOp compile_or(std::vector& res, std::vector& args) { +AsmOp compile_or(std::vector& res, std::vector& args, const SrcLocation& where) { assert(res.size() == 1 && args.size() == 2); VarDescr &r = res[0], &x = args[0], &y = args[1]; if (x.is_int_const() && y.is_int_const()) { @@ -530,7 +540,7 @@ AsmOp compile_or(std::vector& res, std::vector& args) { return exec_op("OR", 2); } -AsmOp compile_xor(std::vector& res, std::vector& args) { +AsmOp compile_xor(std::vector& res, std::vector& args, const SrcLocation& where) { assert(res.size() == 1 && args.size() == 2); VarDescr &r = res[0], &x = args[0], &y = args[1]; if (x.is_int_const() && y.is_int_const()) { @@ -543,7 +553,7 @@ AsmOp compile_xor(std::vector& res, std::vector& args) { return exec_op("XOR", 2); } -AsmOp compile_not(std::vector& res, std::vector& args) { +AsmOp compile_not(std::vector& res, std::vector& args, const SrcLocation& where) { assert(res.size() == 1 && args.size() == 1); VarDescr &r = res[0], &x = args[0]; if (x.is_int_const()) { @@ -555,9 +565,12 @@ AsmOp compile_not(std::vector& res, std::vector& args) { return exec_op("NOT", 1); } -AsmOp compile_mul_internal(VarDescr& r, VarDescr& x, VarDescr& y) { +AsmOp compile_mul_internal(VarDescr& r, VarDescr& x, VarDescr& y, const SrcLocation& where) { if (x.is_int_const() && y.is_int_const()) { r.set_const(x.int_const * y.int_const); + if (!r.int_const->is_valid()) { + throw src::ParseError(where, "integer overflow"); + } x.unused(); y.unused(); return push_const(r.int_const); @@ -620,23 +633,23 @@ AsmOp compile_mul_internal(VarDescr& r, VarDescr& x, VarDescr& y) { return exec_op("MUL", 2); } -AsmOp compile_mul(std::vector& res, std::vector& args) { +AsmOp compile_mul(std::vector& res, std::vector& args, const SrcLocation& where) { assert(res.size() == 1 && args.size() == 2); - return compile_mul_internal(res[0], args[0], args[1]); + return compile_mul_internal(res[0], args[0], args[1], where); } -AsmOp compile_lshift(std::vector& res, std::vector& args) { +AsmOp compile_lshift(std::vector& res, std::vector& args, const SrcLocation& where) { assert(res.size() == 1 && args.size() == 2); VarDescr &r = res[0], &x = args[0], &y = args[1]; if (y.is_int_const()) { auto yv = y.int_const->to_long(); if (yv < 0 || yv > 256) { - r.set_const_nan(); - x.unused(); - y.unused(); - return push_const(r.int_const); + throw src::ParseError(where, "lshift argument is out of range"); } else if (x.is_int_const()) { r.set_const(x.int_const << (int)yv); + if (!r.int_const->is_valid()) { + throw src::ParseError(where, "integer overflow"); + } x.unused(); y.unused(); return push_const(r.int_const); @@ -661,22 +674,20 @@ AsmOp compile_lshift(std::vector& res, std::vector& args) { } if (xv == -1) { x.unused(); - return exec_op("NEGPOW2", 1); + return exec_op("-1 PUSHINT SWAP LSHIFT", 1); } } return exec_op("LSHIFT", 2); } -AsmOp compile_rshift(std::vector& res, std::vector& args, int round_mode) { +AsmOp compile_rshift(std::vector& res, std::vector& args, const SrcLocation& where, + int round_mode) { assert(res.size() == 1 && args.size() == 2); VarDescr &r = res[0], &x = args[0], &y = args[1]; if (y.is_int_const()) { auto yv = y.int_const->to_long(); if (yv < 0 || yv > 256) { - r.set_const_nan(); - x.unused(); - y.unused(); - return push_const(r.int_const); + throw src::ParseError(where, "rshift argument is out of range"); } else if (x.is_int_const()) { r.set_const(td::rshift(x.int_const, (int)yv, round_mode)); x.unused(); @@ -699,9 +710,12 @@ AsmOp compile_rshift(std::vector& res, std::vector& args, in return exec_op(rshift, 2); } -AsmOp compile_div_internal(VarDescr& r, VarDescr& x, VarDescr& y, int round_mode) { +AsmOp compile_div_internal(VarDescr& r, VarDescr& x, VarDescr& y, const SrcLocation& where, int round_mode) { if (x.is_int_const() && y.is_int_const()) { r.set_const(div(x.int_const, y.int_const, round_mode)); + if (!r.int_const->is_valid()) { + throw src::ParseError(where, *y.int_const == 0 ? "division by zero" : "integer overflow"); + } x.unused(); y.unused(); return push_const(r.int_const); @@ -709,10 +723,7 @@ AsmOp compile_div_internal(VarDescr& r, VarDescr& x, VarDescr& y, int round_mode r.val = emulate_div(x.val, y.val); if (y.is_int_const()) { if (*y.int_const == 0) { - x.unused(); - y.unused(); - r.set_const(div(y.int_const, y.int_const)); - return push_const(r.int_const); + throw src::ParseError(where, "division by zero"); } if (*y.int_const == 1 && x.always_finite()) { y.unused(); @@ -739,16 +750,20 @@ AsmOp compile_div_internal(VarDescr& r, VarDescr& x, VarDescr& y, int round_mode return exec_op(op, 2); } -AsmOp compile_div(std::vector& res, std::vector& args, int round_mode) { +AsmOp compile_div(std::vector& res, std::vector& args, const SrcLocation& where, int round_mode) { assert(res.size() == 1 && args.size() == 2); - return compile_div_internal(res[0], args[0], args[1], round_mode); + return compile_div_internal(res[0], args[0], args[1], where, round_mode); } -AsmOp compile_mod(std::vector& res, std::vector& args, int round_mode) { +AsmOp compile_mod(std::vector& res, std::vector& args, const src::SrcLocation& where, + int round_mode) { assert(res.size() == 1 && args.size() == 2); VarDescr &r = res[0], &x = args[0], &y = args[1]; if (x.is_int_const() && y.is_int_const()) { r.set_const(mod(x.int_const, y.int_const, round_mode)); + if (!r.int_const->is_valid()) { + throw src::ParseError(where, *y.int_const == 0 ? "division by zero" : "integer overflow"); + } x.unused(); y.unused(); return push_const(r.int_const); @@ -756,10 +771,7 @@ AsmOp compile_mod(std::vector& res, std::vector& args, int r r.val = emulate_mod(x.val, y.val); if (y.is_int_const()) { if (*y.int_const == 0) { - x.unused(); - y.unused(); - r.set_const(mod(y.int_const, y.int_const)); - return push_const(r.int_const); + throw src::ParseError(where, "division by zero"); } if ((*y.int_const == 1 || *y.int_const == -1) && x.always_finite()) { x.unused(); @@ -784,11 +796,15 @@ AsmOp compile_mod(std::vector& res, std::vector& args, int r return exec_op(op, 2); } -AsmOp compile_muldiv(std::vector& res, std::vector& args, int round_mode) { +AsmOp compile_muldiv(std::vector& res, std::vector& args, const SrcLocation& where, + int round_mode) { assert(res.size() == 1 && args.size() == 3); VarDescr &r = res[0], &x = args[0], &y = args[1], &z = args[2]; if (x.is_int_const() && y.is_int_const() && z.is_int_const()) { r.set_const(muldiv(x.int_const, y.int_const, z.int_const, round_mode)); + if (!r.int_const->is_valid()) { + throw src::ParseError(where, *z.int_const == 0 ? "division by zero" : "integer overflow"); + } x.unused(); y.unused(); z.unused(); @@ -806,24 +822,20 @@ AsmOp compile_muldiv(std::vector& res, std::vector& args, in r.val = emulate_div(emulate_mul(x.val, y.val), z.val); if (z.is_int_const()) { if (*z.int_const == 0) { - x.unused(); - y.unused(); - z.unused(); - r.set_const(div(z.int_const, z.int_const)); - return push_const(r.int_const); + throw src::ParseError(where, "division by zero"); } if (*z.int_const == 1) { z.unused(); - return compile_mul_internal(r, x, y); + return compile_mul_internal(r, x, y, where); } } if (y.is_int_const() && *y.int_const == 1) { y.unused(); - return compile_div_internal(r, x, z, round_mode); + return compile_div_internal(r, x, z, where, round_mode); } if (x.is_int_const() && *x.int_const == 1) { x.unused(); - return compile_div_internal(r, y, z, round_mode); + return compile_div_internal(r, y, z, where, round_mode); } if (z.is_int_const()) { int k = is_pos_pow2(z.int_const); @@ -954,7 +966,7 @@ AsmOp compile_cmp_int(std::vector& res, std::vector& args, i return exec_op(cmp_names[mode], 2); } -AsmOp compile_throw(std::vector& res, std::vector& args) { +AsmOp compile_throw(std::vector& res, std::vector& args, const SrcLocation&) { assert(res.empty() && args.size() == 1); VarDescr& x = args[0]; if (x.is_int_const() && x.int_const->unsigned_fits_bits(11)) { @@ -986,7 +998,7 @@ AsmOp compile_cond_throw(std::vector& res, std::vector& args } } -AsmOp compile_throw_arg(std::vector& res, std::vector& args) { +AsmOp compile_throw_arg(std::vector& res, std::vector& args, const SrcLocation&) { assert(res.empty() && args.size() == 2); VarDescr &x = args[1]; if (x.is_int_const() && x.int_const->unsigned_fits_bits(11)) { @@ -1077,7 +1089,7 @@ AsmOp compile_fetch_slice(std::vector& res, std::vector& arg } // _at(tuple t, int index) asm "INDEXVAR"; -AsmOp compile_tuple_at(std::vector& res, std::vector& args) { +AsmOp compile_tuple_at(std::vector& res, std::vector& args, const SrcLocation&) { assert(args.size() == 2 && res.size() == 1); auto& y = args[1]; if (y.is_int_const() && y.int_const >= 0 && y.int_const < 16) { @@ -1088,7 +1100,7 @@ AsmOp compile_tuple_at(std::vector& res, std::vector& args) } // int null?(X arg) -AsmOp compile_is_null(std::vector& res, std::vector& args) { +AsmOp compile_is_null(std::vector& res, std::vector& args, const SrcLocation&) { assert(args.size() == 1 && res.size() == 1); auto &x = args[0], &r = res[0]; if (x.always_null() || x.always_not_null()) { @@ -1149,21 +1161,21 @@ void define_builtins() { define_builtin_func("_-_", arith_bin_op, compile_sub); define_builtin_func("-_", arith_un_op, compile_negate); define_builtin_func("_*_", arith_bin_op, compile_mul); - define_builtin_func("_/_", arith_bin_op, std::bind(compile_div, _1, _2, -1)); - define_builtin_func("_~/_", arith_bin_op, std::bind(compile_div, _1, _2, 0)); - define_builtin_func("_^/_", arith_bin_op, std::bind(compile_div, _1, _2, 1)); - define_builtin_func("_%_", arith_bin_op, std::bind(compile_mod, _1, _2, -1)); - define_builtin_func("_~%_", arith_bin_op, std::bind(compile_mod, _1, _2, 0)); - define_builtin_func("_^%_", arith_bin_op, std::bind(compile_mod, _1, _2, 1)); + define_builtin_func("_/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, -1)); + define_builtin_func("_~/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 0)); + define_builtin_func("_^/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 1)); + define_builtin_func("_%_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, -1)); + define_builtin_func("_~%_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, 0)); + define_builtin_func("_^%_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, 1)); define_builtin_func("_/%_", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2)); define_builtin_func("divmod", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2)); define_builtin_func("~divmod", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2)); define_builtin_func("moddiv", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2), {}, {1, 0}); define_builtin_func("~moddiv", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2), {}, {1, 0}); define_builtin_func("_<<_", arith_bin_op, compile_lshift); - define_builtin_func("_>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, -1)); - define_builtin_func("_~>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, 0)); - define_builtin_func("_^>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, 1)); + define_builtin_func("_>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, -1)); + define_builtin_func("_~>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 0)); + define_builtin_func("_^>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 1)); define_builtin_func("_&_", arith_bin_op, compile_and); define_builtin_func("_|_", arith_bin_op, compile_or); define_builtin_func("_^_", arith_bin_op, compile_xor); @@ -1171,22 +1183,22 @@ void define_builtins() { define_builtin_func("^_+=_", arith_bin_op, compile_add); define_builtin_func("^_-=_", arith_bin_op, compile_sub); define_builtin_func("^_*=_", arith_bin_op, compile_mul); - define_builtin_func("^_/=_", arith_bin_op, std::bind(compile_div, _1, _2, -1)); - define_builtin_func("^_~/=_", arith_bin_op, std::bind(compile_div, _1, _2, 0)); - define_builtin_func("^_^/=_", arith_bin_op, std::bind(compile_div, _1, _2, 1)); - define_builtin_func("^_%=_", arith_bin_op, std::bind(compile_mod, _1, _2, -1)); - define_builtin_func("^_~%=_", arith_bin_op, std::bind(compile_mod, _1, _2, 0)); - define_builtin_func("^_^%=_", arith_bin_op, std::bind(compile_mod, _1, _2, 1)); + define_builtin_func("^_/=_", arith_bin_op, std::bind(compile_div, _1, _2, _3, -1)); + define_builtin_func("^_~/=_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 0)); + define_builtin_func("^_^/=_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 1)); + define_builtin_func("^_%=_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, -1)); + define_builtin_func("^_~%=_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, 0)); + define_builtin_func("^_^%=_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, 1)); define_builtin_func("^_<<=_", arith_bin_op, compile_lshift); - define_builtin_func("^_>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, -1)); - define_builtin_func("^_~>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, 0)); - define_builtin_func("^_^>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, 1)); + define_builtin_func("^_>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, -1)); + define_builtin_func("^_~>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 0)); + define_builtin_func("^_^>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 1)); define_builtin_func("^_&=_", arith_bin_op, compile_and); define_builtin_func("^_|=_", arith_bin_op, compile_or); define_builtin_func("^_^=_", arith_bin_op, compile_xor); - define_builtin_func("muldiv", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, -1)); - define_builtin_func("muldivr", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, 0)); - define_builtin_func("muldivc", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, 1)); + define_builtin_func("muldiv", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, -1)); + define_builtin_func("muldivr", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, 0)); + define_builtin_func("muldivc", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, 1)); define_builtin_func("muldivmod", TypeExpr::new_map(Int3, Int2), AsmOp::Custom("MULDIVMOD", 3, 2)); define_builtin_func("_==_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 2)); define_builtin_func("_!=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 5)); @@ -1232,16 +1244,18 @@ void define_builtins() { AsmOp::Custom("s0 DUMP", 1, 1), true); define_builtin_func("~strdump", TypeExpr::new_forall({X}, TypeExpr::new_map(X, TypeExpr::new_tensor({X, Unit}))), AsmOp::Custom("STRDUMP", 1, 1), true); - define_builtin_func("run_method0", TypeExpr::new_map(Int, Unit), - [](auto a, auto b, auto c) { return compile_run_method(a, b, c, 0, false); }, true); - define_builtin_func("run_method1", TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_tensor({Int, X}), Unit)), - [](auto a, auto b, auto c) { return compile_run_method(a, b, c, 1, false); }, {1, 0}, {}, true); + define_builtin_func( + "run_method0", TypeExpr::new_map(Int, Unit), + [](AsmOpList& a, auto b, auto c) { return compile_run_method(a, b, c, 0, false); }, true); + define_builtin_func( + "run_method1", TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_tensor({Int, X}), Unit)), + [](AsmOpList& a, auto b, auto c) { return compile_run_method(a, b, c, 1, false); }, {1, 0}, {}, true); define_builtin_func( "run_method2", TypeExpr::new_forall({X, Y}, TypeExpr::new_map(TypeExpr::new_tensor({Int, X, Y}), Unit)), - [](auto a, auto b, auto c) { return compile_run_method(a, b, c, 2, false); }, {1, 2, 0}, {}, true); + [](AsmOpList& a, auto b, auto c) { return compile_run_method(a, b, c, 2, false); }, {1, 2, 0}, {}, true); define_builtin_func( "run_method3", TypeExpr::new_forall({X, Y, Z}, TypeExpr::new_map(TypeExpr::new_tensor({Int, X, Y, Z}), Unit)), - [](auto a, auto b, auto c) { return compile_run_method(a, b, c, 3, false); }, {1, 2, 3, 0}, {}, true); + [](AsmOpList& a, auto b, auto c) { return compile_run_method(a, b, c, 3, false); }, {1, 2, 3, 0}, {}, true); } } // namespace funC diff --git a/crypto/func/codegen.cpp b/crypto/func/codegen.cpp index ac43d12a..87290965 100644 --- a/crypto/func/codegen.cpp +++ b/crypto/func/codegen.cpp @@ -276,7 +276,6 @@ bool Op::generate_code_step(Stack& stack) { stack.opt_show(); stack.drop_vars_except(var_info); stack.opt_show(); - const auto& next_var_info = next->var_info; bool inline_func = stack.mode & Stack::_InlineFunc; switch (cl) { case _Nop: @@ -291,7 +290,7 @@ bool Op::generate_code_step(Stack& stack) { return false; } case _IntConst: { - auto p = next_var_info[left[0]]; + auto p = next->var_info[left[0]]; if (!p || p->is_unused()) { return true; } @@ -307,7 +306,7 @@ bool Op::generate_code_step(Stack& stack) { return true; } case _SliceConst: { - auto p = next_var_info[left[0]]; + auto p = next->var_info[left[0]]; if (!p || p->is_unused()) { return true; } @@ -319,7 +318,7 @@ bool Op::generate_code_step(Stack& stack) { if (dynamic_cast(fun_ref->value)) { bool used = false; for (auto i : left) { - auto p = next_var_info[i]; + auto p = next->var_info[i]; if (p && !p->is_unused()) { used = true; } @@ -339,7 +338,7 @@ bool Op::generate_code_step(Stack& stack) { return true; } else { assert(left.size() == 1); - auto p = next_var_info[left[0]]; + auto p = next->var_info[left[0]]; if (!p || p->is_unused() || disabled()) { return true; } @@ -360,7 +359,7 @@ bool Op::generate_code_step(Stack& stack) { for (int i = 0; i < wr; i++) { args0.emplace_back(0); } - func->compile(stack.o, res, args0); // compile res := f (args0) + func->compile(stack.o, res, args0, where); // compile res := f (args0) } else { std::string name = sym::symbols.get_name(fun_ref->sym_idx); stack.o << AsmOp::Custom(name + " CALLDICT", (int)right.size(), (int)left.size()); @@ -377,7 +376,7 @@ bool Op::generate_code_step(Stack& stack) { active.reserve(left.size()); for (std::size_t k = 0; k < left.size(); k++) { var_idx_t y = left[k]; // "y" = "x" - auto p = next_var_info[y]; + auto p = next->var_info[y]; active.push_back(p && !p->is_unused()); } for (std::size_t k = 0; k < left.size(); k++) { @@ -489,7 +488,7 @@ bool Op::generate_code_step(Stack& stack) { for (var_idx_t i : left) { res.emplace_back(i); } - func->compile(stack.o, res, args); // compile res := f (args) + func->compile(stack.o, res, args, where); // compile res := f (args) } else { auto fv = dynamic_cast(fun_ref->value); std::string name = sym::symbols.get_name(fun_ref->sym_idx); diff --git a/crypto/func/func-main.cpp b/crypto/func/func-main.cpp index 45194ea3..829d95e4 100644 --- a/crypto/func/func-main.cpp +++ b/crypto/func/func-main.cpp @@ -109,7 +109,7 @@ int main(int argc, char* const argv[]) { std::unique_ptr fs; if (!output_filename.empty()) { - fs = std::make_unique(output_filename, fs->trunc | fs->out); + fs = std::make_unique(output_filename, std::fstream::trunc | std::fstream::out); if (!fs->is_open()) { std::cerr << "failed to create output file " << output_filename << '\n'; return 2; diff --git a/crypto/func/func.h b/crypto/func/func.h index b979afb2..5476f253 100644 --- a/crypto/func/func.h +++ b/crypto/func/func.h @@ -505,7 +505,7 @@ class ListIterator { ptr = ptr->next.get(); return *this; } - ListIterator& operator++(int) { + ListIterator operator++(int) { T* z = ptr; ptr = ptr->next.get(); return ListIterator{z}; @@ -1631,11 +1631,11 @@ struct Stack { * */ -typedef std::function&, std::vector&)> simple_compile_func_t; +typedef std::function&, std::vector&, const SrcLocation)> simple_compile_func_t; typedef std::function&, std::vector&)> compile_func_t; inline simple_compile_func_t make_simple_compile(AsmOp op) { - return [op](std::vector& out, std::vector& in) -> AsmOp { return op; }; + return [op](std::vector& out, std::vector& in, const SrcLocation&) -> AsmOp { return op; }; } inline compile_func_t make_ext_compile(std::vector ops) { @@ -1674,7 +1674,7 @@ struct SymValAsmFunc : SymValFunc { std::initializer_list ret_order = {}, bool impure = false) : SymValFunc(-1, ft, arg_order, ret_order, impure), ext_compile(std::move(_compile)) { } - bool compile(AsmOpList& dest, std::vector& out, std::vector& in) const; + bool compile(AsmOpList& dest, std::vector& out, std::vector& in, const SrcLocation& where) const; }; // defined in builtins.cpp diff --git a/crypto/func/parse-func.cpp b/crypto/func/parse-func.cpp index 011947d5..8f6f7afe 100644 --- a/crypto/func/parse-func.cpp +++ b/crypto/func/parse-func.cpp @@ -487,7 +487,7 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) { Expr* res = new Expr{Expr::_Const, lex.cur().loc}; res->flags = Expr::_IsRvalue; res->intval = td::string_to_int256(lex.cur().str); - if (res->intval.is_null()) { + if (res->intval.is_null() || !res->intval->signed_fits_bits(257)) { lex.cur().error_at("invalid integer constant `", "`"); } res->e_type = TypeExpr::new_atomic(_Int); diff --git a/crypto/test/modbigint.cpp b/crypto/test/modbigint.cpp index b6480d38..b34411f3 100644 --- a/crypto/test/modbigint.cpp +++ b/crypto/test/modbigint.cpp @@ -289,7 +289,7 @@ struct MixedRadix { } explicit operator long long() const { - long long acc = 0.; + unsigned long long acc = 0; for (int i = N - 1; i >= 0; --i) { acc = acc * mod[i] + a[i]; } @@ -903,7 +903,7 @@ struct ModArray { } for (; i < size; i++) { pow += 8; - acc = (acc << 8) + arr[i]; + acc = (acc * 256) + arr[i]; if (pow >= 56) { lshift_add(pow, acc); acc = pow = 0; diff --git a/crypto/test/test-bigint.cpp b/crypto/test/test-bigint.cpp index bf85e2ad..7525c83a 100644 --- a/crypto/test/test-bigint.cpp +++ b/crypto/test/test-bigint.cpp @@ -186,7 +186,7 @@ td::RefInt256 make_special_int(int x, BInt* ptr = nullptr, unsigned char bin[64] int acc = b, r = ord; for (int i = 63; i >= 0; --i) { if (r < 8) { - acc += (a << r); + acc += ((unsigned)a << r); r = 1024; } r -= 8; @@ -215,7 +215,7 @@ int randexp(int max = 63, int min = 0) { } void bin_add_small(unsigned char bin[64], long long val, int shift = 0) { - val <<= shift & 7; + val *= (1 << (shift & 7)); for (int i = 63 - (shift >> 3); i >= 0 && val; --i) { val += bin[i]; bin[i] = (unsigned char)val; diff --git a/dht/dht-query.hpp b/dht/dht-query.hpp index cf085e25..c1db0a0e 100644 --- a/dht/dht-query.hpp +++ b/dht/dht-query.hpp @@ -44,7 +44,7 @@ class DhtQuery : public td::actor::Actor { bool client_only_; public: - DhtQuery(DhtKeyId key, DhtMember::PrintId print_id, adnl::AdnlNodeIdShort src, DhtNodesList list, td::uint32 k, + DhtQuery(DhtKeyId key, DhtMember::PrintId print_id, adnl::AdnlNodeIdShort src, td::uint32 k, td::uint32 a, td::int32 our_network_id, DhtNode self, bool client_only, td::actor::ActorId node, td::actor::ActorId adnl) : key_(key) @@ -57,7 +57,6 @@ class DhtQuery : public td::actor::Actor { , our_network_id_(our_network_id) , node_(node) , adnl_(adnl) { - add_nodes(std::move(list)); } DhtMember::PrintId print_id() const { return print_id_; @@ -112,8 +111,9 @@ class DhtQueryFindNodes : public DhtQuery { td::uint32 k, td::uint32 a, td::int32 our_network_id, DhtNode self, bool client_only, td::actor::ActorId node, td::actor::ActorId adnl, td::Promise promise) - : DhtQuery(key, print_id, src, std::move(list), k, a, our_network_id, std::move(self), client_only, node, adnl) + : DhtQuery(key, print_id, src, k, a, our_network_id, std::move(self), client_only, node, adnl) , promise_(std::move(promise)) { + add_nodes(std::move(list)); } void send_one_query(adnl::AdnlNodeIdShort id) override; void on_result(td::Result R, adnl::AdnlNodeIdShort dst); @@ -132,8 +132,9 @@ class DhtQueryFindValue : public DhtQuery { td::uint32 k, td::uint32 a, td::int32 our_network_id, DhtNode self, bool client_only, td::actor::ActorId node, td::actor::ActorId adnl, td::Promise promise) - : DhtQuery(key, print_id, src, std::move(list), k, a, our_network_id, std::move(self), client_only, node, adnl) + : DhtQuery(key, print_id, src, k, a, our_network_id, std::move(self), client_only, node, adnl) , promise_(std::move(promise)) { + add_nodes(std::move(list)); } void send_one_query(adnl::AdnlNodeIdShort id) override; void send_one_query_nodes(adnl::AdnlNodeIdShort id); @@ -219,11 +220,12 @@ class DhtQueryRequestReversePing : public DhtQuery { td::uint32 a, td::int32 our_network_id, DhtNode self, bool client_only, td::actor::ActorId node, td::actor::ActorId adnl, td::Promise promise) - : DhtQuery(DhtMember::get_reverse_connection_key(client).compute_key_id(), print_id, src, std::move(list), k, a, - our_network_id, std::move(self), client_only, node, adnl) + : DhtQuery(DhtMember::get_reverse_connection_key(client).compute_key_id(), print_id, src, k, a, our_network_id, + std::move(self), client_only, node, adnl) , promise_(std::move(promise)) , query_(create_serialize_tl_object(target.tl(), std::move(signature), client.bits256_value(), k)) { + add_nodes(std::move(list)); } void send_one_query(adnl::AdnlNodeIdShort id) override; void on_result(td::Result R, adnl::AdnlNodeIdShort dst); diff --git a/tdutils/test/List.cpp b/tdutils/test/List.cpp index ae00b499..74fe9c21 100644 --- a/tdutils/test/List.cpp +++ b/tdutils/test/List.cpp @@ -170,8 +170,8 @@ TEST(Misc, TsList) { TEST(Misc, TsListConcurrent) { td::TsList root; - td::vector threads; std::atomic id{0}; + td::vector threads; for (std::size_t i = 0; i < 4; i++) { threads.emplace_back( [&] { do_run_list_test, td::TsList, td::TsListNode>(root, id); }); diff --git a/tl/generate/tl_writer_hpp.cpp b/tl/generate/tl_writer_hpp.cpp index 028aa927..1d75df12 100644 --- a/tl/generate/tl_writer_hpp.cpp +++ b/tl/generate/tl_writer_hpp.cpp @@ -34,6 +34,7 @@ int TD_TL_writer_hpp::get_additional_function_type(const std::string &additional std::vector TD_TL_writer_hpp::get_additional_functions() const { std::vector additional_functions; additional_functions.push_back("downcast_call"); + additional_functions.push_back("downcast_construct"); return additional_functions; } @@ -202,24 +203,40 @@ std::string TD_TL_writer_hpp::gen_additional_proxy_function_begin(const std::str const tl::tl_type *type, const std::string &class_name, int arity, bool is_function) const { - assert(function_name == "downcast_call"); - return "/**\n" - " * Calls specified function object with the specified object downcasted to the most-derived type.\n" - " * \\param[in] obj Object to pass as an argument to the function object.\n" - " * \\param[in] func Function object to which the object will be passed.\n" - " * \\returns whether function object call has happened. Should always return true for correct parameters.\n" - " */\n" - "template \n" - "bool downcast_call(" + - class_name + - " &obj, const T &func) {\n" - " switch (obj.get_id()) {\n"; + if (function_name == "downcast_call") { + return "/**\n" + " * Calls specified function object with the specified object downcasted to the most-derived type.\n" + " * \\param[in] obj Object to pass as an argument to the function object.\n" + " * \\param[in] func Function object to which the object will be passed.\n" + " * \\returns whether function object call has happened. Should always return true for correct parameters.\n" + " */\n" + "template \n" + "bool downcast_call(" + + class_name + + " &obj, const T &func) {\n" + " switch (obj.get_id()) {\n"; + } + if (function_name == "downcast_construct") { + return "/**\n" + "* Constructs tl_object_ptr with the object of the same type as the specified object, calls the specified " + "function.\n" + " * \\param[in] obj Object to get the type from.\n" + " * \\param[in] func Function object to which the new object will be passed.\n" + " * \\returns whether function object call has happened. Should always return true for correct parameters.\n" + "*/" + "template \n" + "bool downcast_construct(" + + class_name + + " &obj, const T &func) {\n" + "switch (obj.get_id()) {"; + } + assert(false); } std::string TD_TL_writer_hpp::gen_additional_proxy_function_case(const std::string &function_name, const tl::tl_type *type, const std::string &class_name, int arity) const { - assert(function_name == "downcast_call"); + //assert(function_name == "downcast_call"); assert(false); return ""; } @@ -227,18 +244,28 @@ std::string TD_TL_writer_hpp::gen_additional_proxy_function_case(const std::stri std::string TD_TL_writer_hpp::gen_additional_proxy_function_case(const std::string &function_name, const tl::tl_type *type, const tl::tl_combinator *t, int arity, bool is_function) const { - assert(function_name == "downcast_call"); - return " case " + gen_class_name(t->name) + - "::ID:\n" - " func(static_cast<" + - gen_class_name(t->name) + - " &>(obj));\n" - " return true;\n"; + if (function_name == "downcast_call") { + return " case " + gen_class_name(t->name) + + "::ID:\n" + " func(static_cast<" + + gen_class_name(t->name) + + " &>(obj));\n" + " return true;\n"; + } + if (function_name == "downcast_construct") { + return " case " + gen_class_name(t->name) + + "::ID:\n" + " func(create_tl_object<" + + gen_class_name(t->name) + + ">());\n" + " return true;\n"; + } + assert(false); } std::string TD_TL_writer_hpp::gen_additional_proxy_function_end(const std::string &function_name, const tl::tl_type *type, bool is_function) const { - assert(function_name == "downcast_call"); + assert(function_name == "downcast_call" || function_name == "downcast_construct"); return " default:\n" " return false;\n" " }\n" diff --git a/tl/tl/tl_json.h b/tl/tl/tl_json.h index 66c639b5..489bd6e8 100644 --- a/tl/tl/tl_json.h +++ b/tl/tl/tl_json.h @@ -277,8 +277,7 @@ std::enable_if_t::value, Status> from_json(ton::tl_obj DowncastHelper helper(constructor); Status status; - bool ok = downcast_call(static_cast(helper), [&](auto &dummy) { - auto result = ton::create_tl_object>(); + bool ok = downcast_construct(static_cast(helper), [&](auto result) { status = from_json(*result, object); to = std::move(result); }); diff --git a/validator/db/celldb.cpp b/validator/db/celldb.cpp index caefe8e4..e142043e 100644 --- a/validator/db/celldb.cpp +++ b/validator/db/celldb.cpp @@ -91,7 +91,7 @@ void CellDbIn::load_cell(RootHash hash, td::Promise> promi } void CellDbIn::store_cell(BlockIdExt block_id, td::Ref cell, td::Promise> promise) { - td::PerfWarningTimer{"storecell", 0.1}; + td::PerfWarningTimer timer{"storecell", 0.1}; auto key_hash = get_key_hash(block_id); auto R = get_block(key_hash); // duplicate @@ -194,7 +194,7 @@ void CellDbIn::gc_cont(BlockHandle handle) { } void CellDbIn::gc_cont2(BlockHandle handle) { - td::PerfWarningTimer{"gccell", 0.1}; + td::PerfWarningTimer timer{"gccell", 0.1}; auto FR = get_block(last_gc_); FR.ensure(); diff --git a/validator/impl/liteserver.cpp b/validator/impl/liteserver.cpp index ac2932bc..1e8bb820 100644 --- a/validator/impl/liteserver.cpp +++ b/validator/impl/liteserver.cpp @@ -1050,9 +1050,9 @@ bool LiteQuery::make_state_root_proof(Ref& proof, Ref state_ && upd_cs.size_ext() == 0x20228)) { return fatal_error("invalid Merkle update in block"); } - auto upd_hash = upd_cs.prefetch_ref(1)->get_hash(0).bits(); - auto state_hash = state_root->get_hash().bits(); - if (upd_hash.compare(state_hash, 256)) { + auto upd_hash = upd_cs.prefetch_ref(1)->get_hash(0); + auto state_hash = state_root->get_hash(); + if (upd_hash != state_hash) { return fatal_error("cannot construct Merkle proof for given masterchain state because of hash mismatch"); } if (!pb.extract_proof_to(proof)) { diff --git a/validator/token-manager.cpp b/validator/token-manager.cpp index 2cca02ed..0bc4a9c6 100644 --- a/validator/token-manager.cpp +++ b/validator/token-manager.cpp @@ -67,11 +67,10 @@ void TokenManager::download_token_cleared(size_t download_size, td::uint32 prior } void TokenManager::alarm() { - for (auto it = pending_.begin(); it != pending_.end(); it++) { + for (auto it = pending_.begin(); it != pending_.end();) { if (it->second.timeout.is_in_past()) { it->second.promise.set_error(td::Status::Error(ErrorCode::timeout, "timeout in wait download token")); - auto it2 = it++; - pending_.erase(it2); + it = pending_.erase(it); } else { it++; } From 706be23c837b38d0adb9200c1119c756b124469e Mon Sep 17 00:00:00 2001 From: SpyCheese Date: Tue, 28 Feb 2023 09:06:57 +0000 Subject: [PATCH 19/46] Limit maximal Merkle depth (#626) --- crypto/block/create-state.cpp | 2 +- crypto/block/transaction.cpp | 77 +++++++++++++++++++++--------- crypto/block/transaction.h | 5 +- crypto/vm/boc.cpp | 78 ++++++++++++++++++++----------- crypto/vm/boc.h | 25 +++++----- crypto/vm/cells/MerkleProof.cpp | 37 +++++++++++---- crypto/vm/cells/MerkleProof.h | 2 +- crypto/vm/cells/MerkleUpdate.cpp | 3 ++ validator/impl/accept-block.cpp | 3 ++ validator/impl/collator.cpp | 5 +- validator/impl/liteserver.cpp | 41 +++++++++++++--- validator/impl/validate-query.cpp | 2 +- 12 files changed, 196 insertions(+), 84 deletions(-) diff --git a/crypto/block/create-state.cpp b/crypto/block/create-state.cpp index 7a734c3a..a7500713 100644 --- a/crypto/block/create-state.cpp +++ b/crypto/block/create-state.cpp @@ -308,7 +308,7 @@ td::RefInt256 create_smartcontract(td::RefInt256 smc_addr, Ref code, R THRERR("cannot create smart-contract AccountStorage"); Ref storage = cb.finalize(); vm::CellStorageStat stats; - PDO(stats.compute_used_storage(Ref(storage))); + PDO(stats.compute_used_storage(Ref(storage)).is_ok()); if (verbosity > 2) { std::cerr << "storage is:\n"; vm::load_cell_slice(storage).print_rec(std::cerr); diff --git a/crypto/block/transaction.cpp b/crypto/block/transaction.cpp index 7607ef81..47ea4e47 100644 --- a/crypto/block/transaction.cpp +++ b/crypto/block/transaction.cpp @@ -590,15 +590,19 @@ bool Transaction::unpack_input_msg(bool ihr_delivered, const ActionPhaseConfig* in_msg_type = 2; in_msg_extern = true; // compute forwarding fees for this external message - vm::CellStorageStat sstat; // for message size - sstat.compute_used_storage(cs); // message body - sstat.bits -= cs.size(); // bits in the root cells are free - sstat.cells--; // the root cell itself is not counted as a cell + vm::CellStorageStat sstat; // for message size + auto cell_info = sstat.compute_used_storage(cs).move_as_ok(); // message body + sstat.bits -= cs.size(); // bits in the root cells are free + sstat.cells--; // the root cell itself is not counted as a cell LOG(DEBUG) << "storage paid for a message: " << sstat.cells << " cells, " << sstat.bits << " bits"; if (sstat.bits > cfg->size_limits.max_msg_bits || sstat.cells > cfg->size_limits.max_msg_cells) { LOG(DEBUG) << "inbound external message too large, invalid"; return false; } + if (cell_info.max_merkle_depth > max_allowed_merkle_depth) { + LOG(DEBUG) << "inbound external message has too big merkle depth, invalid"; + return false; + } // fetch message pricing info CHECK(cfg); const MsgPrices& msg_prices = cfg->fetch_msg_prices(account.is_masterchain()); @@ -1157,19 +1161,20 @@ bool Transaction::prepare_action_phase(const ActionPhaseConfig& cfg) { ap.reserved_balance.set_zero(); td::Ref old_code = new_code, old_data = new_data, old_library = new_library; - auto enforce_state_size_limits = [&]() { + auto enforce_state_limits = [&]() { if (account.is_special) { return true; } - if (!check_state_size_limit(cfg)) { + auto S = check_state_limits(cfg); + if (S.is_error()) { // Rollback changes to state, fail action phase - LOG(INFO) << "Account state size exceeded limits"; + LOG(INFO) << "Account state size exceeded limits: " << S.move_as_error(); new_storage_stat.clear(); new_code = old_code; new_data = old_data; new_library = old_library; ap.result_code = 50; - ap.state_size_too_big = true; + ap.state_exceeds_limits = true; return false; } return true; @@ -1250,8 +1255,8 @@ bool Transaction::prepare_action_phase(const ActionPhaseConfig& cfg) { ap.no_funds = true; } LOG(DEBUG) << "invalid action " << ap.result_arg << " in action list: error code " << ap.result_code; - // This is reuqired here because changes to libraries are applied even if actipn phase fails - enforce_state_size_limits(); + // This is reuqired here because changes to libraries are applied even if action phase fails + enforce_state_limits(); return true; } } @@ -1261,7 +1266,7 @@ bool Transaction::prepare_action_phase(const ActionPhaseConfig& cfg) { new_code = ap.new_code; } new_data = compute_phase->new_data; // tentative persistent data update applied - if (!enforce_state_size_limits()) { + if (!enforce_state_limits()) { return true; } @@ -1334,8 +1339,8 @@ int Transaction::try_action_change_library(vm::CellSlice& cs, ActionPhase& ap, c return 41; } vm::CellStorageStat sstat; - sstat.compute_used_storage(lib_ref); - if (sstat.cells > cfg.size_limits.max_library_cells) { + auto cell_info = sstat.compute_used_storage(lib_ref).move_as_ok(); + if (sstat.cells > cfg.size_limits.max_library_cells || cell_info.max_merkle_depth > max_allowed_merkle_depth) { return 43; } vm::CellBuilder cb; @@ -1608,16 +1613,27 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, // compute size of message vm::CellStorageStat sstat; // for message size // preliminary storage estimation of the resulting message - sstat.add_used_storage(msg.init, true, 3); // message init - sstat.add_used_storage(msg.body, true, 3); // message body (the root cell itself is not counted) + unsigned max_merkle_depth = 0; + auto add_used_storage = [&](const auto& x, unsigned skip_root_count) { + if (x.not_null()) { + auto res = sstat.add_used_storage(x, true, skip_root_count).move_as_ok(); + max_merkle_depth = std::max(max_merkle_depth, res.max_merkle_depth); + } + }; + add_used_storage(msg.init, 3); // message init + add_used_storage(msg.body, 3); // message body (the root cell itself is not counted) if (!ext_msg) { - sstat.add_used_storage(info.value->prefetch_ref()); + add_used_storage(info.value->prefetch_ref(), 0); } LOG(DEBUG) << "storage paid for a message: " << sstat.cells << " cells, " << sstat.bits << " bits"; if (sstat.bits > cfg.size_limits.max_msg_bits || sstat.cells > cfg.size_limits.max_msg_cells) { LOG(DEBUG) << "message too large, invalid"; return skip_invalid ? 0 : 40; } + if (max_merkle_depth > max_allowed_merkle_depth) { + LOG(DEBUG) << "message has too big merkle depth, invalid"; + return skip_invalid ? 0 : 40; + } // compute forwarding fees auto fees_c = msg_prices.compute_fwd_ihr_fees(sstat.cells, sstat.bits, info.ihr_disabled); @@ -1869,7 +1885,7 @@ int Transaction::try_action_reserve_currency(vm::CellSlice& cs, ActionPhase& ap, return 0; } -bool Transaction::check_state_size_limit(const ActionPhaseConfig& cfg) { +td::Status Transaction::check_state_limits(const ActionPhaseConfig& cfg) { auto cell_equal = [](const td::Ref& a, const td::Ref& b) -> bool { if (a.is_null()) { return b.is_null(); @@ -1881,21 +1897,36 @@ bool Transaction::check_state_size_limit(const ActionPhaseConfig& cfg) { }; if (cell_equal(account.code, new_code) && cell_equal(account.data, new_data) && cell_equal(account.library, new_library)) { - return true; + return td::Status::OK(); } // new_storage_stat is used here beause these stats will be reused in compute_state() new_storage_stat.limit_cells = cfg.size_limits.max_acc_state_cells; new_storage_stat.limit_bits = cfg.size_limits.max_acc_state_bits; - new_storage_stat.add_used_storage(new_code); - new_storage_stat.add_used_storage(new_data); - new_storage_stat.add_used_storage(new_library); + td::Timer timer; + auto add_used_storage = [&](const td::Ref& cell) -> td::Status { + if (cell.not_null()) { + TRY_RESULT(res, new_storage_stat.add_used_storage(cell)); + if (res.max_merkle_depth > max_allowed_merkle_depth) { + return td::Status::Error("too big merkle depth"); + } + } + return td::Status::OK(); + }; + TRY_STATUS(add_used_storage(new_code)); + TRY_STATUS(add_used_storage(new_data)); + TRY_STATUS(add_used_storage(new_library)); + if (timer.elapsed() > 0.1) { + LOG(INFO) << "Compute used storage took " << timer.elapsed() << "s"; + } if (acc_status == Account::acc_active) { new_storage_stat.clear_limit(); } else { new_storage_stat.clear(); } return new_storage_stat.cells <= cfg.size_limits.max_acc_state_cells && - new_storage_stat.bits <= cfg.size_limits.max_acc_state_bits; + new_storage_stat.bits <= cfg.size_limits.max_acc_state_bits + ? td::Status::OK() + : td::Status::Error("state too big"); } bool Transaction::prepare_bounce_phase(const ActionPhaseConfig& cfg) { @@ -2124,7 +2155,7 @@ bool Transaction::compute_state() { stats = new_stats.unwrap(); } else { td::Timer timer; - CHECK(stats.add_used_storage(Ref(storage))); + stats.add_used_storage(Ref(storage)).ensure(); if (timer.elapsed() > 0.1) { LOG(INFO) << "Compute used storage took " << timer.elapsed() << "s"; } diff --git a/crypto/block/transaction.h b/crypto/block/transaction.h index 2e4463bd..6346ddcd 100644 --- a/crypto/block/transaction.h +++ b/crypto/block/transaction.h @@ -191,7 +191,7 @@ struct ActionPhase { bool code_changed{false}; bool action_list_invalid{false}; bool acc_delete_req{false}; - bool state_size_too_big{false}; + bool state_exceeds_limits{false}; enum { acst_unchanged = 0, acst_frozen = 2, acst_deleted = 3 }; int acc_status_change{acst_unchanged}; td::RefInt256 total_fwd_fees; // all fees debited from the account @@ -295,6 +295,7 @@ struct Account { namespace transaction { struct Transaction { + static constexpr unsigned max_allowed_merkle_depth = 2; enum { tr_none, tr_ord, @@ -360,7 +361,7 @@ struct Transaction { std::vector> compute_vm_libraries(const ComputePhaseConfig& cfg); bool prepare_compute_phase(const ComputePhaseConfig& cfg); bool prepare_action_phase(const ActionPhaseConfig& cfg); - bool check_state_size_limit(const ActionPhaseConfig& cfg); + td::Status check_state_limits(const ActionPhaseConfig& cfg); bool prepare_bounce_phase(const ActionPhaseConfig& cfg); bool compute_state(); bool serialize(); diff --git a/crypto/vm/boc.cpp b/crypto/vm/boc.cpp index f438d480..11583ede 100644 --- a/crypto/vm/boc.cpp +++ b/crypto/vm/boc.cpp @@ -1008,27 +1008,40 @@ td::Result std_boc_serialize_multi(std::vector> roots * */ -bool CellStorageStat::compute_used_storage(Ref cs_ref, bool kill_dup, unsigned skip_count_root) { +td::Result CellStorageStat::compute_used_storage(Ref cs_ref, bool kill_dup, + unsigned skip_count_root) { clear(); - return add_used_storage(std::move(cs_ref), kill_dup, skip_count_root) && clear_seen(); + TRY_RESULT(res, add_used_storage(std::move(cs_ref), kill_dup, skip_count_root)); + clear_seen(); + return res; } -bool CellStorageStat::compute_used_storage(const CellSlice& cs, bool kill_dup, unsigned skip_count_root) { +td::Result CellStorageStat::compute_used_storage(const CellSlice& cs, bool kill_dup, + unsigned skip_count_root) { clear(); - return add_used_storage(cs, kill_dup, skip_count_root) && clear_seen(); + TRY_RESULT(res, add_used_storage(cs, kill_dup, skip_count_root)); + clear_seen(); + return res; } -bool CellStorageStat::compute_used_storage(CellSlice&& cs, bool kill_dup, unsigned skip_count_root) { +td::Result CellStorageStat::compute_used_storage(CellSlice&& cs, bool kill_dup, + unsigned skip_count_root) { clear(); - return add_used_storage(std::move(cs), kill_dup, skip_count_root) && clear_seen(); + TRY_RESULT(res, add_used_storage(std::move(cs), kill_dup, skip_count_root)); + clear_seen(); + return res; } -bool CellStorageStat::compute_used_storage(Ref cell, bool kill_dup, unsigned skip_count_root) { +td::Result CellStorageStat::compute_used_storage(Ref cell, bool kill_dup, + unsigned skip_count_root) { clear(); - return add_used_storage(std::move(cell), kill_dup, skip_count_root) && clear_seen(); + TRY_RESULT(res, add_used_storage(std::move(cell), kill_dup, skip_count_root)); + clear_seen(); + return res; } -bool CellStorageStat::add_used_storage(Ref cs_ref, bool kill_dup, unsigned skip_count_root) { +td::Result CellStorageStat::add_used_storage(Ref cs_ref, bool kill_dup, + unsigned skip_count_root) { if (cs_ref->is_unique()) { return add_used_storage(std::move(cs_ref.unique_write()), kill_dup, skip_count_root); } else { @@ -1036,56 +1049,67 @@ bool CellStorageStat::add_used_storage(Ref cs_ref, bool kill_dup, } } -bool CellStorageStat::add_used_storage(const CellSlice& cs, bool kill_dup, unsigned skip_count_root) { +td::Result CellStorageStat::add_used_storage(const CellSlice& cs, bool kill_dup, + unsigned skip_count_root) { if (!(skip_count_root & 1)) { ++cells; if (cells > limit_cells) { - return false; + return td::Status::Error("too many cells"); } } if (!(skip_count_root & 2)) { bits += cs.size(); if (bits > limit_bits) { - return false; + return td::Status::Error("too many bits"); } } + CellInfo res; for (unsigned i = 0; i < cs.size_refs(); i++) { - if (!add_used_storage(cs.prefetch_ref(i), kill_dup)) { - return false; - } + TRY_RESULT(child, add_used_storage(cs.prefetch_ref(i), kill_dup)); + res.max_merkle_depth = std::max(res.max_merkle_depth, child.max_merkle_depth); } - return true; + if (cs.special_type() == CellTraits::SpecialType::MerkleProof || + cs.special_type() == CellTraits::SpecialType::MerkleUpdate) { + ++res.max_merkle_depth; + } + return res; } -bool CellStorageStat::add_used_storage(CellSlice&& cs, bool kill_dup, unsigned skip_count_root) { +td::Result CellStorageStat::add_used_storage(CellSlice&& cs, bool kill_dup, + unsigned skip_count_root) { if (!(skip_count_root & 1)) { ++cells; if (cells > limit_cells) { - return false; + return td::Status::Error("too many cells"); } } if (!(skip_count_root & 2)) { bits += cs.size(); if (bits > limit_bits) { - return false; + return td::Status::Error("too many bits"); } } + CellInfo res; while (cs.size_refs()) { - if (!add_used_storage(cs.fetch_ref(), kill_dup)) { - return false; - } + TRY_RESULT(child, add_used_storage(cs.fetch_ref(), kill_dup)); + res.max_merkle_depth = std::max(res.max_merkle_depth, child.max_merkle_depth); } - return true; + if (cs.special_type() == CellTraits::SpecialType::MerkleProof || + cs.special_type() == CellTraits::SpecialType::MerkleUpdate) { + ++res.max_merkle_depth; + } + return res; } -bool CellStorageStat::add_used_storage(Ref cell, bool kill_dup, unsigned skip_count_root) { +td::Result CellStorageStat::add_used_storage(Ref cell, bool kill_dup, + unsigned skip_count_root) { if (cell.is_null()) { - return false; + return td::Status::Error("cell is null"); } if (kill_dup) { - auto ins = seen.insert(cell->get_hash()); + auto ins = seen.emplace(cell->get_hash(), CellInfo{}); if (!ins.second) { - return true; + return ins.first->second; } } vm::CellSlice cs{vm::NoVm{}, std::move(cell)}; diff --git a/crypto/vm/boc.h b/crypto/vm/boc.h index dd74a6d1..c4e6de45 100644 --- a/crypto/vm/boc.h +++ b/crypto/vm/boc.h @@ -108,12 +108,14 @@ struct CellStorageStat { unsigned long long cells; unsigned long long bits; unsigned long long public_cells; - std::set seen; + struct CellInfo { + td::uint32 max_merkle_depth = 0; + }; + std::map seen; CellStorageStat() : cells(0), bits(0), public_cells(0) { } - bool clear_seen() { + void clear_seen() { seen.clear(); - return true; } void clear() { cells = bits = public_cells = 0; @@ -124,15 +126,16 @@ struct CellStorageStat { limit_cells = std::numeric_limits::max(); limit_bits = std::numeric_limits::max(); } - bool compute_used_storage(Ref cs_ref, bool kill_dup = true, unsigned skip_count_root = 0); - bool compute_used_storage(const CellSlice& cs, bool kill_dup = true, unsigned skip_count_root = 0); - bool compute_used_storage(CellSlice&& cs, bool kill_dup = true, unsigned skip_count_root = 0); - bool compute_used_storage(Ref cell, bool kill_dup = true, unsigned skip_count_root = 0); + td::Result compute_used_storage(Ref cs_ref, bool kill_dup = true, + unsigned skip_count_root = 0); + td::Result compute_used_storage(const CellSlice& cs, bool kill_dup = true, unsigned skip_count_root = 0); + td::Result compute_used_storage(CellSlice&& cs, bool kill_dup = true, unsigned skip_count_root = 0); + td::Result compute_used_storage(Ref cell, bool kill_dup = true, unsigned skip_count_root = 0); - bool add_used_storage(Ref cs_ref, bool kill_dup = true, unsigned skip_count_root = 0); - bool add_used_storage(const CellSlice& cs, bool kill_dup = true, unsigned skip_count_root = 0); - bool add_used_storage(CellSlice&& cs, bool kill_dup = true, unsigned skip_count_root = 0); - bool add_used_storage(Ref cell, bool kill_dup = true, unsigned skip_count_root = 0); + td::Result add_used_storage(Ref cs_ref, bool kill_dup = true, unsigned skip_count_root = 0); + td::Result add_used_storage(const CellSlice& cs, bool kill_dup = true, unsigned skip_count_root = 0); + td::Result add_used_storage(CellSlice&& cs, bool kill_dup = true, unsigned skip_count_root = 0); + td::Result add_used_storage(Ref cell, bool kill_dup = true, unsigned skip_count_root = 0); unsigned long long limit_cells = std::numeric_limits::max(); unsigned long long limit_bits = std::numeric_limits::max(); diff --git a/crypto/vm/cells/MerkleProof.cpp b/crypto/vm/cells/MerkleProof.cpp index aee34367..26dff787 100644 --- a/crypto/vm/cells/MerkleProof.cpp +++ b/crypto/vm/cells/MerkleProof.cpp @@ -39,7 +39,13 @@ class MerkleProofImpl { dfs_usage_tree(cell, usage_tree_->root_id()); is_prunned_ = [this](const Ref &cell) { return visited_cells_.count(cell->get_hash()) == 0; }; } - return dfs(cell, cell->get_level()); + try { + return dfs(cell, cell->get_level()); + } catch (CellBuilder::CellWriteError &) { + return {}; + } catch (CellBuilder::CellCreateError &) { + return {}; + } } private: @@ -119,6 +125,9 @@ Ref MerkleProof::generate(Ref cell, CellUsageTree *usage_tree) { return {}; } auto raw = generate_raw(std::move(cell), usage_tree); + if (raw.is_null()) { + return {}; + } return CellBuilder::create_merkle_proof(std::move(raw)); } @@ -384,21 +393,29 @@ bool MerkleProofBuilder::clear() { return true; } -Ref MerkleProofBuilder::extract_proof() const { - return MerkleProof::generate(orig_root, usage_tree.get()); +td::Result> MerkleProofBuilder::extract_proof() const { + Ref proof = MerkleProof::generate(orig_root, usage_tree.get()); + if (proof.is_null()) { + return td::Status::Error("cannot create Merkle proof"); + } + return proof; } bool MerkleProofBuilder::extract_proof_to(Ref &proof_root) const { - return orig_root.not_null() && (proof_root = extract_proof()).not_null(); + if (orig_root.is_null()) { + return false; + } + auto R = extract_proof(); + if (R.is_error()) { + return false; + } + proof_root = R.move_as_ok(); + return true; } td::Result MerkleProofBuilder::extract_proof_boc() const { - Ref proof_root = extract_proof(); - if (proof_root.is_null()) { - return td::Status::Error("cannot create Merkle proof"); - } else { - return std_boc_serialize(std::move(proof_root)); - } + TRY_RESULT(proof_root, extract_proof()); + return std_boc_serialize(std::move(proof_root)); } } // namespace vm diff --git a/crypto/vm/cells/MerkleProof.h b/crypto/vm/cells/MerkleProof.h index 4f2add6c..9c50fd07 100644 --- a/crypto/vm/cells/MerkleProof.h +++ b/crypto/vm/cells/MerkleProof.h @@ -63,7 +63,7 @@ class MerkleProofBuilder { Ref root() const { return usage_root; } - Ref extract_proof() const; + td::Result> extract_proof() const; bool extract_proof_to(Ref &proof_root) const; td::Result extract_proof_boc() const; }; diff --git a/crypto/vm/cells/MerkleUpdate.cpp b/crypto/vm/cells/MerkleUpdate.cpp index 3cc656c0..894612bd 100644 --- a/crypto/vm/cells/MerkleUpdate.cpp +++ b/crypto/vm/cells/MerkleUpdate.cpp @@ -221,6 +221,9 @@ Ref MerkleUpdate::generate(Ref from, Ref to, CellUsageTree *us return {}; } auto res = generate_raw(std::move(from), std::move(to), usage_tree); + if (res.first.is_null() || res.second.is_null()) { + return {}; + } return CellBuilder::create_merkle_update(res.first, res.second); } diff --git a/validator/impl/accept-block.cpp b/validator/impl/accept-block.cpp index ff953b66..cda1c787 100644 --- a/validator/impl/accept-block.cpp +++ b/validator/impl/accept-block.cpp @@ -225,6 +225,9 @@ bool AcceptBlockQuery::create_new_proof() { } // 5. finish constructing Merkle proof from visited cells auto proof = vm::MerkleProof::generate(block_root_, usage_tree.get()); + if (proof.is_null()) { + return fatal_error("cannot create proof"); + } proof_roots_.push_back(proof); // 6. extract some information from state update state_old_hash_ = upd_cs.prefetch_ref(0)->get_hash(0).bits(); diff --git a/validator/impl/collator.cpp b/validator/impl/collator.cpp index 8eab9103..83bc710c 100644 --- a/validator/impl/collator.cpp +++ b/validator/impl/collator.cpp @@ -2274,7 +2274,7 @@ td::Result> Collator::impl_crea return td::Status::Error( -669, "cannot create action phase of a new transaction for smart contract "s + acc->addr.to_hex()); } - if (trans->bounce_enabled && (!trans->compute_phase->success || trans->action_phase->state_size_too_big) && + if (trans->bounce_enabled && (!trans->compute_phase->success || trans->action_phase->state_exceeds_limits) && !trans->prepare_bounce_phase(*action_phase_cfg)) { return td::Status::Error( -669, "cannot create bounce phase of a new transaction for smart contract "s + acc->addr.to_hex()); @@ -3640,6 +3640,9 @@ bool Collator::create_shard_state() { } LOG(INFO) << "creating Merkle update for the ShardState"; state_update = vm::MerkleUpdate::generate(prev_state_root_, state_root, state_usage_tree_.get()); + if (state_update.is_null()) { + return fatal_error("cannot create Merkle update for ShardState"); + } if (verbosity > 2) { std::cerr << "Merkle Update for ShardState: "; vm::CellSlice cs{vm::NoVm{}, state_update}; diff --git a/validator/impl/liteserver.cpp b/validator/impl/liteserver.cpp index 1e8bb820..66e73d8e 100644 --- a/validator/impl/liteserver.cpp +++ b/validator/impl/liteserver.cpp @@ -1168,7 +1168,7 @@ void LiteQuery::continue_getAccountState() { void LiteQuery::finish_getAccountState(td::BufferSlice shard_proof) { LOG(INFO) << "completing getAccountState() query"; - Ref proof1; + Ref proof1, proof2; if (!make_state_root_proof(proof1)) { return; } @@ -1197,7 +1197,11 @@ void LiteQuery::finish_getAccountState(td::BufferSlice shard_proof) { if (acc_csr.not_null()) { acc_root = acc_csr->prefetch_ref(); } - auto proof = vm::std_boc_serialize_multi({std::move(proof1), pb.extract_proof()}); + if (!pb.extract_proof_to(proof2)) { + fatal_error("unknown error creating Merkle proof"); + return; + } + auto proof = vm::std_boc_serialize_multi({std::move(proof1), std::move(proof2)}); pb.clear(); if (proof.is_error()) { fatal_error(proof.move_as_error()); @@ -1221,7 +1225,10 @@ void LiteQuery::finish_getAccountState(td::BufferSlice shard_proof) { fatal_error(S.move_as_error_prefix("Failed to load account: ")); return; } - acc_root = mpb.extract_proof(); + if (!mpb.extract_proof_to(acc_root)) { + fatal_error("unknown error creating Merkle proof"); + return; + } } auto res = vm::std_boc_serialize(std::move(acc_root)); if (res.is_error()) { @@ -1283,10 +1290,20 @@ void LiteQuery::finish_runSmcMethod(td::BufferSlice shard_proof, td::BufferSlice balance.validate_unpack(store.balance) && store.state->prefetch_ulong(1) == 1 && store.state.write().advance(1) && tlb::csr_unpack(std::move(store.state), state_init))) { LOG(INFO) << "error unpacking account state, or account is frozen or uninitialized"; + td::Result proof_boc; + if (mode & 2) { + proof_boc = pb.extract_proof_boc(); + if (proof_boc.is_error()) { + fatal_error(proof_boc.move_as_error()); + return; + } + } else { + proof_boc = td::BufferSlice(); + } auto b = ton::create_serialize_tl_object( mode, ton::create_tl_lite_block_id(base_blk_id_), ton::create_tl_lite_block_id(blk_id_), std::move(shard_proof), - std::move(state_proof), mode & 2 ? pb.extract_proof_boc().move_as_ok() : td::BufferSlice(), td::BufferSlice(), - td::BufferSlice(), -0x100, td::BufferSlice()); + std::move(state_proof), proof_boc.move_as_ok(), td::BufferSlice(), td::BufferSlice(), -0x100, + td::BufferSlice()); finish_query(std::move(b)); return; } @@ -1340,10 +1357,20 @@ void LiteQuery::finish_runSmcMethod(td::BufferSlice shard_proof, td::BufferSlice } result = res.move_as_ok(); } + td::Result proof_boc; + if (mode & 2) { + proof_boc = pb.extract_proof_boc(); + if (proof_boc.is_error()) { + fatal_error(proof_boc.move_as_error()); + return; + } + } else { + proof_boc = td::BufferSlice(); + } auto b = ton::create_serialize_tl_object( mode, ton::create_tl_lite_block_id(base_blk_id_), ton::create_tl_lite_block_id(blk_id_), std::move(shard_proof), - std::move(state_proof), mode & 2 ? pb.extract_proof_boc().move_as_ok() : td::BufferSlice(), std::move(c7_info), - td::BufferSlice(), exit_code, std::move(result)); + std::move(state_proof), proof_boc.move_as_ok(), std::move(c7_info), td::BufferSlice(), exit_code, + std::move(result)); finish_query(std::move(b)); } diff --git a/validator/impl/validate-query.cpp b/validator/impl/validate-query.cpp index 50a3e591..7d8104e0 100644 --- a/validator/impl/validate-query.cpp +++ b/validator/impl/validate-query.cpp @@ -4506,7 +4506,7 @@ bool ValidateQuery::check_one_transaction(block::Account& account, ton::LogicalT return reject_query(PSTRING() << "cannot re-create action phase of transaction " << lt << " for smart contract " << addr.to_hex()); } - if (trs->bounce_enabled && (!trs->compute_phase->success || trs->action_phase->state_size_too_big) && + if (trs->bounce_enabled && (!trs->compute_phase->success || trs->action_phase->state_exceeds_limits) && !trs->prepare_bounce_phase(action_phase_cfg_)) { return reject_query(PSTRING() << "cannot re-create bounce phase of transaction " << lt << " for smart contract " << addr.to_hex()); From 308dcc9206b809cbce48361bce9cc0390a9a5177 Mon Sep 17 00:00:00 2001 From: SpyCheese Date: Tue, 28 Feb 2023 09:45:49 +0000 Subject: [PATCH 20/46] Fix CE (#627) --- crypto/vm/boc.h | 1 + tl/generate/tl_writer_hpp.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crypto/vm/boc.h b/crypto/vm/boc.h index c4e6de45..73c20bdf 100644 --- a/crypto/vm/boc.h +++ b/crypto/vm/boc.h @@ -18,6 +18,7 @@ */ #pragma once #include +#include #include "vm/db/DynamicBagOfCellsDb.h" #include "vm/cells.h" #include "td/utils/Status.h" diff --git a/tl/generate/tl_writer_hpp.cpp b/tl/generate/tl_writer_hpp.cpp index 1d75df12..cdcf26c4 100644 --- a/tl/generate/tl_writer_hpp.cpp +++ b/tl/generate/tl_writer_hpp.cpp @@ -27,7 +27,7 @@ bool TD_TL_writer_hpp::is_documentation_generated() const { } int TD_TL_writer_hpp::get_additional_function_type(const std::string &additional_function_name) const { - assert(additional_function_name == "downcast_call"); + assert(additional_function_name == "downcast_call" || additional_function_name == "downcast_construct"); return 2; } @@ -195,7 +195,7 @@ std::string TD_TL_writer_hpp::gen_fetch_switch_end() const { std::string TD_TL_writer_hpp::gen_additional_function(const std::string &function_name, const tl::tl_combinator *t, bool is_function) const { - assert(function_name == "downcast_call"); + assert(function_name == "downcast_call" || function_name == "downcast_construct"); return ""; } From fafb90b5fa67d7510a137f0d47b1514dff9d8b00 Mon Sep 17 00:00:00 2001 From: SpyCheese Date: Thu, 2 Mar 2023 17:39:00 +0000 Subject: [PATCH 21/46] Fix compilation error in rldp-http-proxy (#630) --- rldp-http-proxy/rldp-http-proxy.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rldp-http-proxy/rldp-http-proxy.cpp b/rldp-http-proxy/rldp-http-proxy.cpp index 124168cc..e9186822 100644 --- a/rldp-http-proxy/rldp-http-proxy.cpp +++ b/rldp-http-proxy/rldp-http-proxy.cpp @@ -1370,10 +1370,10 @@ class RldpHttpProxy : public td::actor::Actor { auto &c = peer_capabilities_[peer]; if (!c.received && c.retry_at.is_in_past()) { c.retry_at = td::Timestamp::in(30.0); - auto send_query = [&](const ton::adnl::AdnlNodeIdShort &local_id) { + auto send_query = [&, this, SelfId = actor_id(this)](const ton::adnl::AdnlNodeIdShort &local_id) { td::actor::send_closure( adnl_, &ton::adnl::Adnl::send_query, local_id, peer, "q", - [SelfId = actor_id(this), peer](td::Result R) { + [SelfId, peer](td::Result R) { if (R.is_error()) { return; } From c2c9a939169c446d1c25de8b8f40ca4305869892 Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Fri, 3 Mar 2023 10:33:22 +0300 Subject: [PATCH 22/46] Bump funC version to 0.4.2 --- crypto/func/func.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/func/func.h b/crypto/func/func.h index 5476f253..1d534dc3 100644 --- a/crypto/func/func.h +++ b/crypto/func/func.h @@ -39,7 +39,7 @@ extern std::string generated_from; constexpr int optimize_depth = 20; -const std::string func_version{"0.4.1"}; +const std::string func_version{"0.4.2"}; enum Keyword { _Eof = -1, From e62830fb104eec1758ab78e836b70e338f8a4173 Mon Sep 17 00:00:00 2001 From: neodiX42 Date: Sat, 4 Mar 2023 13:34:36 +0100 Subject: [PATCH 23/46] Fix failing release creation due to missing windows binaries (#632) * Update create-release.yml * Update win-2019-compile.yml * Update create-release.yml --- .github/workflows/create-release.yml | 2 +- .github/workflows/win-2019-compile.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index a9ff3c4c..85d0dc25 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -384,4 +384,4 @@ jobs: repo_token: ${{ secrets.GITHUB_TOKEN }} file: artifacts/ton-wasm-binaries.zip asset_name: ton-wasm-binaries.zip - tag: v${{ steps.date.outputs.date }} \ No newline at end of file + tag: v${{ steps.date.outputs.date }} diff --git a/.github/workflows/win-2019-compile.yml b/.github/workflows/win-2019-compile.yml index e3d0aa1e..bd174a15 100644 --- a/.github/workflows/win-2019-compile.yml +++ b/.github/workflows/win-2019-compile.yml @@ -79,7 +79,7 @@ jobs: mkdir artifacts\smartcont mkdir artifacts\lib - for %%I in (build\storage\storage-daemon\storage-daemon.exe build\storage\storage-daemon\storage-daemon-cli.exe build\blockchain-explorer\blockchain-explorer.exe build\crypto\Release\fift.exe build\crypto\Release\tlbc.exe build\crypto\Release\func.exe build\crypto\Release\create-state.exe build\validator-engine-console\Release\validator-engine-console.exe build\tonlib\Release\tonlib-cli.exe build\tonlib\Release\tonlibjson.dll build\http\Release\http-proxy.exe build\rldp-http-proxy\Release\rldp-http-proxy.exe build\dht-server\Release\dht-server.exe build\lite-client\Release\lite-client.exe build\validator-engine\Release\validator-engine.exe build\utils\Release\generate-random-id.exe build\utils\Release\json2tlo.exe build\adnl\Release\adnl-proxy.exe) do copy %%I artifacts\ + for %%I in (build\storage\storage-daemon\Release\storage-daemon.exe build\storage\storage-daemon\Release\storage-daemon-cli.exe build\blockchain-explorer\blockchain-explorer.exe build\crypto\Release\fift.exe build\crypto\Release\tlbc.exe build\crypto\Release\func.exe build\crypto\Release\create-state.exe build\validator-engine-console\Release\validator-engine-console.exe build\tonlib\Release\tonlib-cli.exe build\tonlib\Release\tonlibjson.dll build\http\Release\http-proxy.exe build\rldp-http-proxy\Release\rldp-http-proxy.exe build\dht-server\Release\dht-server.exe build\lite-client\Release\lite-client.exe build\validator-engine\Release\validator-engine.exe build\utils\Release\generate-random-id.exe build\utils\Release\json2tlo.exe build\adnl\Release\adnl-proxy.exe) do copy %%I artifacts\ xcopy /e /k /h /i crypto\smartcont artifacts\smartcont xcopy /e /k /h /i crypto\fift\lib artifacts\lib From d9eb0bbd3b8e4516d5afb0f2950e59a993edbfad Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Sat, 4 Mar 2023 19:17:39 +0300 Subject: [PATCH 24/46] Add func mathlib (#633) --- crypto/smartcont/mathlib.fc | 860 ++++++++++++++++++++++++++++++++++ crypto/smartcont/test-math.fc | 100 ++++ 2 files changed, 960 insertions(+) create mode 100644 crypto/smartcont/mathlib.fc create mode 100644 crypto/smartcont/test-math.fc diff --git a/crypto/smartcont/mathlib.fc b/crypto/smartcont/mathlib.fc new file mode 100644 index 00000000..cc6d1a46 --- /dev/null +++ b/crypto/smartcont/mathlib.fc @@ -0,0 +1,860 @@ +{- + - + - FunC fixed-point mathematical library + - + -} + +{---------------- HIGH-LEVEL FUNCTION DECLARATIONS -----------------} +{- + Most functions declared here work either with integers or with fixed-point numbers of type `fixed248`. + `fixedNNN` informally denotes an alias for type `int` used to represent fixed-point numbers with scale 2^NNN. + Prefix `fixedNNN::` is prepended to the names of high-level functions that accept arguments and return values of type `fixedNNN`. +-} + +{- function declarations have been commented out, otherwise they are not inlined by the current FunC compiler + +;; nearest integer to sqrt(a*b) for non-negative integers or fixed-point numbers a and b +int geom_mean(int a, int b) inline_ref; +;; integer square root +int sqrt(int a) inline; +;; fixed-point square root +;; fixed248 sqrt(fixed248 x) +int fixed248::sqrt(int x) inline; + +int fixed248::sqr(int x) inline; +const int fixed248::One; + +;; log(2) as fixed248 +int fixed248::log2_const() inline; +;; Pi as fixed248 +int fixed248::Pi_const() inline; + +;; fixed248 exp(fixed248 x) +int fixed248::exp(int x) inline_ref; +;; fixed248 exp2(fixed248 x) +int fixed248::exp2(int x) inline_ref; + +;; fixed248 log(fixed248 x) +int fixed248::log(int x) inline_ref; +;; fixed248 log2(fixed248 x) +int fixed248::log2(int x) inline; + +;; fixed248 pow(fixed248 x, fixed248 y) +int fixed248::pow(int x, int y) inline_ref; + +;; (fixed248, fixed248) sincos(fixed248 x); +(int, int) fixed248::sincos(int x) inline_ref; +;; fixed248 sin(fixed248 x); +int fixed248::sin(int x) inline; +;; fixed248 cos(fixed248 x); +int fixed248::cos(int x) inline; +;; fixed248 tan(fixed248 x); +int fixed248::tan(int x) inline_ref; +;; fixed248 cot(fixed248 x); +int fixed248::cot(int x) inline_ref; + + +;; fixed248 asin(fixed248 x); +int fixed248::asin(int x) inline; +;; fixed248 acos(fixed248 x); +int fixed248::acos(int x) inline; +;; fixed248 atan(fixed248 x); +int fixed248::atan(int x) inline_ref; +;; fixed248 acot(fixed248 x); +int fixed248::acot(int x) inline_ref; + +-} ;; end (declarations) + +{-------------------- INTERMEDIATE FUNCTIONS -----------------------} + +{- + Intermediate functions are used in the implementations of high-level `fixedNNN::...` functions + if necessary, they can be used to define additional high-level functions for other fixed-point types, such as fixed128, outside this library. They can be also used in a hypothetical floating-point FunC library. + For these reasons, the declarations of these functions are collected here. +-} + +{- function declarations have been commented out, otherwise they are not inlined by the current FunC compiler + +;; fixed258 tanh(fixed258 x, int steps); +int tanh_f258(int x, int n); + +;; computes exp(x)-1 for |x| <= log(2)/2. +;; fixed257 expm1(fixed257 x); +int expm1_f257(int x); + +;; computes (sin(x+xe),-cos(x+xe)) for |x| <= Pi/4, xe very small +;; this function is very accurate, error less than 0.7 ulp (consumes ~ 5500 gas) +;; (fixed256, fixed256) sincosn(fixed256 x, fixed259 xe) +(int, int) sincosn_f256(int x, int xe); + +;; compute (sin(x),1-cos(x)) in fixed256 for |x| < 16*atan(1/16) = 0.9987 +;; (fixed256, fixed257) sincosm1_f256(fixed256 x); +;; slightly less accurate than sincosn_f256() (error up to 3/2^256), but faster (~ 4k gas) and shorter +(int, int) sincosm1_f256(int x); + +;; compute (p, q) such that p/q = tan(x) for |x|<2*atan(1/2)=1899/2048=0.927 +;; (int, int) tan_aux(fixed256 x); +(int, int) tan_aux_f256(int x); + +;; returns (y, s) such that log(x) = y/2^256 + s*log(2) for positive integer x +;; this function is very precise (error less than 0.6 ulp) and consumes < 7k gas +;; (fixed256, int) log_aux_f256(int x); +(int, int) log_aux_f256(int x); + +;; returns (y, s) such that log2(x) = y/2^256 + s for positive integer x +;; this function is very precise (error less than 0.6 ulp) and consumes < 7k gas +;; (fixed256, int) log2_aux_f256(int x); +(int, int) log2_aux_f256(int x); + +;; compute (q, z) such that atan(x)=q*atan(1/32)+z for -1 <= x < 1 +;; this function is reasonably accurate (error < 7 ulp with ulp = 2^-261), but it consumes >7k gas +;; this is sufficient for most purposes +;; (int, fixed261) atan_aux(fixed256 x) +(int, int) atan_aux_f256(int x); + +;; fixed255 atan(fixed255 x); +int atan_f255(int x); + +;; for -1 <= x < 1 only +;; fixed256 atan_small(fixed256 x); +int atan_f256_small(int x); + +;; fixed255 asin(fixed255 x); +int asin_f255(int x); + +;; fixed254 acos(fixed255 x); +int acos_f255(int x); + + +-} ;; end (declarations) + +{---------------- MISSING OPERATIONS AND BUILT-INS -----------------} + +int sgn(int x) asm "SGN"; +int abs(int x) asm "ABS"; +int min(int x, int y) asm "MIN"; + +;; compute floor(log2(x))+1 +int log2_floor_p1(int x) asm "UBITSIZE"; + +;; FunC compiler emits MULDIVC instruction, but Asm.fif usually does not know it +;; add the following line to your Asm.fif if necessary +;; x{A986} @Defop MULDIVC + +;; the following instructions might have been emitted by muldivmod() and muldivmodr() builtins, but FunC does not have these +;; x{A9A5} @Defop MULRSHIFTR +int mulrshiftr(int x, int y, int s) asm "MULRSHIFTR"; +;; x{A9B5} @Defop(8u+1) MULRSHIFTR# +int mulrshiftr256(int x, int y) asm "256 MULRSHIFTR#"; +;; x{A9BC} @Defop(8u+1) MULRSHIFT#MOD +(int, int) mulrshift256mod(int x, int y) asm "256 MULRSHIFT#MOD"; +;; x{A9BD} @Defop(8u+1) MULRSHIFTR#MOD +(int, int) mulrshiftr256mod(int x, int y) asm "256 MULRSHIFTR#MOD"; +(int, int) mulrshiftr255mod(int x, int y) asm "255 MULRSHIFTR#MOD"; +(int, int) mulrshiftr248mod(int x, int y) asm "248 MULRSHIFTR#MOD"; +(int, int) mulrshiftr5mod(int x, int y) asm "5 MULRSHIFTR#MOD"; +(int, int) mulrshiftr6mod(int x, int y) asm "6 MULRSHIFTR#MOD"; +(int, int) mulrshiftr7mod(int x, int y) asm "7 MULRSHIFTR#MOD"; + +;; these instructions might have been emitted by muldivmodr(..., 1 << N, ...) built-ins +;; x{A9D5} @Defop(8u+1) LSHIFT#DIVR +int lshift256divr(int x, int y) asm "256 LSHIFT#DIVR"; +;; x{A9DD} @Defop(8u+1) LSHIFT#DIVMODR +(int, int) lshift256divmodr(int x, int y) asm "256 LSHIFT#DIVMODR"; +(int, int) lshift255divmodr(int x, int y) asm "255 LSHIFT#DIVMODR"; +(int, int) lshift2divmodr(int x, int y) asm "2 LSHIFT#DIVMODR"; +(int, int) lshift7divmodr(int x, int y) asm "7 LSHIFT#DIVMODR"; +;; x{A9CD} @Defop LSHIFTDIVMODR +(int, int) lshiftdivmodr(int x, int y, int s) asm "LSHIFTDIVMODR"; + +;; these instructions might have been emitted by divmodr(..., 1 << NN) or divmod(..., 1 << N) built-ins +;; but FunC does not have divmodr(), and divmod() always compiles to "DIVMOD" +;; x{A93D} @Defop(8u+1) RSHIFTR#MOD +(int, int) rshiftr256mod(int x) asm "256 RSHIFTR#MOD"; +(int, int) rshiftr248mod(int x) asm "248 RSHIFTR#MOD"; +(int, int) rshiftr4mod(int x) asm "4 RSHIFTR#MOD"; +;; x{A93C} @Defop(8u+1) RSHIFT#MOD +(int, int) rshift3mod(int x) asm "3 RSHIFT#MOD"; + +;; computes y - x (FunC compiler does not try to use this by itself) +int sub_rev(int x, int y) asm "SUBR"; + +{------------------------ SQUARE ROOTS ----------------------------} + +;; computes sqrt(a*b) exactly rounded to the nearest integer +;; for all 0 <= a, b <= 2^256-1 +;; may be used with b=1 or b=scale of fixed-point numbers +int geom_mean(int a, int b) inline_ref { + if (min(a, b) <= 0) { + return 0; + } + int s = log2_floor_p1(a); + int t = log2_floor_p1(b); + ;; NB: (a-b)/2+b == (a+b)/2, but without overflow for large a and b + int x = (s == t ? (a - b) / 2 + b : 1 << ((s + t) / 2)); + do { + ;; if always used with b=2^const, may be optimized to "const LSHIFTDIVC#" + ;; it is important to use `muldivc` here, not `muldiv` or `muldivr` + int q = (muldivc(a, b, x) - x) / 2; + x += q; + } until (q == 0); + return x; +} + +;; integer square root, computes round(sqrt(a)) for all a>=0. +;; note: `inline` is better than `inline_ref` for such simple functions +int sqrt(int a) inline { + return geom_mean(a, 1); +} + +;; version for fixed248 = fixed-point numbers with scale 2^248 +;; fixed248 sqrt(fixed248 x) +int fixed248::sqrt(int x) inline { + return geom_mean(x, 1 << 248); +} + +;; fixed255 sqrt(fixed255 x) +int fixed255::sqrt(int x) inline { + return geom_mean(x, 1 << 255); +} + +;; fixed248 sqr(fixed248 x); +int fixed248::sqr(int x) inline { + return muldivr(x, x, 1 << 248); +} + +;; fixed255 sqr(fixed255 x); +int fixed255::sqr(int x) inline { + return muldivr(x, x, 1 << 255); +} + +const int fixed248::One = (1 << 248); +const int fixed255::One = (1 << 255); + +{-------------------- USEFUL CONSTANTS --------------------} + +;; store huge constants in inline_ref functions for reuse +;; (y,z) where y=round(log(2)*2^256), z=round((log(2)*2^256-y)*2^128) +;; then log(2) = y/2^256 + z/2^384 +(int, int) log2_xconst_f256() inline_ref { + return (80260960185991308862233904206310070533990667611589946606122867505419956976172, -32272921378999278490133606779486332143); +} + +;; (y,z) where Pi = y/2^254 + z/2^382 +(int, int) Pi_xconst_f254() inline_ref { + return (90942894222941581070058735694432465663348344332098107489693037779484723616546, 108051869516004014909778934258921521947); +} + +;; atan(1/16) as fixed260 +int Atan1_16_f260() inline_ref { + return 115641670674223639132965820642403718536242645001775371762318060545014644837101; ;; true value is ...101.0089... +} + +;; atan(1/8) as fixed259 +int Atan1_8_f259() inline_ref { + return 115194597005316551477397594802136977648153890007566736408151129975021336532841; ;; correction -0.1687... +} + +;; atan(1/32) as fixed261 +int Atan1_32_f261() inline_ref { + return 115754418570128574501879331591757054405465733718902755858991306434399246026247; ;; correction 0.395... +} + +;; inline is better than inline_ref for such very small functions +int log2_const_f256() inline { + (int c, _) = log2_xconst_f256(); + return c; +} + +int fixed248::log2_const() inline { + return log2_const_f256() ~>> 8; +} + +int Pi_const_f254() inline { + (int c, _) = Pi_xconst_f254(); + return c; +} + +int fixed248::Pi_const() inline { + return Pi_const_f254() ~>> 6; +} + +{--------------- HYPERBOLIC TANGENT AND EXPONENT -------------------} + +;; hyperbolic tangent of small x via n+2 terms of Lambert's continued fraction +;; n=17: good for |x| < log(2)/4 = 0.173 +;; fixed258 tanh_f258(fixed258 x, int n) +int tanh_f258(int x, int n) inline_ref { + int x2 = muldivr(x, x, 1 << 255); ;; x^2 as fixed261 + int c = int a = (2 * n + 5) << 250; ;; a=2n+5 as fixed250 + int Two = (1 << 251); ;; 2. as fixed250 + repeat (n) { + a = (c -= Two) + muldivr(x2, 1 << 239, a); ;; a := 2k+1+x^2/a as fixed250, k=n+1,n,...,2 + } + a = (touch(3) << 254) + muldivr(x2, 1 << 243, a); ;; a := 3+x^2/a as fixed254 + ;; y = x/(1+a') = x - x*a'/(1+a') = x - x*x^2/(a+x^2) where a' = x^2/a + return x - (muldivr(x, x2, a + (x2 ~>> 7)) ~>> 7); +} + +;; fixed257 expm1_f257(fixed257 x) +;; computes exp(x)-1 for small x via 19 terms of Lambert's continued fraction for tanh(x/2) +;; good for |x| < log(2)/2 = 0.347 (n=17); consumes ~3500 gas +int expm1_f257(int x) inline_ref { + ;; (almost) compute tanh(x/2) first; x/2 as fixed258 = x as fixed257 + int x2 = muldivr(x, x, 1 << 255); ;; x^2 as fixed261 + int Two = (1 << 251); ;; 2. as fixed250 + int c = int a = touch(39) << 250; ;; a=2n+5 as fixed250 + repeat (17) { + a = (c -= Two) + muldivr(x2, 1 << 239, a); ;; a := 2k+1+x^2/a as fixed250, k=n+1,n,...,2 + } + a = (touch(3) << 254) + muldivr(x2, 1 << 243, a); ;; a := 3+x^2/a as fixed254 + ;; now tanh(x/2) = x/(1+a') where a'=x^2/a ; apply exp(x)-1=2*tanh(x/2)/(1-tanh(x/2)) + int t = (x ~>> 4) - a; ;; t:=x-a as fixed254 + return x - muldivr(x2, t / 2, a + mulrshiftr256(x, t) ~/ 4) ~/ 4; ;; x - x^2 * (x-a) / (a + x*(x-a)) +} + +;; used to avoid broken optimisations in older versions of FunC +int minus_one() asm "-1 PUSHINT"; + +;; expm1_f257() may be used to implement specific fixed-point exponentials +;; example: +;; fixed248 exp(fixed248 x) +int fixed248::exp(int x) inline_ref { + var (l2c, l2d) = log2_xconst_f256(); + ;; divide x by log(2) and convert to fixed257 + ;; (int q, x) = muldivmodr(x, 256, l2c); ;; unfortunately, no such built-in + (int q, x) = lshiftdivmodr(x, l2c, 8); + x = 2 * x - muldivr(q, l2d, 1 << 127); + int y = expm1_f257(x); + ;; result is (1 + y) * (2^q) --> ((1 << 257) + y) >> (9 - q) + return (y ~>> (9 - q)) - (minus_one() << (248 + q)); + ;; (-1 << (248 + q)) is compiled into non-existent instruction NEGPOW2 + ;; note that (y ~>> (9 - q)) + (1 << (248 + q)) leads to overflow when q=8 +} + +;; compute 2^x in fixed248 +;; fixed248 exp2(fixed248 x) +int fixed248::exp2(int x) inline_ref { + ;; (int q, x) = divmodr(x, 1 << 248); ;; no such built-in + (int q, x) = rshiftr248mod(x); + x = muldivr(x, log2_const_f256(), 1 << 247); + int y = expm1_f257(x); + return (y ~>> (9 - q)) - (minus_one() << (248 + q)); +} + +{--------------------- TRIGONOMETRIC FUNCTIONS -----------------------} + +;; fixed260 tan(fixed260 x); +;; computes tan(x) for small |x|> 10)) ~>> 9); +} + +;; fixed260 tan(fixed260 x); +int tan_f260(int x) inline_ref { + return tan_f260_inlined(x); +} + +;; fixed258 tan(fixed258 x); +;; computes tan(x) for small |x|> 6)) ~>> 5); +} + +;; fixed258 tan(fixed258 x); +int tan_f258(int x) inline_ref { + return tan_f258_inlined(x); +} + +;; (fixed259, fixed263) sincosm1(fixed259 x) +;; computes (sin(x), 1-cos(x)) for small |x|<2*atan(1/16) +(int, int) sincosm1_f259_inlined(int x) inline { + int t = tan_f260_inlined(x); ;; t=tan(x/2) as fixed260 + int tt = mulrshiftr256(t, t); ;; t^2 as fixed264 + int y = tt ~/ 512 + (1 << 255); ;; 1+t^2 as fixed255 + ;; 2*t/(1+t^2) as fixed259 and 2*t^2/(1+t^2) as fixed263 + ;; return (muldivr(t, 1 << 255, y), muldivr(tt, 1 << 255, y)); + return (t - muldivr(t / 2, tt, y) ~/ 256, tt - muldivr(tt / 2, tt, y) ~/ 256); +} + +(int, int) sincosm1_f259(int x) inline_ref { + return sincosm1_f259_inlined(x); +} + +;; computes (sin(x+xe),-cos(x+xe)) for |x| <= Pi/4, xe very small +;; this function is very accurate, error less than 0.7 ulp (consumes ~ 5500 gas) +;; (fixed256, fixed256) sincosn(fixed256 x, fixed259 xe) +(int, int) sincosn_f256(int x, int xe) inline_ref { + ;; var (q, x1) = muldivmodr(x, 8, Atan1_8_f259()); ;; no muldivmodr() builtin + var (q, x1) = lshift2divmodr(abs(x), Atan1_8_f259()); ;; reduce mod theta where theta=2*atan(1/8) + var (si, co) = sincosm1_f259(x1 * 2 + xe); + var (a, b, c) = (-1, 0, 1); + repeat (q) { ;; (a+b*I) *= (8+I)^2 = 63+16*I + (a, b, c) = (63 * a - 16 * b, 16 * a + 63 * b, 65 * c); + } + ;; now a/c = cos(q*theta), b/c = sin(q*theta) exactly(!) + ;; compute (a+b*I)*(1-co+si*I)/c + ;; (b, a) = (lshift256divr(b, c), lshift256divr(a, c)); + (b, int br) = lshift256divmodr(b, c); br = muldivr(br, 128, c); + (a, int ar) = lshift256divmodr(a, c); ar = muldivr(ar, 128, c); + return (sgn(x) * (((mulrshiftr256(b, co) - br) ~/ 16 - mulrshiftr256(a, si)) ~/ 8 - b), + a - ((mulrshiftr256(a, co) - ar) ~/ 16 + mulrshiftr256(b, si)) ~/ 8); +} + +;; compute (sin(x),1-cos(x)) in fixed256 for |x| < 16*atan(1/16) = 0.9987 +;; (fixed256, fixed257) sincosm1_f256(fixed256 x); +;; slightly less accurate than sincosn_f256() (error up to 3/2^256), but faster (~ 4k gas) and shorter +(int, int) sincosm1_f256(int x) inline_ref { + var (si, co) = sincosm1_f259_inlined(x); ;; compute (sin,1-cos)(x/8) in (fixed259,fixed263) + int r = 7; + repeat (r / 2) { + ;; 1-cos(2*x) = 2*sin(x)^2, sin(2*x) = 2*sin(x)*cos(x) + (co, si) = (mulrshiftr256(si, si), si - (mulrshiftr256(si, co) ~>> r)); + r -= 2; + } + return (si, co); +} + +;; compute (p, q) such that p/q = tan(x) for |x|<2*atan(1/2)=1899/2048=0.927 +;; (int, int) tan_aux(fixed256 x); +(int, int) tan_aux_f256(int x) inline_ref { + int t = tan_f258_inlined(x); ;; t=tan(x/4) as fixed258 + ;; t:=2*t/(1-t^2)=2*(t-t^3/(t^2-1)) + int tt = mulrshiftr256(t, t); ;; t^2 as fixed260 + t = muldivr(t, tt, tt ~/ 16 + (-1 << 256)) ~/ 16 - t; ;; now t=-tan(x/2) as fixed259 + return (t, mulrshiftr256(t, t) ~/ 4 + (-1 << 256)); ;; return (2*t, t^2-1) as fixed256 +} + +;; sincosm1_f256() and sincosn_f256() may be used to implement trigonometric functions for different fixed-point types +;; example: +;; (fixed248, fixed248) sincos(fixed248 x); +(int, int) fixed248::sincos(int x) inline_ref { + var (Pic, Pid) = Pi_xconst_f254(); + ;; (int q, x) = muldivmodr(x, 128, Pic); ;; no muldivmodr() builtin + (int q, x) = lshift7divmodr(x, Pic); ;; reduce mod Pi/2 + x = 2 * x - muldivr(q, Pid, 1 << 127); + (int si, int co) = sincosm1_f256(x); ;; doesn't make sense to use more accurate sincosn_f256() + co = (1 << 248) - (co ~>> 9); + si ~>>= 8; + repeat (q & 3) { + (si, co) = (co, - si); + } + return (si, co); +} + +;; fixed248 sin(fixed248 x); +;; inline is better than inline_ref for such simple functions +int fixed248::sin(int x) inline { + (int si, _) = fixed248::sincos(x); + return si; +} + +;; fixed248 cos(fixed248 x); +int fixed248::cos(int x) inline { + (_, int co) = fixed248::sincos(x); + return co; +} + +;; similarly, tan_aux_f256() may be used to implement tan() and cot() for specific fixed-point formats +;; fixed248 tan(fixed248 x); +;; not very accurate when |tan(x)| is very large (difficult to do better without floating-point numbers) +;; however, the relative accuracy is approximately 2^-247 in all cases, which is good enough for arguments given up to 2^-249 +int fixed248::tan(int x) inline_ref { + var (Pic, Pid) = Pi_xconst_f254(); + ;; (int q, x) = muldivmodr(x, 128, Pic); ;; no muldivmodr() builtin + (int q, x) = lshift7divmodr(x, Pic); ;; reduce mod Pi/2 + x = 2 * x - muldivr(q, Pid, 1 << 127); + var (a, b) = tan_aux_f256(x); ;; now a/b = tan(x') + if (q & 1) { + (a, b) = (b, - a); + } + return muldivr(a, 1 << 248, b); ;; either -b/a or a/b as fixed248 +} + +;; fixed248 cot(fixed248 x); +int fixed248::cot(int x) inline_ref { + var (Pic, Pid) = Pi_xconst_f254(); + (int q, x) = lshift7divmodr(x, Pic); ;; reduce mod Pi/2 + x = 2 * x - muldivr(q, Pid, 1 << 127); + var (b, a) = tan_aux_f256(x); ;; now b/a = tan(x') + if (q & 1) { + (a, b) = (b, - a); + } + return muldivr(a, 1 << 248, b); ;; either -b/a or a/b as fixed248 +} + +{----------------- INVERSE HYPERBOLIC TANGENT AND LOGARITHMS -----------------} + +;; inverse hyperbolic tangent of small x, evaluated by means of n terms of the continued fraction +;; valid for |x| < 2^-2.5 ~ 0.18 if n=37 (slightly less accurate with n=36) +;; |x| < 1/8 if n=32; |x| < 2^-3.5 if n=28; |x| < 1/16 if n=25 +;; |x| < 2^-4.5 if n=23; |x| < 1/32 if n=21; |x| < 1/64 if n=18 +;; fixed258 atanh(fixed258 x); +int atanh_f258(int x, int n) inline_ref { + int x2 = mulrshiftr256(x, x); ;; x^2 as fixed260 + int One = (1 << 254); + int a = One ~/ n + (1 << 255); ;; a := 2 + 1/n as fixed254 + repeat (n - 1) { + ;; a := 1 + (1 - x^2 / a)(1 + 1/n) as fixed254 + int t = One - muldivr(x2, 1 << 248, a); ;; t := 1 - x^2 / a + a = muldivr(t, n, (int n1 = n - 1)) + One; + n = n1; + } + ;; x / (1 - x^2 / a) = x / (1 - d) = x + x * d / (1 - d) for d = x^2 / a + ;; int d = muldivr(x2, 1 << 255, a - (x2 ~>> 6)); ;; d/(1-d) = x^2/(a-x^2) as fixed261 + ;; return x + (mulrshiftr256(x, d) ~>> 5); + return x + muldivr(x, x2 / 2, a - x2 ~/ 64) ~/ 32; +} + +;; number of terms n should be chosen as for atanh_f258() +;; fixed261 atanh(fixed261 x); +int atanh_f261_inlined(int x, int n) inline { + int x2 = mulrshiftr256(x, x); ;; x^2 as fixed266 + int One = (1 << 254); + int a = One ~/ n + (1 << 255); ;; a := 2 + 1/n as fixed254 + repeat (n - 1) { + ;; a := 1 + (1 - x^2 / a)(1 + 1/n) as fixed254 + int t = One - muldivr(x2, 1 << 242, a); ;; t := 1 - x^2 / a + a = muldivr(t, n, (int n1 = n - 1)) + One; + n = n1; + } + ;; x / (1 - x^2 / a) = x / (1 - d) = x + x * d / (1 - d) for d = x^2 / a + ;; int d = muldivr(x2, 1 << 255, a - (x2 ~>> 12)); ;; d/(1-d) = x^2/(a-x^2) as fixed267 + ;; return x + (mulrshiftr256(x, d) ~>> 11); + return x + muldivr(x, x2, a - x2 ~/ 4096) ~/ 4096; +} + +;; fixed261 atanh(fixed261 x); +int atanh_f261(int x, int n) inline_ref { + return atanh_f261_inlined(x, n); +} + +;; returns (y, s) such that log(x) = y/2^257 + s*log(2) for positive integer x +;; (fixed257, int) log_aux(int x) +(int, int) log_aux_f257(int x) inline_ref { + int s = log2_floor_p1(x); + x <<= 256 - s; + int t = touch(-1 << 256); + if ((x >> 249) <= 90) { + ;; t~touch(); + t >>= 1; + s -= 1; + } + x += t; + int 2x = 2 * x; + int y = lshift256divr(2x, (x >> 1) - t); + ;; y = 2x - (mulrshiftr256(2x, y) ~>> 2); ;; this line could improve precision on very rare occasions + return (atanh_f258(y, 36), s); +} + +;; computes 33^m for small m +int pow33(int m) inline { + int t = 1; + repeat (m) { t *= 33; } + return t; +} + +;; computes 33^m for small 0<=m<=22 +;; slightly faster than pow33() +int pow33b(int m) inline { + (int mh, int ml) = m /% 5; + int t = 1; + repeat (ml) { t *= 33; } + repeat (mh) { t *= 33 * 33 * 33 * 33 * 33; } + return t; +} + +;; returns (s, q, y) such that log(x) = s*log(2) + q*log(33/32) + y/2^260 for positive integer x +;; (int, int, fixed260) log_auxx_f260(int x); +(int, int, int) log_auxx_f260(int x) inline_ref { + int s = log2_floor_p1(x) - 1; + x <<= 255 - s; ;; rescale to 1 <= x < 2 as fixed255 + int t = touch(2873) << 244; ;; ~ (33/32)^11 ~ sqrt(2) as fixed255 + int x1 = (x - t) >> 1; + int q = muldivr(x1, 65, x1 + t) + 11; ;; crude approximation to round(log(x)/log(33/32)) + ;; t = 1; repeat (q) { t *= 33; } ;; t:=33^q, 0<=q<=22 + t = pow33b(q); + t <<= (51 - q) * 5; ;; t:=(33/32)^q as fixed255, nearest power of 33/32 to x + x -= t; + int y = lshift256divr(x << 4, (x >> 1) + t); ;; y = (x-t)/(x+t) as fixed261 + y = atanh_f261(y, 18); ;; atanh((x-t)/(x+t)) as fixed261, or log(x/t) as fixed260 + return (s, q, y); +} + +;; returns (y, s) such that log(x) = y/2^256 + s*log(2) for positive integer x +;; this function is very precise (error less than 0.6 ulp) and consumes < 7k gas +;; (fixed256, int) log_aux_f256(int x); +(int, int) log_aux_f256(int x) inline_ref { + var (s, q, y) = log_auxx_f260(x); + var (yh, yl) = rshiftr4mod(y); ;; y ~/% 16 , but FunC does not optimize this to RSHIFTR#MOD + ;; int Log33_32 = 3563114646320977386603103333812068872452913448227778071188132859183498739150; ;; log(33/32) as fixed256 + ;; int Log33_32_l = -3769; ;; log(33/32) = Log33_32 / 2^256 + Log33_32_l / 2^269 + yh += (yl * 512 + q * -3769) ~>> 13; ;; compensation, may be removed if slightly worse accuracy is acceptable + int Log33_32 = 3563114646320977386603103333812068872452913448227778071188132859183498739150; ;; log(33/32) as fixed256 + return (yh + q * Log33_32, s); +} + +;; returns (y, s) such that log2(x) = y/2^256 + s for positive integer x +;; this function is very precise (error less than 0.6 ulp) and consumes < 7k gas +;; (fixed256, int) log2_aux_f256(int x); +(int, int) log2_aux_f256(int x) inline_ref { + var (s, q, y) = log_auxx_f260(x); + y = lshift256divr(y, log2_const_f256()) ~>> 4; ;; y/log(2) as fixed256 + int Log33_32 = 5140487830366106860412008603913034462883915832139695448455767612111363481357; ;; log_2(33/32) as fixed256 + ;; Log33_32/2^256 happens to be a very precise approximation to log_2(33/32), no compensation required + return (y + q * Log33_32, s); +} + +;; functions log_aux_f256() and log2_aux_f256() may be used to implement specific fixed-point instances of log() and log2() + +;; fixed248 log(fixed248 x) +int fixed248::log(int x) inline_ref { + var (y, s) = log_aux_f256(x); + return muldivr(s - 248, log2_const_f256(), 1 << 8) + (y ~>> 8); + ;; return muldivr(s - 248, 80260960185991308862233904206310070533990667611589946606122867505419956976172, 1 << 8) + (y ~>> 8); +} + +;; fixed248 log2(fixed248 x) +int fixed248::log2(int x) inline { + var (y, s) = log2_aux_f256(x); + return ((s - 248) << 248) + (y ~>> 8); +} + +;; computes x^y as exp(y*log(x)), x >= 0 +;; fixed248 pow(fixed248 x, fixed248 y); +int fixed248::pow(int x, int y) inline_ref { + ifnot (y) { + return 1 << 248; ;; x^0 = 1 + } + if (x <= 0) { + int bad = (x | y) < 0; + return 0 >> bad; ;; 0^y = 0 if x=0 and y>=0; "out of range" exception otherwise + } + var (l, s) = log2_aux_f256(x); + s -= 248; ;; log_2(x) = s+l, l is fixed256, 0<=l<1 + ;; compute (s+l)*y = q+ll + var (q1, r1) = mulrshiftr248mod(s, y); ;; muldivmodr(s, y, 1 << 248) + var (q2, r2) = mulrshift256mod(l, y); + r2 >>= 247; + var (q3, r3) = rshiftr248mod(q2); ;; divmodr(q2, 1 << 248); + var (q, ll) = rshiftr248mod(r1 + r3); + ll = 512 * ll + r2; + q += q1 + q3; + ;; now log_2(x^y) = y*log_2(x) = q + ll, ss integer, ll fixed257, -1/2<=ll<1/2 + int sq = q + 248; + if (sq <= 0) { + return - (sq == 0); ;; underflow + } + int y = expm1_f257(mulrshiftr256(ll, log2_const_f256())); + return (y ~>> (9 - q)) - (minus_one() << sq); +} + +{--------------------- INVERSE TRIGONOMETRIC FUNCTIONS -------------------} + +;; number of terms n should be chosen as for atanh_f258() +;; fixed259 atan(fixed259 x); +int atan_f259(int x, int n) inline_ref { + int x2 = mulrshiftr256(x, x); ;; x^2 as fixed262 + int One = (1 << 254); + int a = One ~/ n + (1 << 255); ;; a := 2 + 1/n as fixed254 + repeat (n - 1) { + ;; a := 1 + (1 + x^2 / a)(1 + 1/n) as fixed254 + int t = One + muldivr(x2, 1 << 246, a); ;; t := 1 + x^2 / a + a = muldivr(t, n, (int n1 = n - 1)) + One; + n = n1; + } + ;; x / (1 + x^2 / a) = x / (1 + d) = x - x * d / (1 + d) = x - x * x^2/(a+x^2) for d = x^2 / a + return x - muldivr(x, x2, a + x2 ~/ 256) ~/ 256; +} + +;; number of terms n should be chosen as for atanh_f261() +;; fixed261 atan(fixed261 x); +int atan_f261_inlined(int x, int n) inline { + int x2 = mulrshiftr256(x, x); ;; x^2 as fixed266 + int One = (1 << 254); + int a = One ~/ n + (1 << 255); ;; a := 2 + 1/n as fixed254 + repeat (n - 1) { + ;; a := 1 + (1 + x^2 / a)(1 + 1/n) as fixed254 + int t = One + muldivr(x2, 1 << 242, a); ;; t := 1 + x^2 / a + a = muldivr(t, n, (int n1 = n - 1)) + One; + n = n1; + } + ;; x / (1 + x^2 / a) = x / (1 + d) = x - x * d / (1 + d) = x - x * x^2/(a+x^2) for d = x^2 / a + return x - muldivr(x, x2, a + x2 ~/ 4096) ~/ 4096; +} + +;; fixed261 atan(fixed261 x); +int atan_f261(int x, int n) inline_ref { + return atan_f261_inlined(x, n); +} + +;; computes (q,a,b) such that q is approximately atan(x)/atan(1/32) and a+b*I=(1+I/32)^q as fixed255 +;; then b/a=atan(q*atan(1/32)) exactly, and (a,b) is almost a unit vector pointing in the direction of (1,x) +;; must have |x|<1.1, x is fixed24 +;; (int, fixed255, fixed255) atan_aux_prereduce(fixed24 x); +(int, int, int) atan_aux_prereduce(int x) inline_ref { + int xu = abs(x); + int tc = 7214596; ;; tan(13*theta) as fixed24 where theta=atan(1/32) + int t1 = muldivr(xu - tc, 1 << 88, xu * tc + (1 << 48)); ;; tan(x') as fixed64 where x'=atan(x)-13*theta + ;; t1/(3+t1^2) * 3073/32 = x'/3 * 3072/32 = x' / (96/3072) = x' / theta + int q = muldivr(t1 * 3073, 1 << 59, t1 * t1 + (touch(3) << 128)) + 13; ;; approximately round(atan(x)/theta), 0<=q<=25 + var (pa, pb) = (33226912, 5232641); ;; (32+I)^5 + var (qh, ql) = q /% 5; + var (a, b) = (1 << (5 * (51 - q)), 0); ;; (1/32^q, 0) as fixed255 + repeat (ql) { ;; a+b*I *= 32+I + (a, b) = (sub_rev(touch(b), 32 * a), a + 32 * b); ;; same as (32 * a - b, 32 * b + a), but more efficient + } + repeat (qh) { ;; a+b*I *= (32+I)^5 = pa + pb*I + (a, b) = (a * pa - b * pb, a * pb + b * pa); + } + int xs = sgn(x); + return (xs * q, a, xs * b); +} + +;; compute (q, z) such that atan(x)=q*atan(1/32)+z for -1 <= x < 1 +;; this function is reasonably accurate (error < 7 ulp with ulp = 2^-261), but it consumes >7k gas +;; this is sufficient for most purposes +;; (int, fixed261) atan_aux(fixed256 x) +(int, int) atan_aux_f256(int x) inline_ref { + var (q, a, b) = atan_aux_prereduce(x ~>> 232); ;; convert x to fixed24 + ;; now b/a = tan(q*atan(1/32)) exactly, where q is near atan(x)/atan(1/32); so b/a is near x + ;; compute y = u/v = (a*x-b)/(a+b*x) as fixed261 ; then |y|<0.0167 = 1.07/64 and atan(x)=atan(y)+q*atan(1/32) + var (u, ul) = mulrshiftr256mod(a, x); + u = (ul ~>> 250) + ((u - b) << 6); ;; |u| < 1/32, convert fixed255 -> fixed261 + int v = a + mulrshiftr256(b, x); ;; v is scalar product of (a,b) and (1,x), it is approximately in [1..sqrt(2)] as fixed255 + int y = muldivr(u, 1 << 255, v); ;; y = u/v as fixed261 + int z = atan_f261_inlined(y, 18); ;; z = atan(x)-q*atan(1/32) + return (q, z); +} + +;; compute (q, z) such that atan(x)=q*atan(1/32)+z for -1 <= x < 1 +;; this function is very accurate (error < 2 ulp), but it consumes >7k gas +;; in most cases, faster function atan_aux_f256() should be used +;; (int, fixed261) atan_auxx(fixed256 x) +(int, int) atan_auxx_f256(int x) inline_ref { + var (q, a, b) = atan_aux_prereduce(x ~>> 232); ;; convert x to fixed24 + ;; now b/a = tan(q*atan(1/32)) exactly, where q is near atan(x)/atan(1/32); so b/a is near x + ;; compute y = (a*x-b)/(a+b*x) as fixed261 ; then |y|<0.0167 = 1.07/64 and atan(x)=atan(y)+q*atan(1/32) + ;; use sort of double precision arithmetic for this + var (u, ul) = mulrshiftr256mod(a, x); + ul /= 2; + u -= b; ;; |u| < 1/32 as fixed255 + var (v, vl) = mulrshiftr256mod(b, x); + vl /= 2; + v += a; ;; v is scalar product of (a,b) and (1,x), it is approximately in [1..sqrt(2)] as fixed255 + ;; y = (u + ul*eps) / (v + vl*eps) = u/v + (ul - vl * u/v)/v * eps where eps=1/2^255 + var (y, r) = lshift255divmodr(u, v); ;; y = u/v as fixed255 + int yl = muldivr(ul + r, 1 << 255, v) - muldivr(vl, y, v); ;; y/2^255 + yl/2^510 represent u/v + y = (yl ~>> 249) + (y << 6); ;; convert y to fixed261 + int z = atan_f261_inlined(y, 18); ;; z = atan(x)-q*atan(1/32) + return (q, z); +} + +;; consumes ~ 8k gas +;; fixed255 atan(fixed255 x); +int atan_f255(int x) inline_ref { + int s = (x ~>> 256); + touch(x); + if (s) { + x = lshift256divr(-1 << 255, x); ;; x:=-1/x as fixed256 + } else { + x *= 2; ;; convert to fixed256 + } + var (q, z) = atan_aux_f256(x); + ;; now atan(x) = z + q*atan(1/32) + s*(Pi/2), z is fixed261 + var (Pi_h, Pi_l) = Pi_xconst_f254(); ;; Pi/2 as fixed255 + fixed383 + var (qh, ql) = mulrshiftr6mod (q, Atan1_32_f261()); + return qh + s * Pi_h + (z + ql + muldivr(s, Pi_l, 1 << 122)) ~/ 64; +} + +;; computes atan(x) for -1 <= x < 1 only +;; fixed256 atan_small(fixed256 x); +int atan_f256_small(int x) inline_ref { + var (q, z) = atan_aux_f256(x); + ;; now atan(x) = z + q*atan(1/32), z is fixed261 + var (qh, ql) = mulrshiftr5mod (q, Atan1_32_f261()); + return qh + (z + ql) ~/ 32; +} + +;; fixed255 asin(fixed255 x); +int asin_f255(int x) inline_ref { + int a = fixed255::One - fixed255::sqr(x); ;; a:=1-x^2 + if (a <= 0) { + return sgn(x) * Pi_const_f254(); ;; Pi/2 or -Pi/2 + } + int y = fixed255::sqrt(a); ;; sqrt(1-x^2) + int t = - lshift256divr(x, (-1 << 255) - y); ;; t = x/(1+sqrt(1-x^2)) avoiding overflow + return atan_f256_small(t); ;; asin(x)=2*atan(t) +} + +;; fixed254 acos(fixed255 x); +int acos_f255(int x) inline_ref { + int Pi = Pi_const_f254(); + if (x <= (-1 << 255)) { + return Pi; ;; acos(-1) = Pi + } + Pi /= 2; + int y = fixed255::sqrt(fixed255::One - fixed255::sqr(x)); ;; sqrt(1-x^2) + int t = lshift256divr(x, (-1 << 255) - y); ;; t = -x/(1+sqrt(1-x^2)) avoiding overflow + return Pi + atan_f256_small(t) ~/ 2; ;; acos(x)=Pi/2 + 2*atan(t) +} + +;; consumes ~ 10k gas +;; fixed248 asin(fixed248 x) +int fixed248::asin(int x) inline { + return asin_f255(x << 7) ~>> 7; +} + +;; consumes ~ 10k gas +;; fixed248 acos(fixed248 x) +int fixed248::acos(int x) inline { + return acos_f255(x << 7) ~>> 6; +} + +;; consumes ~ 7500 gas +;; fixed248 atan(fixed248 x); +int fixed248::atan(int x) inline_ref { + int s = (x ~>> 249); + touch(x); + if (s) { + s = sgn(s); + x = lshift256divr(-1 << 248, x); ;; x:=-1/x as fixed256 + } else { + x <<= 8; ;; convert to fixed256 + } + var (q, z) = atan_aux_f256(x); + ;; now atan(x) = z + q*atan(1/32) + s*(Pi/2), z is fixed261 + return (z ~/ 64 + s * Pi_const_f254() + muldivr(q, Atan1_32_f261(), 64)) ~/ 128; ;; compute in fixed255, then convert +} + +;; fixed248 acot(fixed248 x); +int fixed248::acot(int x) inline_ref { + int s = (x ~>> 249); + touch(x); + if (s) { + x = lshift256divr(-1 << 248, x); ;; x:=-1/x as fixed256 + s = 0; + } else { + x <<= 8; ;; convert to fixed256 + s = sgn(x); + } + var (q, z) = atan_aux_f256(x); + ;; now acot(x) = - z - q*atan(1/32) + s*(Pi/2), z is fixed261 + return (s * Pi_const_f254() - z ~/ 64 - muldivr(q, Atan1_32_f261(), 64)) ~/ 128; ;; compute in fixed255, then convert +} diff --git a/crypto/smartcont/test-math.fc b/crypto/smartcont/test-math.fc new file mode 100644 index 00000000..1ff43cd9 --- /dev/null +++ b/crypto/smartcont/test-math.fc @@ -0,0 +1,100 @@ +{-------------------------- TESTS for mathlib.fc ----------------------------} + +;; computes 1-acos(x)/Pi by a very simple, extremely slow (~70k gas) and imprecise method +;; fixed256 acos_prepare_slow(fixed255 x); +int acos_prepare_slow_f255(int x) inline { + x -= (x == 0); + int t = 1; + repeat (255) { + t = t * sgn(x) * 2 + 1; ;; decode Gray code (sgn(x_0), sgn(x_1), ...) + x = (-1 << 255) - muldivr(x, - x, 1 << 254); ;; iterate x := 2*x^2 - 1 = cos(2*acos(x)) + } + return abs(t); +} + +;; extremely slow (~70k gas) and somewhat imprecise (very imprecise when x is small), for testing only +;; fixed254 acos_slow(fixed255 x); +int acos_slow_f255(int x) inline_ref { + int t = acos_prepare_slow_f255(x); + return - mulrshiftr256(t + (-1 << 256), Pi_const_f254()); +} + +;; fixed255 asin_slow(fixed255 x); +int asin_slow_f255(int x) inline_ref { + int t = acos_prepare_slow_f255(abs(x)) % (1 << 255); + return muldivr(t, Pi_const_f254(), 1 << 255) * sgn(x); +} + +_ main() { + int One = 1; + ;; repeat(76 / 4) { One *= 10000; } + int sqrt2 = geom_mean(One, 2 * One); + int sqrt3 = geom_mean(One, 3 * One); + ;; return geom_mean((1 << 255) - 1 + (1 << 255), (1 << 255) - 1); + ;; return geom_mean((1 << 255) - 1, (1 << 255) - 2); + ;; return (sqrt2, geom_mean(sqrt2, One)); ;; (sqrt(2), 2^(1/4)) + ;; return (sqrt3, geom_mean(sqrt3, One)); ;; (sqrt(3), 3^(1/4)) + ;; return geom_mean(3 << 254, 1 << 254); + ;; return tan_f260(115641670674223639132965820642403718536242645001775371762318060545014644837101 - 1); + ;; return tan_f260(15 << 252); ;; tan(15/256) * 2^260 + ;; return sincosm1_f259(1 << 255); ;; (sin,1-cos)(1/16) * 2^259 + ;; return sincosm1_f259(115641670674223639132965820642403718536242645001775371762318060545014644837101 - 1); + ;; return sincosm1_f256((1 << 255) - 1 + (1 << 255)); ;; (sin,1-cos)(1-2^(-256)) + ;; return sincosm1_f256(Pi_const_f254()); ;; (sin,1-cos)(Pi/4) + ;; return sincosn_f256(Pi_const_f254(), 0); ;; (sin,-cos)(Pi/4) + ;; return sincosn_f256((1 << 255) + 1, 0); ;; (sin,-cos)(1/2+1/2^256) + ;; return sincosn_f256(1 << 254, 0); + ;; return sincosn_f256(touch(15) << 252, 0); ;; (sin,-cos)(15/16) + ;; return sincosm1_f256(touch(15) << 252); ;; (sin,1-cos)(15/16) + ;; return sincosn_f256(60628596148627720713372490462954977108898896221398738326462025186323149077698, 0); ;; (sin,-cos)(Pi/6) + ;; return sincosm1_f256(60628596148627720713372490462954977108898896221398738326462025186323149077698); ;; (sin,1-cos)(Pi/6) + ;; return tan_aux_f256(1899 << 245); ;; (p,q) such that p/q=tan(1899/2048) + ;; return fixed248::tan(11 << 248); ;; tan(11) + ;; return atanh_alt_f258(1 << 252); ;; atanh(1/64) * 2^258 + ;; return atanh_f258(1 << 252, 18); ;; atanh(1/64) * 2^258 + ;; return atanh_f261(muldivr(64, 1 << 255, 55), 18); ;; atanh(1/55) * 2^261 + ;; return log2_aux_f256(1 << 255); + ;; return log2_aux_f256(-1 - (-1 << 256)); ;; log2(2-1/2^255))*2^256 ~ 2^256 - 1.43 + ;; return log_aux_f256(-1 - (-1 << 256)); + ;; return log_aux_f256(3); ;; log(3/2)*2^256 + ;; return fixed248::pow(3 << 248, 3 << 248); ;; 3^3 + ;; return fixed248::exp(fixed248::log(5 << 248) ~/ 7); ;; exp(log(5)/7) = 5^(1/7) + ;; return fixed248::log(Pi_const_f254() ~>> 6); ;; log(Pi) + ;; return atanh_alt_f258(1 << 255); ;; atanh(1/8) * 2^258 + ;; return atanh_f258(1 << 255, 37); ;; atanh(1/8) * 2^258 + ;; return log_aux_f257(Pi_const_f254()); ;; log(Pi/4) + ;; return log_aux_f257(3 << 254); ;; log(3) + ;; return atanh_alt_f258(81877371507464127617551201542979628307507432471243237061821853600756754782485); ;; atanh(sqrt(2)/8) * 2^258 + ;; return atanh_f258(81877371507464127617551201542979628307507432471243237061821853600756754782485, 36); ;; atanh(sqrt(2)/8) * 2^258 + ;; return fixed248::sincos(Pi_const_f254() ~/ (64 * 3)); ;; (sin,cos)(Pi/3) + ;; return fixed248::exp(3 << 248); ;; exp(3)*2^248 + ;; return fixed248::exp2((1 << 248) ~/ 5); ;; 2^(1/5)*2^248 + ;; return fixed248::pow(3 << 248, -3 << 247); ;; 3^(-1.5) + ;; return fixed248::pow(10 << 248, -70 << 248); ;; 10^(-70) + ;; return fixed248::exp(fixed248::log(fixed248::Pi_const()) * 3); ;; Pi^3 ~ 31.006 + ;; return fixed248::pow(fixed248::Pi_const(), touch(3) << 248); ;; Pi^3 ~ 31.006, computed more precisely + ;; return fixed248::exp(muldivr(fixed248::log(fixed248::Pi_const()), fixed248::Pi_const(), 1 << 248)); ;; Pi^Pi + ;; return fixed248::pow(fixed248::Pi_const(), fixed248::Pi_const()); ;; Pi^Pi, more precisely + ;; return fixed248::sin(fixed248::log(fixed248::exp(fixed248::Pi_const()))); ;; sin(log(e^Pi)) + ;; return expm1_f257(1 << 255); ;; (exp(1/4)-1)*2^256 + ;; return expm1_f257(-1 << 256); ;; (exp(-1/2)-1)*2^256 (argument out of range, will overflow) + ;; return expm1_f257(log2_const_f256()); ;; (exp(log(2)/2)-1)*2^256 + ;; return expm1_f257(- log2_const_f256()); ;; (exp(-log(2)/2)-1)*2^256 + ;; return tanh_f258(log2_const_f256(), 17); ;; tanh(log(2)/4)*2^258 + ;; return atan_f255(0xa0 << 247); + ;; return atan_f259(1 << 255, 26); ;; atan(1/16) + ;; return atan_f259(touch(2273) << 244, 26); ;; atan(2273/2^15) + ;; return atan_aux_f256(0xa0 << 248); + ;; return atan_aux_f256(-1 - (-1 << 256)); + ;; return atan_aux_f256(-1 << 256); + ;; return atan_aux_f256(1); ;; atan(1/2^256)*2^261 = 32 + int One = touch(1 << 255); + ;; return asin_f255(-2 * One ~/ -3); + int arg = muldivr(12, One, 17); ;; 12/17 + ;; return [ asin_slow_f255(arg), asin_f255(arg) ]; + ;; return [ acos_slow_f255(arg), acos_f255(arg) ]; + return 4 * atan_f255(One ~/ 5) - atan_f255(One ~/ 239); ;; 4 * atan(1/5) - atan(1/239) = Pi/4 as fixed255 + int One = touch(1 << 248); + ;; return fixed248::atan(One) ~/ 5); ;; atan(1/5) + ;; return fixed248::acot(One ~/ 239); ;; atan(1/5) +} From 4873bd77cdb75b72eea3aa14957a11d0acbb46e7 Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Sat, 4 Mar 2023 22:22:11 +0300 Subject: [PATCH 25/46] Add doc links to README --- README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0b1205d1..818e71d6 100644 --- a/README.md +++ b/README.md @@ -3,17 +3,19 @@ TON logo +

Reference implementation of TON Node and tools


+## [![TON Overflow Group][ton-overflow-badge]][ton-overflow-url] [![Stack Overflow Group][stack-overflow-badge]][stack-overflow-url] [![Telegram Foundation Group][telegram-foundation-badge]][telegram-foundation-url] [![Telegram Community Group][telegram-community-badge]][telegram-community-url] [![Twitter Group][twitter-badge]][twitter-url] -[telegram-foundation-badge]: https://img.shields.io/badge/-TON%20Foundation-2CA5E0?style=flat&logo=telegram&logoColor=white -[telegram-community-badge]: https://img.shields.io/badge/-TON%20Community-2CA5E0?style=flat&logo=telegram&logoColor=white +[telegram-foundation-badge]: https://img.shields.io/badge/channel-TON%20Foundation-2CA5E0?logo=telegram&logoColor=white&style=flat +[telegram-community-badge]: https://img.shields.io/badge/chat-TON%20Community-2CA5E0?logo=telegram&logoColor=white&style=flat [telegram-foundation-url]: https://t.me/tonblockchain [telegram-community-url]: https://t.me/toncoin [twitter-badge]: https://img.shields.io/twitter/follow/ton_blockchain @@ -27,6 +29,15 @@ Main TON monorepo, which includes the code of the node/validator, lite-client, tonlib, FunC compiler, etc. +## The Open Network + +__The Open Network (TON)__ is a fast, secure, scalable blockchain focused on handling _millions of transactions per second_ (TPS) with the goal of reaching hundreds of millions of blockchain users. +- To learn more about different aspects of TON blockchain and its underlying ecosystem check [documentation](ton.org/docs) +- To run node, validator or lite-server check [Participate section](https://ton.org/docs/participate/nodes/run-node) +- To develop decentralised apps check [Tutorials](https://ton.org/docs/develop/smart-contracts/), [FunC docs](https://ton.org/docs/develop/func/overview) and [DApp tutorials](https://ton.org/docs/develop/dapps/) +- To work on TON check [wallets](https://ton.app/wallets), [explorers](https://ton.app/explorers), [DEXes](https://ton.app/dex) and [utilities](https://ton.app/utilities) +- To interact with TON check [APIs](https://ton.org/docs/develop/dapps/apis/) + ## Updates flow: * **master branch** - mainnet is running on this stable branch. @@ -49,4 +60,4 @@ Usually, the response to your pull request will indicate which section it falls If a CI workflow fails not because of your changes but workflow issues, try to fix it yourself or contact one of the persons listed below via Telegram messenger: * **C/C++ CI (ccpp-linux.yml)**: TBD -* **C/C++ CI Win64 Compile (ccpp-win64.yml)**: TBD \ No newline at end of file +* **C/C++ CI Win64 Compile (ccpp-win64.yml)**: TBD From 436b9f127d16c5d7d78f15347054e75ba00e38f8 Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Sun, 5 Mar 2023 14:01:26 +0300 Subject: [PATCH 26/46] Make Ton logo in README a link to ton.org --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 818e71d6..9dbbadfa 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@
- - - TON logo - + + + + TON logo + +

Reference implementation of TON Node and tools


From 1366a2e1aa76a07ed9f5123008a93e229e3be00c Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Sun, 5 Mar 2023 14:11:42 +0300 Subject: [PATCH 27/46] Add link to tondev --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9dbbadfa..6bc3b1f5 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,17 @@ ## [![TON Overflow Group][ton-overflow-badge]][ton-overflow-url] [![Stack Overflow Group][stack-overflow-badge]][stack-overflow-url] -[![Telegram Foundation Group][telegram-foundation-badge]][telegram-foundation-url] +[![Telegram Community Chat][telegram-tondev-badge]][telegram-tondev-url] [![Telegram Community Group][telegram-community-badge]][telegram-community-url] +[![Telegram Foundation Group][telegram-foundation-badge]][telegram-foundation-url] [![Twitter Group][twitter-badge]][twitter-url] -[telegram-foundation-badge]: https://img.shields.io/badge/channel-TON%20Foundation-2CA5E0?logo=telegram&logoColor=white&style=flat -[telegram-community-badge]: https://img.shields.io/badge/chat-TON%20Community-2CA5E0?logo=telegram&logoColor=white&style=flat +[telegram-foundation-badge]: https://img.shields.io/badge/TON%20Foundation-2CA5E0?logo=telegram&logoColor=white&style=flat +[telegram-community-badge]: https://img.shields.io/badge/TON%20Community-2CA5E0?logo=telegram&logoColor=white&style=flat +[telegram-tondev-badge]: https://img.shields.io/badge/chat-TONDev-2CA5E0?logo=telegram&logoColor=white&style=flat [telegram-foundation-url]: https://t.me/tonblockchain [telegram-community-url]: https://t.me/toncoin +[telegram-tondev-url]: https://t.me/tondev_eng [twitter-badge]: https://img.shields.io/twitter/follow/ton_blockchain [twitter-url]: https://twitter.com/ton_blockchain [stack-overflow-badge]: https://img.shields.io/badge/-Stack%20Overflow-FE7A16?style=flat&logo=stack-overflow&logoColor=white From 16e54339818a1c4a5ff5f3cc90a344c1114546e0 Mon Sep 17 00:00:00 2001 From: Behrang Norouzinia Date: Sun, 5 Mar 2023 14:45:37 +0330 Subject: [PATCH 28/46] Fix bug in docs for storing and loading coins (#617) Coins are 120-bit integer, not 128-bit. Co-authored-by: EmelyanenkoK --- crypto/smartcont/stdlib.fc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crypto/smartcont/stdlib.fc b/crypto/smartcont/stdlib.fc index 3531608a..03da3fe4 100644 --- a/crypto/smartcont/stdlib.fc +++ b/crypto/smartcont/stdlib.fc @@ -310,7 +310,7 @@ cell preload_ref(slice s) asm "PLDREF"; ;;; Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. ;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; -;;; Loads serialized amount of TonCoins (any unsigned integer up to `2^128 - 1`). +;;; Loads serialized amount of TonCoins (any unsigned integer up to `2^120 - 1`). (slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; (slice, int) load_coins(slice s) asm( -> 1 0) "LDGRAMS"; @@ -426,7 +426,7 @@ builder store_ref(builder b, cell c) asm(c b) "STREF"; ;;; Stores `slice` [s] into `builder` [b] builder store_slice(builder b, slice s) asm "STSLICER"; -;;; Stores (serializes) an integer [x] in the range `0..2^128 − 1` into `builder` [b]. +;;; Stores (serializes) an integer [x] in the range `0..2^120 − 1` into `builder` [b]. ;;; The serialization of [x] consists of a 4-bit unsigned big-endian integer `l`, ;;; which is the smallest integer `l ≥ 0`, such that `x < 2^8l`, ;;; followed by an `8l`-bit unsigned big-endian representation of [x]. From f06d5cb0535c1e1aa0ab2da1315d3e666033b0d2 Mon Sep 17 00:00:00 2001 From: neodiX42 Date: Sun, 5 Mar 2023 12:15:53 +0100 Subject: [PATCH 29/46] Make path separator cross-platform in few places (#628) * make path separator cross-platform in few places * reuse path separator definer --- validator/db/archive-manager.cpp | 2 +- validator/manager.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/validator/db/archive-manager.cpp b/validator/db/archive-manager.cpp index 6bea5db6..bd239bfc 100644 --- a/validator/db/archive-manager.cpp +++ b/validator/db/archive-manager.cpp @@ -906,7 +906,7 @@ void ArchiveManager::start_up() { td::WalkPath::run(db_root_ + "/archive/states/", [&](td::CSlice fname, td::WalkPath::Type t) -> void { if (t == td::WalkPath::Type::NotDir) { LOG(ERROR) << "checking file " << fname; - auto pos = fname.rfind('/'); + auto pos = fname.rfind(TD_DIR_SLASH); if (pos != td::Slice::npos) { fname.remove_prefix(pos + 1); } diff --git a/validator/manager.cpp b/validator/manager.cpp index 5199eb01..e22616ea 100644 --- a/validator/manager.cpp +++ b/validator/manager.cpp @@ -1464,7 +1464,7 @@ void ValidatorManagerImpl::start_up() { auto S = td::WalkPath::run(to_import_dir, [&](td::CSlice cfname, td::WalkPath::Type t) -> void { auto fname = td::Slice(cfname); if (t == td::WalkPath::Type::NotDir) { - auto d = fname.rfind('/'); + auto d = fname.rfind(TD_DIR_SLASH); if (d != td::Slice::npos) { fname = fname.substr(d + 1); } From 04d4ae2decc20b378a0309ddbc7a1f0b833bcc29 Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Mon, 6 Mar 2023 14:03:29 +0300 Subject: [PATCH 30/46] Add version printing to legacy tester --- crypto/func/auto-tests/legacy_tester.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crypto/func/auto-tests/legacy_tester.py b/crypto/func/auto-tests/legacy_tester.py index 6db332e5..e852a043 100644 --- a/crypto/func/auto-tests/legacy_tester.py +++ b/crypto/func/auto-tests/legacy_tester.py @@ -112,6 +112,12 @@ def run_runner(): s = s.strip() return int(s) +def get_version(): + res = subprocess.run([FUNC_EXECUTABLE, "-s"], capture_output=True, timeout=10) + if res.returncode != 0: + raise ExecutionError(str(res.stderr, "utf-8")) + s = str(res.stdout, "utf-8") + return s.strip() success = 0 for ti, t in enumerate(tests): @@ -142,4 +148,6 @@ for ti, t in enumerate(tests): #exit(2) print(" OK ", file=sys.stderr) +print(get_version()) print("Done: Success %d, Error: %d"%(success, len(tests)-success), file=sys.stderr) + From dbecfe6f285862fcd19a566eacaeee6f4953e5bc Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Mon, 6 Mar 2023 14:03:49 +0300 Subject: [PATCH 31/46] Add 03.2023 update to Changelog --- Changelog.md | 100 ++++++++++++++++++++++++++++----------------------- 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/Changelog.md b/Changelog.md index f3c76217..be18efc2 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,43 +1,16 @@ -## 05.2022 Update -* Initial synchronization improved: adjusted timeouts for state download and the way of choosing which state to download. Nodes with low network speed and/or bad connectivity will synchronize faster and consistently. -* Improved peer-to-peer network stability and DDoS resistance: now peers will only relay valid messages to the network. Large messages, which require splitting for relaying, will be retranslated as well, but only after the node gets all parts, and reassembles and checks them. Validators may sign certificates for network peers, which allow relaying large messages by parts without checks. It is used now by validators to faster relay new blocks. Sign and import certificate commands are exposed via `validator-engine-console`. -* Fixed some rare edge cases in TVM arithmetic operations related to big numbers (`2**63+`) -* Improved fixes used to combat wrong activate-destruct-activate contract behavior last November. -* Improved tonlib: support libraries (with client-side caching), getmethods completely fill c7 register, getmethods support slice arguments, improved messages listing for transactions, added extended block header params, added getConfig method. -* RocksDB updated to a newer version. -* Improved persistent state serialization: memory usage during serialization was optimized; the start of serialization on different nodes was sparsed. -* FunC update: support for string literals and constants (including precompiled constant expressions), semver, `include` expressions. -* Fixed rarely manifested bugs in `Asm.fif`. -* LiteClient supports key as cli parameter. -* Improved Liteserver DoS resistance for running getmethods. +## 03.2023 Update +1. Improvement of ADNL connection stability +2. Transaction emulator support and getAccountStateByTransaction method +3. Fixes of typos, undefined behavior and timer warnings +4. Handling incorrect integer literal values in funC; funC version bumped to 0.4.2 +5. FunC Mathlib -Besides the work of the core team, this update is based on the efforts of @tvorogme (added support for slice arguments and noted bugs in Asm.fif), @akifoq (fixed bug in Asm.fif), @cryshado (noted strange behavior of LS, which, upon inspection, turned out to be a vector of DoS attack). -## 08.2022 Update -* Blockchain state serialization now works via separate db-handler which simplfies memory clearing after serialization -* CellDB now works asynchronously which substantially increase database access throughput -* Abseil-cpp and crc32 updated: solve issues with compilation on recent OS distributives -* Fixed a series of UBs and issues for exotic endianness hosts -* Added detailed network stats for overlays (can be accessed via `validator-console`) -* Improved auto-builds for wide range of systems. -* Added extended error information for unaccepted external messages: `exit_code` and TVM trace (where applicable). -* [Improved catchain DoS resistance](https://github.com/ton-blockchain/ton/blob/master/doc/catchain-dos.md) -* A series of FunC improvements, summarized [here](https://github.com/ton-blockchain/ton/pull/378) -#### Update delay -Update coincided with persistent state serialization event which lead to block production speed deterioration (issue substantially mitigated in update itself). This phenomena was aggravated by the fact that after update some validators lost ability to participate in block creation. The last was caused by threshold based hardcoded protocol version bump, where threshold was set in such manner (based on block height with value higher than 9m), that it eluded detection in private net tests. The update was temporarily paused and resumed after persistent state serialization ended and issues with block creation were resolved. - -Besides the work of the core team, this update is based on the efforts of @awesome-doge (help with abseil-cpp upgrade), @rec00rsiff (noted issues for exotic endianess and implemented network stats) and third-party security auditors. - -## 10.2022 Update -* Added extended block creation and general perfomance stats gathering -* Forbidden report data on blocks not committed to the master chain for LS -* Improved debug in TVM -* FunC 0.3.0: multi-line asms, bitwise operations for constants, duplication of identical definition for constants and asms now allowed -* New tonlib methods: sendMessageReturnHash, getTransactionsV2, getMasterchainBlockSignatures, getShardBlockProof, getLibraries. -* Fixed bugs related to invalid TVM output (c4, c5, libaries) and non-validated network data; avoided too deep recursion in libraries loading -* Fixed multiple undefined behavior issues -* Added build of FunC and Fift to WASM - -Besides the work of the core team, this update is based on the efforts of @tvorogme (debug improvements), @AlexeyFSL (WASM builds) and third-party security auditors. +## 01.2023 Update +1. Added ConfigParam 44: `SuspendedAddressList`. Upon being set this config suspends initialisation of **uninit** addresses from the list for given time. +2. FunC: `v0.4.1` added pragmas for precise control of computation order +3. FunC: fixed compiler crashes for some exotic inputs +4. FunC: added legacy tester, a collection of smart-contracts which is used to check whether compilator update change compilation result +5. Improved archive manager: proper handling of recently garbage-collected blocks ## 12.2022 Update Node update: @@ -54,9 +27,46 @@ Node update: Besides the work of the core team, this update is based on the efforts of @vtamara (help with abseil-cpp upgrade), @krigga(in-place modification of global variables) and third-party security auditors. -## 01.2023 Update -1. Added ConfigParam 44: `SuspendedAddressList`. Upon being set this config suspends initialisation of **uninit** addresses from the list for given time. -2. FunC: `v0.4.1` added pragmas for precise control of computation order -3. FunC: fixed compiler crashes for some exotic inputs -4. FunC: added legacy tester, a collection of smart-contracts which is used to check whether compilator update change compilation result -5. Improved archive manager: proper handling of recently garbage-collected blocks +## 10.2022 Update +* Added extended block creation and general perfomance stats gathering +* Forbidden report data on blocks not committed to the master chain for LS +* Improved debug in TVM +* FunC 0.3.0: multi-line asms, bitwise operations for constants, duplication of identical definition for constants and asms now allowed +* New tonlib methods: sendMessageReturnHash, getTransactionsV2, getMasterchainBlockSignatures, getShardBlockProof, getLibraries. +* Fixed bugs related to invalid TVM output (c4, c5, libaries) and non-validated network data; avoided too deep recursion in libraries loading +* Fixed multiple undefined behavior issues +* Added build of FunC and Fift to WASM + +Besides the work of the core team, this update is based on the efforts of @tvorogme (debug improvements), @AlexeyFSL (WASM builds) and third-party security auditors. + +## 08.2022 Update +* Blockchain state serialization now works via separate db-handler which simplfies memory clearing after serialization +* CellDB now works asynchronously which substantially increase database access throughput +* Abseil-cpp and crc32 updated: solve issues with compilation on recent OS distributives +* Fixed a series of UBs and issues for exotic endianness hosts +* Added detailed network stats for overlays (can be accessed via `validator-console`) +* Improved auto-builds for wide range of systems. +* Added extended error information for unaccepted external messages: `exit_code` and TVM trace (where applicable). +* [Improved catchain DoS resistance](https://github.com/ton-blockchain/ton/blob/master/doc/catchain-dos.md) +* A series of FunC improvements, summarized [here](https://github.com/ton-blockchain/ton/pull/378) +#### Update delay +Update coincided with persistent state serialization event which lead to block production speed deterioration (issue substantially mitigated in update itself). This phenomena was aggravated by the fact that after update some validators lost ability to participate in block creation. The last was caused by threshold based hardcoded protocol version bump, where threshold was set in such manner (based on block height with value higher than 9m), that it eluded detection in private net tests. The update was temporarily paused and resumed after persistent state serialization ended and issues with block creation were resolved. + +Besides the work of the core team, this update is based on the efforts of @awesome-doge (help with abseil-cpp upgrade), @rec00rsiff (noted issues for exotic endianess and implemented network stats) and third-party security auditors. + +## 05.2022 Update +* Initial synchronization improved: adjusted timeouts for state download and the way of choosing which state to download. Nodes with low network speed and/or bad connectivity will synchronize faster and consistently. +* Improved peer-to-peer network stability and DDoS resistance: now peers will only relay valid messages to the network. Large messages, which require splitting for relaying, will be retranslated as well, but only after the node gets all parts, and reassembles and checks them. Validators may sign certificates for network peers, which allow relaying large messages by parts without checks. It is used now by validators to faster relay new blocks. Sign and import certificate commands are exposed via `validator-engine-console`. +* Fixed some rare edge cases in TVM arithmetic operations related to big numbers (`2**63+`) +* Improved fixes used to combat wrong activate-destruct-activate contract behavior last November. +* Improved tonlib: support libraries (with client-side caching), getmethods completely fill c7 register, getmethods support slice arguments, improved messages listing for transactions, added extended block header params, added getConfig method. +* RocksDB updated to a newer version. +* Improved persistent state serialization: memory usage during serialization was optimized; the start of serialization on different nodes was sparsed. +* FunC update: support for string literals and constants (including precompiled constant expressions), semver, `include` expressions. +* Fixed rarely manifested bugs in `Asm.fif`. +* LiteClient supports key as cli parameter. +* Improved Liteserver DoS resistance for running getmethods. + +Besides the work of the core team, this update is based on the efforts of @tvorogme (added support for slice arguments and noted bugs in Asm.fif), @akifoq (fixed bug in Asm.fif), @cryshado (noted strange behavior of LS, which, upon inspection, turned out to be a vector of DoS attack). + + From 4db7ad039a50386bcb20ee39fa7f1a2d29a02568 Mon Sep 17 00:00:00 2001 From: SpyCheese Date: Mon, 6 Mar 2023 15:29:23 +0000 Subject: [PATCH 32/46] Fix size estimation in send_message_in (#637) --- adnl/adnl-peer.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/adnl/adnl-peer.cpp b/adnl/adnl-peer.cpp index 108d5211..9e106585 100644 --- a/adnl/adnl-peer.cpp +++ b/adnl/adnl-peer.cpp @@ -70,7 +70,7 @@ void AdnlPeerPairImpl::alarm() { retry_send_at_ = td::Timestamp::never(); auto messages = std::move(pending_messages_); pending_messages_.clear(); - send_messages_in(std::move(messages), true); + send_messages_in(std::move(messages), false); } alarm_timestamp().relax(next_dht_query_at_); alarm_timestamp().relax(next_db_update_at_); @@ -267,7 +267,9 @@ void AdnlPeerPairImpl::send_messages_in(std::vector message size_t ptr = 0; bool first = true; do { - size_t s = (channel_ready_ ? channel_packet_header_max_size() : packet_header_max_size()); + bool try_reinit = try_reinit_at_ && try_reinit_at_.is_in_past(); + bool via_channel = channel_ready_ && !try_reinit; + size_t s = (via_channel ? channel_packet_header_max_size() : packet_header_max_size()); if (first) { s += 2 * addr_list_max_size(); } @@ -311,8 +313,6 @@ void AdnlPeerPairImpl::send_messages_in(std::vector message } } - bool try_reinit = try_reinit_at_ && try_reinit_at_.is_in_past(); - bool via_channel = channel_ready_ && !try_reinit; if (!via_channel) { packet.set_reinit_date(Adnl::adnl_start_time(), reinit_date_); packet.set_source(local_id_); From 078aabe50e23bd2279bac08c46ac4009b2ae67e5 Mon Sep 17 00:00:00 2001 From: Marat <98183742+dungeon-master-666@users.noreply.github.com> Date: Tue, 7 Mar 2023 16:49:25 +0000 Subject: [PATCH 33/46] Add method listBlockTransactionsExt to liteserver (#399) * Verify proof for method blocks.getTransactions * check completeness of response * fix start_lt * fix mode & 128, check bTxes->ids_ out of bounds * Improve gitactions; separate cpp-check (#346) * Use ninja build tool and compile blockchain-explorer Ninja builds TON much faster; * Use clang instead of gcc * remove blockchain-explorer since target not found on github action * move ccpcheck to other gitaction * run nativelib-java only against wallets branch for now * rename gitaction * Update windows2019x64-tonlib-java.yml * Update windows2019x64-tonlib-java.yml * Update macos-10.15-tonlib-java.yml * Update windows2019x64-tonlib-java.yml * Update windows2019x64-tonlib-java.yml * rebase * update tlo's * Revert "Improve gitactions; separate cpp-check (#346)" This reverts commit bd1d96e6d391e48840d81cfcf10d2692848e504e. * add checks, simplify ls response * Revert workflows * Add verifying proofs * fix win build --------- Co-authored-by: neodiX42 --- crypto/block/check-proof.cpp | 107 +++++++++++++ crypto/block/check-proof.h | 32 ++++ tl/generate/scheme/lite_api.tl | 2 + tl/generate/scheme/lite_api.tlo | Bin 13532 -> 14148 bytes tl/generate/scheme/tonlib_api.tl | 4 +- tl/generate/scheme/tonlib_api.tlo | Bin 31788 -> 32296 bytes tonlib/tonlib/TonlibClient.cpp | 252 ++++++++++++++++++++++++++++-- tonlib/tonlib/TonlibClient.h | 2 + validator/impl/liteserver.cpp | 117 ++++++++++++++ validator/impl/liteserver.hpp | 2 + 10 files changed, 502 insertions(+), 16 deletions(-) diff --git a/crypto/block/check-proof.cpp b/crypto/block/check-proof.cpp index 4abba64c..431a03fe 100644 --- a/crypto/block/check-proof.cpp +++ b/crypto/block/check-proof.cpp @@ -315,6 +315,113 @@ td::Result TransactionList::validate() const { return std::move(res); } +td::Result BlockTransaction::validate(bool check_proof) const { + if (root.is_null()) { + return td::Status::Error("transactions are expected to be non-empty"); + } + if (check_proof && proof->get_hash().bits().compare(root->get_hash().bits(), 256)) { + return td::Status::Error(PSLICE() << "transaction hash mismatch: Merkle proof expects " + << proof->get_hash().bits().to_hex(256) + << " but received data has " << root->get_hash().bits().to_hex(256)); + } + block::gen::Transaction::Record trans; + if (!tlb::unpack_cell(root, trans)) { + return td::Status::Error("cannot unpack transaction cell"); + } + Info res; + res.blkid = blkid; + res.now = trans.now; + res.lt = trans.lt; + res.hash = root->get_hash().bits(); + res.transaction = root; + return std::move(res); +} + +td::Result BlockTransactionList::validate(bool check_proof) const { + constexpr int max_answer_transactions = 256; + + TRY_RESULT_PREFIX(list, vm::std_boc_deserialize_multi(std::move(transactions_boc)), "cannot deserialize transactions boc: "); + std::vector> tx_proofs(list.size()); + + if (check_proof) { + try { + TRY_RESULT(proof_cell, vm::std_boc_deserialize(std::move(proof_boc))); + auto virt_root = vm::MerkleProof::virtualize(proof_cell, 1); + + if (blkid.root_hash != virt_root->get_hash().bits()) { + return td::Status::Error("Invalid block proof root hash"); + } + block::gen::Block::Record blk; + block::gen::BlockExtra::Record extra; + if (!(tlb::unpack_cell(virt_root, blk) && tlb::unpack_cell(std::move(blk.extra), extra))) { + return td::Status::Error("Error unpacking proof cell"); + } + vm::AugmentedDictionary acc_dict{vm::load_cell_slice_ref(extra.account_blocks), 256, + block::tlb::aug_ShardAccountBlocks}; + + bool eof = false; + ton::LogicalTime reverse = reverse_mode ? ~0ULL : 0; + ton::LogicalTime trans_lt = static_cast(start_lt); + td::Bits256 cur_addr = start_addr; + bool allow_same = true; + int count = 0; + while (!eof && count < req_count && count < max_answer_transactions) { + auto value = acc_dict.extract_value( + acc_dict.vm::DictionaryFixed::lookup_nearest_key(cur_addr.bits(), 256, !reverse, allow_same)); + if (value.is_null()) { + eof = true; + break; + } + allow_same = false; + if (cur_addr != start_addr) { + trans_lt = reverse; + } + + block::gen::AccountBlock::Record acc_blk; + if (!tlb::csr_unpack(std::move(value), acc_blk) || acc_blk.account_addr != cur_addr) { + return td::Status::Error("Error unpacking proof account block"); + } + vm::AugmentedDictionary trans_dict{vm::DictNonEmpty(), std::move(acc_blk.transactions), 64, + block::tlb::aug_AccountTransactions}; + td::BitArray<64> cur_trans{(long long)trans_lt}; + while (count < req_count && count < max_answer_transactions) { + auto tvalue = trans_dict.extract_value_ref( + trans_dict.vm::DictionaryFixed::lookup_nearest_key(cur_trans.bits(), 64, !reverse)); + if (tvalue.is_null()) { + trans_lt = reverse; + break; + } + if (static_cast(count) < tx_proofs.size()) { + tx_proofs[count] = std::move(tvalue); + } + count++; + } + } + if (static_cast(count) != list.size()) { + return td::Status::Error(PSLICE() << "Txs count mismatch in proof (" << count << ") and response (" << list.size() << ")"); + } + } catch (vm::VmError& err) { + return err.as_status("Couldn't verify proof: "); + } catch (vm::VmVirtError& err) { + return err.as_status("Couldn't verify proof: "); + } catch (...) { + return td::Status::Error("Unknown exception raised while verifying proof"); + } + } + + Info res; + for (int i = 0; i < static_cast(list.size()); i++) { + auto& root = list[i]; + BlockTransaction transaction; + transaction.root = root; + transaction.blkid = blkid; + transaction.proof = tx_proofs[i]; + TRY_RESULT(info, transaction.validate(check_proof)); + res.transactions.push_back(std::move(info)); + } + return std::move(res); +} + td::Status BlockProofLink::validate(td::uint32* save_utime) const { if (save_utime) { *save_utime = 0; diff --git a/crypto/block/check-proof.h b/crypto/block/check-proof.h index 527f3138..497a4eba 100644 --- a/crypto/block/check-proof.h +++ b/crypto/block/check-proof.h @@ -88,4 +88,36 @@ struct TransactionList { td::Result validate() const; }; +struct BlockTransaction { + ton::BlockIdExt blkid; + td::Ref root; + td::Ref proof; + + struct Info { + ton::BlockIdExt blkid; + td::uint32 now; + ton::LogicalTime lt; + ton::Bits256 hash; + td::Ref transaction; + }; + td::Result validate(bool check_proof) const; +}; + +struct BlockTransactionList { + ton::BlockIdExt blkid; + td::BufferSlice transactions_boc; + td::BufferSlice proof_boc; + ton::LogicalTime start_lt; + td::Bits256 start_addr; + bool reverse_mode; + int req_count; + + struct Info { + ton::BlockIdExt blkid; + std::vector transactions; + }; + + td::Result validate(bool check_proof) const; +}; + } // namespace block diff --git a/tl/generate/scheme/lite_api.tl b/tl/generate/scheme/lite_api.tl index a01da11a..ce4d2731 100644 --- a/tl/generate/scheme/lite_api.tl +++ b/tl/generate/scheme/lite_api.tl @@ -44,6 +44,7 @@ liteServer.transactionList ids:(vector tonNode.blockIdExt) transactions:bytes = liteServer.transactionId mode:# account:mode.0?int256 lt:mode.1?long hash:mode.2?int256 = liteServer.TransactionId; liteServer.transactionId3 account:int256 lt:long = liteServer.TransactionId3; liteServer.blockTransactions id:tonNode.blockIdExt req_count:# incomplete:Bool ids:(vector liteServer.transactionId) proof:bytes = liteServer.BlockTransactions; +liteServer.blockTransactionsExt id:tonNode.blockIdExt req_count:# incomplete:Bool transactions:bytes proof:bytes = liteServer.BlockTransactionsExt; liteServer.signature node_id_short:int256 signature:bytes = liteServer.Signature; liteServer.signatureSet validator_set_hash:int catchain_seqno:int signatures:(vector liteServer.signature) = liteServer.SignatureSet; liteServer.blockLinkBack to_key_block:Bool from:tonNode.blockIdExt to:tonNode.blockIdExt dest_proof:bytes proof:bytes state_proof:bytes = liteServer.BlockLink; @@ -76,6 +77,7 @@ liteServer.getOneTransaction id:tonNode.blockIdExt account:liteServer.accountId liteServer.getTransactions count:# account:liteServer.accountId lt:long hash:int256 = liteServer.TransactionList; liteServer.lookupBlock mode:# id:tonNode.blockId lt:mode.1?long utime:mode.2?int = liteServer.BlockHeader; liteServer.listBlockTransactions id:tonNode.blockIdExt mode:# count:# after:mode.7?liteServer.transactionId3 reverse_order:mode.6?true want_proof:mode.5?true = liteServer.BlockTransactions; +liteServer.listBlockTransactionsExt id:tonNode.blockIdExt mode:# count:# after:mode.7?liteServer.transactionId3 reverse_order:mode.6?true want_proof:mode.5?true = liteServer.BlockTransactionsExt; liteServer.getBlockProof mode:# known_block:tonNode.blockIdExt target_block:mode.0?tonNode.blockIdExt = liteServer.PartialBlockProof; liteServer.getConfigAll mode:# id:tonNode.blockIdExt = liteServer.ConfigInfo; liteServer.getConfigParams mode:# id:tonNode.blockIdExt param_list:(vector int) = liteServer.ConfigInfo; diff --git a/tl/generate/scheme/lite_api.tlo b/tl/generate/scheme/lite_api.tlo index 0572fd46ea0b4cf65c807d446eb130fd34e6b1ce..da64ac53bcac03fb1d2eb1ca4fe3940d7e56c29b 100644 GIT binary patch delta 170 zcmcbUc_feb(QJJy1}HGx$a|TY^~s<9-|~}JGRrW#R+LQKAhG!Yvkfbw(PmlB$=teN z$(+oR)Zo;jveY8Iq@4WZ?2w|wyyC>p#w7j D2arYE delta 51 zcmX?-cPEqg(QJJy1}HGt$a|T2vkZ$3E2H7&NY2UJo1cij5oDCwd{Ld7d$WLv2{QnY Ccn;eD diff --git a/tl/generate/scheme/tonlib_api.tl b/tl/generate/scheme/tonlib_api.tl index c9a7df3d..bcfc625d 100644 --- a/tl/generate/scheme/tonlib_api.tl +++ b/tl/generate/scheme/tonlib_api.tl @@ -52,7 +52,7 @@ ton.blockIdExt workchain:int32 shard:int64 seqno:int32 root_hash:bytes file_hash raw.fullAccountState balance:int64 code:bytes data:bytes last_transaction_id:internal.transactionId block_id:ton.blockIdExt frozen_hash:bytes sync_utime:int53 = raw.FullAccountState; raw.message source:accountAddress destination:accountAddress value:int64 fwd_fee:int64 ihr_fee:int64 created_lt:int64 body_hash:bytes msg_data:msg.Data = raw.Message; -raw.transaction utime:int53 data:bytes transaction_id:internal.transactionId fee:int64 storage_fee:int64 other_fee:int64 in_msg:raw.message out_msgs:vector = raw.Transaction; +raw.transaction address:accountAddress utime:int53 data:bytes transaction_id:internal.transactionId fee:int64 storage_fee:int64 other_fee:int64 in_msg:raw.message out_msgs:vector = raw.Transaction; raw.transactions transactions:vector previous_transaction_id:internal.transactionId = raw.Transactions; raw.extMessageInfo hash:bytes = raw.ExtMessageInfo; @@ -215,6 +215,7 @@ blocks.shards shards:vector = blocks.Shards; blocks.accountTransactionId account:bytes lt:int64 = blocks.AccountTransactionId; blocks.shortTxId mode:# account:mode.0?bytes lt:mode.1?int64 hash:mode.2?bytes = liteServer.TransactionId; blocks.transactions id:ton.blockIdExt req_count:int32 incomplete:Bool transactions:vector = blocks.Transactions; +blocks.transactionsExt id:ton.blockIdExt req_count:int32 incomplete:Bool transactions:vector = blocks.TransactionsExt; blocks.header id:ton.blockIdExt global_id:int32 version:int32 flags:# after_merge:Bool after_split:Bool before_split:Bool want_merge:Bool want_split:Bool validator_list_hash_short:int32 catchain_seqno:int32 min_ref_mc_seqno:int32 is_key_block:Bool prev_key_block_seqno:int32 start_lt:int64 end_lt:int64 gen_utime:int53 vert_seqno:# prev_blocks:vector = blocks.Header; //blocks.shortData header:blocks.Header transactions:blocks.Header = blocks.BlockData; @@ -319,6 +320,7 @@ blocks.getMasterchainInfo = blocks.MasterchainInfo; blocks.getShards id:ton.blockIdExt = blocks.Shards; blocks.lookupBlock mode:int32 id:ton.blockId lt:int64 utime:int32 = ton.BlockIdExt; blocks.getTransactions id:ton.blockIdExt mode:# count:# after:blocks.accountTransactionId = blocks.Transactions; +blocks.getTransactionsExt id:ton.blockIdExt mode:# count:# after:blocks.accountTransactionId = blocks.TransactionsExt; blocks.getBlockHeader id:ton.blockIdExt = blocks.Header; blocks.getMasterchainBlockSignatures seqno:int32 = blocks.BlockSignatures; blocks.getShardBlockProof id:ton.blockIdExt mode:# from:mode.0?ton.blockIdExt = blocks.ShardBlockProof; diff --git a/tl/generate/scheme/tonlib_api.tlo b/tl/generate/scheme/tonlib_api.tlo index ffe4b1e702b193c1451a469bf8ed1d53dd5a11d7..023a4953e5fdbe655e9c6e28499ca757b344c5ce 100644 GIT binary patch delta 237 zcmZ4UgK@tJDUmZ@yz|Bj; o;+a*>v3yt}nUs^CoL#J!o>~%uFs2yf6qsA4uz>CS5ow|X07l9!TuwF(Dt0&7!{gj9`tEJ^T_NCam$(;baWhd^0?rc~gvr F5&(t+DyaYf diff --git a/tonlib/tonlib/TonlibClient.cpp b/tonlib/tonlib/TonlibClient.cpp index d56bcd1a..c3c8bdb2 100644 --- a/tonlib/tonlib/TonlibClient.cpp +++ b/tonlib/tonlib/TonlibClient.cpp @@ -2966,6 +2966,7 @@ struct ToRawTransactions { std::vector> out_msgs; td::int64 fees = 0; td::int64 storage_fee = 0; + td::string address; if (info.transaction.not_null()) { data = to_bytes(info.transaction); block::gen::Transaction::Record trans; @@ -2974,11 +2975,6 @@ struct ToRawTransactions { } TRY_RESULT_ASSIGN(fees, to_balance(trans.total_fees)); - //LOG(ERROR) << fees; - - //std::ostringstream outp; - //block::gen::t_Transaction.print_ref(outp, info.transaction); - //LOG(INFO) << outp.str(); auto is_just = trans.r1.in_msg->prefetch_long(1); if (is_just == trans.r1.in_msg->fetch_long_eof) { @@ -3004,8 +3000,11 @@ struct ToRawTransactions { return td::Status::Error("Failed to fetch storage fee from transaction"); } storage_fee = storage_fees->to_long(); + auto std_address = block::StdAddress(info.blkid.id.workchain, trans.account_addr); + address = std_address.rserialize(true); } return tonlib_api::make_object( + tonlib_api::make_object(std::move(address)), info.now, data, tonlib_api::make_object(info.prev_trans_lt, info.prev_trans_hash.as_slice().str()), @@ -3032,6 +3031,74 @@ struct ToRawTransactions { return tonlib_api::make_object(std::move(transactions), std::move(transaction_id)); } + + td::Result> to_raw_transaction_or_throw( + block::BlockTransaction::Info&& info) { + std::string data; + + tonlib_api::object_ptr in_msg; + std::vector> out_msgs; + td::int64 fees = 0; + td::int64 storage_fee = 0; + td::string address; + if (info.transaction.not_null()) { + data = to_bytes(info.transaction); + block::gen::Transaction::Record trans; + if (!tlb::unpack_cell(info.transaction, trans)) { + return td::Status::Error("Failed to unpack Transaction"); + } + + TRY_RESULT_ASSIGN(fees, to_balance(trans.total_fees)); + + auto is_just = trans.r1.in_msg->prefetch_long(1); + if (is_just == trans.r1.in_msg->fetch_long_eof) { + return td::Status::Error("Failed to parse long"); + } + if (is_just == -1) { + auto msg = trans.r1.in_msg->prefetch_ref(); + TRY_RESULT(in_msg_copy, to_raw_message(trans.r1.in_msg->prefetch_ref())); + in_msg = std::move(in_msg_copy); + } + + if (trans.outmsg_cnt != 0) { + vm::Dictionary dict{trans.r1.out_msgs, 15}; + for (int x = 0; x < trans.outmsg_cnt; x++) { + TRY_RESULT(out_msg, to_raw_message(dict.lookup_ref(td::BitArray<15>{x}))); + fees += out_msg->fwd_fee_; + fees += out_msg->ihr_fee_; + out_msgs.push_back(std::move(out_msg)); + } + } + td::RefInt256 storage_fees; + if (!block::tlb::t_TransactionDescr.get_storage_fees(trans.description, storage_fees)) { + return td::Status::Error("Failed to fetch storage fee from transaction"); + } + storage_fee = storage_fees->to_long(); + auto std_address = block::StdAddress(info.blkid.id.workchain, trans.account_addr); + address = std_address.rserialize(true); + } + return tonlib_api::make_object( + tonlib_api::make_object(std::move(address)), + info.now, data, + tonlib_api::make_object(info.lt, + info.hash.as_slice().str()), + fees, storage_fee, fees - storage_fee, std::move(in_msg), std::move(out_msgs)); + } + + td::Result> to_raw_transaction(block::BlockTransaction::Info&& info) { + return TRY_VM(to_raw_transaction_or_throw(std::move(info))); + } + + td::Result>> to_raw_transactions( + block::BlockTransactionList::Info&& info) { + std::vector> transactions; + for (auto& transaction : info.transactions) { + TRY_RESULT(raw_transaction, to_raw_transaction(std::move(transaction))); + transactions.push_back(std::move(raw_transaction)); + } + + return std::move(transactions); + } }; // Raw @@ -5215,25 +5282,116 @@ auto to_tonlib_api(const ton::lite_api::liteServer_transactionId& txid) td::Status TonlibClient::do_request(const tonlib_api::blocks_getTransactions& request, td::Promise>&& promise) { TRY_RESULT(block, to_lite_api(*request.id_)) - TRY_RESULT(account, to_bits256((*request.after_).account_, "account")); - auto after = ton::lite_api::make_object(account, (*request.after_).lt_); + auto root_hash = block->root_hash_; + bool check_proof = request.mode_ & 32; + bool reverse_mode = request.mode_ & 64; + bool has_starting_tx = request.mode_ & 128; + + td::Bits256 start_addr; + ton::LogicalTime start_lt; + ton::lite_api::object_ptr after; + if (has_starting_tx) { + if (!request.after_) { + return td::Status::Error("Missing field `after`"); + } + TRY_RESULT_ASSIGN(start_addr, to_bits256(request.after_->account_, "account")); + start_lt = request.after_->lt_; + after = ton::lite_api::make_object(start_addr, start_lt); + } else { + start_addr = reverse_mode ? td::Bits256::ones() : td::Bits256::zero(); + start_lt = reverse_mode ? ~0ULL : 0; + after = nullptr; + } + client_.send_query(ton::lite_api::liteServer_listBlockTransactions( std::move(block), request.mode_, request.count_, std::move(after), - false, - false), - promise.wrap([](lite_api_ptr&& bTxes) { - const auto& id = bTxes->id_; - //for (auto id : ids) { + reverse_mode, + check_proof), + promise.wrap([check_proof, reverse_mode, root_hash, req_count = request.count_, start_addr, start_lt, mode = request.mode_] + (lite_api_ptr&& bTxes) -> td::Result> { + if (check_proof) { + try { + constexpr int max_answer_transactions = 256; + TRY_RESULT(proof_cell, vm::std_boc_deserialize(std::move(bTxes->proof_))); + auto virt_root = vm::MerkleProof::virtualize(proof_cell, 1); + + if (root_hash != virt_root->get_hash().bits()) { + return td::Status::Error("Invalid block proof root hash"); + } + block::gen::Block::Record blk; + block::gen::BlockExtra::Record extra; + if (!(tlb::unpack_cell(virt_root, blk) && tlb::unpack_cell(std::move(blk.extra), extra))) { + return td::Status::Error("Error unpacking proof cell"); + } + vm::AugmentedDictionary acc_dict{vm::load_cell_slice_ref(extra.account_blocks), 256, + block::tlb::aug_ShardAccountBlocks}; + + bool eof = false; + ton::LogicalTime reverse = reverse_mode ? ~0ULL : 0; + ton::LogicalTime trans_lt = static_cast(start_lt); + td::Bits256 cur_addr = start_addr; + bool allow_same = true; + int count = 0; + while (!eof && count < req_count && count < max_answer_transactions) { + auto value = acc_dict.extract_value( + acc_dict.vm::DictionaryFixed::lookup_nearest_key(cur_addr.bits(), 256, !reverse, allow_same)); + if (value.is_null()) { + eof = true; + break; + } + allow_same = false; + if (cur_addr != start_addr) { + trans_lt = reverse; + } + + block::gen::AccountBlock::Record acc_blk; + if (!tlb::csr_unpack(std::move(value), acc_blk) || acc_blk.account_addr != cur_addr) { + return td::Status::Error("Error unpacking proof account block"); + } + vm::AugmentedDictionary trans_dict{vm::DictNonEmpty(), std::move(acc_blk.transactions), 64, + block::tlb::aug_AccountTransactions}; + td::BitArray<64> cur_trans{(long long)trans_lt}; + while (count < req_count && count < max_answer_transactions) { + auto tvalue = trans_dict.extract_value_ref( + trans_dict.vm::DictionaryFixed::lookup_nearest_key(cur_trans.bits(), 64, !reverse)); + if (tvalue.is_null()) { + trans_lt = reverse; + break; + } + if (static_cast(count) < bTxes->ids_.size()) { + if (mode & 4 && !tvalue->get_hash().bits().equals(bTxes->ids_[count]->hash_.bits(), 256)) { + return td::Status::Error("Couldn't verify proof (hash)"); + } + if (mode & 2 && cur_trans != td::BitArray<64>(bTxes->ids_[count]->lt_)) { + return td::Status::Error("Couldn't verify proof (lt)"); + } + if (mode & 1 && cur_addr != bTxes->ids_[count]->account_) { + return td::Status::Error("Couldn't verify proof (account)"); + } + } + count++; + } + } + if (static_cast(count) != bTxes->ids_.size()) { + return td::Status::Error(PSLICE() << "Txs count mismatch in proof (" << count << ") and response (" << bTxes->ids_.size() << ")"); + } + } catch (vm::VmError& err) { + return err.as_status("Couldn't verify proof: "); + } catch (vm::VmVirtError& err) { + return err.as_status("Couldn't verify proof: "); + } catch (...) { + return td::Status::Error("Unknown exception raised while verifying proof"); + } + } + tonlib_api::blocks_transactions r; - r.id_ = to_tonlib_api(*id); + r.id_ = to_tonlib_api(*bTxes->id_); r.req_count_ = bTxes->req_count_; r.incomplete_ = bTxes->incomplete_; for (auto& id: bTxes->ids_) { - //tonlib_api::blocks_shortTxId txid = tonlib_api::blocks_shortTxId(id->mode_, id->account_.as_slice().str(), id->lt_, id->hash_.as_slice().str()); - //r.transactions_.push_back(txid); r.transactions_.push_back(to_tonlib_api(*id)); } return tonlib_api::make_object(std::move(r)); @@ -5241,6 +5399,70 @@ td::Status TonlibClient::do_request(const tonlib_api::blocks_getTransactions& re return td::Status::OK(); } +td::Status TonlibClient::do_request(const tonlib_api::blocks_getTransactionsExt& request, + td::Promise>&& promise) { + TRY_RESULT(block, to_lite_api(*request.id_)) + bool check_proof = request.mode_ & 32; + bool reverse_mode = request.mode_ & 64; + bool has_starting_tx = request.mode_ & 128; + + td::Bits256 start_addr; + ton::LogicalTime start_lt; + ton::lite_api::object_ptr after; + if (has_starting_tx) { + if (!request.after_) { + return td::Status::Error("Missing field `after`"); + } + TRY_RESULT_ASSIGN(start_addr, to_bits256(request.after_->account_, "account")); + start_lt = request.after_->lt_; + after = ton::lite_api::make_object(start_addr, start_lt); + } else { + start_addr = reverse_mode ? td::Bits256::ones() : td::Bits256::zero(); + start_lt = reverse_mode ? ~0ULL : 0; + after = nullptr; + } + auto block_id = ton::create_block_id(block); + client_.send_query(ton::lite_api::liteServer_listBlockTransactionsExt( + std::move(block), + request.mode_, + request.count_, + std::move(after), + reverse_mode, + check_proof), + promise.wrap([block_id, check_proof, reverse_mode, start_addr, start_lt, req_count = request.count_] + (lite_api_ptr&& bTxes) -> td::Result> { + if (block_id != create_block_id(bTxes->id_)) { + return td::Status::Error("Liteserver responded with wrong block"); + } + + block::BlockTransactionList list; + list.blkid = block_id; + list.transactions_boc = std::move(bTxes->transactions_); + list.proof_boc = std::move(bTxes->proof_); + list.reverse_mode = reverse_mode; + list.start_lt = start_lt; + list.start_addr = start_addr; + list.req_count = req_count; + auto info = list.validate(check_proof); + if (info.is_error()) { + return info.move_as_error_prefix("Validation of block::BlockTransactionList failed: "); + } + + auto raw_transactions = ToRawTransactions(td::optional()).to_raw_transactions(info.move_as_ok()); + if (raw_transactions.is_error()) { + return raw_transactions.move_as_error_prefix("Error occured while creating tonlib_api::raw_transaction: "); + } + + tonlib_api::blocks_transactionsExt r; + r.id_ = to_tonlib_api(*bTxes->id_); + r.req_count_ = bTxes->req_count_; + r.incomplete_ = bTxes->incomplete_; + r.transactions_ = raw_transactions.move_as_ok(); + return tonlib_api::make_object(std::move(r)); + })); + return td::Status::OK(); +} + td::Status TonlibClient::do_request(const tonlib_api::blocks_getBlockHeader& request, td::Promise>&& promise) { TRY_RESULT(block, to_lite_api(*request.id_)) diff --git a/tonlib/tonlib/TonlibClient.h b/tonlib/tonlib/TonlibClient.h index dbbb62a2..ed81760f 100644 --- a/tonlib/tonlib/TonlibClient.h +++ b/tonlib/tonlib/TonlibClient.h @@ -375,6 +375,8 @@ class TonlibClient : public td::actor::Actor { td::Promise>&& promise); td::Status do_request(const tonlib_api::blocks_getTransactions& block_data, td::Promise>&& promise); + td::Status do_request(const tonlib_api::blocks_getTransactionsExt& request, + td::Promise>&& promise); td::Status do_request(const tonlib_api::blocks_getBlockHeader& request, td::Promise>&& promise); td::Status do_request(const tonlib_api::blocks_getMasterchainBlockSignatures& request, diff --git a/validator/impl/liteserver.cpp b/validator/impl/liteserver.cpp index 66e73d8e..69861ba3 100644 --- a/validator/impl/liteserver.cpp +++ b/validator/impl/liteserver.cpp @@ -182,6 +182,11 @@ void LiteQuery::start_up() { (q.mode_ & 128) ? q.after_->account_ : td::Bits256::zero(), static_cast((q.mode_ & 128) ? (q.after_->lt_) : 0)); }, + [&](lite_api::liteServer_listBlockTransactionsExt& q) { + this->perform_listBlockTransactionsExt(ton::create_block_id(q.id_), q.mode_, q.count_, + (q.mode_ & 128) ? q.after_->account_ : td::Bits256::zero(), + static_cast((q.mode_ & 128) ? (q.after_->lt_) : 0)); + }, [&](lite_api::liteServer_getConfigParams& q) { this->perform_getConfigParams(ton::create_block_id(q.id_), (q.mode_ & 0xffff) | 0x10000, q.param_list_); }, @@ -1964,6 +1969,118 @@ void LiteQuery::finish_listBlockTransactions(int mode, int req_count) { finish_query(std::move(b)); } +void LiteQuery::perform_listBlockTransactionsExt(BlockIdExt blkid, int mode, int count, Bits256 account, LogicalTime lt) { + LOG(INFO) << "started a listBlockTransactionsExt(" << blkid.to_str() << ", " << mode << ", " << count << ", " + << account.to_hex() << ", " << lt << ") liteserver query"; + base_blk_id_ = blkid; + acc_addr_ = account; + trans_lt_ = lt; + set_continuation([this, mode, count]() -> void { finish_listBlockTransactionsExt(mode, count); }); + request_block_data(blkid); +} + +void LiteQuery::finish_listBlockTransactionsExt(int mode, int req_count) { + LOG(INFO) << "completing a listBlockTransactionsExt(" << base_blk_id_.to_str() << ", " << mode << ", " << req_count + << ", " << acc_addr_.to_hex() << ", " << trans_lt_ << ") liteserver query"; + constexpr int max_answer_transactions = 256; + CHECK(block_.not_null()); + auto block_root = block_->root_cell(); + CHECK(block_root.not_null()); + RootHash rhash{block_root->get_hash().bits()}; + CHECK(rhash == base_blk_id_.root_hash); + vm::MerkleProofBuilder pb; + auto virt_root = block_root; + if (mode & 32) { + // proof requested + virt_root = pb.init(std::move(virt_root)); + } + if ((mode & 192) == 64) { // reverse order, no starting point + acc_addr_.set_ones(); + trans_lt_ = ~0ULL; + } + std::vector> trans_roots; + bool eof = false; + ton::LogicalTime reverse = (mode & 64) ? ~0ULL : 0; + try { + block::gen::Block::Record blk; + block::gen::BlockExtra::Record extra; + if (!(tlb::unpack_cell(virt_root, blk) && tlb::unpack_cell(std::move(blk.extra), extra))) { + fatal_error("cannot find account transaction data in block "s + base_blk_id_.to_str()); + return; + } + vm::AugmentedDictionary acc_dict{vm::load_cell_slice_ref(extra.account_blocks), 256, + block::tlb::aug_ShardAccountBlocks}; + int count = 0; + bool allow_same = true; + td::Bits256 cur_addr = acc_addr_; + while (!eof && count < req_count && count < max_answer_transactions) { + Ref value; + try { + value = acc_dict.extract_value( + acc_dict.vm::DictionaryFixed::lookup_nearest_key(cur_addr.bits(), 256, !reverse, allow_same)); + } catch (vm::VmError err) { + fatal_error("error while traversing account block dictionary: "s + err.get_msg()); + return; + } + if (value.is_null()) { + eof = true; + break; + } + allow_same = false; + if (cur_addr != acc_addr_) { + trans_lt_ = reverse; + } + block::gen::AccountBlock::Record acc_blk; + if (!(tlb::csr_unpack(std::move(value), acc_blk) && acc_blk.account_addr == cur_addr)) { + fatal_error("invalid AccountBlock for account "s + cur_addr.to_hex()); + return; + } + vm::AugmentedDictionary trans_dict{vm::DictNonEmpty(), std::move(acc_blk.transactions), 64, + block::tlb::aug_AccountTransactions}; + td::BitArray<64> cur_trans{(long long)trans_lt_}; + while (count < req_count && count < max_answer_transactions) { + Ref tvalue; + try { + tvalue = trans_dict.extract_value_ref( + trans_dict.vm::DictionaryFixed::lookup_nearest_key(cur_trans.bits(), 64, !reverse)); + } catch (vm::VmError err) { + fatal_error("error while traversing transaction dictionary of an AccountBlock: "s + err.get_msg()); + return; + } + if (tvalue.is_null()) { + trans_lt_ = reverse; + break; + } + trans_roots.push_back(std::move(tvalue)); + ++count; + } + } + } catch (vm::VmError err) { + fatal_error("error while parsing AccountBlocks of block "s + base_blk_id_.to_str() + " : " + err.get_msg()); + return; + } + td::BufferSlice proof_data; + if (mode & 32) { + // create proof + auto proof_boc = pb.extract_proof_boc(); + if (proof_boc.is_error()) { + fatal_error(proof_boc.move_as_error()); + return; + } + proof_data = proof_boc.move_as_ok(); + } + auto res = vm::std_boc_serialize_multi(std::move(trans_roots)); + if (res.is_error()) { + fatal_error(res.move_as_error()); + return; + } + + auto b = ton::create_serialize_tl_object( + ton::create_tl_lite_block_id(base_blk_id_), req_count, !eof, res.move_as_ok(), std::move(proof_data)); + LOG(INFO) << "listBlockTransactionsExt() query completed"; + finish_query(std::move(b)); +} + void LiteQuery::perform_getBlockProof(ton::BlockIdExt from, ton::BlockIdExt to, int mode) { if (!(mode & 1)) { to.invalidate_clear(); diff --git a/validator/impl/liteserver.hpp b/validator/impl/liteserver.hpp index 47970aae..2707fdfe 100644 --- a/validator/impl/liteserver.hpp +++ b/validator/impl/liteserver.hpp @@ -133,6 +133,8 @@ class LiteQuery : public td::actor::Actor { void perform_lookupBlock(BlockId blkid, int mode, LogicalTime lt, UnixTime utime); void perform_listBlockTransactions(BlockIdExt blkid, int mode, int count, Bits256 account, LogicalTime lt); void finish_listBlockTransactions(int mode, int count); + void perform_listBlockTransactionsExt(BlockIdExt blkid, int mode, int count, Bits256 account, LogicalTime lt); + void finish_listBlockTransactionsExt(int mode, int count); void perform_getBlockProof(BlockIdExt from, BlockIdExt to, int mode); void continue_getBlockProof(BlockIdExt from, BlockIdExt to, int mode, BlockIdExt baseblk, Ref state); From 5a3e6ec5597ec953a0fcfdff7bbdabb770cfe7f2 Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Tue, 7 Mar 2023 19:50:07 +0300 Subject: [PATCH 34/46] Update ubuntu:20.04 dockerfile (#636) (#640) * Update Dockerfile * Update Dockerfile --------- Co-authored-by: neodiX42 --- docker/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 7536a492..38ea6267 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,6 +1,6 @@ FROM ubuntu:20.04 as builder RUN apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential cmake clang-6.0 openssl libssl-dev zlib1g-dev gperf wget git ninja-build && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential cmake clang-6.0 openssl libmicrohttpd-dev pkg-config libssl-dev zlib1g-dev gperf wget git ninja-build && \ rm -rf /var/lib/apt/lists/* ENV CC clang-6.0 ENV CXX clang++-6.0 @@ -33,4 +33,4 @@ WORKDIR /var/ton-work/db COPY init.sh control.template ./ RUN chmod +x init.sh -ENTRYPOINT ["/var/ton-work/db/init.sh"] \ No newline at end of file +ENTRYPOINT ["/var/ton-work/db/init.sh"] From 6000a2646c00f80f58802ff49b7b4e86295741a3 Mon Sep 17 00:00:00 2001 From: Marat <98183742+dungeon-master-666@users.noreply.github.com> Date: Tue, 7 Mar 2023 16:51:09 +0000 Subject: [PATCH 35/46] Add transaction running elapsed time to emulator response (#616) --- emulator/emulator-extern.cpp | 13 +++++++++---- emulator/transaction-emulator.cpp | 9 +++++++-- emulator/transaction-emulator.h | 13 +++++++------ 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/emulator/emulator-extern.cpp b/emulator/emulator-extern.cpp index 811316db..e589032f 100644 --- a/emulator/emulator-extern.cpp +++ b/emulator/emulator-extern.cpp @@ -19,7 +19,8 @@ td::Result cell_to_boc_b64(td::Ref cell) { return td::base64_encode(boc.as_slice()); } -const char *success_response(std::string&& transaction, std::string&& new_shard_account, std::string&& vm_log, td::optional&& actions) { +const char *success_response(std::string&& transaction, std::string&& new_shard_account, std::string&& vm_log, + td::optional&& actions, double elapsed_time) { td::JsonBuilder jb; auto json_obj = jb.enter_object(); json_obj("success", td::JsonTrue()); @@ -31,6 +32,7 @@ const char *success_response(std::string&& transaction, std::string&& new_shard_ } else { json_obj("actions", td::JsonNull()); } + json_obj("elapsed_time", elapsed_time); json_obj.leave(); return strdup(jb.string_builder().as_cslice().c_str()); } @@ -44,13 +46,14 @@ const char *error_response(std::string&& error) { return strdup(jb.string_builder().as_cslice().c_str()); } -const char *external_not_accepted_response(std::string&& vm_log, int vm_exit_code) { +const char *external_not_accepted_response(std::string&& vm_log, int vm_exit_code, double elapsed_time) { td::JsonBuilder jb; auto json_obj = jb.enter_object(); json_obj("success", td::JsonFalse()); json_obj("error", "External message not accepted by smart contract"); json_obj("vm_log", std::move(vm_log)); json_obj("vm_exit_code", vm_exit_code); + json_obj("elapsed_time", elapsed_time); json_obj.leave(); return strdup(jb.string_builder().as_cslice().c_str()); } @@ -142,7 +145,8 @@ const char *transaction_emulator_emulate_transaction(void *transaction_emulator, auto external_not_accepted = dynamic_cast(emulation_result.get()); if (external_not_accepted) { - return external_not_accepted_response(std::move(external_not_accepted->vm_log), external_not_accepted->vm_exit_code); + return external_not_accepted_response(std::move(external_not_accepted->vm_log), external_not_accepted->vm_exit_code, + external_not_accepted->elapsed_time); } auto emulation_success = dynamic_cast(*emulation_result); @@ -168,7 +172,8 @@ const char *transaction_emulator_emulate_transaction(void *transaction_emulator, actions_boc_b64 = actions_boc_b64_result.move_as_ok(); } - return success_response(trans_boc_b64.move_as_ok(), new_shard_account_boc_b64.move_as_ok(), std::move(emulation_success.vm_log), std::move(actions_boc_b64)); + return success_response(trans_boc_b64.move_as_ok(), new_shard_account_boc_b64.move_as_ok(), std::move(emulation_success.vm_log), + std::move(actions_boc_b64), emulation_success.elapsed_time); } bool transaction_emulator_set_unixtime(void *transaction_emulator, uint32_t unixtime) { diff --git a/emulator/transaction-emulator.cpp b/emulator/transaction-emulator.cpp index 4a616418..a5fea51c 100644 --- a/emulator/transaction-emulator.cpp +++ b/emulator/transaction-emulator.cpp @@ -2,6 +2,7 @@ #include "transaction-emulator.h" #include "crypto/common/refcnt.hpp" #include "vm/cp0.h" +#include "tdutils/td/utils/Time.h" using td::Ref; using namespace std::string_literals; @@ -47,9 +48,12 @@ td::Result> TransactionEmu compute_phase_cfg.with_vm_log = true; compute_phase_cfg.vm_log_verbosity = vm_log_verbosity_; + double start_time = td::Time::now(); auto res = create_transaction(msg_root, &account, utime, lt, trans_type, &storage_phase_cfg, &compute_phase_cfg, &action_phase_cfg); + double elapsed = td::Time::now() - start_time; + if(res.is_error()) { return res.move_as_error_prefix("cannot run message on account "); } @@ -58,7 +62,7 @@ td::Result> TransactionEmu if (!trans->compute_phase->accepted && trans->in_msg_extern) { auto vm_log = trans->compute_phase->vm_log; auto vm_exit_code = trans->compute_phase->exit_code; - return std::make_unique(std::move(vm_log), vm_exit_code); + return std::make_unique(std::move(vm_log), vm_exit_code, elapsed); } if (!trans->serialize()) { @@ -70,7 +74,8 @@ td::Result> TransactionEmu return td::Status::Error(PSLICE() << "cannot commit new transaction for smart contract"); } - return std::make_unique(std::move(trans_root), std::move(account), std::move(trans->compute_phase->vm_log), std::move(trans->compute_phase->actions)); + return std::make_unique(std::move(trans_root), std::move(account), + std::move(trans->compute_phase->vm_log), std::move(trans->compute_phase->actions), elapsed); } td::Result TransactionEmulator::emulate_transaction(block::Account&& account, td::Ref original_trans) { diff --git a/emulator/transaction-emulator.h b/emulator/transaction-emulator.h index 025bfa93..02f875e2 100644 --- a/emulator/transaction-emulator.h +++ b/emulator/transaction-emulator.h @@ -25,26 +25,27 @@ public: struct EmulationResult { std::string vm_log; + double elapsed_time; - EmulationResult(std::string vm_log_) : vm_log(vm_log_) {} + EmulationResult(std::string vm_log_, double elapsed_time_) : vm_log(vm_log_), elapsed_time(elapsed_time_) {} virtual ~EmulationResult() = default; }; struct EmulationSuccess: EmulationResult { td::Ref transaction; block::Account account; - td::Ref actions; + td::Ref actions; - EmulationSuccess(td::Ref transaction_, block::Account account_, std::string vm_log_, td::Ref actions_) : - EmulationResult(vm_log_), transaction(transaction_), account(account_) , actions(actions_) + EmulationSuccess(td::Ref transaction_, block::Account account_, std::string vm_log_, td::Ref actions_, double elapsed_time_) : + EmulationResult(vm_log_, elapsed_time_), transaction(transaction_), account(account_) , actions(actions_) {} }; struct EmulationExternalNotAccepted: EmulationResult { int vm_exit_code; - EmulationExternalNotAccepted(std::string vm_log_, int vm_exit_code_) : - EmulationResult(vm_log_), vm_exit_code(vm_exit_code_) + EmulationExternalNotAccepted(std::string vm_log_, int vm_exit_code_, double elapsed_time_) : + EmulationResult(vm_log_, elapsed_time_), vm_exit_code(vm_exit_code_) {} }; From 82e231d0a79c68958bc2c7fd526322b5542653ad Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Tue, 7 Mar 2023 21:22:17 +0300 Subject: [PATCH 36/46] Fix deploy_storage_contract detection --- storage/storage-daemon/StorageProvider.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/storage-daemon/StorageProvider.cpp b/storage/storage-daemon/StorageProvider.cpp index da4b5042..e1eb55d6 100644 --- a/storage/storage-daemon/StorageProvider.cpp +++ b/storage/storage-daemon/StorageProvider.cpp @@ -259,7 +259,7 @@ void StorageProvider::process_transaction(tl_object_ptr body = r_body.move_as_ok(); vm::CellSlice cs = vm::load_cell_slice(body); if (cs.size() >= 32) { - long long op_code = cs.prefetch_long(32); + long long op_code = cs.prefetch_ulong(32); // const op::offer_storage_contract = 0x107c49ef; -- old versions // const op::deploy_storage_contract = 0xe4748df1; -- new versions if((op_code == 0x107c49ef) || (op_code == 0xe4748df1)) { From 4590ed381bf82be4b6fe37aa9f7f7db587418ec3 Mon Sep 17 00:00:00 2001 From: Dan Volkov Date: Thu, 9 Mar 2023 18:37:15 +0400 Subject: [PATCH 37/46] Make funcfiftlib compilation compatible with modern compilers (#618) * wip: make funcfiftlib compilation compatible with modern compilers * wip: add methods needed for another compiler * fix: tdutils port config if emscripten * feat: func source and realpath callback * fix: invalid fift compilation exceptions --------- Co-authored-by: krigga --- crypto/CMakeLists.txt | 15 ++++++++--- crypto/func/func-main.cpp | 2 ++ crypto/func/func.cpp | 24 +++++++++++++++++ crypto/func/func.h | 33 +++++++++++++++++++++++ crypto/func/parse-func.cpp | 14 +++++----- crypto/funcfiftlib/funcfiftlib.cpp | 43 +++++++++++++++++++++++++++++- tdutils/td/utils/port/config.h | 28 +++++++++---------- tdutils/td/utils/port/platform.h | 14 +++++++--- 8 files changed, 142 insertions(+), 31 deletions(-) diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index a1b05b81..20e6bbc7 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -300,6 +300,10 @@ target_link_libraries(test-ed25519-crypto PUBLIC ton_crypto) add_library(fift-lib ${FIFT_SOURCE}) target_include_directories(fift-lib PUBLIC $) target_link_libraries(fift-lib PUBLIC ton_crypto ton_db tdutils ton_block) +if (USE_EMSCRIPTEN) + target_link_options(fift-lib PRIVATE -fexceptions) + target_compile_options(fift-lib PRIVATE -fexceptions) +endif() set_target_properties(fift-lib PROPERTIES OUTPUT_NAME fift) add_executable(fift fift/fift-main.cpp) @@ -328,17 +332,20 @@ if (USE_EMSCRIPTEN) add_executable(funcfiftlib funcfiftlib/funcfiftlib.cpp ${FUNC_LIB_SOURCE}) target_include_directories(funcfiftlib PUBLIC $) target_link_libraries(funcfiftlib PUBLIC fift-lib src_parser git) - target_link_options(funcfiftlib PRIVATE -sEXPORTED_RUNTIME_METHODS=FS,ccall,cwrap,_malloc,free,UTF8ToString,stringToUTF8) - target_link_options(funcfiftlib PRIVATE -sEXPORTED_FUNCTIONS=_func_compile,_version) + target_link_options(funcfiftlib PRIVATE -sEXPORTED_RUNTIME_METHODS=FS,ccall,cwrap,UTF8ToString,stringToUTF8,lengthBytesUTF8,addFunction,removeFunction,setValue) + target_link_options(funcfiftlib PRIVATE -sEXPORTED_FUNCTIONS=_func_compile,_version,_malloc,_free,_setThrew) target_link_options(funcfiftlib PRIVATE -sEXPORT_NAME=CompilerModule) target_link_options(funcfiftlib PRIVATE -sERROR_ON_UNDEFINED_SYMBOLS=0) - target_link_options(funcfiftlib PRIVATE -sFILESYSTEM=1) + target_link_options(funcfiftlib PRIVATE -sFILESYSTEM=1 -lnodefs.js) target_link_options(funcfiftlib PRIVATE -Oz) target_link_options(funcfiftlib PRIVATE -sIGNORE_MISSING_MAIN=1) target_link_options(funcfiftlib PRIVATE -sAUTO_NATIVE_LIBRARIES=0) target_link_options(funcfiftlib PRIVATE -sMODULARIZE=1) + target_link_options(funcfiftlib PRIVATE -sALLOW_MEMORY_GROWTH=1) + target_link_options(funcfiftlib PRIVATE -sALLOW_TABLE_GROWTH=1) target_link_options(funcfiftlib PRIVATE --embed-file ${CMAKE_CURRENT_SOURCE_DIR}/fift/lib@/fiftlib) - target_compile_options(funcfiftlib PRIVATE -sDISABLE_EXCEPTION_CATCHING=0) + target_link_options(funcfiftlib PRIVATE -fexceptions) + target_compile_options(funcfiftlib PRIVATE -fexceptions) endif() add_executable(tlbc tl/tlbc.cpp) diff --git a/crypto/func/func-main.cpp b/crypto/func/func-main.cpp index 829d95e4..c8208eee 100644 --- a/crypto/func/func-main.cpp +++ b/crypto/func/func-main.cpp @@ -123,5 +123,7 @@ int main(int argc, char* const argv[]) { sources.push_back(std::string(argv[optind++])); } + funC::read_callback = funC::fs_read_callback; + return funC::func_proceed(sources, *outs, std::cerr); } diff --git a/crypto/func/func.cpp b/crypto/func/func.cpp index be44d2ea..87bf23e9 100644 --- a/crypto/func/func.cpp +++ b/crypto/func/func.cpp @@ -30,6 +30,8 @@ #include "parser/lexer.h" #include #include "git.h" +#include +#include "td/utils/port/path.h" namespace funC { @@ -39,6 +41,28 @@ bool interactive = false; GlobalPragma pragma_allow_post_modification{"allow-post-modification"}; GlobalPragma pragma_compute_asm_ltr{"compute-asm-ltr"}; std::string generated_from, boc_output_filename; +ReadCallback::Callback read_callback; + +td::Result fs_read_callback(ReadCallback::Kind kind, const char* query) { + switch (kind) { + case ReadCallback::Kind::ReadFile: { + std::ifstream ifs{query}; + if (ifs.fail()) { + auto msg = std::string{"cannot open source file `"} + query + "`"; + return td::Status::Error(msg); + } + std::stringstream ss; + ss << ifs.rdbuf(); + return ss.str(); + } + case ReadCallback::Kind::Realpath: { + return td::realpath(td::CSlice(query)); + } + default: { + return td::Status::Error("Unknown query kind"); + } + } +} /* * diff --git a/crypto/func/func.h b/crypto/func/func.h index 1d534dc3..27656fed 100644 --- a/crypto/func/func.h +++ b/crypto/func/func.h @@ -30,6 +30,7 @@ #include "parser/srcread.h" #include "parser/lexer.h" #include "parser/symtable.h" +#include "td/utils/Status.h" namespace funC { @@ -845,6 +846,35 @@ extern std::vector glob_func, glob_vars; * */ +class ReadCallback { +public: + /// Noncopyable. + ReadCallback(ReadCallback const&) = delete; + ReadCallback& operator=(ReadCallback const&) = delete; + + enum class Kind + { + ReadFile, + Realpath + }; + + static std::string kindString(Kind _kind) + { + switch (_kind) + { + case Kind::ReadFile: + return "source"; + case Kind::Realpath: + return "realpath"; + default: + throw ""; // todo ? + } + } + + /// File reading or generic query callback. + using Callback = std::function(ReadCallback::Kind, const char*)>; +}; + // defined in parse-func.cpp bool parse_source(std::istream* is, const src::FileDescr* fdescr); bool parse_source_file(const char* filename, src::Lexem lex = {}, bool is_main = false); @@ -1691,6 +1721,9 @@ void define_builtins(); extern int verbosity, indent, opt_level; extern bool stack_layout_comments, op_rewrite_comments, program_envelope, asm_preamble, interactive; extern std::string generated_from, boc_output_filename; +extern ReadCallback::Callback read_callback; + +td::Result fs_read_callback(ReadCallback::Kind kind, const char* query); class GlobalPragma { public: diff --git a/crypto/func/parse-func.cpp b/crypto/func/parse-func.cpp index 8f6f7afe..19481a07 100644 --- a/crypto/func/parse-func.cpp +++ b/crypto/func/parse-func.cpp @@ -22,8 +22,6 @@ #include "openssl/digest.hpp" #include "block/block.h" #include "block-parse.h" -#include -#include "td/utils/port/path.h" namespace sym { @@ -1757,7 +1755,7 @@ bool parse_source_file(const char* filename, src::Lexem lex, bool is_main) { } } - auto path_res = td::realpath(td::CSlice(filename)); + auto path_res = read_callback(ReadCallback::Kind::Realpath, filename); if (path_res.is_error()) { auto error = path_res.move_as_error(); lex.error(error.message().c_str()); @@ -1784,17 +1782,19 @@ bool parse_source_file(const char* filename, src::Lexem lex, bool is_main) { source_files[real_filename] = cur_source; cur_source->is_main = is_main; source_fdescr.push_back(cur_source); - std::ifstream ifs{filename}; - if (ifs.fail()) { - auto msg = std::string{"cannot open source file `"} + filename + "`"; + auto file_res = read_callback(ReadCallback::Kind::ReadFile, filename); + if (file_res.is_error()) { + auto msg = file_res.move_as_error().message().str(); if (lex.tp) { lex.error(msg); } else { throw src::Fatal{msg}; } } + auto file_str = file_res.move_as_ok(); + std::stringstream ss{file_str}; inclusion_locations.push(lex.loc); - bool res = parse_source(&ifs, cur_source); + bool res = parse_source(&ss, cur_source); inclusion_locations.pop(); return res; } diff --git a/crypto/funcfiftlib/funcfiftlib.cpp b/crypto/funcfiftlib/funcfiftlib.cpp index 6c8912bc..070c3e0d 100644 --- a/crypto/funcfiftlib/funcfiftlib.cpp +++ b/crypto/funcfiftlib/funcfiftlib.cpp @@ -30,6 +30,7 @@ #include "td/utils/JsonBuilder.h" #include "fift/utils.h" #include "td/utils/base64.h" +#include "td/utils/Status.h" #include #include @@ -99,6 +100,40 @@ td::Result compile_internal(char *config_json) { return result_json.string_builder().as_cslice().str(); } +/// Callback used to retrieve additional source files or data. +/// +/// @param _kind The kind of callback (a string). +/// @param _data The data for the callback (a string). +/// @param o_contents A pointer to the contents of the file, if found. Allocated via malloc(). +/// @param o_error A pointer to an error message, if there is one. Allocated via malloc(). +/// +/// The callback implementor must use malloc() to allocate storage for +/// contents or error. The callback implementor must use free() to free +/// said storage after func_compile returns. +/// +/// If the callback is not supported, *o_contents and *o_error must be set to NULL. +typedef void (*CStyleReadFileCallback)(char const* _kind, char const* _data, char** o_contents, char** o_error); + +funC::ReadCallback::Callback wrapReadCallback(CStyleReadFileCallback _readCallback) +{ + funC::ReadCallback::Callback readCallback; + if (_readCallback) { + readCallback = [=](funC::ReadCallback::Kind _kind, char const* _data) -> td::Result { + char* contents_c = nullptr; + char* error_c = nullptr; + _readCallback(funC::ReadCallback::kindString(_kind).data(), _data, &contents_c, &error_c); + if (!contents_c && !error_c) { + return td::Status::Error("Callback not supported"); + } + if (contents_c) { + return contents_c; + } + return td::Status::Error(std::string(error_c)); + }; + } + return readCallback; +} + extern "C" { const char* version() { @@ -111,7 +146,13 @@ const char* version() { return strdup(version_json.string_builder().as_cslice().c_str()); } -const char *func_compile(char *config_json) { +const char *func_compile(char *config_json, CStyleReadFileCallback callback) { + if (callback) { + funC::read_callback = wrapReadCallback(callback); + } else { + funC::read_callback = funC::fs_read_callback; + } + auto res = compile_internal(config_json); if (res.is_error()) { diff --git a/tdutils/td/utils/port/config.h b/tdutils/td/utils/port/config.h index 77143668..f89082f3 100644 --- a/tdutils/td/utils/port/config.h +++ b/tdutils/td/utils/port/config.h @@ -28,35 +28,33 @@ #define TD_PORT_POSIX 1 #endif -#if TD_LINUX || TD_ANDROID || TD_TIZEN +#if TD_EMSCRIPTEN + #define TD_POLL_POLL 1 +#elif TD_LINUX || TD_ANDROID || TD_TIZEN #define TD_POLL_EPOLL 1 - #define TD_EVENTFD_LINUX 1 #elif TD_FREEBSD || TD_OPENBSD || TD_NETBSD #define TD_POLL_KQUEUE 1 - #define TD_EVENTFD_BSD 1 #elif TD_CYGWIN #define TD_POLL_SELECT 1 - #define TD_EVENTFD_BSD 1 -#elif TD_EMSCRIPTEN - #define TD_POLL_POLL 1 - // #define TD_EVENTFD_UNSUPPORTED 1 #elif TD_DARWIN #define TD_POLL_KQUEUE 1 - #define TD_EVENTFD_BSD 1 #elif TD_WINDOWS #define TD_POLL_WINEVENT 1 - #define TD_EVENTFD_WINDOWS 1 #else #error "Poll's implementation is not defined" #endif -#if TD_EMSCRIPTEN - // #define TD_THREAD_UNSUPPORTED 1 - #define TD_POLL_EPOLL 1 - #define TD_EVENTFD_UNSUPPORTED 0 - #define TD_THREAD_PTHREAD 1 +#if TD_LINUX || TD_ANDROID || TD_TIZEN #define TD_EVENTFD_LINUX 1 -#elif TD_TIZEN || TD_LINUX || TD_DARWIN +#elif TD_FREEBSD || TD_OPENBSD || TD_NETBSD || TD_CYGWIN || TD_DARWIN + #define TD_EVENTFD_BSD 1 +#elif TD_WINDOWS + #define TD_EVENTFD_WINDOWS 1 +#else + #error "eventfd's implementation is not defined" +#endif + +#if TD_TIZEN || TD_LINUX || TD_DARWIN || TD_EMSCRIPTEN #define TD_THREAD_PTHREAD 1 #else #define TD_THREAD_STL 1 diff --git a/tdutils/td/utils/port/platform.h b/tdutils/td/utils/port/platform.h index 783dd399..a7b19220 100644 --- a/tdutils/td/utils/port/platform.h +++ b/tdutils/td/utils/port/platform.h @@ -20,6 +20,11 @@ // clang-format off +/*** Determine emscripten ***/ +#if defined(__EMSCRIPTEN__) + #define TD_EMSCRIPTEN 1 +#endif + /*** Platform macros ***/ #if defined(_WIN32) || defined(_WINDOWS) // _WINDOWS is defined by CMake #if defined(__cplusplus_winrt) @@ -63,10 +68,11 @@ #define TD_NETBSD 1 #elif defined(__CYGWIN__) #define TD_CYGWIN 1 -#elif defined(__EMSCRIPTEN__) - #define TD_EMSCRIPTEN 1 -#elif defined(__unix__) // all unices not caught above - #warning "Probably unsupported Unix platform. Feel free to try to compile" +#elif defined(__unix__) // all unices not caught above + // supress if emscripten + #if !TD_EMSCRIPTEN + #warning "Probably unsupported Unix platform. Feel free to try to compile" + #endif #define TD_CYGWIN 1 #else #error "Probably unsupported platform. Feel free to remove the error and try to recompile" From 865ebfce8d5603734f3b5f282497bef5ce2ffe19 Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Fri, 10 Mar 2023 14:16:29 +0300 Subject: [PATCH 38/46] Add namespaces to Fift (#641) * Add fift-based disassembler * Fift improvements: namespaces, hashmaps, flow controls * Fift: add lib with better block structuring and more * Minor changes in fift HashMap + tests (#643) * Minor changes in fift HashMap * Add tests for extended fift --------- Co-authored-by: OmicronTau Co-authored-by: Tolya <1449561+tolya-yanot@users.noreply.github.com> Co-authored-by: SpyCheese --- crypto/CMakeLists.txt | 2 + crypto/block/create-state.cpp | 6 +- crypto/fift/Continuation.cpp | 143 ++++++- crypto/fift/Continuation.h | 96 +++++ crypto/fift/Dictionary.cpp | 233 +++--------- crypto/fift/Dictionary.h | 167 +++----- crypto/fift/Fift.cpp | 6 +- crypto/fift/HashMap.cpp | 371 ++++++++++++++++++ crypto/fift/HashMap.h | 306 +++++++++++++++ crypto/fift/IntCtx.cpp | 140 +++---- crypto/fift/IntCtx.h | 108 +++--- crypto/fift/lib/Asm.fif | 17 +- crypto/fift/lib/Disasm.fif | 141 +++++++ crypto/fift/lib/Fift.fif | 2 + crypto/fift/lib/FiftExt.fif | 118 ++++++ crypto/fift/utils.cpp | 24 +- crypto/fift/words.cpp | 650 ++++++++++++++++++++++++-------- crypto/test/fift.cpp | 16 + crypto/test/fift/disasm.fif | 70 ++++ crypto/test/fift/fift-ext.fif | 107 ++++++ crypto/test/fift/hmap.fif | 69 ++++ crypto/test/fift/namespaces.fif | 29 ++ crypto/vm/arithops.cpp | 84 +++-- crypto/vm/box.hpp | 2 +- crypto/vm/cellops.cpp | 20 +- crypto/vm/contops.cpp | 16 +- crypto/vm/dictops.cpp | 16 +- crypto/vm/opctable.cpp | 44 +-- crypto/vm/stack.hpp | 3 + crypto/vm/stackops.cpp | 12 +- test/regression-tests.ans | 4 + 31 files changed, 2323 insertions(+), 699 deletions(-) create mode 100644 crypto/fift/HashMap.cpp create mode 100644 crypto/fift/HashMap.h create mode 100644 crypto/fift/lib/Disasm.fif create mode 100644 crypto/fift/lib/FiftExt.fif create mode 100644 crypto/test/fift/disasm.fif create mode 100644 crypto/test/fift/fift-ext.fif create mode 100644 crypto/test/fift/hmap.fif create mode 100644 crypto/test/fift/namespaces.fif diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index 20e6bbc7..598169f7 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -143,6 +143,7 @@ set(FIFT_SOURCE fift/Dictionary.cpp fift/Fift.cpp fift/IntCtx.cpp + fift/HashMap.cpp fift/Continuation.cpp fift/SourceLookup.cpp fift/utils.cpp @@ -151,6 +152,7 @@ set(FIFT_SOURCE fift/Dictionary.h fift/Fift.h fift/IntCtx.h + fift/HashMap.h fift/Continuation.h fift/SourceLookup.h fift/utils.h diff --git a/crypto/block/create-state.cpp b/crypto/block/create-state.cpp index a7500713..183da0a7 100644 --- a/crypto/block/create-state.cpp +++ b/crypto/block/create-state.cpp @@ -47,6 +47,7 @@ #include "fift/Fift.h" #include "fift/Dictionary.h" #include "fift/SourceLookup.h" +#include "fift/IntCtx.h" #include "fift/words.h" #include "td/utils/logging.h" @@ -866,8 +867,9 @@ int main(int argc, char* const argv[]) { case 'v': new_verbosity_level = VERBOSITY_NAME(FATAL) + (verbosity = td::to_integer(td::Slice(optarg))); break; - case 'V': - std::cout << "create-state build information: [ Commit: " << GitMetadata::CommitSHA1() << ", Date: " << GitMetadata::CommitDate() << "]\n"; + case 'V': + std::cout << "create-state build information: [ Commit: " << GitMetadata::CommitSHA1() + << ", Date: " << GitMetadata::CommitDate() << "]\n"; std::exit(0); break; case 'h': diff --git a/crypto/fift/Continuation.cpp b/crypto/fift/Continuation.cpp index 7e3b5ea2..e895082f 100644 --- a/crypto/fift/Continuation.cpp +++ b/crypto/fift/Continuation.cpp @@ -27,7 +27,7 @@ namespace fift { // bool FiftCont::print_dict_name(std::ostream& os, const IntCtx& ctx) const { std::string word_name; - if (ctx.dictionary && ctx.dictionary->lookup_def(this, &word_name)) { + if (ctx.dictionary.lookup_def(this, &word_name)) { if (word_name.size() && word_name.back() == ' ') { word_name.pop_back(); } @@ -39,7 +39,7 @@ bool FiftCont::print_dict_name(std::ostream& os, const IntCtx& ctx) const { std::string FiftCont::get_dict_name(const IntCtx& ctx) const { std::string word_name; - if (ctx.dictionary && ctx.dictionary->lookup_def(this, &word_name)) { + if (ctx.dictionary.lookup_def(this, &word_name)) { if (word_name.size() && word_name.back() == ' ') { word_name.pop_back(); } @@ -63,6 +63,140 @@ bool FiftCont::dump(std::ostream& os, const IntCtx& ctx) const { return ok; } +// +// StackWord +// +Ref StackWord::run_tail(IntCtx& ctx) const { + f(ctx.stack); + return {}; +} + +// +// CtxWord +// +Ref CtxWord::run_tail(IntCtx& ctx) const { + f(ctx); + return {}; +} + +// +// CtxTailWord +// +Ref CtxTailWord::run_tail(IntCtx& ctx) const { + return f(ctx); +} + +// +// WordList +// +WordList::WordList(std::vector>&& _list) : list(std::move(_list)) { +} + +WordList::WordList(const std::vector>& _list) : list(_list) { +} + +WordList& WordList::push_back(Ref word_def) { + list.push_back(std::move(word_def)); + return *this; +} + +WordList& WordList::push_back(FiftCont& wd) { + list.emplace_back(&wd); + return *this; +} + +Ref WordList::run_tail(IntCtx& ctx) const { + if (list.empty()) { + return {}; + } + if (list.size() > 1) { + ctx.next = td::make_ref(std::move(ctx.next), Ref(this), 1); + } + return list[0]; +} + +void WordList::close() { + list.shrink_to_fit(); +} + +WordList& WordList::append(const std::vector>& other) { + list.insert(list.end(), other.begin(), other.end()); + return *this; +} + +WordList& WordList::append(const Ref* begin, const Ref* end) { + list.insert(list.end(), begin, end); + return *this; +} + +bool WordList::dump(std::ostream& os, const IntCtx& ctx) const { + os << "{"; + for (auto entry : list) { + os << ' '; + entry->print_name(os, ctx); + } + os << " }" << std::endl; + return true; +} + +// +// ListCont +// + +Ref ListCont::run_tail(IntCtx& ctx) const { + auto sz = list->size(); + if (pos >= sz) { + return std::move(ctx.next); + } else if (ctx.next.not_null()) { + ctx.next = td::make_ref(SeqCont::seq(next, std::move(ctx.next)), list, pos + 1); + } else if (pos + 1 == sz) { + ctx.next = next; + } else { + ctx.next = td::make_ref(next, list, pos + 1); + } + return list->at(pos); +} + +Ref ListCont::run_modify(IntCtx& ctx) { + auto sz = list->size(); + if (pos >= sz) { + return std::move(ctx.next); + } + auto cur = list->at(pos++); + if (ctx.next.not_null()) { + next = SeqCont::seq(next, std::move(ctx.next)); + } + if (pos == sz) { + ctx.next = std::move(next); + } else { + ctx.next = self(); + } + return cur; +} + +bool ListCont::dump(std::ostream& os, const IntCtx& ctx) const { + std::string dict_name = list->get_dict_name(ctx); + if (!dict_name.empty()) { + os << "[in " << dict_name << ":] "; + } + std::size_t sz = list->size(), i, a = (pos >= 16 ? pos - 16 : 0), b = std::min(pos + 16, sz); + if (a > 0) { + os << "... "; + } + for (i = a; i < b; i++) { + if (i == pos) { + os << "**HERE** "; + } + list->at(i)->print_name(os, ctx); + os << ' '; + } + if (b < sz) { + os << "..."; + } + os << std::endl; + return true; +} + // // QuitCont // @@ -295,12 +429,15 @@ bool GenericLitCont::print_name(std::ostream& os, const IntCtx& ctx) const { bool sp = false; for (auto entry : list) { if (sp) { - os << sp; + os << ' '; } sp = true; int tp = entry.type(); if (entry.is_int() || entry.is(vm::StackEntry::t_string) || entry.is(vm::StackEntry::t_bytes)) { entry.dump(os); + } else if (entry.is_atom()) { + os << '`'; + entry.dump(os); } else { auto cont_lit = entry.as_object(); if (cont_lit.not_null()) { diff --git a/crypto/fift/Continuation.h b/crypto/fift/Continuation.h index f2c44e7b..6623b642 100644 --- a/crypto/fift/Continuation.h +++ b/crypto/fift/Continuation.h @@ -17,6 +17,7 @@ Copyright 2020 Telegram Systems LLP */ #pragma once +#include #include "common/refcnt.hpp" #include "common/refint.h" #include "vm/stack.hpp" @@ -76,6 +77,101 @@ class FiftCont : public td::CntObject { } }; +typedef std::function StackWordFunc; +typedef std::function CtxWordFunc; +typedef std::function(IntCtx&)> CtxTailWordFunc; + +class NopWord : public FiftCont { + public: + NopWord() = default; + ~NopWord() override = default; + Ref run_tail(IntCtx& ctx) const override { + return {}; + } +}; + +class StackWord : public FiftCont { + StackWordFunc f; + + public: + StackWord(StackWordFunc _f) : f(std::move(_f)) { + } + ~StackWord() override = default; + Ref run_tail(IntCtx& ctx) const override; +}; + +class CtxWord : public FiftCont { + CtxWordFunc f; + + public: + CtxWord(CtxWordFunc _f) : f(std::move(_f)) { + } + ~CtxWord() override = default; + Ref run_tail(IntCtx& ctx) const override; +}; + +class CtxTailWord : public FiftCont { + CtxTailWordFunc f; + + public: + CtxTailWord(CtxTailWordFunc _f) : f(std::move(_f)) { + } + ~CtxTailWord() override = default; + Ref run_tail(IntCtx& ctx) const override; +}; + +class WordList : public FiftCont { + std::vector> list; + + public: + ~WordList() override = default; + WordList() = default; + WordList(std::vector>&& _list); + WordList(const std::vector>& _list); + WordList& push_back(Ref word_def); + WordList& push_back(FiftCont& wd); + Ref run_tail(IntCtx& ctx) const override; + void close(); + bool is_list() const override { + return true; + } + long long list_size() const override { + return (long long)list.size(); + } + std::size_t size() const { + return list.size(); + } + const Ref& at(std::size_t idx) const { + return list.at(idx); + } + const Ref* get_list() const override { + return list.data(); + } + WordList& append(const std::vector>& other); + WordList& append(const Ref* begin, const Ref* end); + WordList* make_copy() const override { + return new WordList(list); + } + bool dump(std::ostream& os, const IntCtx& ctx) const override; +}; + +class ListCont : public FiftCont { + Ref next; + Ref list; + std::size_t pos; + + public: + ListCont(Ref nxt, Ref wl, std::size_t p = 0) : next(std::move(nxt)), list(std::move(wl)), pos(p) { + } + ~ListCont() override = default; + Ref run_tail(IntCtx& ctx) const override; + Ref run_modify(IntCtx& ctx) override; + Ref up() const override { + return next; + } + bool dump(std::ostream& os, const IntCtx& ctx) const override; +}; + class QuitCont : public FiftCont { int exit_code; diff --git a/crypto/fift/Dictionary.cpp b/crypto/fift/Dictionary.cpp index 59da278f..d2eae0a3 100644 --- a/crypto/fift/Dictionary.cpp +++ b/crypto/fift/Dictionary.cpp @@ -17,143 +17,10 @@ Copyright 2017-2020 Telegram Systems LLP */ #include "Dictionary.h" +#include "IntCtx.h" namespace fift { -// -// StackWord -// -Ref StackWord::run_tail(IntCtx& ctx) const { - f(ctx.stack); - return {}; -} - -// -// CtxWord -// -Ref CtxWord::run_tail(IntCtx& ctx) const { - f(ctx); - return {}; -} - -// -// CtxTailWord -// -Ref CtxTailWord::run_tail(IntCtx& ctx) const { - return f(ctx); -} - -// -// WordList -// -WordList::WordList(std::vector>&& _list) : list(std::move(_list)) { -} - -WordList::WordList(const std::vector>& _list) : list(_list) { -} - -WordList& WordList::push_back(Ref word_def) { - list.push_back(std::move(word_def)); - return *this; -} - -WordList& WordList::push_back(FiftCont& wd) { - list.emplace_back(&wd); - return *this; -} - -Ref WordList::run_tail(IntCtx& ctx) const { - if (list.empty()) { - return {}; - } - if (list.size() > 1) { - ctx.next = td::make_ref(std::move(ctx.next), Ref(this), 1); - } - return list[0]; -} - -void WordList::close() { - list.shrink_to_fit(); -} - -WordList& WordList::append(const std::vector>& other) { - list.insert(list.end(), other.begin(), other.end()); - return *this; -} - -WordList& WordList::append(const Ref* begin, const Ref* end) { - list.insert(list.end(), begin, end); - return *this; -} - -bool WordList::dump(std::ostream& os, const IntCtx& ctx) const { - os << "{"; - for (auto entry : list) { - os << ' '; - entry->print_name(os, ctx); - } - os << " }" << std::endl; - return true; -} - -// -// ListCont -// - -Ref ListCont::run_tail(IntCtx& ctx) const { - auto sz = list->size(); - if (pos >= sz) { - return std::move(ctx.next); - } else if (ctx.next.not_null()) { - ctx.next = td::make_ref(SeqCont::seq(next, std::move(ctx.next)), list, pos + 1); - } else if (pos + 1 == sz) { - ctx.next = next; - } else { - ctx.next = td::make_ref(next, list, pos + 1); - } - return list->at(pos); -} - -Ref ListCont::run_modify(IntCtx& ctx) { - auto sz = list->size(); - if (pos >= sz) { - return std::move(ctx.next); - } - auto cur = list->at(pos++); - if (ctx.next.not_null()) { - next = SeqCont::seq(next, std::move(ctx.next)); - } - if (pos == sz) { - ctx.next = std::move(next); - } else { - ctx.next = self(); - } - return cur; -} - -bool ListCont::dump(std::ostream& os, const IntCtx& ctx) const { - std::string dict_name = list->get_dict_name(ctx); - if (!dict_name.empty()) { - os << "[in " << dict_name << ":] "; - } - std::size_t sz = list->size(), i, a = (pos >= 16 ? pos - 16 : 0), b = std::min(pos + 16, sz); - if (a > 0) { - os << "... "; - } - for (i = a; i < b; i++) { - if (i == pos) { - os << "**HERE** "; - } - list->at(i)->print_name(os, ctx); - os << ' '; - } - if (b < sz) { - os << "..."; - } - os << std::endl; - return true; -} - // // DictEntry // @@ -167,15 +34,49 @@ DictEntry::DictEntry(CtxWordFunc func, bool _act) : def(Ref{true, std:: DictEntry::DictEntry(CtxTailWordFunc func, bool _act) : def(Ref{true, std::move(func)}), active(_act) { } +DictEntry DictEntry::create_from(vm::StackEntry se) { + if (se.is_tuple()) { + auto& tuple = *se.as_tuple(); + if (tuple.size() == 1) { + auto def = tuple[0].as_object(); + if (def.not_null()) { + return DictEntry{std::move(def), true}; + } + } + } else { + auto def = std::move(se).as_object(); + if (def.not_null()) { + return DictEntry{std::move(def)}; + } + } + return {}; +} + +DictEntry::operator vm::StackEntry() const& { + if (def.is_null()) { + return {}; + } else if (active) { + return vm::make_tuple_ref(vm::StackEntry{vm::from_object, def}); + } else { + return {vm::from_object, def}; + } +} + +DictEntry::operator vm::StackEntry() && { + if (def.is_null()) { + return {}; + } else if (active) { + return vm::make_tuple_ref(vm::StackEntry{vm::from_object, std::move(def)}); + } else { + return {vm::from_object, std::move(def)}; + } +} + // // Dictionary // -DictEntry* Dictionary::lookup(td::Slice name) { - auto it = words_.find(name); - if (it == words_.end()) { - return nullptr; - } - return &it->second; +DictEntry Dictionary::lookup(std::string name) const { + return DictEntry::create_from(words().get(name)); } void Dictionary::def_ctx_word(std::string name, CtxWordFunc func) { @@ -196,26 +97,27 @@ void Dictionary::def_ctx_tail_word(std::string name, CtxTailWordFunc func) { } void Dictionary::def_word(std::string name, DictEntry word) { - auto res = words_.emplace(name, std::move(word)); - LOG_IF(FATAL, !res.second) << "Cannot redefine word: " << name; + auto dict = words(); + dict.set(std::move(name), vm::StackEntry(std::move(word))); + set_words(dict); } -void Dictionary::undef_word(td::Slice name) { - auto it = words_.find(name); - if (it == words_.end()) { - return; +void Dictionary::undef_word(std::string name) { + auto dict = words(); + if (dict.remove(name)) { + set_words(dict); } - words_.erase(it); } bool Dictionary::lookup_def(const FiftCont* cont, std::string* word_ptr) const { if (!cont) { return false; } - for (const auto& entry : words_) { - if (entry.second.get_def().get() == cont) { + for (auto entry : words()) { + auto val = DictEntry::create_from(entry.value()); + if (val.get_def().get() == cont && entry.key().is_string()) { if (word_ptr) { - *word_ptr = entry.first; + *word_ptr = vm::StackEntry(entry.key()).as_string(); } return true; } @@ -223,35 +125,4 @@ bool Dictionary::lookup_def(const FiftCont* cont, std::string* word_ptr) const { return false; } -void interpret_nop(vm::Stack& stack) { -} - -Ref Dictionary::nop_word_def = Ref{true, interpret_nop}; - -// -// functions for wordef -// -Ref pop_exec_token(vm::Stack& stack) { - stack.check_underflow(1); - auto wd_ref = stack.pop().as_object(); - if (wd_ref.is_null()) { - throw IntError{"execution token expected"}; - } - return wd_ref; -} - -Ref pop_word_list(vm::Stack& stack) { - stack.check_underflow(1); - auto wl_ref = stack.pop().as_object(); - if (wl_ref.is_null()) { - throw IntError{"word list expected"}; - } - return wl_ref; -} - -void push_argcount(vm::Stack& stack, int args) { - stack.push_smallint(args); - stack.push({vm::from_object, Dictionary::nop_word_def}); -} - } // namespace fift diff --git a/crypto/fift/Dictionary.h b/crypto/fift/Dictionary.h index 7307cdbe..b24bc742 100644 --- a/crypto/fift/Dictionary.h +++ b/crypto/fift/Dictionary.h @@ -17,115 +17,27 @@ Copyright 2017-2020 Telegram Systems LLP */ #pragma once - -#include -#include - -#include "IntCtx.h" #include "Continuation.h" +#include "HashMap.h" +#include "vm/box.hpp" namespace fift { using td::Ref; +struct IntCtx; + /* * * WORD CLASSES * */ -typedef std::function StackWordFunc; -typedef std::function CtxWordFunc; - -class StackWord : public FiftCont { - StackWordFunc f; - - public: - StackWord(StackWordFunc _f) : f(std::move(_f)) { - } - ~StackWord() override = default; - Ref run_tail(IntCtx& ctx) const override; -}; - -class CtxWord : public FiftCont { - CtxWordFunc f; - - public: - CtxWord(CtxWordFunc _f) : f(std::move(_f)) { - } - ~CtxWord() override = default; - Ref run_tail(IntCtx& ctx) const override; -}; - -typedef std::function(IntCtx&)> CtxTailWordFunc; - -class CtxTailWord : public FiftCont { - CtxTailWordFunc f; - - public: - CtxTailWord(CtxTailWordFunc _f) : f(std::move(_f)) { - } - ~CtxTailWord() override = default; - Ref run_tail(IntCtx& ctx) const override; -}; - -class WordList : public FiftCont { - std::vector> list; - - public: - ~WordList() override = default; - WordList() = default; - WordList(std::vector>&& _list); - WordList(const std::vector>& _list); - WordList& push_back(Ref word_def); - WordList& push_back(FiftCont& wd); - Ref run_tail(IntCtx& ctx) const override; - void close(); - bool is_list() const override { - return true; - } - long long list_size() const override { - return (long long)list.size(); - } - std::size_t size() const { - return list.size(); - } - const Ref& at(std::size_t idx) const { - return list.at(idx); - } - const Ref* get_list() const override { - return list.data(); - } - WordList& append(const std::vector>& other); - WordList& append(const Ref* begin, const Ref* end); - WordList* make_copy() const override { - return new WordList(list); - } - bool dump(std::ostream& os, const IntCtx& ctx) const override; -}; - -class ListCont : public FiftCont { - Ref next; - Ref list; - std::size_t pos; - - public: - ListCont(Ref nxt, Ref wl, std::size_t p = 0) : next(std::move(nxt)), list(std::move(wl)), pos(p) { - } - ~ListCont() override = default; - Ref run_tail(IntCtx& ctx) const override; - Ref run_modify(IntCtx& ctx) override; - Ref up() const override { - return next; - } - bool dump(std::ostream& os, const IntCtx& ctx) const override; -}; - class DictEntry { Ref def; - bool active; + bool active{false}; public: - DictEntry() = delete; + DictEntry() = default; DictEntry(const DictEntry& ref) = default; DictEntry(DictEntry&& ref) = default; DictEntry(Ref _def, bool _act = false) : def(std::move(_def)), active(_act) { @@ -137,6 +49,9 @@ class DictEntry { //DictEntry(std::vector>&& word_list); DictEntry& operator=(const DictEntry&) = default; DictEntry& operator=(DictEntry&&) = default; + static DictEntry create_from(vm::StackEntry se); + explicit operator vm::StackEntry() const&; + explicit operator vm::StackEntry() &&; Ref get_def() const& { return def; } @@ -146,16 +61,17 @@ class DictEntry { bool is_active() const { return active; } + bool empty() const { + return def.is_null(); + } + explicit operator bool() const { + return def.not_null(); + } + bool operator!() const { + return def.is_null(); + } }; -/* -DictEntry::DictEntry(const std::vector>& word_list) : def(Ref{true, word_list}) { -} - -DictEntry::DictEntry(std::vector>&& word_list) : def(Ref{true, std::move(word_list)}) { -} -*/ - /* * * DICTIONARIES @@ -164,37 +80,52 @@ DictEntry::DictEntry(std::vector>&& word_list) : def(Ref class Dictionary { public: - DictEntry* lookup(td::Slice name); + Dictionary() : box_(true) { + } + Dictionary(Ref box) : box_(std::move(box)) { + } + Dictionary(Ref hmap) : box_(true, vm::from_object, std::move(hmap)) { + } + + DictEntry lookup(std::string name) const; void def_ctx_word(std::string name, CtxWordFunc func); void def_ctx_tail_word(std::string name, CtxTailWordFunc func); void def_active_word(std::string name, CtxWordFunc func); void def_stack_word(std::string name, StackWordFunc func); void def_word(std::string name, DictEntry word); - void undef_word(td::Slice name); + void undef_word(std::string name); bool lookup_def(const FiftCont* cont, std::string* word_ptr = nullptr) const; bool lookup_def(Ref cont, std::string* word_ptr = nullptr) const { return lookup_def(cont.get(), word_ptr); } auto begin() const { - return words_.begin(); + return words().begin(); } auto end() const { - return words_.end(); + return words().end(); + } + HashmapKeeper words() const { + if (box_->empty()) { + return {}; + } else { + return box_->get().as_object(); + } + } + Ref get_box() const { + return box_; + } + void set_words(Ref new_words) { + box_->set(vm::StackEntry{vm::from_object, std::move(new_words)}); + } + bool operator==(const Dictionary& other) const { + return box_ == other.box_; + } + bool operator!=(const Dictionary& other) const { + return box_ != other.box_; } - static Ref nop_word_def; - private: - std::map> words_; + Ref box_; }; -/* - * - * AUX FUNCTIONS FOR WORD DEFS - * - */ - -Ref pop_exec_token(vm::Stack& stack); -Ref pop_word_list(vm::Stack& stack); -void push_argcount(vm::Stack& stack, int args); } // namespace fift diff --git a/crypto/fift/Fift.cpp b/crypto/fift/Fift.cpp index ef265657..85511a38 100644 --- a/crypto/fift/Fift.cpp +++ b/crypto/fift/Fift.cpp @@ -17,7 +17,7 @@ Copyright 2017-2020 Telegram Systems LLP */ #include "Fift.h" - +#include "IntCtx.h" #include "words.h" #include "td/utils/PathView.h" @@ -51,7 +51,7 @@ td::Result Fift::interpret_istream(std::istream& stream, std::string curren td::Result Fift::do_interpret(IntCtx& ctx, bool is_interactive) { ctx.ton_db = &config_.ton_db; ctx.source_lookup = &config_.source_lookup; - ctx.dictionary = &config_.dictionary; + ctx.dictionary = ctx.main_dictionary = ctx.context = config_.dictionary; ctx.output_stream = config_.output_stream; ctx.error_stream = config_.error_stream; if (!ctx.output_stream) { @@ -71,7 +71,7 @@ td::Result Fift::do_interpret(IntCtx& ctx, bool is_interactive) { ctx.top_ctx(); ctx.clear_error(); ctx.stack.clear(); - ctx.load_next_line(); + ctx.parser->load_next_line(); continue; } } diff --git a/crypto/fift/HashMap.cpp b/crypto/fift/HashMap.cpp new file mode 100644 index 00000000..a4ca0e9b --- /dev/null +++ b/crypto/fift/HashMap.cpp @@ -0,0 +1,371 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#include "HashMap.h" +#include "td/utils/Random.h" +#include "IntCtx.h" + +namespace fift { +using td::Ref; + +DictKey::DictKey(vm::StackEntry se) { + auto tp = tp_ = se.type(); + switch (tp) { + case Type::t_int: + ref_ = se.as_int(); + break; + case Type::t_atom: + ref_ = se.as_atom(); + break; + case Type::t_string: + ref_ = se.as_string_ref(); + break; + case Type::t_bytes: + ref_ = se.as_bytes_ref(); + break; + case Type::t_null: + break; + default: + throw IntError{"unsupported key type"}; + } + compute_hash(); +} + +DictKey::operator vm::StackEntry() const& { + switch (tp_) { + case Type::t_int: + return value(); + case Type::t_atom: + return value(); + case Type::t_string: + case Type::t_bytes: + return {value>(), tp_ == Type::t_bytes}; + default: + return {}; + } +} + +DictKey::operator vm::StackEntry() && { + switch (tp_) { + case Type::t_int: + return move_value(); + case Type::t_atom: + return move_value(); + case Type::t_string: + case Type::t_bytes: + return {move_value>(), tp_ == Type::t_bytes}; + default: + return {}; + } +} + +std::ostream& operator<<(std::ostream& os, const DictKey& dkey) { + return os << vm::StackEntry(dkey).to_string(); +} + +int DictKey::cmp_internal(const DictKey& other) const { + if (tp_ != other.tp_) { + return tp_ < other.tp_ ? -1 : 1; + } + switch (tp_) { + case Type::t_int: + return td::cmp(value(), other.value()); + case Type::t_atom: { + int u = value()->index(), v = other.value()->index(); + return u == v ? 0 : (u < v ? -1 : 1); + } + case Type::t_string: + case Type::t_bytes: + return value>()->compare(*other.value>()); + default: + return 0; + } +} + +int DictKey::cmp(const DictKey& other) const { + if (hash_ < other.hash_) { + return -1; + } else if (hash_ > other.hash_) { + return 1; + } else { + return cmp_internal(other); + } +} + +DictKey::keyhash_t DictKey::compute_str_hash(DictKey::keyhash_t h, const char* str, std::size_t len) { + const char* end = str + len; + while (str < end) { + h = h * StrHash + (unsigned char)*str++; + } + return h; +} + +DictKey::keyhash_t DictKey::compute_int_hash(td::AnyIntView<> x) { + keyhash_t h = IntHash0; + for (int i = 0; i < x.size(); i++) { + h = h * MixConst3 + x.digits[i]; + } + return h * MixConst4; +} + +DictKey::keyhash_t DictKey::compute_hash() { + switch (tp_) { + case Type::t_int: + return hash_ = compute_int_hash(value()->as_any_int()); + case Type::t_atom: + return hash_ = value()->index() * MixConst1 + MixConst2; + case Type::t_string: + case Type::t_bytes: { + auto ref = value>(); + return hash_ = compute_str_hash(tp_, ref->data(), ref->size()); + } + default: + return hash_ = 0; + } +} + +const Hashmap* Hashmap::lookup_key_aux(const Hashmap* root, const DictKey& key) { + if (key.is_null()) { + return nullptr; + } + while (root) { + int r = key.cmp(root->key_); + if (!r) { + break; + } + root = (r < 0 ? root->left_.get() : root->right_.get()); + } + return root; +} + +Ref Hashmap::lookup_key(Ref root, const DictKey& key) { + return Ref(lookup_key_aux(root.get(), key)); +} + +vm::StackEntry Hashmap::get_key(Ref root, const DictKey& key) { + auto node = lookup_key_aux(root.get(), key); + if (node) { + return node->value_; + } else { + return {}; + } +} + +std::pair, vm::StackEntry> Hashmap::get_remove_key(Ref root, const DictKey& key) { + if (root.is_null() || key.is_null()) { + return {std::move(root), {}}; + } + vm::StackEntry val; + auto res = root->get_remove_internal(key, val); + if (val.is_null()) { + return {std::move(root), {}}; + } else { + return {std::move(res), std::move(val)}; + } +} + +Ref Hashmap::remove_key(Ref root, const DictKey& key) { + if (root.is_null() || key.is_null()) { + return root; + } + vm::StackEntry val; + auto res = root->get_remove_internal(key, val); + if (val.is_null()) { + return root; + } else { + return res; + } +} + +Ref Hashmap::get_remove_internal(const DictKey& key, vm::StackEntry& val) const { + int r = key.cmp(key_); + if (!r) { + val = value_; + return merge(left_, right_); + } else if (r < 0) { + if (left_.is_null()) { + return {}; + } else { + auto res = left_->get_remove_internal(key, val); + if (val.is_null()) { + return res; + } else { + return td::make_ref(key_, value_, std::move(res), right_, y_); + } + } + } else if (right_.is_null()) { + return {}; + } else { + auto res = right_->get_remove_internal(key, val); + if (val.is_null()) { + return res; + } else { + return td::make_ref(key_, value_, left_, std::move(res), y_); + } + } +} + +Ref Hashmap::merge(Ref a, Ref b) { + if (a.is_null()) { + return b; + } else if (b.is_null()) { + return a; + } else if (a->y_ > b->y_) { + auto& aa = a.write(); + aa.right_ = merge(std::move(aa.right_), std::move(b)); + return a; + } else { + auto& bb = b.write(); + bb.left_ = merge(std::move(a), std::move(bb.left_)); + return b; + } +} + +Ref Hashmap::set(Ref root, const DictKey& key, vm::StackEntry value) { + if (!key.is_null() && !replace(root, key, value) && !value.is_null()) { + insert(root, key, value, new_y()); + } + return root; +} + +bool Hashmap::replace(Ref& root, const DictKey& key, vm::StackEntry value) { + if (root.is_null() || key.is_null()) { + return false; + } + if (value.is_null()) { + auto res = root->get_remove_internal(key, value); + if (value.is_null()) { + return false; + } else { + root = std::move(res); + return true; + } + } + bool found = false; + auto res = root->replace_internal(key, std::move(value), found); + if (found) { + root = std::move(res); + } + return found; +} + +Ref Hashmap::replace_internal(const DictKey& key, const vm::StackEntry& value, bool& found) const { + int r = key.cmp(key_); + if (!r) { + found = true; + return td::make_ref(key_, value, left_, right_, y_); + } else if (r < 0) { + if (left_.is_null()) { + found = false; + return {}; + } + auto res = left_->replace_internal(key, value, found); + if (!found) { + return {}; + } + return td::make_ref(key_, value_, std::move(res), right_, y_); + } else { + if (right_.is_null()) { + found = false; + return {}; + } + auto res = right_->replace_internal(key, value, found); + if (!found) { + return {}; + } + return td::make_ref(key_, value_, left_, std::move(res), y_); + } +} + +void Hashmap::insert(Ref& root, const DictKey& key, vm::StackEntry value, long long y) { + if (root.is_null()) { + root = td::make_ref(key, std::move(value), empty(), empty(), y); + return; + } + if (root->y_ <= y) { + auto res = split(std::move(root), key); + root = td::make_ref(key, std::move(value), std::move(res.first), std::move(res.second), y); + return; + } + int r = key.cmp(root->key_); + CHECK(r); + insert(r < 0 ? root.write().left_ : root.write().right_, key, std::move(value), y); +} + +std::pair, Ref> Hashmap::split(Ref root, const DictKey& key, bool cmpv) { + if (root.is_null()) { + return {{}, {}}; + } + int r = key.cmp(root->key_); + if (r < (int)cmpv) { + if (root->left_.is_null()) { + return {{}, std::move(root)}; + } + auto res = split(root->left_, key, cmpv); + return {std::move(res.first), + td::make_ref(root->key_, root->value_, std::move(res.second), root->right_, root->y_)}; + } else { + if (root->right_.is_null()) { + return {std::move(root), {}}; + } + auto res = split(root->right_, key, cmpv); + return {td::make_ref(root->key_, root->value_, root->left_, std::move(res.first), root->y_), + std::move(res.second)}; + } +} + +long long Hashmap::new_y() { + return td::Random::fast_uint64(); +} + +bool HashmapIterator::unwind(Ref root) { + if (root.is_null()) { + return false; + } + while (true) { + auto left = root->lr(down_); + if (left.is_null()) { + cur_ = std::move(root); + return true; + } + stack_.push_back(std::move(root)); + root = std::move(left); + } +} + +bool HashmapIterator::next() { + if (cur_.not_null()) { + cur_ = cur_->rl(down_); + if (cur_.not_null()) { + while (true) { + auto left = cur_->lr(down_); + if (left.is_null()) { + return true; + } + stack_.push_back(std::move(cur_)); + cur_ = std::move(left); + } + } + } + if (stack_.empty()) { + return false; + } + cur_ = std::move(stack_.back()); + stack_.pop_back(); + return true; +} + +} // namespace fift diff --git a/crypto/fift/HashMap.h b/crypto/fift/HashMap.h new file mode 100644 index 00000000..d3d9ffbe --- /dev/null +++ b/crypto/fift/HashMap.h @@ -0,0 +1,306 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#pragma once +#include "common/refcnt.hpp" +#include "vm/stack.hpp" +#include "vm/atom.h" + +namespace fift { + +using td::Ref; +using td::RefAny; + +class DictKey { + public: + typedef vm::StackEntry::Type Type; + typedef unsigned long long keyhash_t; + + private: + RefAny ref_; + Type tp_ = Type::t_null; + keyhash_t hash_ = 0; + + static constexpr keyhash_t IntHash0 = 0xce6ab89d724409ed, MixConst1 = 0xcd5c126501510979, + MixConst2 = 0xb8f44d7fd6274ad1, MixConst3 = 0xd08726ea2422e405, + MixConst4 = 0x6407d2aeb5039dfb, StrHash = 0x93ff128344add06d; + keyhash_t compute_hash(); + static keyhash_t compute_str_hash(DictKey::keyhash_t h, const char* str, std::size_t len); + static keyhash_t compute_int_hash(td::AnyIntView<> x); + int cmp_internal(const DictKey& other) const; + template + Ref value() const { + return Ref{td::static_cast_ref(), ref_}; + } + template + Ref move_value() { + return Ref{td::static_cast_ref(), std::move(ref_)}; + } + + public: + DictKey() : ref_(), tp_(Type::t_null) { + } + DictKey(const DictKey& other) = default; + DictKey(DictKey&& other) = default; + DictKey& operator=(const DictKey& other) = default; + DictKey& operator=(DictKey&& other) = default; + DictKey(Ref atom_ref) : ref_(std::move(atom_ref)), tp_(Type::t_atom) { + compute_hash(); + } + DictKey(td::RefInt256 int_ref) : ref_(std::move(int_ref)), tp_(Type::t_int) { + compute_hash(); + } + explicit DictKey(vm::StackEntry se); + DictKey(std::string str, bool bytes = false) : ref_(), tp_(bytes ? Type::t_bytes : Type::t_string) { + ref_ = Ref>{true, std::move(str)}; + compute_hash(); + } + Type type() const { + return tp_; + } + void swap(DictKey& other) { + ref_.swap(other.ref_); + std::swap(tp_, other.tp_); + } + + operator vm::StackEntry() const&; + operator vm::StackEntry() &&; + int cmp(const DictKey& other) const; + bool operator==(const DictKey& other) const { + return hash_ == other.hash_ && !cmp_internal(other); + } + bool operator!=(const DictKey& other) const { + return hash_ != other.hash_ || cmp_internal(other); + } + bool operator<(const DictKey& other) const { + return hash_ < other.hash_ || (hash_ == other.hash_ && cmp_internal(other) < 0); + } + bool is_null() const { + return tp_ == Type::t_null; + } + bool is_string() const { + return tp_ == Type::t_string; + } +}; + +std::ostream& operator<<(std::ostream& os, const DictKey& dkey); + +class Hashmap : public td::CntObject { + DictKey key_; + vm::StackEntry value_; + Ref left_; + Ref right_; + long long y_; + + public: + Hashmap(DictKey key, vm::StackEntry value, Ref left, Ref right, long long y) + : key_(std::move(key)), value_(std::move(value)), left_(std::move(left)), right_(std::move(right)), y_(y) { + } + Hashmap(const Hashmap& other) = default; + Hashmap(Hashmap&& other) = default; + virtual ~Hashmap() { + } + Hashmap* make_copy() const override { + return new Hashmap(*this); + } + const DictKey& key() const& { + return key_; + } + DictKey key() && { + return std::move(key_); + } + const vm::StackEntry& value() const& { + return value_; + } + vm::StackEntry value() && { + return std::move(value_); + } + Ref left() const { + return left_; + } + Ref right() const { + return right_; + } + Ref lr(bool branch) const { + return branch ? right_ : left_; + } + Ref rl(bool branch) const { + return branch ? left_ : right_; + } + static Ref lookup_key(Ref root, const DictKey& key); + template + static Ref lookup(Ref root, Args&&... args) { + return lookup_key(std::move(root), DictKey{std::forward(args)...}); + } + static vm::StackEntry get_key(Ref root, const DictKey& key); + template + static vm::StackEntry get(Ref root, Args&&... args) { + return get_key(std::move(root), DictKey{std::forward(args)...}); + } + static Ref remove_key(Ref root, const DictKey& key); + template + static Ref remove(Ref root, Args&&... args) { + return remove_key(std::move(root), DictKey{std::forward(args)...}); + } + static std::pair, vm::StackEntry> get_remove_key(Ref root, const DictKey& key); + template + static std::pair, vm::StackEntry> get_remove(Ref root, Args&&... args) { + return get_remove_key(std::move(root), DictKey{std::forward(args)...}); + } + static Ref set(Ref root, const DictKey& key, vm::StackEntry value); + static bool replace(Ref& root, const DictKey& key, vm::StackEntry value); + static std::pair, Ref> split(Ref root, const DictKey& key, bool eq_left = false); + static Ref empty() { + return {}; + } + + private: + static Ref merge(Ref a, Ref b); + static const Hashmap* lookup_key_aux(const Hashmap* root, const DictKey& key); + Ref get_remove_internal(const DictKey& key, vm::StackEntry& val) const; + Ref replace_internal(const DictKey& key, const vm::StackEntry& value, bool& found) const; + static void insert(Ref& root, const DictKey& key, vm::StackEntry value, long long y); + static long long new_y(); +}; + +struct HashmapIdx { + Ref& root_; + DictKey idx_; + template + HashmapIdx(Ref& root, Args&&... args) : root_(root), idx_(std::forward(args)...) { + } + operator vm::StackEntry() const { + return Hashmap::get(root_, idx_); + } + template + HashmapIdx& operator=(T&& value) { + root_ = Hashmap::set(root_, idx_, vm::StackEntry(std::forward(value))); + return *this; + } +}; + +class HashmapIterator { + std::vector> stack_; + Ref cur_; + const bool down_{false}; + bool unwind(Ref root); + + public: + HashmapIterator() = default; + HashmapIterator(Ref root, bool down = false) : down_(down) { + unwind(std::move(root)); + } + const Hashmap& operator*() const { + return *cur_; + } + const Hashmap* operator->() const { + return cur_.get(); + } + bool eof() { + return cur_.is_null(); + } + bool next(); + bool operator<(const HashmapIterator& other) const { + if (other.cur_.is_null()) { + return cur_.not_null(); + } else if (cur_.is_null()) { + return false; + } else { + return cur_->key().cmp(other.cur_->key()) * (down_ ? -1 : 1) < 0; + } + } + bool operator==(const HashmapIterator& other) const { + return other.cur_.is_null() ? cur_.is_null() : (cur_.not_null() && cur_->key() == other.cur_->key()); + } + bool operator!=(const HashmapIterator& other) const { + return other.cur_.is_null() ? cur_.not_null() : (cur_.is_null() || cur_->key() != other.cur_->key()); + } + HashmapIterator& operator++() { + next(); + return *this; + } +}; + +struct HashmapKeeper { + Ref root; + HashmapKeeper() = default; + HashmapKeeper(Ref _root) : root(std::move(_root)) { + } + Ref extract() { + return std::move(root); + } + operator Ref() const& { + return root; + } + operator Ref() && { + return std::move(root); + } + template + HashmapIdx operator[](Args&&... args) { + return HashmapIdx{root, DictKey{std::forward(args)...}}; + } + template + vm::StackEntry operator[](Args&&... args) const { + return Hashmap::get(root, DictKey{std::forward(args)...}); + } + vm::StackEntry get_key(const DictKey& key) const { + return Hashmap::get(root, key); + } + template + vm::StackEntry get(Args&&... args) const { + return Hashmap::get(root, DictKey{std::forward(args)...}); + } + vm::StackEntry get_remove_key(const DictKey& key) { + auto res = Hashmap::get_remove_key(root, key); + root = std::move(res.first); + return std::move(res.second); + } + template + vm::StackEntry get_remove(Args&&... args) { + return get_remove_key(DictKey{std::forward(args)...}); + } + bool remove_key(const DictKey& key) { + auto res = Hashmap::get_remove(root, key); + root = std::move(res.first); + return !res.second.is_null(); + } + template + bool remove(Args&&... args) { + return remove_key(DictKey{std::forward(args)...}); + } + template + void set(T key, vm::StackEntry value) { + root = Hashmap::set(root, DictKey(key), std::move(value)); + } + template + bool replace(T key, vm::StackEntry value) { + return Hashmap::replace(root, DictKey(key), std::move(value)); + } + HashmapIterator begin(bool reverse = false) const { + return HashmapIterator{root, reverse}; + } + HashmapIterator end() const { + return HashmapIterator{}; + } + HashmapIterator rbegin() const { + return HashmapIterator{root, true}; + } + HashmapIterator rend() const { + return HashmapIterator{}; + } +}; + +} // namespace fift diff --git a/crypto/fift/IntCtx.cpp b/crypto/fift/IntCtx.cpp index 9d623b06..2cac8849 100644 --- a/crypto/fift/IntCtx.cpp +++ b/crypto/fift/IntCtx.cpp @@ -20,7 +20,7 @@ namespace fift { -td::StringBuilder& operator<<(td::StringBuilder& os, const IntCtx& ctx) { +td::StringBuilder& operator<<(td::StringBuilder& os, const ParseCtx& ctx) { if (ctx.include_depth) { return os << ctx.filename << ":" << ctx.line_no << ": "; } else { @@ -28,7 +28,7 @@ td::StringBuilder& operator<<(td::StringBuilder& os, const IntCtx& ctx) { } } -std::ostream& operator<<(std::ostream& os, const IntCtx& ctx) { +std::ostream& operator<<(std::ostream& os, const ParseCtx& ctx) { return os << (PSLICE() << ctx).c_str(); } @@ -67,73 +67,7 @@ void CharClassifier::set_char_class(int c, int cl) { *p = static_cast((*p & ~mask) | cl); } -IntCtx::Savepoint::Savepoint(IntCtx& _ctx, std::string new_filename, std::string new_current_dir, - std::unique_ptr new_input_stream) - : ctx(_ctx) - , old_line_no(_ctx.line_no) - , old_need_line(_ctx.need_line) - , old_filename(_ctx.filename) - , old_current_dir(_ctx.currentd_dir) - , old_input_stream(_ctx.input_stream) - , old_input_stream_holder(std::move(_ctx.input_stream_holder)) - , old_curline(_ctx.str) - , old_curpos(_ctx.input_ptr - _ctx.str.c_str()) - , old_word(_ctx.word) { - ctx.line_no = 0; - ctx.filename = new_filename; - ctx.currentd_dir = new_current_dir; - ctx.input_stream = new_input_stream.get(); - ctx.input_stream_holder = std::move(new_input_stream); - ctx.str = ""; - ctx.input_ptr = 0; - ++(ctx.include_depth); -} - -bool IntCtx::Savepoint::restore(IntCtx& _ctx) { - if (restored || &ctx != &_ctx) { - return false; - } - ctx.line_no = old_line_no; - ctx.need_line = old_need_line; - ctx.filename = old_filename; - ctx.currentd_dir = old_current_dir; - ctx.input_stream = old_input_stream; - ctx.input_stream_holder = std::move(old_input_stream_holder); - ctx.str = old_curline; - ctx.input_ptr = ctx.str.c_str() + old_curpos; - ctx.word = old_word; - --(ctx.include_depth); - return restored = true; -} - -bool IntCtx::enter_ctx(std::string new_filename, std::string new_current_dir, - std::unique_ptr new_input_stream) { - if (!new_input_stream) { - return false; - } - ctx_save_stack.emplace_back(*this, std::move(new_filename), std::move(new_current_dir), std::move(new_input_stream)); - return true; -} - -bool IntCtx::leave_ctx() { - if (ctx_save_stack.empty()) { - return false; - } - bool ok = ctx_save_stack.back().restore(*this); - ctx_save_stack.pop_back(); - return ok; -} - -bool IntCtx::top_ctx() { - while (!ctx_save_stack.empty()) { - if (!leave_ctx()) { - return false; - } - } - return true; -} - -bool IntCtx::load_next_line() { +bool ParseCtx::load_next_line() { if (!std::getline(*input_stream, str)) { return false; } @@ -145,11 +79,11 @@ bool IntCtx::load_next_line() { return true; } -bool IntCtx::is_sb() const { +bool ParseCtx::is_sb() const { return !eof() && line_no == 1 && *input_ptr == '#' && input_ptr[1] == '!'; } -td::Slice IntCtx::scan_word_to(char delim, bool err_endl) { +td::Slice ParseCtx::scan_word_to(char delim, bool err_endl) { load_next_line_ifreq(); auto ptr = input_ptr; while (*ptr && *ptr != delim) { @@ -167,7 +101,7 @@ td::Slice IntCtx::scan_word_to(char delim, bool err_endl) { } } -td::Slice IntCtx::scan_word() { +td::Slice ParseCtx::scan_word() { skipspc(true); auto ptr = input_ptr; while (*ptr && *ptr != ' ' && *ptr != '\t' && *ptr != '\r') { @@ -179,7 +113,7 @@ td::Slice IntCtx::scan_word() { return td::Slice{ptr, ptr2}; } -td::Slice IntCtx::scan_word_ext(const CharClassifier& classifier) { +td::Slice ParseCtx::scan_word_ext(const CharClassifier& classifier) { skipspc(true); auto ptr = input_ptr; while (*ptr && *ptr != '\r' && *ptr != '\n') { @@ -196,7 +130,7 @@ td::Slice IntCtx::scan_word_ext(const CharClassifier& classifier) { return td::Slice{ptr, input_ptr}; } -void IntCtx::skipspc(bool skip_eol) { +void ParseCtx::skipspc(bool skip_eol) { do { while (*input_ptr == ' ' || *input_ptr == '\t' || *input_ptr == '\r') { ++input_ptr; @@ -207,6 +141,45 @@ void IntCtx::skipspc(bool skip_eol) { } while (load_next_line()); } +bool IntCtx::enter_ctx(std::unique_ptr new_parser) { + if (!new_parser) { + return false; + } + if (parser) { + parser_save_stack.push_back(std::move(parser)); + } + parser = std::move(new_parser); + return true; +} + +bool IntCtx::enter_ctx(std::string new_filename, std::string new_current_dir, + std::unique_ptr new_input_stream) { + if (!new_input_stream) { + return false; + } else { + return enter_ctx( + std::make_unique(std::move(new_input_stream), new_filename, new_current_dir, include_depth() + 1)); + } +} + +bool IntCtx::leave_ctx() { + if (parser_save_stack.empty()) { + return false; + } else { + parser = std::move(parser_save_stack.back()); + parser_save_stack.pop_back(); + return true; + } +} + +bool IntCtx::top_ctx() { + if (!parser_save_stack.empty()) { + parser = std::move(parser_save_stack[0]); + parser_save_stack.clear(); + } + return true; +} + void IntCtx::check_compile() const { if (state <= 0) { throw IntError{"compilation mode only"}; @@ -283,15 +256,20 @@ td::Result IntCtx::get_result() { } } +std::ostream& ParseCtx::show_context(std::ostream& os) const { + if (include_depth && line_no) { + os << filename << ":" << line_no << ":\t"; + } + if (!word.empty()) { + os << word << ":"; + } + return os; +} + td::Status IntCtx::add_error_loc(td::Status err) const { - if (err.is_error()) { + if (err.is_error() && parser) { std::ostringstream os; - if (include_depth && line_no) { - os << filename << ":" << line_no << ":\t"; - } - if (!word.empty()) { - os << word << ":"; - } + parser->show_context(os); return err.move_as_error_prefix(os.str()); } else { return err; diff --git a/crypto/fift/IntCtx.h b/crypto/fift/IntCtx.h index 807a5d01..5d574ae5 100644 --- a/crypto/fift/IntCtx.h +++ b/crypto/fift/IntCtx.h @@ -18,8 +18,8 @@ */ #pragma once -#include "crypto/vm/db/TonDb.h" // FIXME #include "crypto/vm/stack.hpp" +#include "crypto/vm/box.hpp" #include "crypto/common/bitstring.h" #include "td/utils/Status.h" @@ -32,6 +32,11 @@ #include #include +namespace vm { +class TonDbImpl; // from crypto/vm/db/TonDb.h +using TonDb = std::unique_ptr; +} // namespace vm + namespace fift { class Dictionary; class SourceLookup; @@ -68,71 +73,36 @@ class CharClassifier { } }; -struct IntCtx { - vm::Stack stack; - Ref next, exc_handler; - Ref exc_cont, exc_next; - int state{0}; +struct ParseCtx { int include_depth{0}; int line_no{0}; - int exit_code{0}; - td::Status error; bool need_line{true}; std::string filename; std::string currentd_dir; std::istream* input_stream{nullptr}; std::unique_ptr input_stream_holder; - std::ostream* output_stream{nullptr}; - std::ostream* error_stream{nullptr}; - - vm::TonDb* ton_db{nullptr}; - Dictionary* dictionary{nullptr}; - SourceLookup* source_lookup{nullptr}; - int* now{nullptr}; std::string word; private: std::string str; const char* input_ptr = nullptr; - class Savepoint { - IntCtx& ctx; - int old_line_no; - bool old_need_line; - bool restored{false}; - std::string old_filename; - std::string old_current_dir; - std::istream* old_input_stream; - std::unique_ptr old_input_stream_holder; - std::string old_curline; - std::ptrdiff_t old_curpos; - std::string old_word; - - public: - Savepoint(IntCtx& _ctx, std::string new_filename, std::string new_current_dir, - std::unique_ptr new_input_stream); - bool restore(IntCtx& _ctx); - }; - - std::vector ctx_save_stack; - public: - IntCtx() = default; - IntCtx(std::istream& _istream, std::string _filename, std::string _curdir = "", int _depth = 0) + ParseCtx() = default; + ParseCtx(std::istream& _istream, std::string _filename, std::string _curdir = "", int _depth = 0) : include_depth(_depth) , filename(std::move(_filename)) , currentd_dir(std::move(_curdir)) , input_stream(&_istream) { } - - operator vm::Stack&() { - return stack; + ParseCtx(std::unique_ptr _istream_ptr, std::string _filename, std::string _curdir = "", int _depth = 0) + : include_depth(_depth) + , filename(std::move(_filename)) + , currentd_dir(std::move(_curdir)) + , input_stream(_istream_ptr.get()) + , input_stream_holder(std::move(_istream_ptr)) { } - bool enter_ctx(std::string new_filename, std::string new_current_dir, std::unique_ptr new_input_stream); - bool leave_ctx(); - bool top_ctx(); - td::Slice scan_word_to(char delim, bool err_endl = true); td::Slice scan_word(); td::Slice scan_word_ext(const CharClassifier& classifier); @@ -165,6 +135,50 @@ struct IntCtx { bool is_sb() const; + std::ostream& show_context(std::ostream& os) const; +}; + +struct IntCtx { + vm::Stack stack; + Ref next, exc_handler; + Ref exc_cont, exc_next; + int state{0}; + int exit_code{0}; + td::Status error; + + std::unique_ptr parser; + std::vector> parser_save_stack; + + std::ostream* output_stream{nullptr}; // move to OutCtx? + std::ostream* error_stream{nullptr}; + + vm::TonDb* ton_db{nullptr}; + SourceLookup* source_lookup{nullptr}; + int* now{nullptr}; + + Dictionary dictionary, main_dictionary, context; + + public: + IntCtx() = default; + IntCtx(std::istream& _istream, std::string _filename, std::string _curdir = "", int _depth = 0) { + parser = std::make_unique(_istream, _filename, _curdir, _depth); + } + IntCtx(std::unique_ptr _istream, std::string _filename, std::string _curdir = "", int _depth = 0) { + parser = std::make_unique(std::move(_istream), _filename, _curdir, _depth); + } + + bool enter_ctx(std::unique_ptr new_ctx); + bool enter_ctx(std::string new_filename, std::string new_current_dir, std::unique_ptr new_input_stream); + bool leave_ctx(); + bool top_ctx(); + int include_depth() const { + return parser ? parser->include_depth : -1; + } + + operator vm::Stack &() { + return stack; + } + void clear() { state = 0; stack.clear(); @@ -194,6 +208,6 @@ struct IntCtx { td::Result run(Ref cont); }; -td::StringBuilder& operator<<(td::StringBuilder& os, const IntCtx& ctx); -std::ostream& operator<<(std::ostream& os, const IntCtx& ctx); +td::StringBuilder& operator<<(td::StringBuilder& os, const ParseCtx& ctx); +std::ostream& operator<<(std::ostream& os, const ParseCtx& ctx); } // namespace fift diff --git a/crypto/fift/lib/Asm.fif b/crypto/fift/lib/Asm.fif index 01fb8dd0..8898d3fe 100644 --- a/crypto/fift/lib/Asm.fif +++ b/crypto/fift/lib/Asm.fif @@ -1,12 +1,14 @@ library TVM_Asm // simple TVM Assembler +namespace Asm +Asm definitions variable @atend variable @was-split false @was-split ! { "not in asm context" abort } @atend ! { `normal eq? not abort"must be terminated by }>" } : @normal? -{ @atend @ 1 { @atend ! @normal? } does @atend ! } : @pushatend -{ @pushatend { }> b> } : }>c @@ -1219,7 +1221,10 @@ variable asm-mode 1 asm-mode ! { 1 'nop does swap 0 (create) } : @declglobvar { @proccnt @ 1+ dup @proccnt ! 1 @declproc } : @newproc { @gvarcnt @ 1+ dup @gvarcnt ! @declglobvar } : @newglobvar -{ 0 =: main @proclist null! @proccnt 0! @gvarcnt 0! +variable @oldcurrent variable @oldctx +Fift-wordlist dup @oldcurrent ! @oldctx ! +{ current@ @oldcurrent ! context@ @oldctx ! Asm definitions + 0 =: main @proclist null! @proccnt 0! @gvarcnt 0! { bl word @newproc } : NEWPROC { bl word dup (def?) ' drop ' @newproc cond } : DECLPROC { bl word dup find @@ -1293,6 +1298,7 @@ variable asm-mode 1 asm-mode ! } while drop @proclist null! @procinfo null! @proccnt 0! @procdict dup @ swap null! + @oldctx @ context! @oldcurrent @ current! } : }END forget @proclist forget @proccnt { }END <{ SETCP0 swap @procdictkeylen DICTPUSHCONST DICTIGETJMPZ 11 THROWARG }> } : }END> @@ -1319,3 +1325,8 @@ forget @proclist forget @proccnt { spec } : hash>libref // ( c -- c' ) { hash hash>libref } : >libref + +Fift definitions Asm +' <{ : <{ +' PROGRAM{ : PROGRAM{ +Fift diff --git a/crypto/fift/lib/Disasm.fif b/crypto/fift/lib/Disasm.fif new file mode 100644 index 00000000..a46eb5b2 --- /dev/null +++ b/crypto/fift/lib/Disasm.fif @@ -0,0 +1,141 @@ +library TVM_Disasm +// simple TVM Disassembler +"Lists.fif" include + +variable 'disasm +{ 'disasm @ execute } : disasm // disassemble a slice +// usage: x{74B0} disasm + +variable @dismode @dismode 0! +{ rot over @ and rot xor swap ! } : andxor! +{ -2 0 @dismode andxor! } : stack-disasm // output 's1 s4 XCHG' +{ -2 1 @dismode andxor! } : std-disasm // output 'XCHG s1, s4' +{ -3 2 @dismode andxor! } : show-vm-code +{ -3 0 @dismode andxor! } : hide-vm-code +{ @dismode @ 1 and 0= } : stack-disasm? + +variable @indent @indent 0! +{ ' space @indent @ 2* times } : .indent +{ @indent 1+! } : +indent +{ @indent 1-! } : -indent + +{ " " $pos } : spc-pos +{ dup " " $pos swap "," $pos dup 0< { drop } { + over 0< { nip } { min } cond } cond +} : spc-comma-pos +{ { dup spc-pos 0= } { 1 $| nip } while } : -leading +{ -leading -trailing dup spc-pos dup 0< { + drop dup $len { atom single } { drop nil } cond } { + $| swap atom swap -leading 2 { over spc-comma-pos dup 0>= } { + swap 1+ -rot $| 1 $| nip -leading rot + } while drop tuple + } cond +} : parse-op +{ dup "s-1" $= { drop "s(-1)" true } { + dup "s-2" $= { drop "s(-2)" true } { + dup 1 $| swap "x" $= { nip "x{" swap $+ +"}" true } { + 2drop false } cond } cond } cond +} : adj-op-arg +{ over count over <= { drop } { 2dup [] adj-op-arg { swap []= } { drop } cond } cond } : adj-arg[] +{ 1 adj-arg[] 2 adj-arg[] 3 adj-arg[] + dup first + dup `XCHG eq? { + drop dup count 2 = { tpop swap "s0" , swap , } if } { + dup `LSHIFT eq? { + drop dup count 2 = stack-disasm? and { second `LSHIFT# swap pair } if } { + dup `RSHIFT eq? { + drop dup count 2 = stack-disasm? and { second `RSHIFT# swap pair } if } { + drop + } cond } cond } cond +} : adjust-op + +variable @cp @cp 0! +variable @curop +variable @contX variable @contY variable @cdict + +{ atom>$ type } : .atom +{ dup first .atom dup count 1 > { space 0 over count 2- { 1+ 2dup [] type .", " } swap times 1+ [] type } { drop } cond } : std-show-op +{ 0 over count 1- { 1+ 2dup [] type space } swap times drop first .atom } : stk-show-op +{ @dismode @ 2 and { .indent ."// " @curop @ csr. } if } : .curop? +{ .curop? .indent @dismode @ 1 and ' std-show-op ' stk-show-op cond cr +} : show-simple-op +{ dup 4 u@ 9 = { 8 u@+ swap 15 and 3 << s@ } { + dup 7 u@ 0x47 = { 7 u@+ nip 2 u@+ 7 u@+ -rot 3 << swap sr@ } { + dup 8 u@ 0x8A = { ref@ " cr } : show-cont-op +{ swap scont-swap ":<{" show-cont-bodyx scont-swap + "" show-cont-bodyx .indent ."}>" cr } : show-cont2-op + +{ @contX @ null? { "CONT" show-cont-op } ifnot +} : flush-contX +{ @contY @ null? { scont-swap "CONT" show-cont-op scont-swap } ifnot +} : flush-contY +{ flush-contY flush-contX } : flush-cont +{ @contX @ null? not } : have-cont? +{ @contY @ null? not } : have-cont2? +{ flush-contY @contY ! scont-swap } : save-cont-body + +{ @cdict ! } : save-const-dict +{ @cdict null! } : flush-dict +{ @cdict @ null? not } : have-dict? + +{ flush-cont .indent type .":<{" cr + @curop @ ref@ " cr +} : show-ref-op +{ flush-contY .indent rot type .":<{" cr + @curop @ ref@ " cr +} : show-cont-ref-op +{ flush-cont .indent swap type .":<{" cr + @curop @ ref@+ " cr +} : show-ref2-op + +{ flush-cont first atom>$ dup 5 $| drop "DICTI" $= swap + .indent type ." {" cr +indent @cdict @ @cdict null! unpair + rot { + swap .indent . ."=> <{" cr +indent disasm -indent .indent ."}>" cr true + } swap ' idictforeach ' dictforeach cond drop + -indent .indent ."}" cr +} : show-const-dict-op + +( `PUSHCONT `PUSHREFCONT ) constant @PushContL +( `REPEAT `UNTIL `IF `IFNOT `IFJMP `IFNOTJMP ) constant @CmdC1 +( `IFREF `IFNOTREF `IFJMPREF `IFNOTJMPREF `CALLREF `JMPREF ) constant @CmdR1 +( `DICTIGETJMP `DICTIGETJMPZ `DICTUGETJMP `DICTUGETJMPZ `DICTIGETEXEC `DICTUGETEXEC ) constant @JmpDictL +{ dup first `DICTPUSHCONST eq? { + flush-cont @curop @ get-const-dict save-const-dict show-simple-op } { + dup first @JmpDictL list-member? have-dict? and { + flush-cont show-const-dict-op } { + flush-dict + dup first @PushContL list-member? { + drop @curop @ get-cont-body save-cont-body } { + dup first @CmdC1 list-member? have-cont? and { + flush-contY first atom>$ .curop? show-cont-op } { + dup first @CmdR1 list-member? { + flush-cont first atom>$ dup $len 3 - $| drop .curop? show-ref-op } { + dup first `WHILE eq? have-cont2? and { + drop "WHILE" "}>DO<{" .curop? show-cont2-op } { + dup first `IFELSE eq? have-cont2? and { + drop "IF" "}>ELSE<{" .curop? show-cont2-op } { + dup first dup `IFREFELSE eq? swap `IFELSEREF eq? or have-cont? and { + first `IFREFELSE eq? "IF" "}>ELSE<{" rot .curop? show-cont-ref-op } { + dup first `IFREFELSEREF eq? { + drop "IF" "}>ELSE<{" .curop? show-ref2-op } { + flush-cont show-simple-op + } cond } cond } cond } cond } cond } cond } cond } cond } cond +} : show-op +{ dup @cp @ (vmoplen) dup 0> { 65536 /mod swap sr@+ swap dup @cp @ (vmopdump) parse-op swap s> true } { drop false } cond } : fetch-one-op +{ { fetch-one-op } { swap @curop ! adjust-op show-op } while } : disasm-slice +{ { disasm-slice dup sbitrefs 1- or 0= } { ref@ = -rot <= and } : s-fits? diff --git a/crypto/fift/lib/FiftExt.fif b/crypto/fift/lib/FiftExt.fif new file mode 100644 index 00000000..6ed677d7 --- /dev/null +++ b/crypto/fift/lib/FiftExt.fif @@ -0,0 +1,118 @@ +{ ?dup { 1+ { execute } { 0 swap } cond } + { (number) ?dup 0= abort"-?" 'nop } cond +} : (interpret-prepare) +{ { include-depth 0= (seekeof?) not } { + (word-prefix-find) (interpret-prepare) (execute) + } while +} : interpret +{ ({) + { 0 (seekeof?) abort"no }" (word-prefix-find) (interpret-prepare) (compile) over atom? not } until + (}) swap execute +} : begin-block +{ swap 0 'nop } : end-block +{ { 1 'nop } `{ begin-block } +{ { swap `{ eq? not abort"} without {" swap execute } end-block } +:: } :: { + +// if{ ... }then{ ... }elseif{ ... }then{ ... }else{ ... } +{ eq? not abort"unexpected" } : ?pairs +{ dup `if eq? swap `ifnot eq? over or not abort"without if{" } : if-ifnot? +// cond then ? -- exec +{ { ' if } { ' ifnot } cond rot ({) 0 rot (compile) -rot 1 swap (compile) (}) +} : (make-if) +// cond then else -- exec +{ rot ({) 0 rot (compile) -rot 2 ' cond (compile) (}) +} : (make-cond) +{ `noelse `if begin-block } :: if{ +{ `noelse `ifnot begin-block } :: ifnot{ +{ 1 ' end-block does } : end-block-does +{ { over `else eq? } { + nip rot if-ifnot? ' swap ifnot (make-cond) + } while + swap `noelse ?pairs 0 swap +} : finish-else-chain +{ swap dup if-ifnot? drop `then { + swap `then ?pairs + swap if-ifnot? (make-if) finish-else-chain + } `{ begin-block +} end-block-does :: }then{ +{ swap `{ ?pairs nip + swap `then eq? not abort"without }then{" `else +} : ?else-ok +{ ?else-ok { finish-else-chain } `{ begin-block } end-block-does :: }else{ +{ ?else-ok `if begin-block } end-block-does :: }elseif{ +{ ?else-ok `ifnot begin-block } end-block-does :: }elseifnot{ + +// while{ ... }do{ ... } +{ 2 ' while does } : (make-while) +{ `while begin-block } :: while{ +{ swap `while eq? not abort"without while{" `while-do { + swap `while-do ?pairs (make-while) 0 swap + } `{ begin-block +} end-block-does :: }do{ + +// repeat{ ... }until{ ... } +{ swap ({) 0 rot (compile) 0 rot (compile) (}) 1 ' until does } : (make-until) +{ `repeat begin-block } :: repeat{ +{ swap `repeat eq? not abort"without repeat{" `until { + swap `until ?pairs (make-until) 0 swap + } `{ begin-block +} end-block-does :: }until{ + +// def { ... } instead of { ... } : +{ bl word swap bl word "{" $cmp abort"{ expected" `def { + swap `def ?pairs -rot 3 ' (create) + } `{ begin-block +} : (def) +{ 0 (def) } :: def +{ 1 (def) } :: def:: + +// defrec { ... } instead of recursive { ... } swap ! +{ recursive bl word "{" $cmp abort"{ expected" `defrec { + swap `defrec ?pairs swap ! 0 'nop + } `{ begin-block +} :: defrec + +def .sgn { + if{ ?dup 0= }then{ + ."zero" + }elseif{ 0> }then{ + ."positive" + }else{ + ."negative" + } + cr +} +// equivalent to: { ?dup 0= { ."zero" } { 0> { ."positive" } { ."negative" } cond } cond cr } : .sgn + +defrec fact { + if{ dup }then{ + dup 1- fact * + }else{ + drop 1 + } +} +// equivalent to: recursive fact { dup { dup 1- fact * } { drop 1 } cond } swap ! + +// [[ ... ]] computes arbitrary constants inside definitions +// { [[ 5 dup * ]] + } : add25 +// is equivalent to +// { 25 + } : add25 +{ "without [[" abort } box constant ']] +{ ']] @ execute } : ]] +{ { ']] @ 2 { ']] ! call/cc } does ']] ! + interpret 'nop ']] ! "]] not found" abort + } call/cc + drop 1 'nop +} :: [[ + +{ { over @ swap 2 { call/cc } does swap ! + interpret "literal to eof" abort + } call/cc + drop execute 1 'nop +} : interpret-literal-to +// use next line only if Lists.fif is loaded (or move it to Lists.fif if FiftExt.fif becomes part of Fift.fif) +// { ( ') interpret-literal-to } :: '( +// then you can use list literals '( a b c ... ) inside definitions: +// { '( 1 2 3 ) } : test +// { '( ( `a { ."A" } ) ( `b { ."B" } ) ) assoc { cadr execute } { ."???" } cond } : test2 diff --git a/crypto/fift/utils.cpp b/crypto/fift/utils.cpp index b8c5c61f..68fc18c0 100644 --- a/crypto/fift/utils.cpp +++ b/crypto/fift/utils.cpp @@ -59,6 +59,12 @@ td::Result load_GetOpt_fif(std::string dir = "") { td::Result load_wallet3_code_fif(std::string dir = "") { return td::read_file_str(smartcont_dir(dir) + "wallet-v3-code.fif"); } +td::Result load_FiftExt_fif(std::string dir = "") { + return load_source("FiftExt.fif", dir); +} +td::Result load_Disasm_fif(std::string dir = "") { + return load_source("Disasm.fif", dir); +} class MemoryFileLoader : public fift::FileLoader { public: @@ -108,7 +114,8 @@ class MemoryFileLoader : public fift::FileLoader { td::Result create_source_lookup(std::string main, bool need_preamble = true, bool need_asm = true, bool need_ton_util = true, bool need_lisp = true, - bool need_w3_code = true, std::string dir = "") { + bool need_w3_code = true, bool need_fift_ext = true, + bool need_disasm = true, std::string dir = "") { auto loader = std::make_unique(); loader->add_file("/main.fif", std::move(main)); if (need_preamble) { @@ -141,6 +148,14 @@ td::Result create_source_lookup(std::string main, bool need_ TRY_RESULT(f, load_wallet3_code_fif(dir)); loader->add_file("/wallet-v3-code.fif", std::move(f)); } + if (need_fift_ext) { + TRY_RESULT(f, load_FiftExt_fif(dir)); + loader->add_file("/FiftExt.fif", std::move(f)); + } + if (need_disasm) { + TRY_RESULT(f, load_Disasm_fif(dir)); + loader->add_file("/Disasm.fif", std::move(f)); + } auto res = fift::SourceLookup(std::move(loader)); res.add_include_path("/"); return std::move(res); @@ -172,7 +187,7 @@ td::Result run_fift(fift::SourceLookup source_lookup, std::o } // namespace td::Result mem_run_fift(std::string source, std::vector args, std::string fift_dir) { std::stringstream ss; - TRY_RESULT(source_lookup, create_source_lookup(source, true, true, true, true, true, fift_dir)); + TRY_RESULT(source_lookup, create_source_lookup(source, true, true, true, true, true, true, true, fift_dir)); TRY_RESULT_ASSIGN(source_lookup, run_fift(std::move(source_lookup), &ss, true, std::move(args))); FiftOutput res; res.source_lookup = std::move(source_lookup); @@ -190,7 +205,8 @@ td::Result mem_run_fift(SourceLookup source_lookup, std::vector create_mem_source_lookup(std::string main, std::string fift_dir, bool need_preamble, bool need_asm, bool need_ton_util, bool need_lisp, bool need_w3_code) { - return create_source_lookup(main, need_preamble, need_asm, need_ton_util, need_lisp, need_w3_code, fift_dir); + return create_source_lookup(main, need_preamble, need_asm, need_ton_util, need_lisp, need_w3_code, false, false, + fift_dir); } td::Result> compile_asm(td::Slice asm_code, std::string fift_dir, bool is_raw) { @@ -198,7 +214,7 @@ td::Result> compile_asm(td::Slice asm_code, std::string fift_d TRY_RESULT(source_lookup, create_source_lookup(PSTRING() << "\"Asm.fif\" include\n " << (is_raw ? "<{" : "") << asm_code << "\n" << (is_raw ? "}>c" : "") << " boc>B \"res\" B>file", - true, true, true, false, false, fift_dir)); + true, true, true, false, false, false, false, fift_dir)); TRY_RESULT(res, run_fift(std::move(source_lookup), &ss)); TRY_RESULT(boc, res.read_file("res")); return vm::std_boc_deserialize(std::move(boc.data)); diff --git a/crypto/fift/words.cpp b/crypto/fift/words.cpp index 7556a127..964b4328 100644 --- a/crypto/fift/words.cpp +++ b/crypto/fift/words.cpp @@ -21,6 +21,7 @@ #include "Dictionary.h" #include "IntCtx.h" #include "SourceLookup.h" +#include "HashMap.h" #include "common/refcnt.hpp" #include "common/bigint.hpp" @@ -42,6 +43,8 @@ #include "vm/box.hpp" #include "vm/atom.h" +#include "vm/db/TonDb.h" // only for interpret_db_run_vm{,_parallel} + #include "block/block.h" #include "td/utils/filesystem.h" @@ -58,12 +61,31 @@ namespace fift { -void show_total_cells(std::ostream& stream) { - stream << "total cells = " << vm::DataCell::get_total_data_cells() << std::endl; +const Ref nop_word_def = Ref{true}; + +// +// functions for wordef +// +Ref pop_exec_token(vm::Stack& stack) { + auto wd_ref = stack.pop_chk().as_object(); + if (wd_ref.is_null()) { + throw IntError{"execution token expected"}; + } + return wd_ref; } -void do_compile(vm::Stack& stack, Ref word_def); -void do_compile_literals(vm::Stack& stack, int count); +Ref pop_word_list(vm::Stack& stack) { + auto wl_ref = stack.pop_chk().as_object(); + if (wl_ref.is_null()) { + throw IntError{"word list expected"}; + } + return wl_ref; +} + +void push_argcount(vm::Stack& stack, int args) { + stack.push_smallint(args); + stack.push_object(nop_word_def); +} void interpret_dot(IntCtx& ctx, bool space_after) { *ctx.output_stream << dec_string2(ctx.stack.pop_int()) << (space_after ? " " : ""); @@ -121,7 +143,7 @@ void interpret_print_list(IntCtx& ctx) { } void interpret_dottc(IntCtx& ctx) { - show_total_cells(*ctx.output_stream); + *ctx.output_stream << "total cells = " << vm::DataCell::get_total_data_cells() << std::endl; } void interpret_dot_internal(vm::Stack& stack) { @@ -461,7 +483,7 @@ void interpret_make_xchg(vm::Stack& stack) { if (x) { stack.push_object(td::Ref{true, std::bind(interpret_xchg, _1, x, y)}); } else if (y <= 1) { - stack.push_object(y ? swap_word_def : Dictionary::nop_word_def); + stack.push_object(y ? swap_word_def : nop_word_def); } else { stack.push_object(td::Ref{true, std::bind(interpret_xchg0, _1, y)}); } @@ -1080,6 +1102,31 @@ void interpret_fetch_bytes(vm::Stack& stack, int mode) { } } +void interpret_fetch_slice(vm::Stack& stack, int mode) { + unsigned refs = ((mode & 1) ? stack.pop_smallint_range(4) : 0); + unsigned bits = stack.pop_smallint_range(1023); + auto cs = stack.pop_cellslice(); + if (!cs->have(bits, refs)) { + if (mode & 2) { + stack.push(std::move(cs)); + } + stack.push_bool(false); + if (!(mode & 4)) { + throw IntError{"end of data while fetching subslice from cell"}; + } + } else { + if (mode & 2) { + stack.push(cs.write().fetch_subslice(bits, refs)); + stack.push(std::move(cs)); + } else { + stack.push(cs->prefetch_subslice(bits, refs)); + } + if (mode & 4) { + stack.push_bool(true); + } + } +} + void interpret_cell_empty(vm::Stack& stack) { auto cs = stack.pop_cellslice(); stack.push_bool(cs->empty_ext()); @@ -1483,6 +1530,150 @@ void interpret_crc32c(vm::Stack& stack) { stack.push_smallint(td::crc32c(td::Slice{str})); } +// Fift hashmaps + +void push_hmap(vm::Stack& stack, Ref hmap) { + if (hmap.not_null()) { + stack.push_object(std::move(hmap)); + } else { + stack.push({}); + } +} + +void push_hmap(vm::Stack& stack, HashmapKeeper hmap_keep) { + push_hmap(stack, hmap_keep.extract()); +} + +Ref pop_hmap(vm::Stack& stack) { + stack.check_underflow(1); + auto se = stack.pop(); + if (se.is_null()) { + return {}; + } + auto hmap_ref = std::move(se).as_object(); + if (hmap_ref.is_null()) { + throw IntError{"hashmap expected"}; + } + return hmap_ref; +} + +HashmapKeeper pop_hmap_keeper(vm::Stack& stack) { + return HashmapKeeper{pop_hmap(stack)}; +} + +void interpret_hmap_new(vm::Stack& stack) { + stack.push({}); +} + +void interpret_hmap_fetch(vm::Stack& stack, int mode) { + auto hmap = pop_hmap(stack); + auto value = Hashmap::get(std::move(hmap), stack.pop_chk()); + bool found = !value.is_null(); + if ((mode & 8) && !found) { + throw IntError{"hashmap key not found"}; + } + if (mode & (2 << (int)found)) { + stack.push(std::move(value)); + } + if (mode & 1) { + stack.push_bool(found); + } +} + +void interpret_hmap_delete(vm::Stack& stack, int mode) { + auto hmap = pop_hmap(stack); + auto res = Hashmap::get_remove(std::move(hmap), stack.pop_chk()); + push_hmap(stack, std::move(res.first)); + bool found = !res.second.is_null(); + if ((mode & 8) && !found) { + throw IntError{"hashmap key not found"}; + } + if (mode & (2 << (int)found)) { + stack.push(std::move(res.second)); + } + if (mode & 1) { + stack.push_bool(found); + } +} + +void interpret_hmap_store(vm::Stack& stack, int mode) { + stack.check_underflow(3); + auto hmap = pop_hmap_keeper(stack); + auto key = stack.pop(), value = stack.pop(); + bool ok = true; + if (mode & 1) { + hmap.set(std::move(key), std::move(value)); + } else { + ok = hmap.replace(std::move(key), std::move(value)); + } + push_hmap(stack, std::move(hmap)); + if (mode & 2) { + stack.push_bool(ok); + } +} + +void interpret_hmap_is_empty(vm::Stack& stack) { + stack.push_bool(pop_hmap(stack).is_null()); +} + +void interpret_hmap_decompose(vm::Stack& stack, int mode) { + auto hmap = pop_hmap(stack); + if (hmap.is_null()) { + if (mode & 1) { + stack.push_bool(false); + } else { + throw IntError{"empty hmap"}; + } + return; + } + stack.push(hmap->key()); + stack.push(hmap->value()); + push_hmap(stack, hmap->left()); + push_hmap(stack, hmap->right()); + if (mode & 1) { + stack.push_bool(true); + } +} + +class HmapIterCont : public LoopCont { + HashmapIterator it; + bool ok; + + public: + HmapIterCont(Ref _func, Ref _after, HashmapIterator _it) + : LoopCont(std::move(_func), std::move(_after)), it(std::move(_it)), ok(true) { + } + HmapIterCont(const HmapIterCont&) = default; + HmapIterCont* make_copy() const override { + return new HmapIterCont(*this); + } + bool init(IntCtx& ctx) override { + return true; + } + bool pre_exec(IntCtx& ctx) override { + if (it.eof()) { + return false; + } else { + ctx.stack.push(it->key()); + ctx.stack.push(it->value()); + return true; + } + } + bool post_exec(IntCtx& ctx) override { + ok = ctx.stack.pop_bool(); + return ok && it.next(); + } + bool finalize(IntCtx& ctx) override { + ctx.stack.push_bool(ok); + return true; + } +}; + +Ref interpret_hmap_foreach(IntCtx& ctx, int mode) { + auto func = pop_exec_token(ctx); + return td::make_ref(std::move(func), std::move(ctx.next), pop_hmap_keeper(ctx).begin(mode & 1)); +} + // vm dictionaries void interpret_dict_new(vm::Stack& stack) { stack.push({}); @@ -1882,7 +2073,7 @@ void interpret_pfx_dict_get(vm::Stack& stack) { } void interpret_bitstring_hex_literal(IntCtx& ctx) { - auto s = ctx.scan_word_to('}'); + auto s = ctx.parser->scan_word_to('}'); unsigned char buff[128]; int bits = (int)td::bitstring::parse_bitstring_hex_literal(buff, sizeof(buff), s.begin(), s.end()); if (bits < 0) { @@ -1890,11 +2081,11 @@ void interpret_bitstring_hex_literal(IntCtx& ctx) { } auto cs = Ref{true, vm::CellBuilder().store_bits(td::ConstBitPtr{buff}, bits).finalize()}; ctx.stack.push(std::move(cs)); - push_argcount(ctx.stack, 1); + push_argcount(ctx, 1); } void interpret_bitstring_binary_literal(IntCtx& ctx) { - auto s = ctx.scan_word_to('}'); + auto s = ctx.parser->scan_word_to('}'); unsigned char buff[128]; int bits = (int)td::bitstring::parse_bitstring_binary_literal(buff, sizeof(buff), s.begin(), s.end()); if (bits < 0) { @@ -1902,12 +2093,12 @@ void interpret_bitstring_binary_literal(IntCtx& ctx) { } auto cs = Ref{true, vm::CellBuilder().store_bits(td::ConstBitPtr{buff}, bits).finalize()}; ctx.stack.push(std::move(cs)); - push_argcount(ctx.stack, 1); + push_argcount(ctx, 1); } void interpret_word(IntCtx& ctx) { char sep = (char)ctx.stack.pop_smallint_range(127); - auto word = (sep != ' ' ? ctx.scan_word_to(sep, true) : ctx.scan_word()); + auto word = (sep != ' ' ? ctx.parser->scan_word_to(sep, true) : ctx.parser->scan_word()); ctx.stack.push_string(word); } @@ -1915,17 +2106,17 @@ void interpret_word_ext(IntCtx& ctx) { int mode = ctx.stack.pop_smallint_range(11); auto delims = ctx.stack.pop_string(); if (mode & 8) { - ctx.skipspc(mode & 4); + ctx.parser->skipspc(mode & 4); } - ctx.stack.push_string(ctx.scan_word_ext(CharClassifier{delims, mode & 3})); + ctx.stack.push_string(ctx.parser->scan_word_ext(CharClassifier{delims, mode & 3})); } void interpret_skipspc(IntCtx& ctx) { - ctx.skipspc(); + ctx.parser->skipspc(); } void interpret_wordlist_begin_aux(vm::Stack& stack) { - stack.push({vm::from_object, Ref{true}}); + stack.push_make_object(); } void interpret_wordlist_begin(IntCtx& ctx) { @@ -1938,7 +2129,7 @@ void interpret_wordlist_begin(IntCtx& ctx) { void interpret_wordlist_end_aux(vm::Stack& stack) { Ref wordlist_ref = pop_word_list(stack); wordlist_ref.write().close(); - stack.push({vm::from_object, Ref{wordlist_ref}}); + stack.push_object(std::move(wordlist_ref)); } void interpret_wordlist_end(IntCtx& ctx) { @@ -1957,7 +2148,7 @@ void interpret_internal_interpret_begin(IntCtx& ctx) { void interpret_internal_interpret_end(IntCtx& ctx) { ctx.check_int_exec(); ctx.state = -ctx.state; - ctx.stack.push({vm::from_object, Dictionary::nop_word_def}); + ctx.stack.push_object(nop_word_def); } // (create) @@ -1974,18 +2165,12 @@ void interpret_create_aux(IntCtx& ctx, int mode) { if (!(mode & 2)) { word += ' '; } - bool active = (mode & 1); - auto entry = ctx.dictionary->lookup(word); - if (entry) { - *entry = DictEntry{std::move(wd_ref), active}; // redefine word - } else { - ctx.dictionary->def_word(std::move(word), {std::move(wd_ref), active}); - } + ctx.dictionary.def_word(std::move(word), {std::move(wd_ref), (bool)(mode & 1)}); } // { bl word 0 (create) } : create void interpret_create(IntCtx& ctx) { - auto word = ctx.scan_word(); + auto word = ctx.parser->scan_word(); if (!word.size()) { throw IntError{"non-empty word name expected"}; } @@ -1997,10 +2182,10 @@ Ref create_aux_wd{Ref{true, std::bind(interpret_create_aux, s // { bl word 2 ' (create) } :: : void interpret_colon(IntCtx& ctx, int mode) { - ctx.stack.push_string(ctx.scan_word()); + ctx.stack.push_string(ctx.parser->scan_word()); ctx.stack.push_smallint(mode); ctx.stack.push_smallint(2); - ctx.stack.push({vm::from_object, create_aux_wd}); + ctx.stack.push_object(create_aux_wd); //push_argcount(ctx, 2, create_wd); } @@ -2008,27 +2193,27 @@ void interpret_colon(IntCtx& ctx, int mode) { void interpret_forget_aux(IntCtx& ctx) { std::string s = ctx.stack.pop_string(); auto s_copy = s; - auto entry = ctx.dictionary->lookup(s); + auto entry = ctx.dictionary.lookup(s); if (!entry) { s += " "; - entry = ctx.dictionary->lookup(s); + entry = ctx.dictionary.lookup(s); } if (!entry) { throw IntError{"`" + s_copy + "` not found"}; } else { - ctx.dictionary->undef_word(s); + ctx.dictionary.undef_word(s); } } // { bl word (forget) } : forget void interpret_forget(IntCtx& ctx) { - ctx.stack.push_string(ctx.scan_word()); + ctx.stack.push_string(ctx.parser->scan_word()); interpret_forget_aux(ctx); } void interpret_quote_str(IntCtx& ctx) { - ctx.stack.push_string(ctx.scan_word_to('"')); - push_argcount(ctx.stack, 1); + ctx.stack.push_string(ctx.parser->scan_word_to('"')); + push_argcount(ctx, 1); } int str_utf8_code(const char* str, int& len) { @@ -2056,7 +2241,7 @@ int str_utf8_code(const char* str, int& len) { } void interpret_char(IntCtx& ctx) { - auto s = ctx.scan_word(); + auto s = ctx.parser->scan_word(); int len = (s.size() < 10 ? (int)s.size() : 10); int code = str_utf8_code(s.data(), len); if (code < 0 || s.size() != (unsigned)len) { @@ -2197,6 +2382,12 @@ Ref interpret_execute(IntCtx& ctx) { return pop_exec_token(ctx); } +Ref interpret_call_cc(IntCtx& ctx) { + auto next = pop_exec_token(ctx); + ctx.stack.push_object(std::move(ctx.next)); + return next; +} + Ref interpret_execute_times(IntCtx& ctx) { int count = ctx.stack.pop_smallint_range(1000000000); auto body = pop_exec_token(ctx); @@ -2251,30 +2442,46 @@ Ref interpret_until(IntCtx& ctx) { return body; } -void interpret_tick(IntCtx& ctx) { - std::string word = ctx.scan_word().str(); - auto entry = ctx.dictionary->lookup(word); - if (!entry) { - entry = ctx.dictionary->lookup(word + ' '); +DictEntry context_lookup(IntCtx& ctx, std::string word, bool append_space = true) { + if (append_space) { + auto entry = context_lookup(ctx, word, false); if (!entry) { - throw IntError{"word `" + word + "` undefined"}; + entry = context_lookup(ctx, word + ' ', false); } + return entry; } - ctx.stack.push({vm::from_object, entry->get_def()}); + auto entry = ctx.context.lookup(word); + if (!entry && ctx.context != ctx.dictionary) { + entry = ctx.dictionary.lookup(word); + } + if (!entry && ctx.main_dictionary != ctx.context && ctx.main_dictionary != ctx.dictionary) { + entry = ctx.main_dictionary.lookup(word); + } + return entry; +} + +void interpret_tick(IntCtx& ctx) { + std::string word = ctx.parser->scan_word().str(); + auto entry = context_lookup(ctx, word); + if (!entry) { + throw IntError{"word `" + word + "` undefined"}; + } + ctx.stack.push_object(entry.get_def()); push_argcount(ctx, 1); } -void interpret_find(IntCtx& ctx) { +void interpret_find(IntCtx& ctx, int mode) { std::string word = ctx.stack.pop_string(); - auto entry = ctx.dictionary->lookup(word); - if (!entry) { - entry = ctx.dictionary->lookup(word + ' '); - } + auto entry = context_lookup(ctx, word, !(mode & 2)); if (!entry) { ctx.stack.push_bool(false); } else { - ctx.stack.push({vm::from_object, entry->get_def()}); - ctx.stack.push_bool(true); + ctx.stack.push_object(entry.get_def()); + if (!(mode & 1) || !entry.is_active()) { + ctx.stack.push_bool(true); + } else { + ctx.stack.push_smallint(1); + } } } @@ -2284,9 +2491,13 @@ void interpret_leave_source(IntCtx& ctx) { } } +void interpret_include_depth(IntCtx& ctx) { + ctx.stack.push_smallint(ctx.include_depth()); +} + Ref interpret_include(IntCtx& ctx) { auto fname = ctx.stack.pop_string(); - auto r_file = ctx.source_lookup->lookup_source(fname, ctx.currentd_dir); + auto r_file = ctx.source_lookup->lookup_source(fname, ctx.parser->currentd_dir); if (r_file.is_error()) { throw IntError{"cannot locate file `" + fname + "`"}; } @@ -2314,12 +2525,32 @@ Ref interpret_skip_source(IntCtx& ctx) { } void interpret_words(IntCtx& ctx) { - for (const auto& x : *ctx.dictionary) { - *ctx.output_stream << x.first << " "; + for (const auto& x : ctx.dictionary) { + *ctx.output_stream << vm::StackEntry(x.key()).as_string() << " "; } *ctx.output_stream << std::endl; } +void interpret_get_current(IntCtx& ctx) { + ctx.stack.push(ctx.dictionary.get_box()); +} + +void interpret_set_current(IntCtx& ctx) { + ctx.dictionary = ctx.stack.pop_box(); +} + +void interpret_get_context(IntCtx& ctx) { + ctx.stack.push(ctx.context.get_box()); +} + +void interpret_set_context(IntCtx& ctx) { + ctx.context = ctx.stack.pop_box(); +} + +void interpret_set_context_to(IntCtx& ctx, Ref box) { + ctx.context = std::move(box); +} + void interpret_print_backtrace(IntCtx& ctx) { ctx.print_backtrace(*ctx.output_stream, ctx.next); } @@ -2465,6 +2696,28 @@ void interpret_run_vm(IntCtx& ctx, int mode) { } } +void interpret_vmop_len(vm::Stack& stack) { + int cp = stack.pop_smallint_range(0x7fffffff, -0x80000000); + auto cs = stack.pop_cellslice(); + auto dispatch = vm::DispatchTable::get_table(cp); + if (!dispatch) { + throw IntError{"unknown vm codepage"}; + } + stack.push_smallint(dispatch->instr_len(*cs)); +} + +void interpret_vmop_dump(vm::Stack& stack) { + int cp = stack.pop_smallint_range(0x7fffffff, -0x80000000); + auto cs = stack.pop_cellslice(); + auto dispatch = vm::DispatchTable::get_table(cp); + if (!dispatch) { + throw IntError{"unknown vm codepage"}; + } + auto dump = dispatch->dump_instr(cs.write()); + stack.push_cellslice(std::move(cs)); + stack.push_string(std::move(dump)); +} + void do_interpret_db_run_vm_parallel(std::ostream* stream, vm::Stack& stack, vm::TonDb* ton_db_ptr, int threads_n, int tasks_n) { if (!ton_db_ptr || !*ton_db_ptr) { @@ -2623,11 +2876,11 @@ Ref interpret_get_cmdline_arg(IntCtx& ctx) { interpret_get_fixed_cmdline_arg(ctx.stack, n); return {}; } - auto entry = ctx.dictionary->lookup("$0 "); + auto entry = ctx.dictionary.lookup("$0 "); if (!entry) { throw IntError{"-?"}; } else { - return entry->get_def(); + return entry.get_def(); } } @@ -2667,29 +2920,19 @@ Ref interpret_execute_internal(IntCtx& ctx) { return word_def; } -// wl x1 .. xn n 'w --> wl' -void interpret_compile_internal(vm::Stack& stack) { - Ref word_def = pop_exec_token(stack); - int count = stack.pop_smallint_range(255); - do_compile_literals(stack, count); - if (word_def != Dictionary::nop_word_def) { - do_compile(stack, word_def); - } -} - void do_compile(vm::Stack& stack, Ref word_def) { Ref wl_ref = pop_word_list(stack); - if (word_def != Dictionary::nop_word_def) { + if (word_def != nop_word_def) { auto list_size = word_def->list_size(); - if ((td::uint64)list_size <= 1) { - // inline short definitions + if (list_size >= 0 && (list_size <= 2 || word_def.is_unique())) { + // inline short and unique definitions auto list = word_def->get_list(); wl_ref.write().append(list, list + list_size); } else { - wl_ref.write().push_back(word_def); + wl_ref.write().push_back(std::move(word_def)); } } - stack.push({vm::from_object, wl_ref}); + stack.push_object(std::move(wl_ref)); } void compile_one_literal(WordList& wlist, vm::StackEntry val) { @@ -2717,12 +2960,146 @@ void do_compile_literals(vm::Stack& stack, int count) { } } stack.pop_many(count + 1); - stack.push({vm::from_object, wl_ref}); + stack.push_object(std::move(wl_ref)); +} + +// wl x1 .. xn n 'w --> wl' +void interpret_compile_internal(vm::Stack& stack) { + Ref word_def = pop_exec_token(stack); + int count = stack.pop_smallint_range(255); + do_compile_literals(stack, count); + if (word_def != nop_word_def) { + do_compile(stack, std::move(word_def)); + } +} + +Ref interpret_compile_execute(IntCtx& ctx) { + if (ctx.state > 0) { + interpret_compile_internal(ctx.stack); + return {}; + } else { + return interpret_execute_internal(ctx); + } +} + +void interpret_seekeof(IntCtx& ctx, int mode) { + if (mode == -1) { + mode = ctx.stack.pop_smallint_range(3, -1); + } + bool eof = true; + if (ctx.parser && (ctx.parser->get_input() || ctx.parser->load_next_line())) { + while (true) { + if (!ctx.parser->is_sb()) { + ctx.parser->skipspc(); + if (*ctx.parser->get_input()) { + eof = false; + break; + } + } + if (mode & 1) { + *ctx.output_stream << " ok" << std::endl; + } + if (!ctx.parser->load_next_line()) { + break; + } + } + } + ctx.stack.push_bool(eof); +} + +void interpret_word_prefix_find(IntCtx& ctx, int mode) { + const char *ptr = ctx.parser->get_input(), *start = ptr; + if (!ptr) { + ctx.stack.push_string(std::string{}); + ctx.stack.push_bool(false); + return; + } + while (*ptr && *ptr != ' ' && *ptr != '\t') { + ptr++; + } + std::string Word{start, ptr}; + Word.push_back(' '); + auto entry = context_lookup(ctx, Word, false); + Word.pop_back(); + if (entry) { + ctx.parser->set_input(ptr); + ctx.parser->skipspc(); + } else { + const char* ptr_end = ptr; + while (true) { + entry = context_lookup(ctx, Word, false); + if (entry) { + ctx.parser->set_input(ptr); + break; + } + if (ptr == start) { + Word = std::string{start, ptr_end}; + ctx.parser->set_input(ptr_end); + ctx.parser->skipspc(); + break; + } + Word.pop_back(); + --ptr; + } + } + ctx.parser->word = Word; + if (!(mode & 2) || !entry) { + ctx.stack.push_string(std::move(Word)); + } + if (mode & 1) { + if (!entry) { + ctx.stack.push_bool(false); + } else { + ctx.stack.push_object(entry.get_def()); + ctx.stack.push_smallint(entry.is_active() ? 1 : -1); + } + } +} + +// equivalent to +// { ?dup { 1+ { execute } { 0 swap } cond } { (number) ?dup 0= abort"-?" 'nop } cond +// } : (interpret-prepare) +Ref interpret_prepare(IntCtx& ctx) { + int found = ctx.stack.pop_smallint_range(1, -1); + if (!found) { + // numbers + interpret_parse_number(ctx); // (number) + interpret_cond_dup(ctx); // ?dup + auto res = ctx.stack.pop_int(); // 0= abort"-?" + if (res == 0) { + throw IntError{"-?"}; + } + ctx.stack.push_object(nop_word_def); // 'nop + return {}; + } else if (found == -1) { + // ordinary word + ctx.stack.push_smallint(0); // 0 + interpret_swap(ctx); // swap + return {}; + } else { + // active word + return pop_exec_token(ctx); // execute + } +} + +Ref InterpretCont::run_tail(IntCtx& ctx) const { + static Ref interpret_prepare_ref = td::make_ref(interpret_prepare); + static Ref compile_exec_ref = td::make_ref(interpret_compile_execute); + interpret_seekeof(ctx, !ctx.state && !ctx.include_depth()); // seekeof + if (ctx.stack.pop_bool()) { + exit_interpret->clear(); + return {}; // exit loop + } + exit_interpret->set({vm::from_object, ctx.next}); // set 'exit-interpret to current continuation + interpret_word_prefix_find(ctx, 3); // (word-prefix-find) + // (interpet-prepare) (compile-execute) and schedule next loop iteration + ctx.next = SeqCont::seq(compile_exec_ref, SeqCont::seq(self(), std::move(ctx.next))); + return interpret_prepare_ref; // (interpret-prepare) } void init_words_common(Dictionary& d) { using namespace std::placeholders; - d.def_word("nop ", Dictionary::nop_word_def); + d.def_word("nop ", nop_word_def); // stack print/dump words d.def_ctx_word(". ", std::bind(interpret_dot, _1, true)); d.def_ctx_word("._ ", std::bind(interpret_dot, _1, false)); @@ -2942,6 +3319,14 @@ void init_words_common(Dictionary& d) { d.def_stack_word("ref@+ ", std::bind(interpret_fetch_ref, _1, 2)); d.def_stack_word("ref@? ", std::bind(interpret_fetch_ref, _1, 4)); d.def_stack_word("ref@?+ ", std::bind(interpret_fetch_ref, _1, 6)); + d.def_stack_word("s@ ", std::bind(interpret_fetch_slice, _1, 0)); + d.def_stack_word("sr@ ", std::bind(interpret_fetch_slice, _1, 1)); + d.def_stack_word("s@+ ", std::bind(interpret_fetch_slice, _1, 2)); + d.def_stack_word("sr@+ ", std::bind(interpret_fetch_slice, _1, 3)); + d.def_stack_word("s@? ", std::bind(interpret_fetch_slice, _1, 4)); + d.def_stack_word("sr@? ", std::bind(interpret_fetch_slice, _1, 5)); + d.def_stack_word("s@?+ ", std::bind(interpret_fetch_slice, _1, 6)); + d.def_stack_word("sr@?+ ", std::bind(interpret_fetch_slice, _1, 7)); d.def_stack_word("s> ", interpret_cell_check_empty); d.def_stack_word("empty? ", interpret_cell_empty); d.def_stack_word("remaining ", interpret_cell_remaining); @@ -2970,6 +3355,18 @@ void init_words_common(Dictionary& d) { d.def_stack_word("crc16 ", interpret_crc16); d.def_stack_word("crc32 ", interpret_crc32); d.def_stack_word("crc32c ", interpret_crc32c); + // hashmaps + d.def_stack_word("hmapnew ", interpret_hmap_new); + d.def_stack_word("hmap@ ", std::bind(interpret_hmap_fetch, _1, 6)); + d.def_stack_word("hmap@? ", std::bind(interpret_hmap_fetch, _1, 5)); + d.def_stack_word("hmap- ", std::bind(interpret_hmap_delete, _1, 0)); + d.def_stack_word("hmap-? ", std::bind(interpret_hmap_delete, _1, 1)); + d.def_stack_word("hmap@- ", std::bind(interpret_hmap_delete, _1, 6)); + d.def_stack_word("hmap! ", std::bind(interpret_hmap_store, _1, 0)); + d.def_stack_word("hmap!+ ", std::bind(interpret_hmap_store, _1, 1)); + d.def_stack_word("hmapempty? ", interpret_hmap_is_empty); + d.def_stack_word("hmapunpack ", std::bind(interpret_hmap_decompose, _1, 1)); + d.def_ctx_tail_word("hmapforeach ", std::bind(interpret_hmap_foreach, _1, 0)); // vm dictionaries d.def_stack_word("dictnew ", interpret_dict_new); d.def_stack_word("dict>s ", interpret_dict_to_slice); @@ -3041,6 +3438,7 @@ void init_words_common(Dictionary& d) { d.def_stack_word("atom? ", interpret_is_atom); // execution control d.def_ctx_tail_word("execute ", interpret_execute); + d.def_ctx_tail_word("call/cc ", interpret_call_cc); d.def_ctx_tail_word("times ", interpret_execute_times); d.def_ctx_tail_word("if ", interpret_if); d.def_ctx_tail_word("ifnot ", interpret_ifnot); @@ -3056,10 +3454,12 @@ void init_words_common(Dictionary& d) { d.def_stack_word("(}) ", interpret_wordlist_end_aux); d.def_stack_word("(compile) ", interpret_compile_internal); d.def_ctx_tail_word("(execute) ", interpret_execute_internal); + d.def_ctx_tail_word("(interpret-prepare) ", interpret_prepare); d.def_active_word("' ", interpret_tick); - d.def_word("'nop ", LitCont::literal({vm::from_object, Dictionary::nop_word_def})); + d.def_word("'nop ", LitCont::literal({vm::from_object, nop_word_def})); // dictionary manipulation - d.def_ctx_word("find ", interpret_find); + d.def_ctx_word("find ", std::bind(interpret_find, _1, 1)); + d.def_ctx_word("(word-prefix-find) ", std::bind(interpret_word_prefix_find, _1, 3)); d.def_ctx_word("create ", interpret_create); d.def_ctx_word("(create) ", std::bind(interpret_create_aux, _1, -1)); d.def_active_word(": ", std::bind(interpret_colon, _1, 0)); @@ -3069,12 +3469,21 @@ void init_words_common(Dictionary& d) { d.def_ctx_word("(forget) ", interpret_forget_aux); d.def_ctx_word("forget ", interpret_forget); d.def_ctx_word("words ", interpret_words); + d.def_word("Fift-wordlist ", LitCont::literal(d.get_box())); + d.def_ctx_word("Fift ", std::bind(interpret_set_context_to, _1, d.get_box())); + d.def_ctx_word("current@ ", interpret_get_current); + d.def_ctx_word("current! ", interpret_set_current); + d.def_ctx_word("context@ ", interpret_get_context); + d.def_ctx_word("context! ", interpret_set_context); d.def_ctx_word(".bt ", interpret_print_backtrace); d.def_ctx_word("cont. ", interpret_print_continuation); // input parse d.def_ctx_word("word ", interpret_word); d.def_ctx_word("(word) ", interpret_word_ext); d.def_ctx_word("skipspc ", interpret_skipspc); + d.def_ctx_word("seekeof? ", std::bind(interpret_seekeof, _1, 1)); + d.def_ctx_word("(seekeof?) ", std::bind(interpret_seekeof, _1, -1)); + d.def_ctx_word("include-depth ", interpret_include_depth); d.def_ctx_tail_word("include ", interpret_include); d.def_ctx_tail_word("skip-to-eof ", interpret_skip_source); d.def_word("'exit-interpret ", LitCont::literal(exit_interpret)); @@ -3110,6 +3519,8 @@ void init_words_vm(Dictionary& d, bool enable_debug) { d.def_ctx_word("dbrunvm-parallel ", interpret_db_run_vm_parallel); d.def_stack_word("vmcont, ", interpret_store_vm_cont); d.def_stack_word("vmcont@ ", interpret_fetch_vm_cont); + d.def_stack_word("(vmoplen) ", interpret_vmop_len); + d.def_stack_word("(vmopdump) ", interpret_vmop_dump); } void import_cmdline_args(Dictionary& d, std::string arg0, int n, const char* const argv[]) { @@ -3128,99 +3539,4 @@ void import_cmdline_args(Dictionary& d, std::string arg0, int n, const char* con } } -std::pair numeric_value_ext(std::string s, bool allow_frac = true) { - td::RefInt256 num, denom; - int res = parse_number(s, num, denom, allow_frac); - if (res <= 0) { - throw IntError{"-?"}; - } - return std::make_pair(std::move(num), res == 2 ? std::move(denom) : td::RefInt256{}); -} - -td::RefInt256 numeric_value(std::string s) { - td::RefInt256 num, denom; - int res = parse_number(s, num, denom, false); - if (res != 1) { - throw IntError{"-?"}; - } - return num; -} - -Ref interpret_compile_execute(IntCtx& ctx) { - if (ctx.state > 0) { - interpret_compile_internal(ctx.stack); - return {}; - } else { - return interpret_execute_internal(ctx); - } -} - -Ref InterpretCont::run_tail(IntCtx& ctx) const { - if (!ctx.get_input() && !ctx.load_next_line()) { - return {}; - } - while (true) { - if (!ctx.is_sb()) { - ctx.skipspc(); - if (*ctx.get_input()) { - break; - } - } - if (!ctx.state && !ctx.include_depth) { - *ctx.output_stream << " ok" << std::endl; - } - if (!ctx.load_next_line()) { - return {}; - } - } - const char* ptr = ctx.get_input(); - std::string Word; - Word.reserve(128); - auto entry = ctx.dictionary->lookup(""); - std::string entry_word; - const char* ptr_end = ptr; - while (*ptr && *ptr != ' ' && *ptr != '\t') { - Word += *ptr++; - auto cur = ctx.dictionary->lookup(Word); - if (cur) { - entry = cur; - entry_word = Word; - ptr_end = ptr; - } - } - auto cur = ctx.dictionary->lookup(Word + " "); - if (cur || !entry) { - entry = std::move(cur); - ctx.set_input(ptr); - ctx.skipspc(); - } else { - Word = entry_word; - ctx.set_input(ptr_end); - } - ctx.word = Word; - static Ref compile_exec_ref = td::make_ref(interpret_compile_execute); - if (!entry) { - // numbers - auto res = numeric_value_ext(Word); - ctx.stack.push(std::move(res.first)); - if (res.second.not_null()) { - ctx.stack.push(std::move(res.second)); - push_argcount(ctx, 2); - } else { - push_argcount(ctx, 1); - } - } else if (!entry->is_active()) { - // ordinary word - ctx.stack.push_smallint(0); - ctx.stack.push({vm::from_object, entry->get_def()}); - } else { - // active word - ctx.next = SeqCont::seq(compile_exec_ref, SeqCont::seq(self(), std::move(ctx.next))); - return entry->get_def(); - } - exit_interpret->set({vm::from_object, ctx.next}); - ctx.next = SeqCont::seq(self(), std::move(ctx.next)); - return compile_exec_ref; -} - } // namespace fift diff --git a/crypto/test/fift.cpp b/crypto/test/fift.cpp index 466ad4e7..e6cdc9f3 100644 --- a/crypto/test/fift.cpp +++ b/crypto/test/fift.cpp @@ -110,3 +110,19 @@ TEST(Fift, test_sort) { TEST(Fift, test_sort2) { run_fift("sort2.fif"); } + +TEST(Fift, test_hmap) { + run_fift("hmap.fif"); +} + +TEST(Fift, test_disasm) { + run_fift("disasm.fif"); +} + +TEST(Fift, test_fiftext) { + run_fift("fift-ext.fif"); +} + +TEST(Fift, test_namespaces) { + run_fift("namespaces.fif"); +} diff --git a/crypto/test/fift/disasm.fif b/crypto/test/fift/disasm.fif new file mode 100644 index 00000000..8d9f31ac --- /dev/null +++ b/crypto/test/fift/disasm.fif @@ -0,0 +1,70 @@ +"Disasm.fif" include +"Asm.fif" include + +<{ + IF:<{ + 123456789 PUSHINT + }>ELSE<{ + x{12345} PUSHSLICE + }> + WHILE:<{ ADD }>DO<{ + 10 PUSHINT REPEAT:<{ + CONT:<{ NOP }> + CONT:<{ }> + }> + }> +}>s +disasm cr + +x{007A7} disasm cr // Cannot disassemble: x{7} + +<{ + SWAP + s0 s10 XCHG s0 100 s() XCHG + s5 PUSH s6 POP + s4 s10 PUSH2 + 5 10 BLKSWAP + c4 PUSH c5 POP +}>s dup dup +disasm cr +std-disasm disasm cr +stack-disasm show-vm-code disasm cr +hide-vm-code + +<{ + 1 INT 123456789 INT 123456789123456789123456789 INT + PUSHREF + x{567} PUSHSLICE + 10 ADDCONST + DIV DIVR DIVC + RSHIFTC 10 RSHIFTC# + 20 MODPOW2# + 30 MULRSHIFTR# + LSHIFTDIVC 40 LSHIFT#DIVR +}>s +disasm cr + +PROGRAM{ + 11 DECLMETHOD foo1 + 12 DECLMETHOD foo2 + 13 DECLMETHOD foo3 + DECLPROC main + foo1 PROC:<{ + 123 ADDCONST + }> + foo2 PROC:<{ + OVER + 5 EQINT + IFJMP:<{ + NIP + }> + MUL + }> + foo3 PROC:<{ + ADD + foo2 CALLDICT + }> + main PROC:<{ + }> +}END>s +disasm \ No newline at end of file diff --git a/crypto/test/fift/fift-ext.fif b/crypto/test/fift/fift-ext.fif new file mode 100644 index 00000000..b0b08c12 --- /dev/null +++ b/crypto/test/fift/fift-ext.fif @@ -0,0 +1,107 @@ +"FiftExt.fif" include + +// if then +{ + if{ 0> }then{ "Yes" } +} : run +5 run type cr // Yes +-5 run .s // nothing + +// if then else +{ + if{ 0> }then{ "Yes" }else{ "No" } +} : run +5 run type ." " // Yes +-5 run type cr // No + +// if then elseif else +{ + dup dup if{ 20 > }then{ 2drop "AAA" }elseif{ 10 > }then{ drop "BBB" }elseif{ 0> }then{ "CCC" }else{ "DDD" } +} : run +25 run type ." " // AAA +15 run type ." " // BBB +5 run type ." " // CCC +-5 run type cr // DDD + +// while do +1 +while{ dup 100 < }do{ + dup . + 2 * +} +cr // 1 2 4 8 16 32 64 +drop + +// repeat until +1 +repeat{ + dup . + 3 * +}until{ dup 500 > } +cr // 1 3 9 27 81 243 +drop + +// def +def foo1 { * + } +5 10 20 foo1 . // 205 +6 100 100 foo1 . cr // 10006 + +// defrec +defrec fib { + if{ dup 2 < }then{ + drop 1 + }else{ + dup 1- fib swap 2- fib + + } +} +8 fib . // 34 +9 fib . // 55 +20 fib . cr // 10946 + +// [[ ... ]] +def foo2 { [[ ."Exec" cr 5 5 * ]] + } +."Calling foo2: " +100 foo2 . 200 foo2 . cr + +// Nested blocks +def sqr { dup * } +def power { + 1 -rot + while{ dup 0> }do{ + if{ dup 1 and }then{ + -rot tuck * swap rot + } + 2/ + if{ dup 0> }then{ + swap sqr swap + } + } + 2drop +} + +3 0 power . // 1 +3 1 power . // 3 +3 4 power . // 81 +3 12 power . // 531441 +3 50 power . // 717897987691852588770249 +cr + +0 while{ dup 2 < }do{ + ."A" + 0 while{ dup 2 < }do{ + ."B" + 0 while{ dup 2 < }do{ + ."C" + 0 while{ dup 2 < }do{ + ."D" + 0 while{ dup 2 < }do{ + ."E" + 0 while{ dup 2 < }do{ + ."F" + 1+ } drop + 1+ } drop + 1+ } drop + 1+ } drop + 1+ } drop +1+ } drop +cr // ABCDEFFEFFDEFFEFFCDEFFEFFDEFFEFFBCDEFFEFFDEFFEFFCDEFFEFFDEFFEFFABCDEFFEFFDEFFEFFCDEFFEFFDEFFEFFBCDEFFEFFDEFFEFFCDEFFEFFDEFFEFF \ No newline at end of file diff --git a/crypto/test/fift/hmap.fif b/crypto/test/fift/hmap.fif new file mode 100644 index 00000000..190be938 --- /dev/null +++ b/crypto/test/fift/hmap.fif @@ -0,0 +1,69 @@ +hmapnew .s // (null) + +// Get from empty hmap +dup 10 swap hmap@ null? not abort"expected null" +dup "abc" swap hmap@ null? not abort"expected null" +dup 10 swap hmap@? abort"expected 0" +dup "abc" swap hmap@? abort"expected 0" +dup hmapempty? not abort"expected -1" +.s cr // (null) + +// Insert values +"value-1" 123 rot hmap!+ +"value-2" "Abc" rot hmap!+ +"value-3" "xyz" rot hmap!+ +"value-4" B{78797A} rot hmap!+ +"value-5" `xyz rot hmap!+ +"abcdefg" null rot hmap!+ // No effect! +dup hmapempty? abort"expected 0" + +// Get values +dup 123 swap hmap@ type cr // value-1 +dup "Abc" swap hmap@ type cr // value-2 +dup "xyz" swap hmap@ type cr // value-3 +dup B{78797A} swap hmap@ type cr // value-4 +dup `xyz swap hmap@ type cr // value-5 +dup 123 swap hmap@? . type cr // -1 value-1 +dup "Abc" swap hmap@? . type cr // -1 value-2 +dup "xyz" swap hmap@? . type cr // -1 value-3 +dup B{78797A} swap hmap@? . type cr // -1 value-4 +dup `xyz swap hmap@? . type cr // -1 value-5 +// Get null +dup 1234 swap hmap@ null? not abort"expected null" +dup null swap hmap@ null? not abort"expected null" +dup 1234 swap hmap@? abort"expected 0" +dup null swap hmap@? abort"expected 0" +cr + +// hmapforeach +constant hmap +hmap { .s drop drop -1 } hmapforeach +.s drop +3 hmap { .s drop drop 1- dup } hmapforeach +.s cr drop drop hmap + +// hmap! (insert) and hmap!+ (replace) +"value-11" 123 rot hmap!+ +"value-66" 567 rot hmap!+ +"value-33" "xyz" rot hmap! +"value-77" "qwe" rot hmap! // No effect! +dup 123 swap hmap@ type cr // value-11 +dup 567 swap hmap@ type cr // value-66 +dup "xyz" swap hmap@ type cr // value-33 +dup "qwe" swap hmap@ null? not abort"null expected" +cr + +// Delete +567 swap hmap- +789 swap hmap- +"qwe" swap hmap-? . // -1 +"rty" swap hmap-? . // 0 +`xyz swap hmap@- type // value-5 +`zyx swap hmap@- null? not abort"null expected" +null 123 rot hmap! // same as hmap- +null 321 rot hmap! +null `xyz rot hmap!+ +cr +constant hmap +hmap { .s drop drop -1 } hmapforeach +drop diff --git a/crypto/test/fift/namespaces.fif b/crypto/test/fift/namespaces.fif new file mode 100644 index 00000000..3f10f608 --- /dev/null +++ b/crypto/test/fift/namespaces.fif @@ -0,0 +1,29 @@ +namespace My + +"a" constant a +"b" constant b +"c" constant c + +a b c .s { drop } 3 times // "a" "b" "c" + +My definitions +"b-my" constant b +"c-my" constant c +"d-my" constant d + +a b c d .s { drop } 4 times // "a" "b-my" "c-my" "d-my" + +Fift definitions +a b c .s { drop } 3 times // "a" "b-my" "c-my" "d-my" + +My b My c My d .s { drop } 3 times // "b-my" "c-my" "d-my" +a b c .s { drop } 3 times // "a" "b" "c" "d" + +My definitions +a b c d .s { drop } 4 times // "a" "b-my" "c-my" "d-my" +Fift a Fift b Fift c d .s { drop } 4 times // "a" "b" "c" "d-my" + +Fift definitions +cr +My-wordlist @ +{ drop type -1 } hmapforeach drop cr // "b " "d " "c " \ No newline at end of file diff --git a/crypto/vm/arithops.cpp b/crypto/vm/arithops.cpp index 2831944b..24bb8a48 100644 --- a/crypto/vm/arithops.cpp +++ b/crypto/vm/arithops.cpp @@ -38,8 +38,8 @@ int exec_push_tinyint4(VmState* st, unsigned args) { std::string dump_push_tinyint4(CellSlice&, unsigned args) { int x = (int)((args + 5) & 15) - 5; - std::ostringstream os{"PUSHINT "}; - os << x; + std::ostringstream os; + os << "PUSHINT " << x; return os.str(); } @@ -53,8 +53,8 @@ int exec_push_tinyint8(VmState* st, unsigned args) { std::string dump_op_tinyint8(const char* op_prefix, CellSlice&, unsigned args) { int x = (signed char)args; - std::ostringstream os{op_prefix}; - os << x; + std::ostringstream os; + os << op_prefix << x; return os.str(); } @@ -68,8 +68,8 @@ int exec_push_smallint(VmState* st, unsigned args) { std::string dump_push_smallint(CellSlice&, unsigned args) { int x = (short)args; - std::ostringstream os{"PUSHINT "}; - os << x; + std::ostringstream os; + os << "PUSHINT " << x; return os.str(); } @@ -93,8 +93,8 @@ std::string dump_push_int(CellSlice& cs, unsigned args, int pfx_bits) { } cs.advance(pfx_bits); td::RefInt256 x = cs.fetch_int256(3 + l * 8); - std::ostringstream os{"PUSHINT "}; - os << x; + std::ostringstream os; + os << "PUSHINT " << x; return os.str(); } @@ -302,7 +302,7 @@ std::string dump_divmod(CellSlice&, unsigned args, bool quiet) { if (quiet) { s = "Q" + s; } - return s + "FRC"[round_mode]; + return round_mode ? s + "FRC"[round_mode] : s; } int exec_shrmod(VmState* st, unsigned args, int mode) { @@ -352,28 +352,28 @@ std::string dump_shrmod(CellSlice&, unsigned args, int mode) { if (!(args & 12) || round_mode == 3) { return ""; } - std::string s; + std::ostringstream os; + if (mode & 1) { + os << 'Q'; + } switch (args & 12) { case 4: - s = "RSHIFT"; + os << "RSHIFT"; break; case 8: - s = "MODPOW2"; + os << "MODPOW2"; break; case 12: - s = "RSHIFTMOD"; + os << "RSHIFTMOD"; break; } - if (mode & 1) { - s = "Q" + s; + if (round_mode) { + os << "FRC"[round_mode]; } - s += "FRC"[round_mode]; if (mode & 2) { - char buff[8]; - sprintf(buff, " %d", y); - s += buff; + os << ' ' << y; } - return s; + return os.str(); } int exec_muldivmod(VmState* st, unsigned args, int quiet) { @@ -417,7 +417,7 @@ std::string dump_muldivmod(CellSlice&, unsigned args, bool quiet) { if (quiet) { s = "Q" + s; } - return s + "FRC"[round_mode]; + return round_mode ? s + "FRC"[round_mode] : s; } int exec_mulshrmod(VmState* st, unsigned args, int mode) { @@ -474,28 +474,28 @@ std::string dump_mulshrmod(CellSlice&, unsigned args, int mode) { if (!(args & 12) || round_mode == 3) { return ""; } - std::string s; + std::ostringstream os; + if (mode & 1) { + os << 'Q'; + } switch (args & 12) { case 4: - s = "MULRSHIFT"; + os << "MULRSHIFT"; break; case 8: - s = "MULMODPOW2"; + os << "MULMODPOW2"; break; case 12: - s = "MULRSHIFTMOD"; + os << "MULRSHIFTMOD"; break; } - if (mode & 1) { - s = "Q" + s; + if (round_mode) { + os << "FRC"[round_mode]; } - s += "FRC"[round_mode]; if (mode & 2) { - char buff[8]; - sprintf(buff, " %d", y); - s += buff; + os << ' ' << y; } - return s; + return os.str(); } int exec_shldivmod(VmState* st, unsigned args, int mode) { @@ -542,19 +542,25 @@ int exec_shldivmod(VmState* st, unsigned args, int mode) { return 0; } -std::string dump_shldivmod(CellSlice&, unsigned args, bool quiet) { +std::string dump_shldivmod(CellSlice&, unsigned args, int mode) { + int y = -1; + if (mode & 2) { + y = (args & 0xff) + 1; + args >>= 8; + } int round_mode = (int)(args & 3); if (!(args & 12) || round_mode == 3) { return ""; } - std::string s = (args & 4) ? "LSHIFTDIV" : "LSHIFT"; - if (args & 8) { - s += "MOD"; + std::ostringstream os; + os << (mode & 1 ? "Q" : "") << (args & 4 ? "LSHIFTDIV" : "LSHIFT") << (args & 8 ? "MOD" : ""); + if (round_mode) { + os << "FRC"[round_mode]; } - if (quiet) { - s = "Q" + s; + if (y >= 0) { + os << ' ' << y; } - return s + "FRC"[round_mode]; + return os.str(); } void register_div_ops(OpcodeTable& cp0) { diff --git a/crypto/vm/box.hpp b/crypto/vm/box.hpp index 68b131ce..0d676cae 100644 --- a/crypto/vm/box.hpp +++ b/crypto/vm/box.hpp @@ -30,7 +30,7 @@ class Box : public td::CntObject { Box(const Box&) = default; Box(Box&&) = default; template - Box(Args... args) : data_{std::move(args...)} { + Box(Args&&... args) : data_{std::forward(args)...} { } ~Box() override = default; Box(const StackEntry& data) : data_(data) { diff --git a/crypto/vm/cellops.cpp b/crypto/vm/cellops.cpp index 1dfb69fb..9e10e072 100644 --- a/crypto/vm/cellops.cpp +++ b/crypto/vm/cellops.cpp @@ -103,7 +103,8 @@ std::string dump_push_slice_common(CellSlice& cs, unsigned data_bits, unsigned r cs.advance(pfx_bits); auto slice = cs.fetch_subslice(data_bits, refs); slice.unique_write().remove_trailing(); - std::ostringstream os{name}; + std::ostringstream os; + os << name; slice->dump_hex(os, 1, false); return os.str(); } @@ -188,7 +189,8 @@ std::string dump_push_cont(CellSlice& cs, unsigned args, int pfx_bits) { } cs.advance(pfx_bits); auto slice = cs.fetch_subslice(data_bits, refs); - std::ostringstream os{"PUSHCONT "}; + std::ostringstream os; + os << "PUSHCONT "; slice->dump_hex(os, 1, false); return os.str(); } @@ -219,7 +221,8 @@ std::string dump_push_cont_simple(CellSlice& cs, unsigned args, int pfx_bits) { } cs.advance(pfx_bits); auto slice = cs.fetch_subslice(data_bits); - std::ostringstream os{"PUSHCONT "}; + std::ostringstream os; + os << "PUSHCONT "; slice->dump_hex(os, 1, false); return os.str(); } @@ -1060,8 +1063,8 @@ int exec_load_int_fixed2(VmState* st, unsigned args) { } std::string dump_load_int_fixed2(CellSlice&, unsigned args) { - std::ostringstream os{args & 0x200 ? "PLD" : "LD"}; - os << (args & 0x100 ? 'U' : 'I'); + std::ostringstream os; + os << (args & 0x200 ? "PLD" : "LD") << (args & 0x100 ? 'U' : 'I'); if (args & 0x400) { os << 'Q'; } @@ -1081,9 +1084,9 @@ int exec_preload_uint_fixed_0e(VmState* st, unsigned args) { } std::string dump_preload_uint_fixed_0e(CellSlice&, unsigned args) { - std::ostringstream os{"PLDUZ "}; + std::ostringstream os; unsigned bits = ((args & 7) + 1) << 5; - os << bits; + os << "PLDUZ " << bits; return os.str(); } @@ -1108,7 +1111,8 @@ int exec_load_slice_fixed2(VmState* st, unsigned args) { std::string dump_load_slice_fixed2(CellSlice&, unsigned args) { unsigned bits = (args & 0xff) + 1; - std::ostringstream os{args & 0x100 ? "PLDSLICE" : "LDSLICE"}; + std::ostringstream os; + os << (args & 0x100 ? "PLDSLICE" : "LDSLICE"); if (args & 0x200) { os << 'Q'; } diff --git a/crypto/vm/contops.cpp b/crypto/vm/contops.cpp index c3cb36bb..a6a44ad3 100644 --- a/crypto/vm/contops.cpp +++ b/crypto/vm/contops.cpp @@ -378,8 +378,8 @@ int exec_if_bit_jmp(VmState* st, unsigned args) { } std::string dump_if_bit_jmp(CellSlice& cs, unsigned args) { - std::ostringstream os{args & 0x20 ? "IFN" : " IF"}; - os << "BITJMP " << (args & 0x1f); + std::ostringstream os; + os << "IF" << (args & 0x20 ? "N" : "") << "BITJMP " << (args & 0x1f); return os.str(); } @@ -408,8 +408,8 @@ std::string dump_if_bit_jmpref(CellSlice& cs, unsigned args, int pfx_bits) { } cs.advance(pfx_bits); cs.advance_refs(1); - std::ostringstream os{args & 0x20 ? "IFN" : " IF"}; - os << "BITJMPREF " << (args & 0x1f); + std::ostringstream os; + os << "IF" << (args & 0x20 ? "N" : "") << "BITJMPREF " << (args & 0x1f); return os.str(); } @@ -597,8 +597,8 @@ int exec_setcontargs(VmState* st, unsigned args) { std::string dump_setcontargs(CellSlice& cs, unsigned args, const char* name) { int copy = (args >> 4) & 15, more = ((args + 1) & 15) - 1; - std::ostringstream os{name}; - os << ' ' << copy << ',' << more; + std::ostringstream os; + os << name << ' ' << copy << ',' << more; return os.str(); } @@ -1065,8 +1065,8 @@ std::string dump_throw_any(CellSlice& cs, unsigned args) { bool has_param = args & 1; bool has_cond = args & 6; bool throw_cond = args & 2; - std::ostringstream os{has_param ? "THROWARG" : "THROW"}; - os << "ANY"; + std::ostringstream os; + os << "THROW" << (has_param ? "ARG" : "") << "ANY"; if (has_cond) { os << (throw_cond ? "IF" : "IFNOT"); } diff --git a/crypto/vm/dictops.cpp b/crypto/vm/dictops.cpp index c798b333..d0ea8daa 100644 --- a/crypto/vm/dictops.cpp +++ b/crypto/vm/dictops.cpp @@ -172,7 +172,8 @@ int exec_load_dict(VmState* st, unsigned args) { } std::string dump_dictop(unsigned args, const char* name) { - std::ostringstream os{"DICT"}; + std::ostringstream os; + os << "DICT"; if (args & 4) { os << (args & 2 ? 'U' : 'I'); } @@ -184,7 +185,8 @@ std::string dump_dictop(unsigned args, const char* name) { } std::string dump_dictop2(unsigned args, const char* name) { - std::ostringstream os{"DICT"}; + std::ostringstream os; + os << "DICT"; if (args & 2) { os << (args & 1 ? 'U' : 'I'); } @@ -193,7 +195,8 @@ std::string dump_dictop2(unsigned args, const char* name) { } std::string dump_subdictop2(unsigned args, const char* name) { - std::ostringstream os{"SUBDICT"}; + std::ostringstream os; + os << "SUBDICT"; if (args & 2) { os << (args & 1 ? 'U' : 'I'); } @@ -508,7 +511,8 @@ int exec_dict_getmin(VmState* st, unsigned args) { } std::string dump_dictop_getnear(CellSlice& cs, unsigned args) { - std::ostringstream os{"DICT"}; + std::ostringstream os; + os << "DICT"; if (args & 8) { os << (args & 4 ? 'U' : 'I'); } @@ -637,8 +641,8 @@ std::string dump_push_const_dict(CellSlice& cs, int pfx_bits, const char* name) cs.advance(pfx_bits - 11); auto slice = cs.fetch_subslice(1, 1); int n = (int)cs.fetch_ulong(10); - std::ostringstream os{name}; - os << ' ' << n << " ("; + std::ostringstream os; + os << name << ' ' << n << " ("; slice->dump_hex(os, false); os << ')'; return os.str(); diff --git a/crypto/vm/opctable.cpp b/crypto/vm/opctable.cpp index d4f0f3e9..0ee33385 100644 --- a/crypto/vm/opctable.cpp +++ b/crypto/vm/opctable.cpp @@ -357,32 +357,32 @@ using namespace std::placeholders; dump_arg_instr_func_t dump_1sr(std::string prefix, std::string suffix) { return [prefix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << 's' << (args & 15) << suffix; + std::ostringstream os; + os << prefix << 's' << (args & 15) << suffix; return os.str(); }; } dump_arg_instr_func_t dump_1sr_l(std::string prefix, std::string suffix) { return [prefix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << 's' << (args & 255) << suffix; + std::ostringstream os; + os << prefix << 's' << (args & 255) << suffix; return os.str(); }; } dump_arg_instr_func_t dump_2sr(std::string prefix, std::string suffix) { return [prefix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << 's' << ((args >> 4) & 15) << ",s" << (args & 15) << suffix; + std::ostringstream os; + os << prefix << 's' << ((args >> 4) & 15) << ",s" << (args & 15) << suffix; return os.str(); }; } dump_arg_instr_func_t dump_2sr_adj(unsigned adj, std::string prefix, std::string suffix) { return [adj, prefix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << 's' << (int)((args >> 4) & 15) - (int)((adj >> 4) & 15) << ",s" << (int)(args & 15) - (int)(adj & 15) + std::ostringstream os; + os << prefix << 's' << (int)((args >> 4) & 15) - (int)((adj >> 4) & 15) << ",s" << (int)(args & 15) - (int)(adj & 15) << suffix; return os.str(); }; @@ -390,16 +390,16 @@ dump_arg_instr_func_t dump_2sr_adj(unsigned adj, std::string prefix, std::string dump_arg_instr_func_t dump_3sr(std::string prefix, std::string suffix) { return [prefix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << 's' << ((args >> 8) & 15) << ",s" << ((args >> 4) & 15) << ",s" << (args & 15) << suffix; + std::ostringstream os; + os << prefix << 's' << ((args >> 8) & 15) << ",s" << ((args >> 4) & 15) << ",s" << (args & 15) << suffix; return os.str(); }; } dump_arg_instr_func_t dump_3sr_adj(unsigned adj, std::string prefix, std::string suffix) { return [adj, prefix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << 's' << (int)((args >> 8) & 15) - (int)((adj >> 8) & 15) << ",s" + std::ostringstream os; + os << prefix << 's' << (int)((args >> 8) & 15) - (int)((adj >> 8) & 15) << ",s" << (int)((args >> 4) & 15) - (int)((adj >> 4) & 15) << ",s" << (int)(args & 15) - (int)(adj & 15) << suffix; return os.str(); }; @@ -407,40 +407,40 @@ dump_arg_instr_func_t dump_3sr_adj(unsigned adj, std::string prefix, std::string dump_arg_instr_func_t dump_1c(std::string prefix, std::string suffix) { return [prefix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << (args & 15) << suffix; + std::ostringstream os; + os << prefix << (args & 15) << suffix; return os.str(); }; } dump_arg_instr_func_t dump_1c_l_add(int adj, std::string prefix, std::string suffix) { return [adj, prefix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << (int)(args & 255) + adj << suffix; + std::ostringstream os; + os << prefix << (int)(args & 255) + adj << suffix; return os.str(); }; } dump_arg_instr_func_t dump_1c_and(unsigned mask, std::string prefix, std::string suffix) { return [mask, prefix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << (args & mask) << suffix; + std::ostringstream os; + os << prefix << (args & mask) << suffix; return os.str(); }; } dump_arg_instr_func_t dump_2c(std::string prefix, std::string interfix, std::string suffix) { return [prefix, interfix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << ((args >> 4) & 15) << interfix << (args & 15) << suffix; + std::ostringstream os; + os << prefix << ((args >> 4) & 15) << interfix << (args & 15) << suffix; return os.str(); }; } dump_arg_instr_func_t dump_2c_add(unsigned add, std::string prefix, std::string interfix, std::string suffix) { return [add, prefix, interfix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << ((args >> 4) & 15) + ((add >> 4) & 15) << interfix << (args & 15) + (add & 15) << suffix; + std::ostringstream os; + os << prefix << ((args >> 4) & 15) + ((add >> 4) & 15) << interfix << (args & 15) + (add & 15) << suffix; return os.str(); }; } diff --git a/crypto/vm/stack.hpp b/crypto/vm/stack.hpp index 2de7c1e4..bfc9e7ac 100644 --- a/crypto/vm/stack.hpp +++ b/crypto/vm/stack.hpp @@ -103,6 +103,9 @@ class StackEntry { } StackEntry(td::RefInt256 int_ref) : ref(std::move(int_ref)), tp(t_int) { } + StackEntry(Ref> str_ref, bool bytes = false) + : ref(std::move(str_ref)), tp(bytes ? t_bytes : t_string) { + } StackEntry(std::string str, bool bytes = false) : ref(), tp(bytes ? t_bytes : t_string) { ref = Ref>{true, std::move(str)}; } diff --git a/crypto/vm/stackops.cpp b/crypto/vm/stackops.cpp index 0f5be6d4..a52a90e2 100644 --- a/crypto/vm/stackops.cpp +++ b/crypto/vm/stackops.cpp @@ -75,8 +75,8 @@ std::string dump_xchg(CellSlice&, unsigned args) { if (!x || x >= y) { return ""; } - std::ostringstream os{"XCHG s"}; - os << x << ",s" << y; + std::ostringstream os; + os << "XCHG s" << x << ",s" << y; return os.str(); } @@ -92,7 +92,7 @@ int exec_xchg1(VmState* st, unsigned args) { int exec_dup(VmState* st) { Stack& stack = st->get_stack(); - VM_LOG(st) << "execute DUP\n"; + VM_LOG(st) << "execute DUP"; stack.check_underflow(1); stack.push(stack.fetch(0)); return 0; @@ -100,7 +100,7 @@ int exec_dup(VmState* st) { int exec_over(VmState* st) { Stack& stack = st->get_stack(); - VM_LOG(st) << "execute OVER\n"; + VM_LOG(st) << "execute OVER"; stack.check_underflow(2); stack.push(stack.fetch(1)); return 0; @@ -126,7 +126,7 @@ int exec_push_l(VmState* st, unsigned args) { int exec_drop(VmState* st) { Stack& stack = st->get_stack(); - VM_LOG(st) << "execute DROP\n"; + VM_LOG(st) << "execute DROP"; stack.check_underflow(1); stack.pop(); return 0; @@ -134,7 +134,7 @@ int exec_drop(VmState* st) { int exec_nip(VmState* st) { Stack& stack = st->get_stack(); - VM_LOG(st) << "execute NIP\n"; + VM_LOG(st) << "execute NIP"; stack.check_underflow(2); stack.pop(stack[1]); return 0; diff --git a/test/regression-tests.ans b/test/regression-tests.ans index 93fa16ae..84372465 100644 --- a/test/regression-tests.ans +++ b/test/regression-tests.ans @@ -8,7 +8,11 @@ Test_Fift_bug_ufits_default 51bf5a9f1ed7633a193f6fdd17a7a3af8e032dfe72a9669c85e8 Test_Fift_contfrac_default 09ebce5c91bcb70696c6fb6981d82dc3b9e3444dab608a7a1b044c0ddd778a96 Test_Fift_test_default 4e44b3382963ec89f7b5c8f2ebd85da3bc8aebad5b49f5b11b14075061477b4d Test_Fift_test_dict_default a9c8cbcfdece5573185022cea07f59f1bc404e5d879e5157a5745757f8ee0525 +Test_Fift_test_disasm_default dacaa555f5f217b2373e01e3bcd59634e480f5759dcc43edbdef35273ae38492 +Test_Fift_test_fiftext_default 2b0db5d4d4bfbc705b959cc787540d7b3a21a71469eac54756e76953f0d9afca Test_Fift_test_fixed_default 278a19d56b773102caf5c1fe2997ea6c8d0d9e720eff8503feede6398a197eec +Test_Fift_test_hmap_default c269246882039824bb5822e896c3e6e82ef8e1251b6b251f5af8ea9fb8d05067 +Test_Fift_test_namespaces_default e6419619c51332fb5e8bf22043ef415db686c47fe24f03061e5ad831014e7c6c Test_Fift_test_sort2_default 9b57d47e6a10e7d1bbb565db35400debf2f963031f434742a702ec76555a5d3a Test_Fift_test_sort_default 9b57d47e6a10e7d1bbb565db35400debf2f963031f434742a702ec76555a5d3a Test_Fift_testvm2_default 8a6e35fc0224398be9d2de39d31c86ea96965ef1eca2aa9e0af2303150ed4a7b From c74c99774ea9292a1c81f69404566564e719ad27 Mon Sep 17 00:00:00 2001 From: Andrei Kostylev Date: Fri, 10 Mar 2023 14:17:17 +0300 Subject: [PATCH 39/46] fix null serialization in extern tvm emulator (#644) --- emulator/emulator-extern.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/emulator/emulator-extern.cpp b/emulator/emulator-extern.cpp index e589032f..238ff670 100644 --- a/emulator/emulator-extern.cpp +++ b/emulator/emulator-extern.cpp @@ -398,7 +398,11 @@ const char *tvm_emulator_send_external_message(void *tvm_emulator, const char *m } else { json_obj("missing_library", td::Bits256(result.missing_library).to_hex()); } - json_obj("actions", cell_to_boc_b64(result.actions).move_as_ok()); + if (result.actions.is_null()) { + json_obj("actions", td::JsonNull()); + } else { + json_obj("actions", cell_to_boc_b64(result.actions).move_as_ok()); + } json_obj("new_code", cell_to_boc_b64(result.new_state.code).move_as_ok()); json_obj("new_data", cell_to_boc_b64(result.new_state.data).move_as_ok()); json_obj.leave(); @@ -427,7 +431,11 @@ const char *tvm_emulator_send_internal_message(void *tvm_emulator, const char *m } else { json_obj("missing_library", td::Bits256(result.missing_library).to_hex()); } - json_obj("actions", cell_to_boc_b64(result.actions).move_as_ok()); + if (result.actions.is_null()) { + json_obj("actions", td::JsonNull()); + } else { + json_obj("actions", cell_to_boc_b64(result.actions).move_as_ok()); + } json_obj("new_code", cell_to_boc_b64(result.new_state.code).move_as_ok()); json_obj("new_data", cell_to_boc_b64(result.new_state.data).move_as_ok()); json_obj.leave(); From fbb1e548f7b5858bab83807e649376bd1ae93d1e Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Mon, 13 Mar 2023 16:46:54 +0300 Subject: [PATCH 40/46] Fix msg_cell on stack for SmartContract::send_*_message (#647) * Fix msg_cell on stack for SmartContract::send_*_message * Fix src address in SmartContract::send_internal_message --- crypto/smc-envelope/SmartContract.cpp | 82 ++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 7 deletions(-) diff --git a/crypto/smc-envelope/SmartContract.cpp b/crypto/smc-envelope/SmartContract.cpp index 713caf9c..46e90093 100644 --- a/crypto/smc-envelope/SmartContract.cpp +++ b/crypto/smc-envelope/SmartContract.cpp @@ -40,14 +40,82 @@ int SmartContract::Answer::output_actions_count(td::Ref list) { } namespace { -td::Ref prepare_vm_stack(td::RefInt256 amount, td::Ref body) { +td::Ref build_internal_message(td::RefInt256 amount, td::Ref body, SmartContract::Args args) { + vm::CellBuilder cb; + if (args.address) { + td::BigInt256 dest_addr; + dest_addr.import_bits((*args.address).addr.as_bitslice()); + cb.store_ones(1) + .store_zeroes(2) + .store_long((*args.address).workchain, 8) + .store_int256(dest_addr, 256); + } + auto address = cb.finalize(); + + vm::CellBuilder b; + b.store_long(0b0110, 4); // 0 ihr_disabled:Bool bounce:Bool bounced:Bool + // use -1:00..00 as src:MsgAddressInt + // addr_std$10 anycast:(Maybe Anycast) workchain_id:int8 address:bits256 = MsgAddressInt; + b.store_long(0b100, 3); b.store_ones(8); b.store_zeroes(256); + b.append_cellslice(address); // dest:MsgAddressInt + unsigned len = (((unsigned)amount->bit_size(false) + 7) >> 3); + b.store_long_bool(len, 4) && b.store_int256_bool(*amount, len * 8, false); // grams:Grams + b.store_zeroes(1 + 4 + 4 + 64 + 32 + 1); // extre, ihr_fee, fwd_fee, created_lt, created_at, init + // body:(Either X ^X) + if (b.remaining_bits() >= 1 + (*body).size() && b.remaining_refs() >= (*body).size_refs()) { + b.store_zeroes(1); + b.append_cellslice(body); + } else { + b.store_ones(1); + b.store_ref(vm::CellBuilder().append_cellslice(body).finalize_novm()); + } + return b.finalize_novm(); +} + +td::Ref build_external_message(td::RefInt256 amount, td::Ref body, SmartContract::Args args) { + vm::CellBuilder cb; + if (args.address) { + td::BigInt256 dest_addr; + dest_addr.import_bits((*args.address).addr.as_bitslice()); + cb.store_ones(1) + .store_zeroes(2) + .store_long((*args.address).workchain, 8) + .store_int256(dest_addr, 256); + } + auto address = cb.finalize(); + + vm::CellBuilder b; + b.store_long(0b1000, 4); // ext_in_msg_info$10 src:MsgAddressExt + b.append_cellslice(address); // dest:MsgAddressInt + b.store_zeroes(4); //import_fee:Grams + b.store_zeroes(1); // init + // body:(Either X ^X) + if (b.remaining_bits() >= 1 + (*body).size() && b.remaining_refs() >= (*body).size_refs()) { + b.store_zeroes(1); + b.append_cellslice(body); + } else { + b.store_ones(1); + b.store_ref(vm::CellBuilder().append_cellslice(body).finalize_novm()); + } + return b.finalize_novm(); +} + +td::Ref prepare_vm_stack(td::RefInt256 amount, td::Ref body, SmartContract::Args args, int selector) { td::Ref stack_ref{true}; td::RefInt256 acc_addr{true}; //CHECK(acc_addr.write().import_bits(account.addr.cbits(), 256)); vm::Stack& stack = stack_ref.write(); - stack.push_int(td::make_refint(10000000000)); - stack.push_int(std::move(amount)); - stack.push_cell(vm::CellBuilder().finalize()); + if(args.balance) { + stack.push_int(td::make_refint(args.balance)); + } else { + stack.push_int(td::make_refint(10000000000)); + } + stack.push_int(amount); + if(selector == 0) { + stack.push_cell(build_internal_message(amount, body, args)); + } else { + stack.push_cell(build_external_message(amount, body, args)); + } stack.push_cellslice(std::move(body)); return stack_ref; } @@ -143,7 +211,7 @@ SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref= VERBOSITY_NAME(DEBUG)) { LOG(DEBUG) << "VM log\n" << logger.res; @@ -248,10 +316,10 @@ SmartContract::Answer SmartContract::run_get_method(td::Slice method, Args args) SmartContract::Answer SmartContract::send_external_message(td::Ref cell, Args args) { return run_method( - args.set_stack(prepare_vm_stack(td::make_refint(0), vm::load_cell_slice_ref(cell))).set_method_id(-1)); + args.set_stack(prepare_vm_stack(td::make_refint(0), vm::load_cell_slice_ref(cell), args, -1)).set_method_id(-1)); } SmartContract::Answer SmartContract::send_internal_message(td::Ref cell, Args args) { return run_method( - args.set_stack(prepare_vm_stack(td::make_refint(args.amount), vm::load_cell_slice_ref(cell))).set_method_id(0)); + args.set_stack(prepare_vm_stack(td::make_refint(args.amount), vm::load_cell_slice_ref(cell), args, 0)).set_method_id(0)); } } // namespace ton From 4d5ded5761d7535bf3579e0b1b0d5541064d581e Mon Sep 17 00:00:00 2001 From: SpyCheese Date: Mon, 13 Mar 2023 13:48:48 +0000 Subject: [PATCH 41/46] Asm.fif improvements (#631) (#645) * Asm.fif improvements (#631) Add missing opcodes, CUSTOMOP, disallow 256 PUSHPOW256, recursive PROGRAM, require-asm-fif-version * Fix nested PROGRAM, add test * Simplify require-asm-fif-version --------- Co-authored-by: EmelyanenkoK --- crypto/fift/lib/Asm.fif | 101 +++++++++++++++++++++++- crypto/test/fift.cpp | 4 + crypto/test/fift/asm-nested-program.fif | 93 ++++++++++++++++++++++ test/regression-tests.ans | 1 + 4 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 crypto/test/fift/asm-nested-program.fif diff --git a/crypto/fift/lib/Asm.fif b/crypto/fift/lib/Asm.fif index 8898d3fe..5922ee18 100644 --- a/crypto/fift/lib/Asm.fif +++ b/crypto/fift/lib/Asm.fif @@ -2,6 +2,8 @@ library TVM_Asm // simple TVM Assembler namespace Asm Asm definitions +"0.4.3" constant asm-fif-version + variable @atend variable @was-split false @was-split ! @@ -253,7 +255,7 @@ x{7F} @Defop TRUE } cond } cond @addopb } dup : PUSHINT : INT -{ libref } : >libref +{ dup "." $pos dup -1 = + { drop 0 } + { $| 1 $| nip swap (number) 1- abort"invalid version" + dup dup 0 < swap 999 > or abort"invalid version" + } + cond +} : parse-version-level + +{ + 0 swap + "." $+ + { swap 1000 * swap parse-version-level rot + swap } 3 times + "" $= not abort"invalid version" +} : parse-asm-fif-version + +{ + dup =: required-version parse-asm-fif-version + asm-fif-version parse-asm-fif-version + = 1+ { + "Required Asm.fif version: " @' required-version "; actual Asm.fif version: " asm-fif-version $+ $+ $+ abort + } if +} : require-asm-fif-version + +{ + dup =: required-version parse-asm-fif-version + asm-fif-version parse-asm-fif-version + swap + >= 1+ { + "Required Asm.fif version: " @' required-version "; actual Asm.fif version: " asm-fif-version $+ $+ $+ abort + } if +} : require-asm-fif-version>= + + Fift definitions Asm ' <{ : <{ ' PROGRAM{ : PROGRAM{ +' asm-fif-version : asm-fif-version +' require-asm-fif-version : require-asm-fif-version +' require-asm-fif-version>= : require-asm-fif-version>= Fift diff --git a/crypto/test/fift.cpp b/crypto/test/fift.cpp index e6cdc9f3..098c5561 100644 --- a/crypto/test/fift.cpp +++ b/crypto/test/fift.cpp @@ -126,3 +126,7 @@ TEST(Fift, test_fiftext) { TEST(Fift, test_namespaces) { run_fift("namespaces.fif"); } + +TEST(Fift, test_asm_nested_program) { + run_fift("asm-nested-program.fif"); +} diff --git a/crypto/test/fift/asm-nested-program.fif b/crypto/test/fift/asm-nested-program.fif new file mode 100644 index 00000000..ef604d72 --- /dev/null +++ b/crypto/test/fift/asm-nested-program.fif @@ -0,0 +1,93 @@ +"Asm.fif" include + +// Four programs: +// 4 contains 2 and 3, 2 contains 1 + +// Program #1 +PROGRAM{ + DECLPROC foo1 + DECLPROC foo2 + DECLPROC foo3 + DECLPROC main + foo1 PROC:<{ MUL INC }> + foo2 PROCINLINE:<{ PLDREF }> + foo3 PROC:<{ CTOS foo2 INLINECALLDICT CTOS 32 PLDU }> + main PROC:<{ 0 PUSHINT }> +}END>c constant code-1 + +// Program #2 +PROGRAM{ + DECLPROC foo3 + DECLPROC foo4 + DECLPROC main + foo3 PROC:<{ code-1 PUSHREF }> + foo4 PROC:<{ CTOS 8 PLDU }> + main PROC:<{ foo3 CALLDICT foo4 CALLDICT NEWC ROT STUX }> +}END>c constant code-2 + +// Program #3 +PROGRAM{ + DECLPROC foo1 + DECLPROC foo4 + DECLPROC main + foo1 PROC:<{ DUP 137 PUSHINT MUL PAIR }> + foo4 PROC:<{ UNPAIR SWAP DIV }> + main PROC:<{ 70 PUSHINT DIV }> +}END>c constant code-3 + +// Program #4 +PROGRAM{ + DECLPROC foo2 + DECLPROC foo3 + DECLPROC foo5 + DECLPROC main + foo2 PROC:<{ code-2 PUSHREF }> + foo3 PROC:<{ code-3 PUSHREF }> + foo5 PROC:<{ foo2 CALLDICT CTOS 8 PLDU 1 RSHIFT# }> + main PROC:<{ foo5 CALLDICT 5 MULCONST }> +}END>c + +.dump cr + +// Program #4, nested +PROGRAM{ + DECLPROC foo2 + DECLPROC foo3 + DECLPROC foo5 + DECLPROC main + foo2 PROC:<{ + PROGRAM{ + DECLPROC foo3 + DECLPROC foo4 + DECLPROC main + foo3 PROC:<{ + PROGRAM{ + DECLPROC foo1 + DECLPROC foo2 + DECLPROC foo3 + DECLPROC main + foo1 PROC:<{ MUL INC }> + foo2 PROCINLINE:<{ PLDREF }> + foo3 PROC:<{ CTOS foo2 INLINECALLDICT CTOS 32 PLDU }> + main PROC:<{ 0 PUSHINT }> + }END>c PUSHREF + }> + foo4 PROC:<{ CTOS 8 PLDU }> + main PROC:<{ foo3 CALLDICT foo4 CALLDICT NEWC ROT STUX }> + }END>c PUSHREF + }> + foo3 PROC:<{ + PROGRAM{ + DECLPROC foo1 + DECLPROC foo4 + DECLPROC main + foo1 PROC:<{ DUP 137 PUSHINT MUL PAIR }> + foo4 PROC:<{ UNPAIR SWAP DIV }> + main PROC:<{ 70 PUSHINT DIV }> + }END>c PUSHREF + }> + foo5 PROC:<{ foo2 CALLDICT CTOS 8 PLDU 1 RSHIFT# }> + main PROC:<{ foo5 CALLDICT 5 MULCONST }> +}END>c + +.dump cr \ No newline at end of file diff --git a/test/regression-tests.ans b/test/regression-tests.ans index 84372465..61191ea4 100644 --- a/test/regression-tests.ans +++ b/test/regression-tests.ans @@ -6,6 +6,7 @@ Test_Fift_bug_div_default 1ac42861ce96b2896001c587f65e9afe1617db48859f19c2f4e306 Test_Fift_bug_newlize_default e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 Test_Fift_bug_ufits_default 51bf5a9f1ed7633a193f6fdd17a7a3af8e032dfe72a9669c85e8639aa8a7c195 Test_Fift_contfrac_default 09ebce5c91bcb70696c6fb6981d82dc3b9e3444dab608a7a1b044c0ddd778a96 +Test_Fift_test_asm_nested_program_default 2a19decac67adb719c444ab42879a5d894447d450d1998848c469605531076ad Test_Fift_test_default 4e44b3382963ec89f7b5c8f2ebd85da3bc8aebad5b49f5b11b14075061477b4d Test_Fift_test_dict_default a9c8cbcfdece5573185022cea07f59f1bc404e5d879e5157a5745757f8ee0525 Test_Fift_test_disasm_default dacaa555f5f217b2373e01e3bcd59634e480f5759dcc43edbdef35273ae38492 From 7da30e1e7ffecb5cb7f5029e8898e0f884bfc623 Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Mon, 13 Mar 2023 16:49:30 +0300 Subject: [PATCH 42/46] Add PRNG with normal distribution to mathlib.fc (#646) * Add random with normal distribution * Fix hex arguments in mathlib testcases --- crypto/func/auto-tests/run_tests.py | 15 ++ crypto/func/auto-tests/tests/test-math.fc | 275 ++++++++++++++++++++++ crypto/smartcont/mathlib.fc | 126 +++++++--- crypto/smartcont/test-math.fc | 100 -------- 4 files changed, 385 insertions(+), 131 deletions(-) create mode 100644 crypto/func/auto-tests/tests/test-math.fc delete mode 100644 crypto/smartcont/test-math.fc diff --git a/crypto/func/auto-tests/run_tests.py b/crypto/func/auto-tests/run_tests.py index ae9c990c..0f12332d 100644 --- a/crypto/func/auto-tests/run_tests.py +++ b/crypto/func/auto-tests/run_tests.py @@ -66,6 +66,21 @@ for ti, tf in enumerate(tests): print("Error: no test cases", file=sys.stderr) exit(2) + # preprocess arithmetics in input + for i in range(len(cases)): + inputs = cases[i][1].split(" ") + processed_inputs = "" + for in_arg in inputs: + if "x{" in in_arg: + processed_inputs += in_arg + continue + # filter and execute + # is it safe enough? + filtered_in = "".join(filter(lambda x: x in "0x123456789()+-*/<>", in_arg)) + if(filtered_in): + processed_inputs += str(eval(filtered_in)) + " "; + cases[i][1] = processed_inputs.strip() + with open(RUNNER_FIF, "w") as f: print("\"%s\" include (tuple, ()) ~tset(tuple t, int idx, X val) asm(t val idx) "SETINDEXVAR"; + +;; computes 1-acos(x)/Pi by a very simple, extremely slow (~70k gas) and imprecise method +;; fixed256 acos_prepare_slow(fixed255 x); +int acos_prepare_slow_f255(int x) inline { + x -= (x == 0); + int t = 1; + repeat (255) { + t = t * sgn(x) * 2 + 1; ;; decode Gray code (sgn(x_0), sgn(x_1), ...) + x = (-1 << 255) - muldivr(x, - x, 1 << 254); ;; iterate x := 2*x^2 - 1 = cos(2*acos(x)) + } + return abs(t); +} + +;; extremely slow (~70k gas) and somewhat imprecise (very imprecise when x is small), for testing only +;; fixed254 acos_slow(fixed255 x); +int acos_slow_f255(int x) inline_ref { + int t = acos_prepare_slow_f255(x); + return - mulrshiftr256(t + (-1 << 256), Pi_const_f254()); +} + +;; fixed255 asin_slow(fixed255 x); +int asin_slow_f255(int x) inline_ref { + int t = acos_prepare_slow_f255(abs(x)) % (1 << 255); + return muldivr(t, Pi_const_f254(), 1 << 255) * sgn(x); +} + +tuple test_nrand(int n) inline_ref { + tuple t = empty_tuple(); + repeat (255) { + t~tpush(0); + } + repeat (n) { + int x = fixed248::nrand(); + int bucket = (abs(x) >> 243); ;; 255 buckets starting from x=0, each 1/32 wide + t~tset(bucket, t.at(bucket) + 1); + } + return t; +} + +int geom_mean_test(int x, int y) method_id(10000) { + return geom_mean(x, y); +} +int tan_f260_test(int x) method_id(10001) { + return tan_f260(x); +} +(int, int) sincosm1_f259_test(int x) method_id(10002) { + return sincosm1_f259(x); +} +(int, int) sincosn_f256_test(int x, int y) method_id(10003) { + return sincosn_f256(x, y); +} +(int, int) sincosm1_f256_test(int x) method_id(10004) { + return sincosm1_f256(x); +} +(int, int) tan_aux_f256_test(x) method_id(10005) { + return tan_aux_f256(x); +} +(int) fixed248::tan_test(x) method_id(10006) { + return fixed248::tan(x); +} +{- + (int) atanh_alt_f258_test(x) method_id(10007) { + return atanh_alt_f258(x); + } +-} +(int) atanh_f258_test(x, y) method_id(10008) { + return atanh_f258(x, y); +} +(int) atanh_f261_test(x, y) method_id(10009) { + return atanh_f261(x, y); +} + +(int, int) log2_aux_f256_test(x) method_id(10010) { + return log2_aux_f256(x); +} +(int, int) log_aux_f256_test(x) method_id(10011) { + return log_aux_f256(x); +} +int fixed248::pow_test(x, y) method_id(10012) { + return fixed248::pow(x, y); +} +int exp_log_div(x, y) method_id(10013) { + return fixed248::exp(fixed248::log(x << 248) ~/ y); +} +int fixed248::log_test(x) method_id(10014) { + return fixed248::log(x); +} +(int,int) log_aux_f257_test(x) method_id(10015) { + return log_aux_f257(x); +} +(int,int) fixed248::sincos_test(x) method_id(10016) { + return fixed248::sincos(x); +} +int fixed248::exp_test(x) method_id(10017) { + return fixed248::exp(x); +} +int fixed248::exp2_test(x) method_id(10018) { + return fixed248::exp2(x); +} +int expm1_f257_test(x) method_id(10019) { + return expm1_f257(x); +} +int atan_f255_test(x) method_id(10020) { + return atan_f255(x); +} +int atan_f259_test(x, n) method_id(10021) { + return atan_f259(x, n); +} +(int, int) atan_aux_f256_test(x) method_id(10022) { + return atan_aux_f256(x); +} +int asin_f255_test(x) method_id(10023) { + return asin_f255(x); +} +int asin_slow_f255_test(x) method_id(10024) { + return asin_slow_f255(x); +} +int acos_f255_test(x) method_id(10025) { + return acos_f255(x); +} +int acos_slow_f255_test(x) method_id(10026) { + return acos_slow_f255(x); +} +int fixed248::atan_test(x) method_id(10027) { + return fixed248::atan(x); +} +int fixed248::acot_test(x) method_id(10028) { + return fixed248::acot(x); +} + +_ main() { + int One = 1; + ;; repeat(76 / 4) { One *= 10000; } + int sqrt2 = geom_mean(One, 2 * One); + int sqrt3 = geom_mean(One, 3 * One); + ;; return geom_mean(-1 - (-1 << 256), -1 - (-1 << 256)); + ;; return geom_mean(-1 - (-1 << 256), -2 - (-1 << 256)); + ;; return geom_mean(-1 - (-1 << 256), 1 << 255); + ;; return (sqrt2, geom_mean(sqrt2, One)); ;; (sqrt(2), 2^(1/4)) + ;; return (sqrt3, geom_mean(sqrt3, One)); ;; (sqrt(3), 3^(1/4)) + ;; return geom_mean(3 << 254, 1 << 254); + ;; return geom_mean(3, 5); + ;; return tan_f260(115641670674223639132965820642403718536242645001775371762318060545014644837101 - 1); + ;; return tan_f260(15 << 252); ;; tan(15/256) * 2^260 + ;; return sincosm1_f259(1 << 255); ;; (sin,1-cos)(1/16) * 2^259 + ;; return sincosm1_f259(115641670674223639132965820642403718536242645001775371762318060545014644837101 - 1); + ;; return sincosm1_f256((1 << 255) - 1 + (1 << 255)); ;; (sin,1-cos)(1-2^(-256)) + ;; return sincosm1_f256(Pi_const_f254()); ;; (sin,1-cos)(Pi/4) + ;; return sincosn_f256(Pi_const_f254(), 0); ;; (sin,-cos)(Pi/4) + ;; return sincosn_f256((1 << 255) + 1, 0); ;; (sin,-cos)(1/2+1/2^256) + ;; return sincosn_f256(1 << 254, 0); + ;; return sincosn_f256(touch(15) << 252, 0); ;; (sin,-cos)(15/16) + ;; return sincosm1_f256(touch(15) << 252); ;; (sin,1-cos)(15/16) + ;; return sincosn_f256(60628596148627720713372490462954977108898896221398738326462025186323149077698, 0); ;; (sin,-cos)(Pi/6) + ;; return sincosm1_f256(60628596148627720713372490462954977108898896221398738326462025186323149077698); ;; (sin,1-cos)(Pi/6) + ;; return tan_aux_f256(1899 << 245); ;; (p,q) such that p/q=tan(1899/2048) + ;; return fixed248::tan(11 << 248); ;; tan(11) + ;; return atanh_alt_f258(1 << 252); ;; atanh(1/64) * 2^258 + ;; return atanh_f258(1 << 252, 18); ;; atanh(1/64) * 2^258 + ;; return atanh_f261(muldivr(64, 1 << 255, 55), 18); ;; atanh(1/55) * 2^261 + ;; return log2_aux_f256(1 << 255); + ;; return log2_aux_f256(-1 - (-1 << 256)); ;; log2(2-1/2^255))*2^256 ~ 2^256 - 1.43 + ;; return log_aux_f256(-1 - (-1 << 256)); + ;; return log_aux_f256(3); ;; log(3/2)*2^256 + ;; return fixed248::pow(3 << 248, 3 << 248); ;; 3^3 + ;; return fixed248::exp(fixed248::log(5 << 248) ~/ 7); ;; exp(log(5)/7) = 5^(1/7) + ;; return fixed248::log(Pi_const_f254() ~>> 6); ;; log(Pi) + ;; return atanh_alt_f258(1 << 255); ;; atanh(1/8) * 2^258 + ;; return atanh_f258(1 << 255, 37); ;; atanh(1/8) * 2^258 + ;; return atanh_f258(81877371507464127617551201542979628307507432471243237061821853600756754782485, 36); ;; atanh(sqrt(2)/8) * 2^258 + ;; return log_aux_f257(Pi_const_f254()); ;; log(Pi/4) + ;; return log_aux_f257(3 << 254); ;; log(3) + ;; return atanh_alt_f258(81877371507464127617551201542979628307507432471243237061821853600756754782485); ;; atanh(sqrt(2)/8) * 2^258 + ;; return fixed248::sincos(Pi_const_f254() ~/ (64 * 3)); ;; (sin,cos)(Pi/3) + ;; return fixed248::exp(3 << 248); ;; exp(3)*2^248 + ;; return fixed248::exp2((1 << 248) ~/ 5); ;; 2^(1/5)*2^248 + ;; return fixed248::pow(3 << 248, -3 << 247); ;; 3^(-1.5) + ;; return fixed248::pow(10 << 248, -70 << 248); ;; 10^(-70) + ;; return fixed248::pow(fixed248::Pi_const(), touch(3) << 248); ;; Pi^3 ~ 31.006, computed more precisely + ;; return fixed248::pow(fixed248::Pi_const(), fixed248::Pi_const()); ;; Pi^Pi, more precisely + ;; return fixed248::exp(fixed248::log(fixed248::Pi_const()) * 3); ;; Pi^3 ~ 31.006 + ;; return fixed248::exp(muldivr(fixed248::log(fixed248::Pi_const()), fixed248::Pi_const(), 1 << 248)); ;; Pi^Pi + ;; return fixed248::sin(fixed248::log(fixed248::exp(fixed248::Pi_const()))); ;; sin(log(e^Pi)) + ;; return expm1_f257(1 << 255); ;; (exp(1/4)-1)*2^256 + ;; return expm1_f257(-1 << 256); ;; (exp(-1/2)-1)*2^256 (argument out of range, will overflow) + ;; return expm1_f257(log2_const_f256()); ;; (exp(log(2)/2)-1)*2^256 + ;; return expm1_f257(- log2_const_f256()); ;; (exp(-log(2)/2)-1)*2^256 + ;; return tanh_f258(log2_const_f256(), 17); ;; tanh(log(2)/4)*2^258 + ;; return atan_f255(0xa0 << 247); + ;; return atan_f259(1 << 255, 26); ;; atan(1/16) + ;; return atan_f259(touch(2273) << 244, 26); ;; atan(2273/2^15) + ;; return atan_aux_f256(0xa0 << 248); + ;; return atan_aux_f256(-1 - (-1 << 256)); + ;; return atan_aux_f256(-1 << 256); + ;; return atan_aux_f256(1); ;; atan(1/2^256)*2^261 = 32 + ;;return fixed248::nrand(); + ;; return test_nrand(100000); + int One = touch(1 << 255); + ;; return asin_f255(One); + ;; return asin_f255(-2 * One ~/ -3); + int arg = muldivr(12, One, 17); ;; 12/17 + ;; return [ asin_slow_f255(arg), asin_f255(arg) ]; + ;; return [ acos_slow_f255(arg), acos_f255(arg) ]; + ;; return 4 * atan_f255(One ~/ 5) - atan_f255(One ~/ 239); ;; 4 * atan(1/5) - atan(1/239) = Pi/4 as fixed255 + int One = touch(1 << 248); + ;; return fixed248::atan(One) ~/ 5); ;; atan(1/5) + ;; return fixed248::acot(One ~/ 239); ;; atan(1/5) +} + +{- + method_id | in | out +TESTCASE | 10000 | -1-(-1<<256) -1-(-1<<256) | 115792089237316195423570985008687907853269984665640564039457584007913129639935 +TESTCASE | 10000 | -1-(-1<<256) -2-(-1<<256) | 115792089237316195423570985008687907853269984665640564039457584007913129639934 +TESTCASE | 10000 | -1-(-1<<256) 1<<255 | 81877371507464127617551201542979628307507432471243237061821853600756754782485 +TESTCASE | 10000 | 1 2 | 1 +TESTCASE | 10000 | 1 3 | 2 +TESTCASE | 10000 | 3<<254, 1<<254 | 50139445418395255283694704271811692336355250894665672355503583528635147053497 +TESTCASE | 10000 | 3 5 | 4 +TESTCASE | 10001 | 115641670674223639132965820642403718536242645001775371762318060545014644837101-1 | 115792089237316195423570985008687907853269984665640564039457584007913129639935 +TESTCASE | 10001 | 15<<252 | 108679485937549714997960660780289583146059954551846264494610741505469565211201 + +TESTCASE | 10002 | 1<<255 | 57858359242454268843682786479537198006144860419130642837770554273561536355094 28938600351875109040123440645416448095273333920390487381363947585666516031269 +TESTCASE | 10002 | 90942894222941581070058735694432465663348344332098107489693037779484723616546 | 90796875678616203090520439851979829600860326752181983760731669850687818036503 71369031536005973567205947792557760023823761636922618688720973932041901854510 +TESTCASE | 10002 | 115641670674223639132965820642403718536242645001775371762318060545014644837100 | 115341536360906404779899502576747487978354537254490211650198994186870666100480 115341536360906404779899502576747487978354537254490211650198994186870666100479 +TESTCASE | 10003 | 90942894222941581070058735694432465663348344332098107489693037779484723616546 0 | 81877371507464127617551201542979628307507432471243237061821853600756754782485 -81877371507464127617551201542979628307507432471243237061821853600756754782486 +TESTCASE | 10003 | (1<<255)+1 0 | 55513684748706254392157395574451324146997108788015526773113170656738693667657 -101617118319522600545601981648807607350213579319835970884288805016705398675944 +TESTCASE | 10003 | 1<<254 0 | 28647421327665059059430596260119789787021370826354543144805343654507971817712 -112192393597863122712065585177748900737784171216163716639418346853706594800924 +TESTCASE | 10003 | 15<<252 0 | 93337815620236900315136494926097782162348358704087992554326802765553037216157 -68526346066204767396483080633934170508153877799043171682610011603005473885083 +TESTCASE | 10004 | 15<<252 | 93337815620236900315136494926097782162348358704087992554326802765553037216158 94531486342222856054175808749507474690232213733194784713695144809815311509707 +TESTCASE | 10003 | 60628596148627720713372490462954977108898896221398738326462025186323149077698 0 | 57896044618658097711785492504343953926634992332820282019728792003956564819968 -100278890836790510567389408543623384672710501789331344711007167057270294106993 +TESTCASE | 10004 | 60628596148627720713372490462954977108898896221398738326462025186323149077698 | 57896044618658097711785492504343953926634992332820282019728792003956564819968 31026396801051369712363152930129046361118965752618438656900833901285671065886 +TESTCASE | 10005 | 1899<<245 | -115784979074977116522606932816046735344768048129666123117516779696532375620701 -86847621900007587791673148476644866514014227467564880140262768165345715058771 +TESTCASE | 10006 | 11<<248 | -102200470999497240398685962406597118965525125432278008915850368651878945159221 +TESTCASE | 10008 | 1<<252 18 | 7237594612640731814076778712183932891481921212865048737772958953246047977071 +TESTCASE! | 10009 | 64*(1<<255)//55 18 | 67377367986958444187782963285047188951340314639925508148698906136973510008513 +TESTCASE | 10010 | 1<<255 | 0 255 +TESTCASE | 10011 | -1-(-1<<256) | 80260960185991308862233904206310070533990667611589946606122867505419956976171 255 +TESTCASE | 10012 | 3<<248 3<<248 | 12212446911748192486079752325135052781399568695204278238536542063334587891712 +TESTCASE | 10013 | 5 7 | 569235245303856216139605450142923208167703167128528666640203654338408315932 +TESTCASE | 10014 | 1420982722233462204219667745225507275989817880189032929526453715304448806508 | 517776035526939558040896860590142614178014859368681705591403663865964112176 +TESTCASE | 10008 | 1<<255 37 | 58200445412255555045265806996802932280233368707362818578692888102488340124094 +TESTCASE | 10008 | 81877371507464127617551201542979628307507432471243237061821853600756754782485 36 | 82746618329032515754939514227666784789465120373484337368014239356561508382845 +TESTCASE | 10015 | 90942894222941581070058735694432465663348344332098107489693037779484723616546 | -55942510554172181731996424203087263676819062449594753161692794122306202470292 256 +TESTCASE | 10015 | 3<<254 | -66622616410625360568738677407433830899150908037353507097280251369610028875158 256 +TESTCASE | 10016 | 90942894222941581070058735694432465663348344332098107489693037779484723616546//(64*3) | 391714417331212931903864877123528846377775397614575565277371746317462086355 226156424291633194186662080095093570025917938800079226639565593765455331328 +TESTCASE | 10017 | 3<<248 | 9084946421051389814103830025729847734065792062362132089390904679466687950835 +TESTCASE | 10018 | (1<<248)//5 | 519571025111621076330285524602776985448579272766894385941850747946908706857 +TESTCASE | 10012 | 3<<248 -3<<247 | 87047648295825095978636639360784188083950088358794570061638165848324908079 +TESTCASE | 10012 | 10<<248 -70<<248 | 45231 +TESTCASE | 10012 | 1420982722233462204219667745225507275989817880189032929526453715304448806508 3<<248 | 14024537329227316173680050897643053638073167245065581681188087336877135047241 +TESTCASE | 10012 | 1420982722233462204219667745225507275989817880189032929526453715304448806508 1420982722233462204219667745225507275989817880189032929526453715304448806508 | 16492303277433924047657446877966346821161732581471802839855102123372676002295 +TESTCASE | 10019 | 1<<255 | 65775792789545756849501669218806308540691279864498696756901136302101823231959 +TESTCASE | 10019 | -1<<255 | -51226238931640701466578648374135745377468902266335737558089915608594425303282 + +TESTCASE | 10020 | 160<<247 | 32340690885082755723307749066376646841771751777398167772823878380310576779097 +TESTCASE | 10021 | 1<<255 26 | 57820835337111819566482910321201859268121322500887685881159030272507322418551 +TESTCASE | 10021 | 2273<<244 26 | 64153929153128256059565403901040178355488584937372975321150754259394300105908 +TESTCASE | 10022 | 160<<248 | 18 -13775317617017974742132028403521581424991093186766868001115299479309514610238 +TESTCASE | 10022 | -1-(-1<<256) | 25 16312150880916231694896252427912541090503675654570543195394548083530005073282 +TESTCASE | 10022 | -1<<256 | -25 -16312150880916231694896252427912541090503675654570543195394548083530005073298 +TESTCASE | 10022 | 1 | 0 32 + +TESTCASE | 10023 | 1<<255 | 90942894222941581070058735694432465663348344332098107489693037779484723616546 +TESTCASE | 10023 | (1-(1<<255))//-3 | 19675212872822715586637341573564384553677006914302429002469838095945333339604 +TESTCASE | 10023 | 12*(1<<255)//17 | 45371280744427205854111943101074857545572584208710061167826656461897302968384 +TESTCASE | 10024 | 12*(1<<255)//17 | 45371280744427205854111943101074857545572584208710061167826656461897302968387 +TESTCASE | 10025 | 12*(1<<255)//17 | 22785806739257187607973396296678804058887880061694023160933190658793710324081 +TESTCASE | 10026 | 12*(1<<255)//17 | 22785806739257187607973396296678804058887880061694023160933190658793710324080 + +TESTCASE | 10027 | (1<<248)//5 | 89284547973388213553327350968415123522888028497458323165947767504203347189 +TESTCASE | 10028 | (1<<248)//239 | 708598849781543798951441405045469962900811296151941404481049216461523216127 +-} diff --git a/crypto/smartcont/mathlib.fc b/crypto/smartcont/mathlib.fc index cc6d1a46..3dc8590c 100644 --- a/crypto/smartcont/mathlib.fc +++ b/crypto/smartcont/mathlib.fc @@ -3,7 +3,10 @@ - FunC fixed-point mathematical library - -} - + +#include "stdlib.fc"; +#pragma version >=0.4.2; + {---------------- HIGH-LEVEL FUNCTION DECLARATIONS -----------------} {- Most functions declared here work either with integers or with fixed-point numbers of type `fixed248`. @@ -63,6 +66,17 @@ int fixed248::atan(int x) inline_ref; ;; fixed248 acot(fixed248 x); int fixed248::acot(int x) inline_ref; +;; random number uniformly distributed in [0..1) +;; fixed248 random(); +int fixed248::random() impure inline; +;; random number with standard normal distribution (2100 gas on average) +;; fixed248 nrand(); +int fixed248::nrand() impure inline; +;; generates a random number approximately distributed according to the standard normal distribution (1200 gas) +;; (fails chi-squared test, but it is shorter and faster than fixed248::nrand()) +;; fixed248 nrand_fast(); +int fixed248::nrand_fast() impure inline; + -} ;; end (declarations) {-------------------- INTERMEDIATE FUNCTIONS -----------------------} @@ -125,30 +139,27 @@ int asin_f255(int x); ;; fixed254 acos(fixed255 x); int acos_f255(int x); +;; generates normally distributed pseudo-random number +;; fixed252 nrand(); +int nrand_f252(int x); + +;; a faster and shorter variant of nrand_f252() that fails chi-squared test +;; (should suffice for most purposes) +;; fixed252 nrand_fast(); +int nrand_fast_f252(int x); -} ;; end (declarations) {---------------- MISSING OPERATIONS AND BUILT-INS -----------------} int sgn(int x) asm "SGN"; -int abs(int x) asm "ABS"; -int min(int x, int y) asm "MIN"; ;; compute floor(log2(x))+1 int log2_floor_p1(int x) asm "UBITSIZE"; -;; FunC compiler emits MULDIVC instruction, but Asm.fif usually does not know it -;; add the following line to your Asm.fif if necessary -;; x{A986} @Defop MULDIVC - -;; the following instructions might have been emitted by muldivmod() and muldivmodr() builtins, but FunC does not have these -;; x{A9A5} @Defop MULRSHIFTR int mulrshiftr(int x, int y, int s) asm "MULRSHIFTR"; -;; x{A9B5} @Defop(8u+1) MULRSHIFTR# int mulrshiftr256(int x, int y) asm "256 MULRSHIFTR#"; -;; x{A9BC} @Defop(8u+1) MULRSHIFT#MOD (int, int) mulrshift256mod(int x, int y) asm "256 MULRSHIFT#MOD"; -;; x{A9BD} @Defop(8u+1) MULRSHIFTR#MOD (int, int) mulrshiftr256mod(int x, int y) asm "256 MULRSHIFTR#MOD"; (int, int) mulrshiftr255mod(int x, int y) asm "255 MULRSHIFTR#MOD"; (int, int) mulrshiftr248mod(int x, int y) asm "248 MULRSHIFTR#MOD"; @@ -156,39 +167,34 @@ int mulrshiftr256(int x, int y) asm "256 MULRSHIFTR#"; (int, int) mulrshiftr6mod(int x, int y) asm "6 MULRSHIFTR#MOD"; (int, int) mulrshiftr7mod(int x, int y) asm "7 MULRSHIFTR#MOD"; -;; these instructions might have been emitted by muldivmodr(..., 1 << N, ...) built-ins -;; x{A9D5} @Defop(8u+1) LSHIFT#DIVR int lshift256divr(int x, int y) asm "256 LSHIFT#DIVR"; -;; x{A9DD} @Defop(8u+1) LSHIFT#DIVMODR (int, int) lshift256divmodr(int x, int y) asm "256 LSHIFT#DIVMODR"; (int, int) lshift255divmodr(int x, int y) asm "255 LSHIFT#DIVMODR"; (int, int) lshift2divmodr(int x, int y) asm "2 LSHIFT#DIVMODR"; (int, int) lshift7divmodr(int x, int y) asm "7 LSHIFT#DIVMODR"; -;; x{A9CD} @Defop LSHIFTDIVMODR (int, int) lshiftdivmodr(int x, int y, int s) asm "LSHIFTDIVMODR"; -;; these instructions might have been emitted by divmodr(..., 1 << NN) or divmod(..., 1 << N) built-ins -;; but FunC does not have divmodr(), and divmod() always compiles to "DIVMOD" -;; x{A93D} @Defop(8u+1) RSHIFTR#MOD (int, int) rshiftr256mod(int x) asm "256 RSHIFTR#MOD"; (int, int) rshiftr248mod(int x) asm "248 RSHIFTR#MOD"; (int, int) rshiftr4mod(int x) asm "4 RSHIFTR#MOD"; -;; x{A93C} @Defop(8u+1) RSHIFT#MOD (int, int) rshift3mod(int x) asm "3 RSHIFT#MOD"; ;; computes y - x (FunC compiler does not try to use this by itself) int sub_rev(int x, int y) asm "SUBR"; +int nan() asm "PUSHNAN"; +int is_nan(int x) asm "ISNAN"; + {------------------------ SQUARE ROOTS ----------------------------} ;; computes sqrt(a*b) exactly rounded to the nearest integer ;; for all 0 <= a, b <= 2^256-1 ;; may be used with b=1 or b=scale of fixed-point numbers int geom_mean(int a, int b) inline_ref { - if (min(a, b) <= 0) { + ifnot (min(a, b)) { return 0; } - int s = log2_floor_p1(a); + int s = log2_floor_p1(a); ;; throws out of range error if a < 0 or b < 0 int t = log2_floor_p1(b); ;; NB: (a-b)/2+b == (a+b)/2, but without overflow for large a and b int x = (s == t ? (a - b) / 2 + b : 1 << ((s + t) / 2)); @@ -313,9 +319,6 @@ int expm1_f257(int x) inline_ref { return x - muldivr(x2, t / 2, a + mulrshiftr256(x, t) ~/ 4) ~/ 4; ;; x - x^2 * (x-a) / (a + x*(x-a)) } -;; used to avoid broken optimisations in older versions of FunC -int minus_one() asm "-1 PUSHINT"; - ;; expm1_f257() may be used to implement specific fixed-point exponentials ;; example: ;; fixed248 exp(fixed248 x) @@ -327,8 +330,7 @@ int fixed248::exp(int x) inline_ref { x = 2 * x - muldivr(q, l2d, 1 << 127); int y = expm1_f257(x); ;; result is (1 + y) * (2^q) --> ((1 << 257) + y) >> (9 - q) - return (y ~>> (9 - q)) - (minus_one() << (248 + q)); - ;; (-1 << (248 + q)) is compiled into non-existent instruction NEGPOW2 + return (y ~>> (9 - q)) - (-1 << (248 + q)); ;; note that (y ~>> (9 - q)) + (1 << (248 + q)) leads to overflow when q=8 } @@ -339,7 +341,7 @@ int fixed248::exp2(int x) inline_ref { (int q, x) = rshiftr248mod(x); x = muldivr(x, log2_const_f256(), 1 << 247); int y = expm1_f257(x); - return (y ~>> (9 - q)) - (minus_one() << (248 + q)); + return (y ~>> (9 - q)) - (-1 << (248 + q)); } {--------------------- TRIGONOMETRIC FUNCTIONS -----------------------} @@ -662,7 +664,7 @@ int fixed248::pow(int x, int y) inline_ref { return - (sq == 0); ;; underflow } int y = expm1_f257(mulrshiftr256(ll, log2_const_f256())); - return (y ~>> (9 - q)) - (minus_one() << sq); + return (y ~>> (9 - q)) - (-1 << sq); } {--------------------- INVERSE TRIGONOMETRIC FUNCTIONS -------------------} @@ -795,7 +797,7 @@ int atan_f256_small(int x) inline_ref { ;; fixed255 asin(fixed255 x); int asin_f255(int x) inline_ref { int a = fixed255::One - fixed255::sqr(x); ;; a:=1-x^2 - if (a <= 0) { + ifnot (a) { return sgn(x) * Pi_const_f254(); ;; Pi/2 or -Pi/2 } int y = fixed255::sqrt(a); ;; sqrt(1-x^2) @@ -806,7 +808,7 @@ int asin_f255(int x) inline_ref { ;; fixed254 acos(fixed255 x); int acos_f255(int x) inline_ref { int Pi = Pi_const_f254(); - if (x <= (-1 << 255)) { + if (x == (-1 << 255)) { return Pi; ;; acos(-1) = Pi } Pi /= 2; @@ -858,3 +860,65 @@ int fixed248::acot(int x) inline_ref { ;; now acot(x) = - z - q*atan(1/32) + s*(Pi/2), z is fixed261 return (s * Pi_const_f254() - z ~/ 64 - muldivr(q, Atan1_32_f261(), 64)) ~/ 128; ;; compute in fixed255, then convert } + +{--------------------- PSEUDO-RANDOM NUMBERS -------------------} + +;; random number with standard normal distribution N(0,1) +;; generated by Kinderman--Monahan ratio method modified by J.Leva +;; spends ~ 2k..3k gas on average +;; fixed252 nrand(); +int nrand_f252() impure inline_ref { + var (x, s, t, A, B, r0) = (nan(), touch(29483) << 236, touch(-3167) << 239, 12845, 16693, 9043); + ;; 4/sqrt(e*Pi) = 1.369 loop iterations on average + do { + var (u, v) = (random() / 16 + 1, muldivr(random() - (1 << 255), 7027, 1 << 16)); ;; fixed252; 7027=ceil(sqrt(8/e)*2^12) + int va = abs(v); + var (u1, v1) = (u - s, va - t); ;; (u - 29483/2^16, abs(v) + 3167/2^13) as fixed252 + ;; Q := u1^2 + v1 * (A*v1 - B*u1) as fixed252 where A=12845/2^16, B=16693/2^16 + int Q = muldivr(u1, u1, 1 << 252) + muldivr(v1, muldivr(v1, A, 1 << 16) - muldivr(u1, B, 1 << 16), 1 << 252); + ;; must have 9043 / 2^15 < Q < 9125 / 2^15, otherwise accept if smaller, reject if larger + int Qd = (Q >> 237) - r0; + if ((Qd < 9125 - 9043) & (va / u < 16)) { + x = muldivr(v, 1 << 252, u); ;; x:=v/u as fixed252; reject immediately if |v/u| >= 16 + if (Qd >= 0) { ;; immediately accept if Qd < 0 + ;; rarely taken branch - 0.012 times per call on average + ;; check condition v^2 < -4*u^2*log(u), or equivalent condition u < exp(-x^2/4) for x=v/u + int xx = mulrshiftr256(x, x) ~/ 4; ;; x^2/4 as fixed248 + int ex = fixed248::exp(- xx) * 16; ;; exp(-x^2/4) as fixed252 + if (u > ex) { + x = nan(); ;; condition false, reject + } + } + } + } until (~ is_nan(x)); + return x; +} + +;; generates a random number approximately distributed according to the standard normal distribution +;; much faster than nrand_f252(), should be suitable for most purposes when only several random numbers are needed +;; fixed252 nrand_fast(); +int nrand_fast_f252() impure inline_ref { + int t = touch(-3) << 253; ;; -6. as fixed252 + repeat (12) { + t += random() / 16; ;; add together 12 uniformly random numbers + } + return t; +} + +;; random number uniformly distributed in [0..1) +;; fixed248 random(); +int fixed248::random() impure inline { + return random() >> 8; +} + +;; random number with standard normal distribution +;; fixed248 nrand(); +int fixed248::nrand() impure inline { + return nrand_f252() ~>> 4; +} + +;; generates a random number approximately distributed according to the standard normal distribution +;; fixed248 nrand_fast(); +int fixed248::nrand_fast() impure inline { + return nrand_fast_f252() ~>> 4; +} diff --git a/crypto/smartcont/test-math.fc b/crypto/smartcont/test-math.fc deleted file mode 100644 index 1ff43cd9..00000000 --- a/crypto/smartcont/test-math.fc +++ /dev/null @@ -1,100 +0,0 @@ -{-------------------------- TESTS for mathlib.fc ----------------------------} - -;; computes 1-acos(x)/Pi by a very simple, extremely slow (~70k gas) and imprecise method -;; fixed256 acos_prepare_slow(fixed255 x); -int acos_prepare_slow_f255(int x) inline { - x -= (x == 0); - int t = 1; - repeat (255) { - t = t * sgn(x) * 2 + 1; ;; decode Gray code (sgn(x_0), sgn(x_1), ...) - x = (-1 << 255) - muldivr(x, - x, 1 << 254); ;; iterate x := 2*x^2 - 1 = cos(2*acos(x)) - } - return abs(t); -} - -;; extremely slow (~70k gas) and somewhat imprecise (very imprecise when x is small), for testing only -;; fixed254 acos_slow(fixed255 x); -int acos_slow_f255(int x) inline_ref { - int t = acos_prepare_slow_f255(x); - return - mulrshiftr256(t + (-1 << 256), Pi_const_f254()); -} - -;; fixed255 asin_slow(fixed255 x); -int asin_slow_f255(int x) inline_ref { - int t = acos_prepare_slow_f255(abs(x)) % (1 << 255); - return muldivr(t, Pi_const_f254(), 1 << 255) * sgn(x); -} - -_ main() { - int One = 1; - ;; repeat(76 / 4) { One *= 10000; } - int sqrt2 = geom_mean(One, 2 * One); - int sqrt3 = geom_mean(One, 3 * One); - ;; return geom_mean((1 << 255) - 1 + (1 << 255), (1 << 255) - 1); - ;; return geom_mean((1 << 255) - 1, (1 << 255) - 2); - ;; return (sqrt2, geom_mean(sqrt2, One)); ;; (sqrt(2), 2^(1/4)) - ;; return (sqrt3, geom_mean(sqrt3, One)); ;; (sqrt(3), 3^(1/4)) - ;; return geom_mean(3 << 254, 1 << 254); - ;; return tan_f260(115641670674223639132965820642403718536242645001775371762318060545014644837101 - 1); - ;; return tan_f260(15 << 252); ;; tan(15/256) * 2^260 - ;; return sincosm1_f259(1 << 255); ;; (sin,1-cos)(1/16) * 2^259 - ;; return sincosm1_f259(115641670674223639132965820642403718536242645001775371762318060545014644837101 - 1); - ;; return sincosm1_f256((1 << 255) - 1 + (1 << 255)); ;; (sin,1-cos)(1-2^(-256)) - ;; return sincosm1_f256(Pi_const_f254()); ;; (sin,1-cos)(Pi/4) - ;; return sincosn_f256(Pi_const_f254(), 0); ;; (sin,-cos)(Pi/4) - ;; return sincosn_f256((1 << 255) + 1, 0); ;; (sin,-cos)(1/2+1/2^256) - ;; return sincosn_f256(1 << 254, 0); - ;; return sincosn_f256(touch(15) << 252, 0); ;; (sin,-cos)(15/16) - ;; return sincosm1_f256(touch(15) << 252); ;; (sin,1-cos)(15/16) - ;; return sincosn_f256(60628596148627720713372490462954977108898896221398738326462025186323149077698, 0); ;; (sin,-cos)(Pi/6) - ;; return sincosm1_f256(60628596148627720713372490462954977108898896221398738326462025186323149077698); ;; (sin,1-cos)(Pi/6) - ;; return tan_aux_f256(1899 << 245); ;; (p,q) such that p/q=tan(1899/2048) - ;; return fixed248::tan(11 << 248); ;; tan(11) - ;; return atanh_alt_f258(1 << 252); ;; atanh(1/64) * 2^258 - ;; return atanh_f258(1 << 252, 18); ;; atanh(1/64) * 2^258 - ;; return atanh_f261(muldivr(64, 1 << 255, 55), 18); ;; atanh(1/55) * 2^261 - ;; return log2_aux_f256(1 << 255); - ;; return log2_aux_f256(-1 - (-1 << 256)); ;; log2(2-1/2^255))*2^256 ~ 2^256 - 1.43 - ;; return log_aux_f256(-1 - (-1 << 256)); - ;; return log_aux_f256(3); ;; log(3/2)*2^256 - ;; return fixed248::pow(3 << 248, 3 << 248); ;; 3^3 - ;; return fixed248::exp(fixed248::log(5 << 248) ~/ 7); ;; exp(log(5)/7) = 5^(1/7) - ;; return fixed248::log(Pi_const_f254() ~>> 6); ;; log(Pi) - ;; return atanh_alt_f258(1 << 255); ;; atanh(1/8) * 2^258 - ;; return atanh_f258(1 << 255, 37); ;; atanh(1/8) * 2^258 - ;; return log_aux_f257(Pi_const_f254()); ;; log(Pi/4) - ;; return log_aux_f257(3 << 254); ;; log(3) - ;; return atanh_alt_f258(81877371507464127617551201542979628307507432471243237061821853600756754782485); ;; atanh(sqrt(2)/8) * 2^258 - ;; return atanh_f258(81877371507464127617551201542979628307507432471243237061821853600756754782485, 36); ;; atanh(sqrt(2)/8) * 2^258 - ;; return fixed248::sincos(Pi_const_f254() ~/ (64 * 3)); ;; (sin,cos)(Pi/3) - ;; return fixed248::exp(3 << 248); ;; exp(3)*2^248 - ;; return fixed248::exp2((1 << 248) ~/ 5); ;; 2^(1/5)*2^248 - ;; return fixed248::pow(3 << 248, -3 << 247); ;; 3^(-1.5) - ;; return fixed248::pow(10 << 248, -70 << 248); ;; 10^(-70) - ;; return fixed248::exp(fixed248::log(fixed248::Pi_const()) * 3); ;; Pi^3 ~ 31.006 - ;; return fixed248::pow(fixed248::Pi_const(), touch(3) << 248); ;; Pi^3 ~ 31.006, computed more precisely - ;; return fixed248::exp(muldivr(fixed248::log(fixed248::Pi_const()), fixed248::Pi_const(), 1 << 248)); ;; Pi^Pi - ;; return fixed248::pow(fixed248::Pi_const(), fixed248::Pi_const()); ;; Pi^Pi, more precisely - ;; return fixed248::sin(fixed248::log(fixed248::exp(fixed248::Pi_const()))); ;; sin(log(e^Pi)) - ;; return expm1_f257(1 << 255); ;; (exp(1/4)-1)*2^256 - ;; return expm1_f257(-1 << 256); ;; (exp(-1/2)-1)*2^256 (argument out of range, will overflow) - ;; return expm1_f257(log2_const_f256()); ;; (exp(log(2)/2)-1)*2^256 - ;; return expm1_f257(- log2_const_f256()); ;; (exp(-log(2)/2)-1)*2^256 - ;; return tanh_f258(log2_const_f256(), 17); ;; tanh(log(2)/4)*2^258 - ;; return atan_f255(0xa0 << 247); - ;; return atan_f259(1 << 255, 26); ;; atan(1/16) - ;; return atan_f259(touch(2273) << 244, 26); ;; atan(2273/2^15) - ;; return atan_aux_f256(0xa0 << 248); - ;; return atan_aux_f256(-1 - (-1 << 256)); - ;; return atan_aux_f256(-1 << 256); - ;; return atan_aux_f256(1); ;; atan(1/2^256)*2^261 = 32 - int One = touch(1 << 255); - ;; return asin_f255(-2 * One ~/ -3); - int arg = muldivr(12, One, 17); ;; 12/17 - ;; return [ asin_slow_f255(arg), asin_f255(arg) ]; - ;; return [ acos_slow_f255(arg), acos_f255(arg) ]; - return 4 * atan_f255(One ~/ 5) - atan_f255(One ~/ 239); ;; 4 * atan(1/5) - atan(1/239) = Pi/4 as fixed255 - int One = touch(1 << 248); - ;; return fixed248::atan(One) ~/ 5); ;; atan(1/5) - ;; return fixed248::acot(One ~/ 239); ;; atan(1/5) -} From 9b3f9e4ac1717b5470c9ba2ad639526281d14e95 Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Tue, 14 Mar 2023 21:29:33 +0300 Subject: [PATCH 43/46] Add Licenses to funC stdlib --- crypto/smartcont/LICENSE.LGPL | 481 ++++++++++++++++++++++++++++++++++ crypto/smartcont/mathlib.fc | 15 ++ crypto/smartcont/stdlib.fc | 15 ++ 3 files changed, 511 insertions(+) create mode 100644 crypto/smartcont/LICENSE.LGPL diff --git a/crypto/smartcont/LICENSE.LGPL b/crypto/smartcont/LICENSE.LGPL new file mode 100644 index 00000000..b482fc4e --- /dev/null +++ b/crypto/smartcont/LICENSE.LGPL @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/crypto/smartcont/mathlib.fc b/crypto/smartcont/mathlib.fc index 3dc8590c..f2dfd73f 100644 --- a/crypto/smartcont/mathlib.fc +++ b/crypto/smartcont/mathlib.fc @@ -4,6 +4,21 @@ - -} +{- + This file is part of TON FunC Standard Library. + + FunC Standard Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + FunC Standard Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + +-} + #include "stdlib.fc"; #pragma version >=0.4.2; diff --git a/crypto/smartcont/stdlib.fc b/crypto/smartcont/stdlib.fc index 03da3fe4..978b9473 100644 --- a/crypto/smartcont/stdlib.fc +++ b/crypto/smartcont/stdlib.fc @@ -1,6 +1,21 @@ ;; Standard library for funC ;; +{- + This file is part of TON FunC Standard Library. + + FunC Standard Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + FunC Standard Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + +-} + {- # Tuple manipulation primitives The names and the types are mostly self-explaining. From 30c742aedde0bc2f761af600c4f9654e1f9815e7 Mon Sep 17 00:00:00 2001 From: Marat <98183742+dungeon-master-666@users.noreply.github.com> Date: Wed, 15 Mar 2023 07:46:54 +0000 Subject: [PATCH 44/46] Add missing export symbol _transaction_emulator_set_unixtime (#651) --- emulator/emulator_export_list | 1 + 1 file changed, 1 insertion(+) diff --git a/emulator/emulator_export_list b/emulator/emulator_export_list index b53a4114..0e9fd51a 100644 --- a/emulator/emulator_export_list +++ b/emulator/emulator_export_list @@ -1,4 +1,5 @@ _transaction_emulator_create +_transaction_emulator_set_unixtime _transaction_emulator_set_lt _transaction_emulator_set_rand_seed _transaction_emulator_set_ignore_chksig From 47311d6e0e7a4658fe26e8206164ba7d6130b671 Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Wed, 15 Mar 2023 10:47:35 +0300 Subject: [PATCH 45/46] Improve tweaking for high throughput (#610) * Option "--disable-ext-msg-broadcast" * "Get shard out queue size" query * Move disabling ext msg broadcasts from command-line arguments to config * Fix compilation error * Asynchronous store_cell and gc in celldb * Make GC in celldb work evenly over time * Increase timeouts for downloading blocks * Reuse blocks from previous rounds in validator session * Use Rldp2 in FullNode for downloading persistent states and archives * Improve logs in download-archive-slice and download-state * Decrease delay between serializing shards * Make CellDbIn::load_cell synchronous to avoid interfering with store_cell --------- Co-authored-by: SpyCheese --- crypto/vm/db/CellHashTable.h | 2 +- crypto/vm/db/CellStorage.cpp | 14 ++ crypto/vm/db/CellStorage.h | 1 + crypto/vm/db/DynamicBagOfCellsDb.cpp | 192 ++++++++++++++++-- crypto/vm/db/DynamicBagOfCellsDb.h | 1 + dht-server/dht-server.cpp | 2 +- tdutils/td/utils/OptionParser.cpp | 2 +- tl/generate/scheme/ton_api.tl | 8 +- tl/generate/scheme/ton_api.tlo | Bin 83460 -> 84224 bytes .../validator-engine-console-query.cpp | 52 +++++ .../validator-engine-console-query.h | 48 +++++ .../validator-engine-console.cpp | 2 + validator-engine/CMakeLists.txt | 2 +- validator-engine/validator-engine.cpp | 125 +++++++++++- validator-engine/validator-engine.hpp | 7 + validator-session/validator-session.cpp | 38 ++-- validator-session/validator-session.hpp | 4 +- validator/CMakeLists.txt | 2 +- validator/db/celldb.cpp | 149 +++++++++----- validator/db/celldb.hpp | 13 +- validator/full-node-shard.cpp | 51 +++-- validator/full-node-shard.h | 8 +- validator/full-node-shard.hpp | 17 +- validator/full-node.cpp | 43 +++- validator/full-node.h | 16 +- validator/full-node.hpp | 10 +- validator/net/download-archive-slice.cpp | 17 +- validator/net/download-archive-slice.hpp | 14 +- validator/net/download-block-new.cpp | 4 +- validator/net/download-block.cpp | 4 +- validator/net/download-state.cpp | 20 +- validator/net/download-state.hpp | 5 +- validator/state-serializer.cpp | 2 +- 33 files changed, 712 insertions(+), 163 deletions(-) diff --git a/crypto/vm/db/CellHashTable.h b/crypto/vm/db/CellHashTable.h index 7d0308b7..a251c4db 100644 --- a/crypto/vm/db/CellHashTable.h +++ b/crypto/vm/db/CellHashTable.h @@ -43,7 +43,7 @@ class CellHashTable { template void for_each(F &&f) { for (auto &info : set_) { - f(info); + f(const_cast(info)); } } template diff --git a/crypto/vm/db/CellStorage.cpp b/crypto/vm/db/CellStorage.cpp index a1b7365b..470c46a0 100644 --- a/crypto/vm/db/CellStorage.cpp +++ b/crypto/vm/db/CellStorage.cpp @@ -150,6 +150,20 @@ td::Result CellLoader::load(td::Slice hash, bool need_da return res; } +td::Result CellLoader::load_refcnt(td::Slice hash) { + LoadResult res; + std::string serialized; + TRY_RESULT(get_status, reader_->get(hash, serialized)); + if (get_status != KeyValue::GetStatus::Ok) { + DCHECK(get_status == KeyValue::GetStatus::NotFound); + return res; + } + res.status = LoadResult::Ok; + td::TlParser parser(serialized); + td::parse(res.refcnt_, parser); + return res; +} + CellStorer::CellStorer(KeyValue &kv) : kv_(kv) { } diff --git a/crypto/vm/db/CellStorage.h b/crypto/vm/db/CellStorage.h index b705b531..ba93ce20 100644 --- a/crypto/vm/db/CellStorage.h +++ b/crypto/vm/db/CellStorage.h @@ -48,6 +48,7 @@ class CellLoader { }; CellLoader(std::shared_ptr reader); td::Result load(td::Slice hash, bool need_data, ExtCellCreator &ext_cell_creator); + td::Result load_refcnt(td::Slice hash); // This only loads refcnt_, cell_ == null private: std::shared_ptr reader_; diff --git a/crypto/vm/db/DynamicBagOfCellsDb.cpp b/crypto/vm/db/DynamicBagOfCellsDb.cpp index 5441feea..f9fe69cb 100644 --- a/crypto/vm/db/DynamicBagOfCellsDb.cpp +++ b/crypto/vm/db/DynamicBagOfCellsDb.cpp @@ -27,6 +27,9 @@ #include "td/utils/ThreadSafeCounter.h" #include "vm/cellslice.h" +#include +#include "td/actor/actor.h" +#include "common/delay.h" namespace vm { namespace { @@ -138,8 +141,6 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat if (cell->get_virtualization() != 0) { return; } - //LOG(ERROR) << "INC"; - //CellSlice(cell, nullptr).print_rec(std::cout); to_inc_.push_back(cell); } void dec(const Ref &cell) override { @@ -149,8 +150,6 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat if (cell->get_virtualization() != 0) { return; } - //LOG(ERROR) << "DEC"; - //CellSlice(cell, nullptr).print_rec(std::cout); to_dec_.push_back(cell); } @@ -167,25 +166,20 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat if (is_prepared_for_commit()) { return td::Status::OK(); } - //LOG(ERROR) << "dfs_new_cells_in_db"; for (auto &new_cell : to_inc_) { auto &new_cell_info = get_cell_info(new_cell); dfs_new_cells_in_db(new_cell_info); } - //return td::Status::OK(); - //LOG(ERROR) << "dfs_new_cells"; for (auto &new_cell : to_inc_) { auto &new_cell_info = get_cell_info(new_cell); dfs_new_cells(new_cell_info); } - //LOG(ERROR) << "dfs_old_cells"; for (auto &old_cell : to_dec_) { auto &old_cell_info = get_cell_info(old_cell); dfs_old_cells(old_cell_info); } - //LOG(ERROR) << "save_diff_prepare"; save_diff_prepare(); to_inc_.clear(); @@ -363,7 +357,6 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat info.was = true; visited_.push_back(&info); } - //LOG(ERROR) << "dfs new " << td::format::escaped(info.cell->hash()); if (info.was_dfs_new_cells) { return; @@ -384,7 +377,6 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat info.was = true; visited_.push_back(&info); } - //LOG(ERROR) << "dfs old " << td::format::escaped(info.cell->hash()); load_cell(info); @@ -405,7 +397,6 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat } void save_diff(CellStorer &storer) { - //LOG(ERROR) << hash_table_.size(); for (auto info_ptr : visited_) { save_cell(*info_ptr, storer); } @@ -414,7 +405,6 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat void save_cell_prepare(CellInfo &info) { if (info.refcnt_diff == 0) { - //CellSlice(info.cell, nullptr).print_rec(std::cout); return; } load_cell(info); @@ -450,15 +440,11 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat if (info.db_refcnt == 0) { CHECK(info.in_db); - //LOG(ERROR) << "ERASE"; - //CellSlice(NoVm(), info.cell).print_rec(std::cout); storer.erase(info.cell->get_hash().as_slice()); info.in_db = false; hash_table_.erase(info.cell->get_hash().as_slice()); guard.dismiss(); } else { - //LOG(ERROR) << "SAVE " << info.db_refcnt; - //CellSlice(NoVm(), info.cell).print_rec(std::cout); auto loaded_cell = info.cell->load_cell().move_as_ok(); storer.set(info.db_refcnt, *loaded_cell.data_cell); info.in_db = true; @@ -482,7 +468,6 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat CHECK(cell->is_loaded()); vm::CellSlice cs(vm::NoVm{}, cell); // FIXME for (unsigned i = 0; i < cs.size_refs(); i++) { - //LOG(ERROR) << "---> " << td::format::escaped(cell->ref(i)->hash()); f(get_cell_info(cs.prefetch_ref(i))); } } @@ -573,6 +558,177 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat DynamicBocExtCellExtra{cell_db_reader_})); return std::move(res); } + + struct PrepareCommitAsyncState { + size_t remaining_ = 0; + std::shared_ptr executor_; + td::Promise promise_; + + struct CellInfo2 { + CellInfo *info; + std::vector parents; + unsigned remaining_children = 0; + Cell::Hash key() const { + return info->key(); + } + bool operator<(const CellInfo2 &other) const { + return key() < other.key(); + } + + friend bool operator<(const CellInfo2 &a, td::Slice b) { + return a.key().as_slice() < b; + } + + friend bool operator<(td::Slice a, const CellInfo2 &b) { + return a < b.key().as_slice(); + } + }; + + CellHashTable cells_; + }; + std::unique_ptr pca_state_; + + void prepare_commit_async(std::shared_ptr executor, td::Promise promise) override { + if (pca_state_) { + promise.set_error(td::Status::Error("Other prepare_commit_async is not finished")); + return; + } + if (is_prepared_for_commit()) { + promise.set_result(td::Unit()); + return; + } + pca_state_ = std::make_unique(); + pca_state_->executor_ = std::move(executor); + pca_state_->promise_ = std::move(promise); + for (auto &new_cell : to_inc_) { + dfs_new_cells_in_db_async(new_cell); + } + pca_state_->cells_.for_each([&](PrepareCommitAsyncState::CellInfo2 &info) { + ++pca_state_->remaining_; + if (info.remaining_children == 0) { + pca_load_from_db(&info); + } + }); + if (pca_state_->remaining_ == 0) { + prepare_commit_async_cont(); + } + } + + void dfs_new_cells_in_db_async(const td::Ref &cell, PrepareCommitAsyncState::CellInfo2 *parent = nullptr) { + bool exists = true; + pca_state_->cells_.apply(cell->get_hash().as_slice(), [&](PrepareCommitAsyncState::CellInfo2 &info) { + if (info.info == nullptr) { + exists = false; + info.info = &get_cell_info(cell); + } + }); + auto info = pca_state_->cells_.get_if_exists(cell->get_hash().as_slice()); + if (parent) { + info->parents.push_back(parent); + ++parent->remaining_children; + } + if (exists) { + return; + } + if (cell->is_loaded()) { + vm::CellSlice cs(vm::NoVm{}, cell); + for (unsigned i = 0; i < cs.size_refs(); i++) { + dfs_new_cells_in_db_async(cs.prefetch_ref(i), info); + } + } + } + + void pca_load_from_db(PrepareCommitAsyncState::CellInfo2 *info) { + pca_state_->executor_->execute_async( + [db = this, info, executor = pca_state_->executor_, loader = *loader_]() mutable { + auto res = loader.load_refcnt(info->info->cell->get_hash().as_slice()).move_as_ok(); + executor->execute_sync([db, info, res = std::move(res)]() { + db->pca_set_in_db(info, std::move(res)); + }); + }); + } + + void pca_set_in_db(PrepareCommitAsyncState::CellInfo2 *info, CellLoader::LoadResult result) { + info->info->sync_with_db = true; + if (result.status == CellLoader::LoadResult::Ok) { + info->info->in_db = true; + info->info->db_refcnt = result.refcnt(); + } else { + info->info->in_db = false; + } + for (PrepareCommitAsyncState::CellInfo2 *parent_info : info->parents) { + if (parent_info->info->sync_with_db) { + continue; + } + if (!info->info->in_db) { + pca_set_in_db(parent_info, {}); + } else if (--parent_info->remaining_children == 0) { + pca_load_from_db(parent_info); + } + } + if (--pca_state_->remaining_ == 0) { + prepare_commit_async_cont(); + } + } + + void prepare_commit_async_cont() { + for (auto &new_cell : to_inc_) { + auto &new_cell_info = get_cell_info(new_cell); + dfs_new_cells(new_cell_info); + } + + CHECK(pca_state_->remaining_ == 0); + for (auto &old_cell : to_dec_) { + auto &old_cell_info = get_cell_info(old_cell); + dfs_old_cells_async(old_cell_info); + } + if (pca_state_->remaining_ == 0) { + prepare_commit_async_cont2(); + } + } + + void dfs_old_cells_async(CellInfo &info) { + if (!info.was) { + info.was = true; + visited_.push_back(&info); + if (!info.sync_with_db) { + ++pca_state_->remaining_; + load_cell_async( + info.cell->get_hash().as_slice(), pca_state_->executor_, + [executor = pca_state_->executor_, db = this, info = &info](td::Result> R) { + R.ensure(); + executor->execute_sync([db, info]() { + CHECK(info->sync_with_db); + db->dfs_old_cells_async(*info); + if (--db->pca_state_->remaining_ == 0) { + db->prepare_commit_async_cont2(); + } + }); + }); + return; + } + } + info.refcnt_diff--; + if (!info.sync_with_db) { + return; + } + auto new_refcnt = info.refcnt_diff + info.db_refcnt; + CHECK(new_refcnt >= 0); + if (new_refcnt != 0) { + return; + } + + for_each(info, [this](auto &child_info) { dfs_old_cells_async(child_info); }); + } + + void prepare_commit_async_cont2() { + save_diff_prepare(); + to_inc_.clear(); + to_dec_.clear(); + pca_state_->promise_.set_result(td::Unit()); + pca_state_ = {}; + } + }; } // namespace diff --git a/crypto/vm/db/DynamicBagOfCellsDb.h b/crypto/vm/db/DynamicBagOfCellsDb.h index 3569208c..69cab52f 100644 --- a/crypto/vm/db/DynamicBagOfCellsDb.h +++ b/crypto/vm/db/DynamicBagOfCellsDb.h @@ -75,6 +75,7 @@ class DynamicBagOfCellsDb { virtual void load_cell_async(td::Slice hash, std::shared_ptr executor, td::Promise> promise) = 0; + virtual void prepare_commit_async(std::shared_ptr executor, td::Promise promise) = 0; }; } // namespace vm diff --git a/dht-server/dht-server.cpp b/dht-server/dht-server.cpp index 212a6fae..f729105f 100644 --- a/dht-server/dht-server.cpp +++ b/dht-server/dht-server.cpp @@ -170,7 +170,7 @@ ton::tl_object_ptr Config::tl() const { return ton::create_tl_object( out_port, std::move(addrs_vec), std::move(adnl_vec), std::move(dht_vec), std::move(val_vec), ton::PublicKeyHash::zero().tl(), std::move(full_node_slaves_vec), std::move(full_node_masters_vec), - std::move(liteserver_vec), std::move(control_vec), std::move(gc_vec)); + nullptr, std::move(liteserver_vec), std::move(control_vec), std::move(gc_vec)); } td::Result Config::config_add_network_addr(td::IPAddress in_ip, td::IPAddress out_ip, diff --git a/tdutils/td/utils/OptionParser.cpp b/tdutils/td/utils/OptionParser.cpp index b9584856..634570e1 100644 --- a/tdutils/td/utils/OptionParser.cpp +++ b/tdutils/td/utils/OptionParser.cpp @@ -33,7 +33,7 @@ void OptionParser::set_description(string description) { void OptionParser::add_option(Option::Type type, char short_key, Slice long_key, Slice description, std::function callback) { for (auto &option : options_) { - if (option.short_key == short_key || (!long_key.empty() && long_key == option.long_key)) { + if ((short_key != '\0' && option.short_key == short_key) || (!long_key.empty() && long_key == option.long_key)) { LOG(ERROR) << "Ignore duplicated option '" << short_key << "' '" << long_key << "'"; } } diff --git a/tl/generate/scheme/ton_api.tl b/tl/generate/scheme/ton_api.tl index 643825ea..e9b41182 100644 --- a/tl/generate/scheme/ton_api.tl +++ b/tl/generate/scheme/ton_api.tl @@ -588,12 +588,14 @@ engine.gc ids:(vector int256) = engine.Gc; engine.dht.config dht:(vector engine.dht) gc:engine.gc = engine.dht.Config; engine.validator.fullNodeMaster port:int adnl:int256 = engine.validator.FullNodeMaster; engine.validator.fullNodeSlave ip:int port:int adnl:PublicKey = engine.validator.FullNodeSlave; +engine.validator.fullNodeConfig ext_messages_broadcast_disabled:Bool = engine.validator.FullNodeConfig; engine.validator.config out_port:int addrs:(vector engine.Addr) adnl:(vector engine.adnl) dht:(vector engine.dht) validators:(vector engine.validator) fullnode:int256 fullnodeslaves:(vector engine.validator.fullNodeSlave) fullnodemasters:(vector engine.validator.fullNodeMaster) + fullnodeconfig:engine.validator.fullNodeConfig liteservers:(vector engine.liteServer) control:(vector engine.controlInterface) - gc:engine.gc = engine.validator.Config; + gc:engine.gc = engine.validator.Config; ---functions--- ---types--- @@ -642,6 +644,8 @@ engine.validator.onePerfTimerStat time:int min:double avg:double max:double = en engine.validator.perfTimerStatsByName name:string stats:(vector engine.validator.OnePerfTimerStat) = engine.validator.PerfTimerStatsByName; engine.validator.perfTimerStats stats:(vector engine.validator.PerfTimerStatsByName) = engine.validator.PerfTimerStats; +engine.validator.shardOutQueueSize size:int = engine.validator.ShardOutQueueSize; + ---functions--- @@ -693,6 +697,8 @@ engine.validator.signShardOverlayCertificate workchain:int shard:long signed_key engine.validator.importShardOverlayCertificate workchain:int shard:long signed_key:engine.validator.KeyHash cert:overlay.Certificate = engine.validator.Success; engine.validator.getPerfTimerStats name:string = engine.validator.PerfTimerStats; +engine.validator.getShardOutQueueSize flags:# block_id:tonNode.blockId dest_wc:flags.0?int dest_shard:flags.0?long = engine.validator.ShardOutQueueSize; +engine.validator.setExtMessagesBroadcastDisabled disabled:Bool = engine.validator.Success; ---types--- diff --git a/tl/generate/scheme/ton_api.tlo b/tl/generate/scheme/ton_api.tlo index 3c81ca06d4678fc20baf53591156c6a347034762..702fbd6a0c352460d7c5afd18bac916f9a57acda 100644 GIT binary patch delta 525 zcmZqaVQrYi%KK=xz7+!${MyJ{FU)d5PX5K_3Bo#zEFv5KX-+;MBE{yMpO==IK5>J@ z<~=47TMmgE=d1!p7{rTCYY1eT_jrUqwLrB1%jEV=oN zc!C6D`DR^%^RZx)C-q5if^4{XOvs04vtr+LMj5`e(wv;U{FKyWur*-EaM-pT1Y5tR ze+I-w9updvATByHNy-f3B9ID@L5x5>($DT|%BNP8#OJ0K7bm8t7RM(QgAou6a1&T2FHF>)%#kiK`N(ep4)&B(pySGuCkF&52y%fr zV9ObpKw&U_qY|S$*rx4oWEi(F1>WME*kynb?8T`it`#M|P)|F-{OkhtGSHyzWluIE ThY|-oI39kJ-#$T}u|y33M(e~* delta 119 zcmZpe#M;us%KK=xz7+!${Mg7_FU+z;^3b`>6NGgbH~$b3VBYK@ks!fXx_PC+`B<>j zx&Nw+{LJvgSn+Xj}o8L_4;M|-rS0szEW_u|+qpi04q-{?*IS* diff --git a/validator-engine-console/validator-engine-console-query.cpp b/validator-engine-console/validator-engine-console-query.cpp index 1aee7cc4..bd13225a 100644 --- a/validator-engine-console/validator-engine-console-query.cpp +++ b/validator-engine-console/validator-engine-console-query.cpp @@ -32,6 +32,7 @@ #include "terminal/terminal.h" #include "td/utils/filesystem.h" #include "overlay/overlays.h" +#include "ton/ton-tl.hpp" #include #include @@ -1055,3 +1056,54 @@ td::Status GetPerfTimerStatsJsonQuery::receive(td::BufferSlice data) { td::TerminalIO::output(std::string("wrote stats to " + file_name_ + "\n")); return td::Status::OK(); } + +td::Status GetShardOutQueueSizeQuery::run() { + TRY_RESULT_ASSIGN(block_id_.workchain, tokenizer_.get_token()); + TRY_RESULT_ASSIGN(block_id_.shard, tokenizer_.get_token()); + TRY_RESULT_ASSIGN(block_id_.seqno, tokenizer_.get_token()); + if (!tokenizer_.endl()) { + ton::ShardIdFull dest; + TRY_RESULT_ASSIGN(dest.workchain, tokenizer_.get_token()); + TRY_RESULT_ASSIGN(dest.shard, tokenizer_.get_token()); + dest_ = dest; + } + TRY_STATUS(tokenizer_.check_endl()); + return td::Status::OK(); +} + +td::Status GetShardOutQueueSizeQuery::send() { + auto b = ton::create_serialize_tl_object( + dest_ ? 1 : 0, ton::create_tl_block_id_simple(block_id_), dest_ ? dest_.value().workchain : 0, + dest_ ? dest_.value().shard : 0); + td::actor::send_closure(console_, &ValidatorEngineConsole::envelope_send_query, std::move(b), create_promise()); + return td::Status::OK(); +} + +td::Status GetShardOutQueueSizeQuery::receive(td::BufferSlice data) { + TRY_RESULT_PREFIX(f, ton::fetch_tl_object(data.as_slice(), true), + "received incorrect answer: "); + td::TerminalIO::out() << "Queue_size: " << f->size_ << "\n"; + return td::Status::OK(); +} + +td::Status SetExtMessagesBroadcastDisabledQuery::run() { + TRY_RESULT(x, tokenizer_.get_token()); + if (x < 0 || x > 1) { + return td::Status::Error("value should be 0 or 1"); + } + value = x; + return td::Status::OK(); +} + +td::Status SetExtMessagesBroadcastDisabledQuery::send() { + auto b = ton::create_serialize_tl_object(value); + td::actor::send_closure(console_, &ValidatorEngineConsole::envelope_send_query, std::move(b), create_promise()); + return td::Status::OK(); +} + +td::Status SetExtMessagesBroadcastDisabledQuery::receive(td::BufferSlice data) { + TRY_RESULT_PREFIX(f, ton::fetch_tl_object(data.as_slice(), true), + "received incorrect answer: "); + td::TerminalIO::out() << "success\n"; + return td::Status::OK(); +} diff --git a/validator-engine-console/validator-engine-console-query.h b/validator-engine-console/validator-engine-console-query.h index ab2141dd..b1bdac7c 100644 --- a/validator-engine-console/validator-engine-console-query.h +++ b/validator-engine-console/validator-engine-console-query.h @@ -33,6 +33,7 @@ #include "td/utils/SharedSlice.h" #include "td/utils/port/IPAddress.h" #include "td/actor/actor.h" +#include "ton/ton-types.h" #include "keys/keys.hpp" @@ -1096,3 +1097,50 @@ class GetPerfTimerStatsJsonQuery : public Query { private: std::string file_name_; }; + +class GetShardOutQueueSizeQuery : public Query { + public: + GetShardOutQueueSizeQuery(td::actor::ActorId console, Tokenizer tokenizer) + : Query(console, std::move(tokenizer)) { + } + td::Status run() override; + td::Status send() override; + td::Status receive(td::BufferSlice data) override; + static std::string get_name() { + return "getshardoutqueuesize"; + } + static std::string get_help() { + return "getshardoutqueuesize [ ]\treturns number of messages in the " + "queue of the given shard. Destination shard is optional."; + } + std::string name() const override { + return get_name(); + } + + private: + ton::BlockId block_id_; + td::optional dest_; +}; + +class SetExtMessagesBroadcastDisabledQuery : public Query { + public: + SetExtMessagesBroadcastDisabledQuery(td::actor::ActorId console, Tokenizer tokenizer) + : Query(console, std::move(tokenizer)) { + } + td::Status run() override; + td::Status send() override; + td::Status receive(td::BufferSlice data) override; + static std::string get_name() { + return "setextmessagesbroadcastdisabled"; + } + static std::string get_help() { + return "setextmessagesbroadcastdisabled \tdisable broadcasting and rebroadcasting ext messages; value is 0 " + "or 1."; + } + std::string name() const override { + return get_name(); + } + + private: + bool value; +}; diff --git a/validator-engine-console/validator-engine-console.cpp b/validator-engine-console/validator-engine-console.cpp index 5ce8526b..01acced9 100644 --- a/validator-engine-console/validator-engine-console.cpp +++ b/validator-engine-console/validator-engine-console.cpp @@ -141,6 +141,8 @@ void ValidatorEngineConsole::run() { add_query_runner(std::make_unique>()); add_query_runner(std::make_unique>()); add_query_runner(std::make_unique>()); + add_query_runner(std::make_unique>()); + add_query_runner(std::make_unique>()); } bool ValidatorEngineConsole::envelope_send_query(td::BufferSlice query, td::Promise promise) { diff --git a/validator-engine/CMakeLists.txt b/validator-engine/CMakeLists.txt index 6c1ea7e2..d369a2c3 100644 --- a/validator-engine/CMakeLists.txt +++ b/validator-engine/CMakeLists.txt @@ -12,7 +12,7 @@ set(VALIDATOR_ENGINE_SOURCE add_executable(validator-engine ${VALIDATOR_ENGINE_SOURCE}) target_link_libraries(validator-engine overlay tdutils tdactor adnl tl_api dht - rldp catchain validatorsession full-node validator ton_validator validator + rldp rldp2 catchain validatorsession full-node validator ton_validator validator fift-lib memprof git ${JEMALLOC_LIBRARIES}) install(TARGETS validator-engine RUNTIME DESTINATION bin) diff --git a/validator-engine/validator-engine.cpp b/validator-engine/validator-engine.cpp index b6eb8b26..02ab42f9 100644 --- a/validator-engine/validator-engine.cpp +++ b/validator-engine/validator-engine.cpp @@ -69,7 +69,8 @@ #include #include #include "git.h" - +#include "block-auto.h" +#include "block-parse.h" Config::Config() { out_port = 3278; @@ -149,6 +150,10 @@ Config::Config(ton::ton_api::engine_validator_config &config) { config_add_full_node_master(s->port_, ton::PublicKeyHash{s->adnl_}).ensure(); } + if (config.fullnodeconfig_) { + full_node_config = ton::validator::fullnode::FullNodeConfig(config.fullnodeconfig_); + } + for (auto &serv : config.liteservers_) { config_add_lite_server(ton::PublicKeyHash{serv->id_}, serv->port_).ensure(); } @@ -219,6 +224,11 @@ ton::tl_object_ptr Config::tl() const { ton::create_tl_object(x.first, x.second.tl())); } + ton::tl_object_ptr full_node_config_obj = {}; + if (full_node_config != ton::validator::fullnode::FullNodeConfig()) { + full_node_config_obj = full_node_config.tl(); + } + std::vector> liteserver_vec; for (auto &x : liteservers) { liteserver_vec.push_back(ton::create_tl_object(x.second.tl(), x.first)); @@ -240,8 +250,8 @@ ton::tl_object_ptr Config::tl() const { } return ton::create_tl_object( out_port, std::move(addrs_vec), std::move(adnl_vec), std::move(dht_vec), std::move(val_vec), full_node.tl(), - std::move(full_node_slaves_vec), std::move(full_node_masters_vec), std::move(liteserver_vec), - std::move(control_vec), std::move(gc_vec)); + std::move(full_node_slaves_vec), std::move(full_node_masters_vec), std::move(full_node_config_obj), + std::move(liteserver_vec), std::move(control_vec), std::move(gc_vec)); } td::Result Config::config_add_network_addr(td::IPAddress in_ip, td::IPAddress out_ip, @@ -1742,6 +1752,7 @@ void ValidatorEngine::started_dht() { void ValidatorEngine::start_rldp() { rldp_ = ton::rldp::Rldp::create(adnl_.get()); + rldp2_ = ton::rldp2::Rldp::create(adnl_.get()); started_rldp(); } @@ -1804,7 +1815,7 @@ void ValidatorEngine::start_full_node() { } full_node_ = ton::validator::fullnode::FullNode::create( short_id, ton::adnl::AdnlNodeIdShort{config_.full_node}, validator_options_->zero_block_id().file_hash, - keyring_.get(), adnl_.get(), rldp_.get(), + config_.full_node_config, keyring_.get(), adnl_.get(), rldp_.get(), rldp2_.get(), default_dht_node_.is_zero() ? td::actor::ActorId{} : dht_nodes_[default_dht_node_].get(), overlay_manager_.get(), validator_manager_.get(), full_node_client_.get(), db_root_); } @@ -3333,6 +3344,112 @@ void ValidatorEngine::run_control_query(ton::ton_api::engine_validator_getPerfTi td::actor::send_closure(validator_manager_, &ton::validator::ValidatorManagerInterface::prepare_perf_timer_stats, std::move(P)); } +void ValidatorEngine::run_control_query(ton::ton_api::engine_validator_getShardOutQueueSize &query, + td::BufferSlice data, ton::PublicKeyHash src, td::uint32 perm, + td::Promise promise) { + if (!(perm & ValidatorEnginePermissions::vep_default)) { + promise.set_value(create_control_query_error(td::Status::Error(ton::ErrorCode::error, "not authorized"))); + return; + } + + if (validator_manager_.empty()) { + promise.set_value( + create_control_query_error(td::Status::Error(ton::ErrorCode::notready, "validator manager not started"))); + return; + } + + ton::BlockId block_id = ton::create_block_id_simple(query.block_id_); + if (!block_id.is_valid_ext()) { + promise.set_value(create_control_query_error(td::Status::Error(ton::ErrorCode::error, "invalid block id"))); + return; + } + td::optional dest; + if (query.flags_ & 1) { + dest = ton::ShardIdFull{query.dest_wc_, (ton::ShardId)query.dest_shard_}; + if (!dest.value().is_valid_ext()) { + promise.set_value(create_control_query_error(td::Status::Error(ton::ErrorCode::error, "invalid shard"))); + return; + } + } + + td::actor::send_closure( + validator_manager_, &ton::validator::ValidatorManagerInterface::get_block_by_seqno_from_db, + ton::AccountIdPrefixFull{block_id.workchain, block_id.shard}, block_id.seqno, + [=, promise = std::move(promise), + manager = validator_manager_.get()](td::Result R) mutable { + if (R.is_error()) { + promise.set_value(create_control_query_error(R.move_as_error())); + return; + } + auto handle = R.move_as_ok(); + if (handle->id().id != block_id) { + promise.set_value(create_control_query_error(td::Status::Error(ton::ErrorCode::notready, "no such block"))); + return; + } + td::actor::send_closure( + manager, &ton::validator::ValidatorManagerInterface::get_shard_state_from_db, handle, + [=, promise = std::move(promise)](td::Result> R) mutable { + auto res = [&]() -> td::Result { + TRY_RESULT(state, std::move(R)); + TRY_RESULT(outq_descr, state->message_queue()); + block::gen::OutMsgQueueInfo::Record qinfo; + if (!tlb::unpack_cell(outq_descr->root_cell(), qinfo)) { + return td::Status::Error(ton::ErrorCode::error, "invalid message queue"); + } + auto queue = std::make_unique(qinfo.out_queue->prefetch_ref(0), 352, + block::tlb::aug_OutMsgQueue); + if (dest) { + td::BitArray<96> prefix; + td::BitPtr ptr = prefix.bits(); + ptr.store_int(dest.value().workchain, 32); + ptr.advance(32); + ptr.store_uint(dest.value().shard, 64); + if (!queue->cut_prefix_subdict(prefix.bits(), 32 + dest.value().pfx_len())) { + return td::Status::Error(ton::ErrorCode::error, "invalid message queue"); + } + } + int size = 0; + queue->check_for_each([&](td::Ref, td::ConstBitPtr, int) -> bool { + ++size; + return true; + }); + return ton::create_serialize_tl_object(size); + }(); + if (res.is_error()) { + promise.set_value(create_control_query_error(res.move_as_error())); + } else { + promise.set_value(res.move_as_ok()); + } + }); + }); +} + +void ValidatorEngine::run_control_query(ton::ton_api::engine_validator_setExtMessagesBroadcastDisabled &query, td::BufferSlice data, ton::PublicKeyHash src, td::uint32 perm, + td::Promise promise) { + if (!(perm & ValidatorEnginePermissions::vep_modify)) { + promise.set_value(create_control_query_error(td::Status::Error(ton::ErrorCode::error, "not authorized"))); + return; + } + if (!started_) { + promise.set_value(create_control_query_error(td::Status::Error(ton::ErrorCode::notready, "not started"))); + return; + } + + if (config_.full_node_config.ext_messages_broadcast_disabled_ == query.disabled_) { + promise.set_value(ton::create_serialize_tl_object()); + return; + } + config_.full_node_config.ext_messages_broadcast_disabled_ = query.disabled_; + td::actor::send_closure(full_node_, &ton::validator::fullnode::FullNode::set_config, config_.full_node_config); + write_config([promise = std::move(promise)](td::Result R) mutable { + if (R.is_error()) { + promise.set_value(create_control_query_error(R.move_as_error())); + } else { + promise.set_value(ton::create_serialize_tl_object()); + } + }); +} + void ValidatorEngine::process_control_query(td::uint16 port, ton::adnl::AdnlNodeIdShort src, ton::adnl::AdnlNodeIdShort dst, td::BufferSlice data, td::Promise promise) { diff --git a/validator-engine/validator-engine.hpp b/validator-engine/validator-engine.hpp index 7284a5be..e2367419 100644 --- a/validator-engine/validator-engine.hpp +++ b/validator-engine/validator-engine.hpp @@ -30,6 +30,7 @@ #include "adnl/adnl.h" #include "auto/tl/ton_api.h" #include "rldp/rldp.h" +#include "rldp2/rldp.h" #include "dht/dht.h" #include "validator/manager.h" #include "validator/validator.h" @@ -85,6 +86,7 @@ struct Config { std::vector full_node_slaves; std::map full_node_masters; std::map liteservers; + ton::validator::fullnode::FullNodeConfig full_node_config; std::map controls; std::set gc; @@ -137,6 +139,7 @@ class ValidatorEngine : public td::actor::Actor { td::actor::ActorOwn adnl_network_manager_; td::actor::ActorOwn adnl_; td::actor::ActorOwn rldp_; + td::actor::ActorOwn rldp2_; std::map> dht_nodes_; ton::PublicKeyHash default_dht_node_ = ton::PublicKeyHash::zero(); td::actor::ActorOwn overlay_manager_; @@ -409,6 +412,10 @@ class ValidatorEngine : public td::actor::Actor { ton::PublicKeyHash src, td::uint32 perm, td::Promise promise); void run_control_query(ton::ton_api::engine_validator_getPerfTimerStats &query, td::BufferSlice data, ton::PublicKeyHash src, td::uint32 perm, td::Promise promise); + void run_control_query(ton::ton_api::engine_validator_getShardOutQueueSize &query, td::BufferSlice data, + ton::PublicKeyHash src, td::uint32 perm, td::Promise promise); + void run_control_query(ton::ton_api::engine_validator_setExtMessagesBroadcastDisabled &query, td::BufferSlice data, + ton::PublicKeyHash src, td::uint32 perm, td::Promise promise); template void run_control_query(T &query, td::BufferSlice data, ton::PublicKeyHash src, td::uint32 perm, td::Promise promise) { diff --git a/validator-session/validator-session.cpp b/validator-session/validator-session.cpp index 3f56e3a3..2902b082 100644 --- a/validator-session/validator-session.cpp +++ b/validator-session/validator-session.cpp @@ -45,8 +45,8 @@ void ValidatorSessionImpl::process_blocks(std::vector std::vector> msgs; if (generated_ && !sent_generated_) { - auto it = blocks_[0].find(generated_block_); - CHECK(it != blocks_[0].end()); + auto it = blocks_.find(generated_block_); + CHECK(it != blocks_.end()); auto &B = it->second; auto file_hash = sha256_bits256(B->data_); @@ -230,13 +230,15 @@ void ValidatorSessionImpl::process_broadcast(PublicKeyHash src, td::BufferSlice auto block_round = static_cast(candidate->round_); auto block_id = description().candidate_id(src_idx, candidate->root_hash_, file_hash, collated_data_file_hash); - if (block_round < cur_round_ || block_round >= cur_round_ + blocks_.size()) { + if ((td::int32)block_round < (td::int32)cur_round_ - MAX_PAST_ROUND_BLOCK || + block_round >= cur_round_ + MAX_FUTURE_ROUND_BLOCK) { VLOG(VALIDATOR_SESSION_NOTICE) << this << "[node " << src << "][broadcast " << block_id << "]: bad round=" << block_round << " cur_round" << cur_round_; return; } - auto it = blocks_[block_round - cur_round_].find(block_id); - if (it != blocks_[block_round - cur_round_].end()) { + auto it = blocks_.find(block_id); + if (it != blocks_.end()) { + it->second->round_ = std::max(it->second->round_, block_round); VLOG(VALIDATOR_SESSION_INFO) << this << "[node " << src << "][broadcast " << block_id << "]: duplicate"; return; } @@ -248,7 +250,7 @@ void ValidatorSessionImpl::process_broadcast(PublicKeyHash src, td::BufferSlice return; } - blocks_[block_round - cur_round_][block_id] = std::move(candidate); + blocks_[block_id] = std::move(candidate); VLOG(VALIDATOR_SESSION_WARNING) << this << ": received broadcast " << block_id; if (block_round != cur_round_) { @@ -407,7 +409,7 @@ void ValidatorSessionImpl::generated_block(td::uint32 round, ValidatorSessionCan td::actor::send_closure(catchain_, &catchain::CatChain::send_broadcast, std::move(B)); - blocks_[0].emplace(block_id, std::move(b)); + blocks_.emplace(block_id, std::move(b)); pending_generate_ = false; generated_ = true; generated_block_ = block_id; @@ -507,9 +509,10 @@ void ValidatorSessionImpl::try_approve_block(const SentBlock *block) { if (block) { auto T = td::Timestamp::at(round_started_at_.at() + description().get_delay(block->get_src_idx()) + 2.0); - auto it = blocks_[0].find(block_id); + auto it = blocks_.find(block_id); - if (it != blocks_[0].end()) { + if (it != blocks_.end()) { + it->second->round_ = std::max(it->second->round_, cur_round_); td::PerfWarningTimer timer{"too long block validation", 1.0}; auto &B = it->second; @@ -532,7 +535,6 @@ void ValidatorSessionImpl::try_approve_block(const SentBlock *block) { } }); pending_approve_.insert(block_id); - CHECK(static_cast(cur_round_) == B->round_); callback_->on_candidate(cur_round_, description().get_source_public_key(block->get_src_idx()), B->root_hash_, B->data_.clone(), B->collated_data_.clone(), std::move(P)); @@ -556,7 +558,7 @@ void ValidatorSessionImpl::try_approve_block(const SentBlock *block) { get_broadcast_p2p(id, block->get_file_hash(), block->get_collated_data_file_hash(), description().get_source_id(block->get_src_idx()), cur_round_, block->get_root_hash(), - std::move(P), td::Timestamp::in(2.0)); + std::move(P), td::Timestamp::in(15.0)); } else { LOG(VALIDATOR_SESSION_DEBUG) << this << ": no nodes to download candidate " << block << " from"; } @@ -773,7 +775,7 @@ void ValidatorSessionImpl::on_new_round(td::uint32 round) { } } - auto it = blocks_[0].find(SentBlock::get_block_id(block)); + auto it = blocks_.find(SentBlock::get_block_id(block)); bool have_block = (bool)block; if (!have_block) { callback_->on_block_skipped(cur_round_); @@ -788,7 +790,7 @@ void ValidatorSessionImpl::on_new_round(td::uint32 round) { cur_stats_.creator = description().get_source_id(block->get_src_idx()); cur_stats_.self = description().get_source_id(local_idx()); - if (it == blocks_[0].end()) { + if (it == blocks_.end()) { callback_->on_block_committed(cur_round_, description().get_source_public_key(block->get_src_idx()), block->get_root_hash(), block->get_file_hash(), td::BufferSlice(), std::move(export_sigs), std::move(export_approve_sigs), std::move(cur_stats_)); @@ -804,10 +806,14 @@ void ValidatorSessionImpl::on_new_round(td::uint32 round) { } else { stats_add_round(); } - for (size_t i = 0; i < blocks_.size() - 1; i++) { - blocks_[i] = std::move(blocks_[i + 1]); + auto it2 = blocks_.begin(); + while (it2 != blocks_.end()) { + if (it2->second->round_ < (td::int32)cur_round_ - MAX_PAST_ROUND_BLOCK) { + it2 = blocks_.erase(it2); + } else { + ++it2; + } } - blocks_[blocks_.size() - 1].clear(); } round_started_at_ = td::Timestamp::now(); diff --git a/validator-session/validator-session.hpp b/validator-session/validator-session.hpp index 54bba33d..e8274554 100644 --- a/validator-session/validator-session.hpp +++ b/validator-session/validator-session.hpp @@ -73,7 +73,7 @@ class ValidatorSessionImpl : public ValidatorSession { ValidatorSessionCandidateId signed_block_; td::BufferSlice signature_; - std::array>, 100> blocks_; + std::map> blocks_; catchain::CatChainSessionId unique_hash_; @@ -204,6 +204,8 @@ class ValidatorSessionImpl : public ValidatorSession { private: static const size_t MAX_REJECT_REASON_SIZE = 1024; + static const td::int32 MAX_FUTURE_ROUND_BLOCK = 100; + static const td::int32 MAX_PAST_ROUND_BLOCK = 20; }; } // namespace validatorsession diff --git a/validator/CMakeLists.txt b/validator/CMakeLists.txt index 65d7b348..573cd8e5 100644 --- a/validator/CMakeLists.txt +++ b/validator/CMakeLists.txt @@ -198,5 +198,5 @@ target_link_libraries(validator-disk PRIVATE tdutils tdactor adnl rldp tl_api dh target_link_libraries(validator-hardfork PRIVATE tdutils tdactor adnl rldp tl_api dht tdfec overlay catchain validatorsession ton_crypto ton_block ton_db) -target_link_libraries(full-node PRIVATE tdutils tdactor adnl rldp tl_api dht tdfec +target_link_libraries(full-node PRIVATE tdutils tdactor adnl rldp rldp2 tl_api dht tdfec overlay catchain validatorsession ton_crypto ton_block ton_db) diff --git a/validator/db/celldb.cpp b/validator/db/celldb.cpp index e142043e..c633e395 100644 --- a/validator/db/celldb.cpp +++ b/validator/db/celldb.cpp @@ -33,11 +33,11 @@ class CellDbAsyncExecutor : public vm::DynamicBagOfCellsDb::AsyncExecutor { explicit CellDbAsyncExecutor(td::actor::ActorId cell_db) : cell_db_(std::move(cell_db)) { } - void execute_async(std::function f) { + void execute_async(std::function f) override { class Runner : public td::actor::Actor { public: explicit Runner(std::function f) : f_(std::move(f)) {} - void start_up() { + void start_up() override { f_(); stop(); } @@ -47,7 +47,7 @@ class CellDbAsyncExecutor : public vm::DynamicBagOfCellsDb::AsyncExecutor { td::actor::create_actor("executeasync", std::move(f)).release(); } - void execute_sync(std::function f) { + void execute_sync(std::function f) override { td::actor::send_closure(cell_db_, &CellDbBase::execute_sync, std::move(f)); } private: @@ -83,23 +83,45 @@ void CellDbIn::start_up() { set_block(empty, std::move(e)); cell_db_->commit_write_batch().ensure(); } - last_gc_ = empty; } void CellDbIn::load_cell(RootHash hash, td::Promise> promise) { - boc_->load_cell_async(hash.as_slice(), async_executor, std::move(promise)); + enqueue([this, hash, promise = std::move(promise)](td::Result R) mutable { + if (R.is_error()) { + return; + } + promise.set_result(boc_->load_cell(hash.as_slice())); + release_db(); + }); } void CellDbIn::store_cell(BlockIdExt block_id, td::Ref cell, td::Promise> promise) { - td::PerfWarningTimer timer{"storecell", 0.1}; - auto key_hash = get_key_hash(block_id); - auto R = get_block(key_hash); - // duplicate - if (R.is_ok()) { - promise.set_result(boc_->load_cell(cell->get_hash().as_slice())); - return; - } + enqueue([this, block_id, cell = std::move(cell), promise = std::move(promise)](td::Result R0) mutable { + if (R0.is_error()) { + return; + } + promise = promise.wrap([timer = td::PerfWarningTimer{"storecell", 0.1}](td::Ref &&r) { return r; }); + auto key_hash = get_key_hash(block_id); + auto R = get_block(key_hash); + // duplicate + if (R.is_ok()) { + promise.set_result(boc_->load_cell(cell->get_hash().as_slice())); + release_db(); + return; + } + boc_->inc(cell); + boc_->prepare_commit_async( + async_executor, [=, SelfId = actor_id(this), promise = std::move(promise)](td::Result R) mutable { + R.ensure(); + td::actor::send_closure(SelfId, &CellDbIn::store_cell_cont, block_id, cell, std::move(promise)); + }); + }); +} + +void CellDbIn::store_cell_cont(BlockIdExt block_id, td::Ref cell, + td::Promise> promise) { + auto key_hash = get_key_hash(block_id); auto empty = get_empty_key_hash(); auto ER = get_block(empty); ER.ensure(); @@ -120,9 +142,7 @@ void CellDbIn::store_cell(BlockIdExt block_id, td::Ref cell, td::Promi P.prev = key_hash; } - boc_->inc(cell); - boc_->prepare_commit().ensure(); - vm::CellStorer stor{*cell_db_.get()}; + vm::CellStorer stor{*cell_db_}; cell_db_->begin_write_batch().ensure(); boc_->commit(stor).ensure(); set_block(empty, std::move(E)); @@ -134,24 +154,29 @@ void CellDbIn::store_cell(BlockIdExt block_id, td::Ref cell, td::Promi td::actor::send_closure(parent_, &CellDb::update_snapshot, cell_db_->snapshot()); promise.set_result(boc_->load_cell(cell->get_hash().as_slice())); + release_db(); } void CellDbIn::get_cell_db_reader(td::Promise> promise) { - promise.set_result(boc_->get_cell_db_reader()); + enqueue([this, promise = std::move(promise)](td::Result R) mutable { + if (R.is_error()) { + return; + } + promise.set_result(boc_->get_cell_db_reader()); + release_db(); + }); } void CellDbIn::alarm() { - auto R = get_block(last_gc_); - R.ensure(); - - auto N = R.move_as_ok(); + auto E = get_block(get_empty_key_hash()).move_as_ok(); + auto N = get_block(E.next).move_as_ok(); if (N.is_empty()) { - last_gc_ = N.next; alarm_timestamp() = td::Timestamp::in(0.1); return; } - auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) { + auto block_id = N.block_id; + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this), block_id](td::Result R) { if (R.is_error()) { td::actor::send_closure(SelfId, &CellDbIn::skip_gc); } else { @@ -159,24 +184,19 @@ void CellDbIn::alarm() { if (!value) { td::actor::send_closure(SelfId, &CellDbIn::skip_gc); } else { - td::actor::send_closure(SelfId, &CellDbIn::gc); + td::actor::send_closure(SelfId, &CellDbIn::gc, block_id); } } }); - td::actor::send_closure(root_db_, &RootDb::allow_state_gc, N.block_id, std::move(P)); + td::actor::send_closure(root_db_, &RootDb::allow_state_gc, block_id, std::move(P)); } -void CellDbIn::gc() { - auto R = get_block(last_gc_); - R.ensure(); - - auto N = R.move_as_ok(); - +void CellDbIn::gc(BlockIdExt block_id) { auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) { R.ensure(); td::actor::send_closure(SelfId, &CellDbIn::gc_cont, R.move_as_ok()); }); - td::actor::send_closure(root_db_, &RootDb::get_block_handle_external, N.block_id, false, std::move(P)); + td::actor::send_closure(root_db_, &RootDb::get_block_handle_external, block_id, false, std::move(P)); } void CellDbIn::gc_cont(BlockHandle handle) { @@ -194,12 +214,30 @@ void CellDbIn::gc_cont(BlockHandle handle) { } void CellDbIn::gc_cont2(BlockHandle handle) { - td::PerfWarningTimer timer{"gccell", 0.1}; + enqueue([this, handle = std::move(handle)](td::Result R) mutable { + if (R.is_error()) { + return; + } + td::Promise promise = [timer = td::PerfWarningTimer{"gccell", 0.1}](td::Result) {}; + auto FR = get_block(get_key_hash(handle->id())); + FR.ensure(); + auto F = FR.move_as_ok(); + auto cell = boc_->load_cell(F.root_hash.as_slice()).move_as_ok(); - auto FR = get_block(last_gc_); + boc_->dec(cell); + boc_->prepare_commit_async(async_executor, [SelfId = actor_id(this), promise = std::move(promise), + block_id = handle->id()](td::Result R) mutable { + R.ensure(); + td::actor::send_closure(SelfId, &CellDbIn::gc_cont3, block_id, std::move(promise)); + }); + }); +} + +void CellDbIn::gc_cont3(BlockIdExt block_id, td::Promise promise) { + auto key_hash = get_key_hash(block_id); + auto FR = get_block(key_hash); FR.ensure(); auto F = FR.move_as_ok(); - auto PR = get_block(F.prev); PR.ensure(); auto P = PR.move_as_ok(); @@ -214,14 +252,10 @@ void CellDbIn::gc_cont2(BlockHandle handle) { N.next = N.prev; } - auto cell = boc_->load_cell(F.root_hash.as_slice()).move_as_ok(); - - boc_->dec(cell); - boc_->prepare_commit().ensure(); - vm::CellStorer stor{*cell_db_.get()}; + vm::CellStorer stor{*cell_db_}; cell_db_->begin_write_batch().ensure(); boc_->commit(stor).ensure(); - cell_db_->erase(get_key(last_gc_)).ensure(); + cell_db_->erase(get_key(key_hash)).ensure(); set_block(F.prev, std::move(P)); set_block(F.next, std::move(N)); cell_db_->commit_write_batch().ensure(); @@ -230,16 +264,33 @@ void CellDbIn::gc_cont2(BlockHandle handle) { boc_->set_loader(std::make_unique(cell_db_->snapshot())).ensure(); td::actor::send_closure(parent_, &CellDb::update_snapshot, cell_db_->snapshot()); - DCHECK(get_block(last_gc_).is_error()); - last_gc_ = F.next; + DCHECK(get_block(key_hash).is_error()); + promise.set_result(td::Unit()); + release_db(); +} + +void CellDbIn::enqueue(td::Promise promise) { + db_queue_.push(std::move(promise)); + process_event(); +} + +void CellDbIn::release_db() { + db_busy_ = false; + process_event(); +} + +void CellDbIn::process_event() { + if (db_busy_ || db_queue_.empty()) { + return; + } + db_busy_ = true; + auto promise = std::move(db_queue_.front()); + db_queue_.pop(); + promise.set_result(td::Unit()); } void CellDbIn::skip_gc() { - auto FR = get_block(last_gc_); - FR.ensure(); - auto F = FR.move_as_ok(); - last_gc_ = F.next; - alarm_timestamp() = td::Timestamp::in(0.01); + alarm_timestamp() = td::Timestamp::in(1.0); } std::string CellDbIn::get_key(KeyHash key_hash) { @@ -296,7 +347,7 @@ void CellDb::load_cell(RootHash hash, td::Promise> promise } else { promise.set_result(R.move_as_ok()); } - }); + }); boc_->load_cell_async(hash.as_slice(), async_executor, std::move(P)); } } diff --git a/validator/db/celldb.hpp b/validator/db/celldb.hpp index e54526b9..08d5a184 100644 --- a/validator/db/celldb.hpp +++ b/validator/db/celldb.hpp @@ -25,6 +25,7 @@ #include "ton/ton-types.h" #include "interfaces/block-handle.h" #include "auto/tl/ton_api.h" +#include namespace ton { @@ -84,11 +85,14 @@ class CellDbIn : public CellDbBase { static BlockIdExt get_empty_key(); KeyHash get_empty_key_hash(); - void gc(); + void gc(BlockIdExt block_id); void gc_cont(BlockHandle handle); void gc_cont2(BlockHandle handle); + void gc_cont3(BlockIdExt block_id, td::Promise promise); void skip_gc(); + void store_cell_cont(BlockIdExt block_id, td::Ref cell, td::Promise> promise); + td::actor::ActorId root_db_; td::actor::ActorId parent_; @@ -97,7 +101,12 @@ class CellDbIn : public CellDbBase { std::unique_ptr boc_; std::shared_ptr cell_db_; - KeyHash last_gc_; + std::queue> db_queue_; + bool db_busy_ = false; + + void enqueue(td::Promise promise); + void release_db(); + void process_event(); }; class CellDb : public CellDbBase { diff --git a/validator/full-node-shard.cpp b/validator/full-node-shard.cpp index 4cb5627d..0f495077 100644 --- a/validator/full-node-shard.cpp +++ b/validator/full-node-shard.cpp @@ -96,6 +96,7 @@ void FullNodeShardImpl::create_overlay() { std::make_unique(actor_id(this)), rules_, PSTRING() << "{ \"type\": \"shard\", \"shard_id\": " << get_shard() << ", \"workchain_id\": " << get_workchain() << " }"); td::actor::send_closure(rldp_, &rldp::Rldp::add_id, adnl_id_); + td::actor::send_closure(rldp2_, &rldp2::Rldp::add_id, adnl_id_); if (cert_) { td::actor::send_closure(overlays_, &overlay::Overlays::update_certificate, adnl_id_, overlay_id_, local_id_, cert_); } @@ -108,15 +109,17 @@ void FullNodeShardImpl::check_broadcast(PublicKeyHash src, td::BufferSlice broad } auto q = B.move_as_ok(); + if (config_.ext_messages_broadcast_disabled_) { + promise.set_error(td::Status::Error("rebroadcasting external messages is disabled")); + promise = [manager = validator_manager_, message = q->message_->data_.clone()](td::Result R) mutable { + if (R.is_ok()) { + td::actor::send_closure(manager, &ValidatorManagerInterface::new_external_message, std::move(message)); + } + }; + } td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::check_external_message, std::move(q->message_->data_), - [promise = std::move(promise)](td::Result> R) mutable { - if (R.is_error()) { - promise.set_error(R.move_as_error()); - } else { - promise.set_result(td::Unit()); - } - }); + promise.wrap([](td::Ref) { return td::Unit(); })); } void FullNodeShardImpl::update_adnl_id(adnl::AdnlNodeIdShort adnl_id, td::Promise promise) { @@ -685,6 +688,9 @@ void FullNodeShardImpl::send_ihr_message(td::BufferSlice data) { } void FullNodeShardImpl::send_external_message(td::BufferSlice data) { + if (config_.ext_messages_broadcast_disabled_) { + return; + } if (!client_.empty()) { td::actor::send_closure(client_, &adnl::AdnlExtClient::send_query, "send_ext_query", create_serialize_tl_object_suffix( @@ -768,9 +774,11 @@ void FullNodeShardImpl::download_zero_state(BlockIdExt id, td::uint32 priority, void FullNodeShardImpl::download_persistent_state(BlockIdExt id, BlockIdExt masterchain_block_id, td::uint32 priority, td::Timestamp timeout, td::Promise promise) { + auto &b = choose_neighbour(); td::actor::create_actor(PSTRING() << "downloadstatereq" << id.id.to_str(), id, masterchain_block_id, - adnl_id_, overlay_id_, adnl::AdnlNodeIdShort::zero(), priority, timeout, - validator_manager_, rldp_, overlays_, adnl_, client_, std::move(promise)) + adnl_id_, overlay_id_, b.adnl_id, priority, timeout, validator_manager_, + b.use_rldp2() ? (td::actor::ActorId)rldp2_ : rldp_, + overlays_, adnl_, client_, std::move(promise)) .release(); } @@ -805,8 +813,9 @@ void FullNodeShardImpl::download_archive(BlockSeqno masterchain_seqno, std::stri td::Promise promise) { auto &b = choose_neighbour(); td::actor::create_actor( - "archive", masterchain_seqno, std::move(tmp_dir), adnl_id_, overlay_id_, adnl::AdnlNodeIdShort::zero(), timeout, - validator_manager_, rldp_, overlays_, adnl_, client_, create_neighbour_promise(b, std::move(promise))) + "archive", masterchain_seqno, std::move(tmp_dir), adnl_id_, overlay_id_, b.adnl_id, timeout, validator_manager_, + b.use_rldp2() ? (td::actor::ActorId)rldp2_ : rldp_, overlays_, adnl_, client_, + create_neighbour_promise(b, std::move(promise))) .release(); } @@ -1100,8 +1109,9 @@ void FullNodeShardImpl::ping_neighbours() { } FullNodeShardImpl::FullNodeShardImpl(ShardIdFull shard, PublicKeyHash local_id, adnl::AdnlNodeIdShort adnl_id, - FileHash zero_state_file_hash, td::actor::ActorId keyring, - td::actor::ActorId adnl, td::actor::ActorId rldp, + FileHash zero_state_file_hash, FullNodeConfig config, + td::actor::ActorId keyring, td::actor::ActorId adnl, + td::actor::ActorId rldp, td::actor::ActorId rldp2, td::actor::ActorId overlays, td::actor::ActorId validator_manager, td::actor::ActorId client) @@ -1112,18 +1122,21 @@ FullNodeShardImpl::FullNodeShardImpl(ShardIdFull shard, PublicKeyHash local_id, , keyring_(keyring) , adnl_(adnl) , rldp_(rldp) + , rldp2_(rldp2) , overlays_(overlays) , validator_manager_(validator_manager) - , client_(client) { + , client_(client) + , config_(config) { } td::actor::ActorOwn FullNodeShard::create( ShardIdFull shard, PublicKeyHash local_id, adnl::AdnlNodeIdShort adnl_id, FileHash zero_state_file_hash, - td::actor::ActorId keyring, td::actor::ActorId adnl, - td::actor::ActorId rldp, td::actor::ActorId overlays, - td::actor::ActorId validator_manager, td::actor::ActorId client) { - return td::actor::create_actor("tonnode", shard, local_id, adnl_id, zero_state_file_hash, keyring, - adnl, rldp, overlays, validator_manager, client); + FullNodeConfig config, td::actor::ActorId keyring, td::actor::ActorId adnl, + td::actor::ActorId rldp, td::actor::ActorId rldp2, + td::actor::ActorId overlays, td::actor::ActorId validator_manager, + td::actor::ActorId client) { + return td::actor::create_actor("tonnode", shard, local_id, adnl_id, zero_state_file_hash, config, + keyring, adnl, rldp, rldp2, overlays, validator_manager, client); } } // namespace fullnode diff --git a/validator/full-node-shard.h b/validator/full-node-shard.h index c1712baf..1b742fb9 100644 --- a/validator/full-node-shard.h +++ b/validator/full-node-shard.h @@ -36,6 +36,7 @@ class FullNodeShard : public td::actor::Actor { virtual ShardIdFull get_shard_full() const = 0; virtual void update_adnl_id(adnl::AdnlNodeIdShort adnl_id, td::Promise promise) = 0; + virtual void set_config(FullNodeConfig config) = 0; virtual void send_ihr_message(td::BufferSlice data) = 0; virtual void send_external_message(td::BufferSlice data) = 0; @@ -68,9 +69,10 @@ class FullNodeShard : public td::actor::Actor { static td::actor::ActorOwn create( ShardIdFull shard, PublicKeyHash local_id, adnl::AdnlNodeIdShort adnl_id, FileHash zero_state_file_hash, - td::actor::ActorId keyring, td::actor::ActorId adnl, - td::actor::ActorId rldp, td::actor::ActorId overlays, - td::actor::ActorId validator_manager, td::actor::ActorId client); + FullNodeConfig config, td::actor::ActorId keyring, td::actor::ActorId adnl, + td::actor::ActorId rldp, td::actor::ActorId rldp2, + td::actor::ActorId overlays, td::actor::ActorId validator_manager, + td::actor::ActorId client); }; } // namespace fullnode diff --git a/validator/full-node-shard.hpp b/validator/full-node-shard.hpp index a2dd5cc4..dcf4c649 100644 --- a/validator/full-node-shard.hpp +++ b/validator/full-node-shard.hpp @@ -44,6 +44,10 @@ struct Neighbour { void query_failed(); void update_roundtrip(double t); + bool use_rldp2() const { + return std::make_pair(proto_version, capabilities) >= std::make_pair(2, 2); + } + static Neighbour zero; }; @@ -66,7 +70,7 @@ class FullNodeShardImpl : public FullNodeShard { return 2; } static constexpr td::uint64 proto_capabilities() { - return 1; + return 2; } static constexpr td::uint32 max_neighbours() { return 16; @@ -81,6 +85,10 @@ class FullNodeShardImpl : public FullNodeShard { void create_overlay(); void update_adnl_id(adnl::AdnlNodeIdShort adnl_id, td::Promise promise) override; + void set_config(FullNodeConfig config) override { + config_ = config; + } + //td::Result fetch_block(td::BufferSlice data); void prevalidate_block(BlockIdExt block_id, td::BufferSlice data, td::BufferSlice proof, td::Promise promise); @@ -198,9 +206,9 @@ class FullNodeShardImpl : public FullNodeShard { } FullNodeShardImpl(ShardIdFull shard, PublicKeyHash local_id, adnl::AdnlNodeIdShort adnl_id, - FileHash zero_state_file_hash, td::actor::ActorId keyring, + FileHash zero_state_file_hash, FullNodeConfig config, td::actor::ActorId keyring, td::actor::ActorId adnl, td::actor::ActorId rldp, - td::actor::ActorId overlays, + td::actor::ActorId rldp2, td::actor::ActorId overlays, td::actor::ActorId validator_manager, td::actor::ActorId client); @@ -220,6 +228,7 @@ class FullNodeShardImpl : public FullNodeShard { td::actor::ActorId keyring_; td::actor::ActorId adnl_; td::actor::ActorId rldp_; + td::actor::ActorId rldp2_; td::actor::ActorId overlays_; td::actor::ActorId validator_manager_; td::actor::ActorId client_; @@ -239,6 +248,8 @@ class FullNodeShardImpl : public FullNodeShard { td::Timestamp reload_neighbours_at_; td::Timestamp ping_neighbours_at_; adnl::AdnlNodeIdShort last_pinged_neighbour_ = adnl::AdnlNodeIdShort::zero(); + + FullNodeConfig config_; }; } // namespace fullnode diff --git a/validator/full-node.cpp b/validator/full-node.cpp index 6606f215..ebba50a0 100644 --- a/validator/full-node.cpp +++ b/validator/full-node.cpp @@ -20,6 +20,7 @@ #include "ton/ton-shard.h" #include "ton/ton-io.hpp" #include "td/actor/MultiPromise.h" +#include "full-node.h" namespace ton { @@ -110,6 +111,13 @@ void FullNodeImpl::update_adnl_id(adnl::AdnlNodeIdShort adnl_id, td::Promise R) { R.ensure(); @@ -123,8 +131,8 @@ void FullNodeImpl::initial_read_complete(BlockHandle top_handle) { void FullNodeImpl::add_shard(ShardIdFull shard) { while (true) { if (shards_.count(shard) == 0) { - shards_.emplace(shard, FullNodeShard::create(shard, local_id_, adnl_id_, zero_state_file_hash_, keyring_, adnl_, - rldp_, overlays_, validator_manager_, client_)); + shards_.emplace(shard, FullNodeShard::create(shard, local_id_, adnl_id_, zero_state_file_hash_, config_, keyring_, + adnl_, rldp_, rldp2_, overlays_, validator_manager_, client_)); if (all_validators_.size() > 0) { td::actor::send_closure(shards_[shard], &FullNodeShard::update_validators, all_validators_, sign_cert_by_); } @@ -449,8 +457,9 @@ void FullNodeImpl::start_up() { } FullNodeImpl::FullNodeImpl(PublicKeyHash local_id, adnl::AdnlNodeIdShort adnl_id, FileHash zero_state_file_hash, - td::actor::ActorId keyring, td::actor::ActorId adnl, - td::actor::ActorId rldp, td::actor::ActorId dht, + FullNodeConfig config, td::actor::ActorId keyring, + td::actor::ActorId adnl, td::actor::ActorId rldp, + td::actor::ActorId rldp2, td::actor::ActorId dht, td::actor::ActorId overlays, td::actor::ActorId validator_manager, td::actor::ActorId client, std::string db_root) @@ -460,24 +469,40 @@ FullNodeImpl::FullNodeImpl(PublicKeyHash local_id, adnl::AdnlNodeIdShort adnl_id , keyring_(keyring) , adnl_(adnl) , rldp_(rldp) + , rldp2_(rldp2) , dht_(dht) , overlays_(overlays) , validator_manager_(validator_manager) , client_(client) - , db_root_(db_root) { + , db_root_(db_root) + , config_(config) { add_shard(ShardIdFull{masterchainId}); } td::actor::ActorOwn FullNode::create(ton::PublicKeyHash local_id, adnl::AdnlNodeIdShort adnl_id, - FileHash zero_state_file_hash, + FileHash zero_state_file_hash, FullNodeConfig config, td::actor::ActorId keyring, td::actor::ActorId adnl, td::actor::ActorId rldp, - td::actor::ActorId dht, + td::actor::ActorId rldp2, td::actor::ActorId dht, td::actor::ActorId overlays, td::actor::ActorId validator_manager, td::actor::ActorId client, std::string db_root) { - return td::actor::create_actor("fullnode", local_id, adnl_id, zero_state_file_hash, keyring, adnl, rldp, - dht, overlays, validator_manager, client, db_root); + return td::actor::create_actor("fullnode", local_id, adnl_id, zero_state_file_hash, config, keyring, + adnl, rldp, rldp2, dht, overlays, validator_manager, client, db_root); +} + +FullNodeConfig::FullNodeConfig(const tl_object_ptr &obj) + : ext_messages_broadcast_disabled_(obj->ext_messages_broadcast_disabled_) { +} + +tl_object_ptr FullNodeConfig::tl() const { + return create_tl_object(ext_messages_broadcast_disabled_); +} +bool FullNodeConfig::operator==(const FullNodeConfig &rhs) const { + return ext_messages_broadcast_disabled_ == rhs.ext_messages_broadcast_disabled_; +} +bool FullNodeConfig::operator!=(const FullNodeConfig &rhs) const { + return !(*this == rhs); } } // namespace fullnode diff --git a/validator/full-node.h b/validator/full-node.h index cdf39d6f..15d54b55 100644 --- a/validator/full-node.h +++ b/validator/full-node.h @@ -27,6 +27,7 @@ #include "adnl/adnl.h" #include "rldp/rldp.h" +#include "rldp2/rldp.h" #include "dht/dht.h" #include "overlay/overlays.h" #include "validator/validator.h" @@ -44,6 +45,16 @@ constexpr int VERBOSITY_NAME(FULL_NODE_INFO) = verbosity_DEBUG; constexpr int VERBOSITY_NAME(FULL_NODE_DEBUG) = verbosity_DEBUG; constexpr int VERBOSITY_NAME(FULL_NODE_EXTRA_DEBUG) = verbosity_DEBUG + 1; +struct FullNodeConfig { + FullNodeConfig() = default; + FullNodeConfig(const tl_object_ptr& obj); + tl_object_ptr tl() const; + bool operator==(const FullNodeConfig& rhs) const; + bool operator!=(const FullNodeConfig& rhs) const; + + bool ext_messages_broadcast_disabled_ = false; +}; + class FullNode : public td::actor::Actor { public: virtual ~FullNode() = default; @@ -61,6 +72,7 @@ class FullNode : public td::actor::Actor { td::Promise promise) = 0; virtual void update_adnl_id(adnl::AdnlNodeIdShort adnl_id, td::Promise promise) = 0; + virtual void set_config(FullNodeConfig config) = 0; static constexpr td::uint32 max_block_size() { return 4 << 20; @@ -73,10 +85,10 @@ class FullNode : public td::actor::Actor { } static td::actor::ActorOwn create(ton::PublicKeyHash local_id, adnl::AdnlNodeIdShort adnl_id, - FileHash zero_state_file_hash, + FileHash zero_state_file_hash, FullNodeConfig config, td::actor::ActorId keyring, td::actor::ActorId adnl, td::actor::ActorId rldp, - td::actor::ActorId dht, + td::actor::ActorId rldp2, td::actor::ActorId dht, td::actor::ActorId overlays, td::actor::ActorId validator_manager, td::actor::ActorId client, std::string db_root); diff --git a/validator/full-node.hpp b/validator/full-node.hpp index 6d57f4a8..fc2dd75c 100644 --- a/validator/full-node.hpp +++ b/validator/full-node.hpp @@ -49,8 +49,8 @@ class FullNodeImpl : public FullNode { std::shared_ptr cert, td::Promise promise) override; - void update_adnl_id(adnl::AdnlNodeIdShort adnl_id, td::Promise promise) override; + void set_config(FullNodeConfig config) override; void add_shard(ShardIdFull shard); void del_shard(ShardIdFull shard); @@ -82,9 +82,9 @@ class FullNodeImpl : public FullNode { void start_up() override; FullNodeImpl(PublicKeyHash local_id, adnl::AdnlNodeIdShort adnl_id, FileHash zero_state_file_hash, - td::actor::ActorId keyring, td::actor::ActorId adnl, - td::actor::ActorId rldp, td::actor::ActorId dht, - td::actor::ActorId overlays, + FullNodeConfig config, td::actor::ActorId keyring, td::actor::ActorId adnl, + td::actor::ActorId rldp, td::actor::ActorId rldp2, + td::actor::ActorId dht, td::actor::ActorId overlays, td::actor::ActorId validator_manager, td::actor::ActorId client, std::string db_root); @@ -101,6 +101,7 @@ class FullNodeImpl : public FullNode { td::actor::ActorId keyring_; td::actor::ActorId adnl_; td::actor::ActorId rldp_; + td::actor::ActorId rldp2_; td::actor::ActorId dht_; td::actor::ActorId overlays_; td::actor::ActorId validator_manager_; @@ -112,6 +113,7 @@ class FullNodeImpl : public FullNode { std::vector all_validators_; std::set local_keys_; + FullNodeConfig config_; }; } // namespace fullnode diff --git a/validator/net/download-archive-slice.cpp b/validator/net/download-archive-slice.cpp index e3acb4ba..6235b8b0 100644 --- a/validator/net/download-archive-slice.cpp +++ b/validator/net/download-archive-slice.cpp @@ -29,7 +29,7 @@ namespace fullnode { DownloadArchiveSlice::DownloadArchiveSlice( BlockSeqno masterchain_seqno, std::string tmp_dir, adnl::AdnlNodeIdShort local_id, overlay::OverlayIdShort overlay_id, adnl::AdnlNodeIdShort download_from, td::Timestamp timeout, - td::actor::ActorId validator_manager, td::actor::ActorId rldp, + td::actor::ActorId validator_manager, td::actor::ActorId rldp, td::actor::ActorId overlays, td::actor::ActorId adnl, td::actor::ActorId client, td::Promise promise) : masterchain_seqno_(masterchain_seqno) @@ -144,6 +144,8 @@ void DownloadArchiveSlice::got_archive_info(td::BufferSlice data) { return; } + prev_logged_timer_ = td::Timer(); + LOG(INFO) << "downloading archive slice #" << masterchain_seqno_ << " from " << download_from_; get_archive_slice(); } @@ -159,12 +161,12 @@ void DownloadArchiveSlice::get_archive_slice() { auto q = create_serialize_tl_object(archive_id_, offset_, slice_size()); if (client_.empty()) { td::actor::send_closure(overlays_, &overlay::Overlays::send_query_via, download_from_, local_id_, overlay_id_, - "get_archive_slice", std::move(P), td::Timestamp::in(3.0), std::move(q), + "get_archive_slice", std::move(P), td::Timestamp::in(15.0), std::move(q), slice_size() + 1024, rldp_); } else { td::actor::send_closure(client_, &adnl::AdnlExtClient::send_query, "get_archive_slice", create_serialize_tl_object_suffix(std::move(q)), - td::Timestamp::in(1.0), std::move(P)); + td::Timestamp::in(15.0), std::move(P)); } } @@ -181,7 +183,16 @@ void DownloadArchiveSlice::got_archive_slice(td::BufferSlice data) { offset_ += data.size(); + double elapsed = prev_logged_timer_.elapsed(); + if (elapsed > 10.0) { + prev_logged_timer_ = td::Timer(); + LOG(INFO) << "downloading archive slice #" << masterchain_seqno_ << ": total=" << offset_ << " (" + << td::format::as_size((td::uint64)(double(offset_ - prev_logged_sum_) / elapsed)) << "/s)"; + prev_logged_sum_ = offset_; + } + if (data.size() < slice_size()) { + LOG(INFO) << "finished downloading arcrive slice #" << masterchain_seqno_ << ": total=" << offset_; finish_query(); } else { get_archive_slice(); diff --git a/validator/net/download-archive-slice.hpp b/validator/net/download-archive-slice.hpp index 6ef11d1a..0384ac8c 100644 --- a/validator/net/download-archive-slice.hpp +++ b/validator/net/download-archive-slice.hpp @@ -21,7 +21,6 @@ #include "overlay/overlays.h" #include "ton/ton-types.h" #include "validator/validator.h" -#include "rldp/rldp.h" #include "adnl/adnl-ext-client.h" #include "td/utils/port/FileFd.h" @@ -36,9 +35,9 @@ class DownloadArchiveSlice : public td::actor::Actor { DownloadArchiveSlice(BlockSeqno masterchain_seqno, std::string tmp_dir, adnl::AdnlNodeIdShort local_id, overlay::OverlayIdShort overlay_id, adnl::AdnlNodeIdShort download_from, td::Timestamp timeout, td::actor::ActorId validator_manager, - td::actor::ActorId rldp, td::actor::ActorId overlays, - td::actor::ActorId adnl, td::actor::ActorId client, - td::Promise promise); + td::actor::ActorId rldp, + td::actor::ActorId overlays, td::actor::ActorId adnl, + td::actor::ActorId client, td::Promise promise); void abort_query(td::Status reason); void alarm() override; @@ -51,7 +50,7 @@ class DownloadArchiveSlice : public td::actor::Actor { void got_archive_slice(td::BufferSlice data); static constexpr td::uint32 slice_size() { - return 1 << 17; + return 1 << 21; } private: @@ -68,11 +67,14 @@ class DownloadArchiveSlice : public td::actor::Actor { td::Timestamp timeout_; td::actor::ActorId validator_manager_; - td::actor::ActorId rldp_; + td::actor::ActorId rldp_; td::actor::ActorId overlays_; td::actor::ActorId adnl_; td::actor::ActorId client_; td::Promise promise_; + + td::uint64 prev_logged_sum_ = 0; + td::Timer prev_logged_timer_; }; } // namespace fullnode diff --git a/validator/net/download-block-new.cpp b/validator/net/download-block-new.cpp index ef5ed7e5..14754f64 100644 --- a/validator/net/download-block-new.cpp +++ b/validator/net/download-block-new.cpp @@ -201,12 +201,12 @@ void DownloadBlockNew::got_node_to_download(adnl::AdnlNodeIdShort node) { } if (client_.empty()) { td::actor::send_closure(overlays_, &overlay::Overlays::send_query_via, download_from_, local_id_, overlay_id_, - "get_proof", std::move(P), td::Timestamp::in(3.0), std::move(q), + "get_proof", std::move(P), td::Timestamp::in(15.0), std::move(q), FullNode::max_proof_size() + FullNode::max_block_size() + 128, rldp_); } else { td::actor::send_closure(client_, &adnl::AdnlExtClient::send_query, "get_prepare", create_serialize_tl_object_suffix(std::move(q)), - td::Timestamp::in(1.0), std::move(P)); + td::Timestamp::in(15.0), std::move(P)); } } diff --git a/validator/net/download-block.cpp b/validator/net/download-block.cpp index 5e7c0be9..9ca84be2 100644 --- a/validator/net/download-block.cpp +++ b/validator/net/download-block.cpp @@ -373,12 +373,12 @@ void DownloadBlock::got_block_data_description(td::BufferSlice data_description) auto q = create_serialize_tl_object(create_tl_block_id(block_id_)); if (client_.empty()) { td::actor::send_closure(overlays_, &overlay::Overlays::send_query_via, download_from_, local_id_, - overlay_id_, "get_block", std::move(P), td::Timestamp::in(3.0), std::move(q), + overlay_id_, "get_block", std::move(P), td::Timestamp::in(15.0), std::move(q), FullNode::max_block_size(), rldp_); } else { td::actor::send_closure(client_, &adnl::AdnlExtClient::send_query, "get_block", create_serialize_tl_object_suffix(std::move(q)), - td::Timestamp::in(3.0), std::move(P)); + td::Timestamp::in(15.0), std::move(P)); } }, [&](ton_api::tonNode_notFound &val) { diff --git a/validator/net/download-state.cpp b/validator/net/download-state.cpp index fedfaae8..2740ce41 100644 --- a/validator/net/download-state.cpp +++ b/validator/net/download-state.cpp @@ -32,9 +32,9 @@ DownloadState::DownloadState(BlockIdExt block_id, BlockIdExt masterchain_block_i overlay::OverlayIdShort overlay_id, adnl::AdnlNodeIdShort download_from, td::uint32 priority, td::Timestamp timeout, td::actor::ActorId validator_manager, - td::actor::ActorId rldp, td::actor::ActorId overlays, - td::actor::ActorId adnl, td::actor::ActorId client, - td::Promise promise) + td::actor::ActorId rldp, + td::actor::ActorId overlays, td::actor::ActorId adnl, + td::actor::ActorId client, td::Promise promise) : block_id_(block_id) , masterchain_block_id_(masterchain_block_id) , local_id_(local_id) @@ -115,7 +115,7 @@ void DownloadState::got_block_handle(BlockHandle handle) { void DownloadState::got_node_to_download(adnl::AdnlNodeIdShort node) { download_from_ = node; - LOG(INFO) << "downloading state " << block_id_ << " from " << download_from_; + LOG(INFO) << "downloading state " << block_id_.to_str() << " from " << download_from_; auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) mutable { if (R.is_error()) { @@ -192,8 +192,8 @@ void DownloadState::got_block_state_part(td::BufferSlice data, td::uint32 reques double elapsed = prev_logged_timer_.elapsed(); if (elapsed > 10.0) { prev_logged_timer_ = td::Timer(); - LOG(INFO) << "downloading state " << block_id_ << ": total=" << sum_ << - " (" << double(sum_ - prev_logged_sum_) / elapsed << " B/s)"; + LOG(INFO) << "downloading state " << block_id_.to_str() << ": total=" << sum_ << " (" + << td::format::as_size((td::uint64)(double(sum_ - prev_logged_sum_) / elapsed)) << "/s)"; prev_logged_sum_ = sum_; } @@ -210,7 +210,7 @@ void DownloadState::got_block_state_part(td::BufferSlice data, td::uint32 reques return; } - td::uint32 part_size = 1 << 18; + td::uint32 part_size = 1 << 21; auto P = td::PromiseCreator::lambda([SelfId = actor_id(this), part_size](td::Result R) { if (R.is_error()) { td::actor::send_closure(SelfId, &DownloadState::abort_query, R.move_as_error()); @@ -223,18 +223,18 @@ void DownloadState::got_block_state_part(td::BufferSlice data, td::uint32 reques create_tl_block_id(block_id_), create_tl_block_id(masterchain_block_id_), sum_, part_size); if (client_.empty()) { td::actor::send_closure(overlays_, &overlay::Overlays::send_query_via, download_from_, local_id_, overlay_id_, - "download state", std::move(P), td::Timestamp::in(10.0), std::move(query), + "download state", std::move(P), td::Timestamp::in(20.0), std::move(query), FullNode::max_state_size(), rldp_); } else { td::actor::send_closure(client_, &adnl::AdnlExtClient::send_query, "download state", create_serialize_tl_object_suffix(std::move(query)), - td::Timestamp::in(10.0), std::move(P)); + td::Timestamp::in(20.0), std::move(P)); } } void DownloadState::got_block_state(td::BufferSlice data) { state_ = std::move(data); - LOG(INFO) << "finished downloading state " << block_id_ << ": total=" << sum_; + LOG(INFO) << "finished downloading state " << block_id_.to_str() << ": total=" << sum_; finish_query(); } diff --git a/validator/net/download-state.hpp b/validator/net/download-state.hpp index a586f61f..7db1327f 100644 --- a/validator/net/download-state.hpp +++ b/validator/net/download-state.hpp @@ -21,7 +21,6 @@ #include "overlay/overlays.h" #include "ton/ton-types.h" #include "validator/validator.h" -#include "rldp/rldp.h" #include "adnl/adnl-ext-client.h" namespace ton { @@ -35,7 +34,7 @@ class DownloadState : public td::actor::Actor { DownloadState(BlockIdExt block_id, BlockIdExt masterchain_block_id, adnl::AdnlNodeIdShort local_id, overlay::OverlayIdShort overlay_id, adnl::AdnlNodeIdShort download_from, td::uint32 priority, td::Timestamp timeout, td::actor::ActorId validator_manager, - td::actor::ActorId rldp, td::actor::ActorId overlays, + td::actor::ActorId rldp, td::actor::ActorId overlays, td::actor::ActorId adnl, td::actor::ActorId client, td::Promise promise); @@ -62,7 +61,7 @@ class DownloadState : public td::actor::Actor { td::Timestamp timeout_; td::actor::ActorId validator_manager_; - td::actor::ActorId rldp_; + td::actor::ActorId rldp_; td::actor::ActorId overlays_; td::actor::ActorId adnl_; td::actor::ActorId client_; diff --git a/validator/state-serializer.cpp b/validator/state-serializer.cpp index 1bb932c6..4ac4fc70 100644 --- a/validator/state-serializer.cpp +++ b/validator/state-serializer.cpp @@ -148,7 +148,7 @@ void AsyncStateSerializer::next_iteration() { running_ = true; delay_action( [SelfId = actor_id(this), shard = shards_[next_idx_]]() { td::actor::send_closure(SelfId, &AsyncStateSerializer::request_shard_state, shard); }, - td::Timestamp::in(td::Random::fast(0, 4 * 3600))); + td::Timestamp::in(td::Random::fast(0, 1800))); return; } } From b76158a753edf173b878ded8353a0cc0b47db273 Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Wed, 22 Mar 2023 14:01:17 +0300 Subject: [PATCH 46/46] Move forward init_block in tonlib config --- tonlib/tonlib/TonlibClient.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tonlib/tonlib/TonlibClient.cpp b/tonlib/tonlib/TonlibClient.cpp index c3c8bdb2..a2dfa9cf 100644 --- a/tonlib/tonlib/TonlibClient.cpp +++ b/tonlib/tonlib/TonlibClient.cpp @@ -2594,9 +2594,9 @@ const MasterConfig& get_default_master_config() { "file_hash": "XplPz01CXAps5qeSWUtxcyBfdAo5zVb1N979KLSKD24=" }, "init_block" : { - "root_hash": "irEt9whDfgaYwD+8AzBlYzrMZHhrkhSVp3PU1s4DOz4=", - "seqno": 10171687, - "file_hash": "lay/bUKUUFDJXU9S6gx9GACQFl+uK+zX8SqHWS9oLZc=", + "root_hash": "YRkrcmZMvLBvjanwKCyL3w4oceGPtFfgx8ym1QKCK/4=", + "seqno": 27747086, + "file_hash": "N42xzPnJjDlE3hxPXOb+pNzXomgRtpX5AZzMPnIA41s=", "workchain": -1, "shard": -9223372036854775808 },