mirror of
https://github.com/ton-blockchain/ton
synced 2025-02-12 19:22:37 +00:00
Add legacy_tester for existing funC contracts (#588)
* Add legacy_tester for existing funC contracts * Add storage-contracts and pragma options
This commit is contained in:
parent
13b9f460af
commit
6b49d6a382
70 changed files with 14495 additions and 0 deletions
145
crypto/func/auto-tests/legacy_tester.py
Normal file
145
crypto/func/auto-tests/legacy_tester.py
Normal file
|
@ -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)
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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";
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
643
crypto/func/auto-tests/legacy_tests/config/config-code.fc
Normal file
643
crypto/func/auto-tests/legacy_tests/config/config-code.fc
Normal file
|
@ -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;
|
||||||
|
}
|
208
crypto/func/auto-tests/legacy_tests/config/stdlib.fc
Normal file
208
crypto/func/auto-tests/legacy_tests/config/stdlib.fc
Normal file
|
@ -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";
|
110
crypto/func/auto-tests/legacy_tests/dns-collection/dns-utils.fc
Normal file
110
crypto/func/auto-tests/legacy_tests/dns-collection/dns-utils.fc
Normal file
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
216
crypto/func/auto-tests/legacy_tests/dns-collection/stdlib.fc
Normal file
216
crypto/func/auto-tests/legacy_tests/dns-collection/stdlib.fc
Normal file
|
@ -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";
|
||||||
|
|
1189
crypto/func/auto-tests/legacy_tests/elector/elector-code.fc
Normal file
1189
crypto/func/auto-tests/legacy_tests/elector/elector-code.fc
Normal file
File diff suppressed because it is too large
Load diff
208
crypto/func/auto-tests/legacy_tests/elector/stdlib.fc
Normal file
208
crypto/func/auto-tests/legacy_tests/elector/stdlib.fc
Normal file
|
@ -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";
|
|
@ -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;
|
||||||
|
}
|
|
@ -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";
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
215
crypto/func/auto-tests/legacy_tests/gg-marketplace/stdlib.fc
Normal file
215
crypto/func/auto-tests/legacy_tests/gg-marketplace/stdlib.fc
Normal file
|
@ -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";
|
|
@ -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
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
|
@ -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";
|
|
@ -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());
|
||||||
|
}
|
|
@ -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";
|
|
@ -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
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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";
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
|
@ -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";
|
|
@ -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());
|
||||||
|
}
|
|
@ -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";
|
|
@ -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
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
|
@ -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";
|
10
crypto/func/auto-tests/legacy_tests/nft-collection/params.fc
Normal file
10
crypto/func/auto-tests/legacy_tests/nft-collection/params.fc
Normal file
|
@ -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";
|
215
crypto/func/auto-tests/legacy_tests/nft-collection/stdlib.fc
Normal file
215
crypto/func/auto-tests/legacy_tests/nft-collection/stdlib.fc
Normal file
|
@ -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";
|
746
crypto/func/auto-tests/legacy_tests/nominator-pool/pool.fc
Normal file
746
crypto/func/auto-tests/legacy_tests/nominator-pool/pool.fc
Normal file
|
@ -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;
|
||||||
|
}
|
211
crypto/func/auto-tests/legacy_tests/nominator-pool/stdlib.fc
Normal file
211
crypto/func/auto-tests/legacy_tests/nominator-pool/stdlib.fc
Normal file
|
@ -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";
|
23
crypto/func/auto-tests/legacy_tests/storage/constants.fc
Normal file
23
crypto/func/auto-tests/legacy_tests/storage/constants.fc
Normal file
|
@ -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;
|
625
crypto/func/auto-tests/legacy_tests/storage/stdlib.fc
Normal file
625
crypto/func/auto-tests/legacy_tests/storage/stdlib.fc
Normal file
|
@ -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";
|
||||||
|
|
266
crypto/func/auto-tests/legacy_tests/storage/storage-contract.fc
Normal file
266
crypto/func/auto-tests/legacy_tests/storage/storage-contract.fc
Normal file
|
@ -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);
|
||||||
|
}
|
228
crypto/func/auto-tests/legacy_tests/storage/storage-provider.fc
Normal file
228
crypto/func/auto-tests/legacy_tests/storage/storage-provider.fc
Normal file
|
@ -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);
|
||||||
|
}
|
|
@ -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";
|
||||||
|
}
|
|
@ -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";
|
||||||
|
}
|
|
@ -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";
|
||||||
|
}
|
624
crypto/func/auto-tests/legacy_tests/tact-examples/stdlib.fc
Normal file
624
crypto/func/auto-tests/legacy_tests/tact-examples/stdlib.fc
Normal file
|
@ -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";
|
|
@ -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";
|
||||||
|
}
|
416
crypto/func/auto-tests/legacy_tests/tele-nft-item/common.fc
Normal file
416
crypto/func/auto-tests/legacy_tests/tele-nft-item/common.fc
Normal file
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
367
crypto/func/auto-tests/legacy_tests/tele-nft-item/nft-item.fc
Normal file
367
crypto/func/auto-tests/legacy_tests/tele-nft-item/nft-item.fc
Normal file
|
@ -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);
|
||||||
|
}
|
208
crypto/func/auto-tests/legacy_tests/tele-nft-item/stdlib.fc
Normal file
208
crypto/func/auto-tests/legacy_tests/tele-nft-item/stdlib.fc
Normal file
|
@ -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";
|
211
crypto/func/auto-tests/legacy_tests/uni-lock-wallet/stdlib.fc
Normal file
211
crypto/func/auto-tests/legacy_tests/uni-lock-wallet/stdlib.fc
Normal file
|
@ -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";
|
|
@ -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);
|
||||||
|
}
|
215
crypto/func/auto-tests/legacy_tests/wallet-v4/stdlib.fc
Normal file
215
crypto/func/auto-tests/legacy_tests/wallet-v4/stdlib.fc
Normal file
|
@ -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";
|
199
crypto/func/auto-tests/legacy_tests/wallet-v4/wallet-v4-code.fc
Normal file
199
crypto/func/auto-tests/legacy_tests/wallet-v4/wallet-v4-code.fc
Normal file
|
@ -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;
|
||||||
|
}
|
676
crypto/func/auto-tests/legacy_tests/whales-nominators/LICENSE
Normal file
676
crypto/func/auto-tests/legacy_tests/whales-nominators/LICENSE
Normal file
|
@ -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. <https://fsf.org/>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
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
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
|
@ -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";
|
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
(int, int) get_stake_parameters() {
|
||||||
|
return (1000, 100);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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.'
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
212
crypto/func/auto-tests/legacy_tests/whales-nominators/stdlib.fc
Normal file
212
crypto/func/auto-tests/legacy_tests/whales-nominators/stdlib.fc
Normal file
|
@ -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";
|
Loading…
Reference in a new issue