1
0
Fork 0
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:
EmelyanenkoK 2023-01-12 12:33:15 +03:00 committed by GitHub
parent 13b9f460af
commit 6b49d6a382
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
70 changed files with 14495 additions and 0 deletions

View 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)

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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";

View file

@ -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);
}

View 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;
}

View 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";

View 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;
}

View file

@ -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);
}

View file

@ -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());
}

View 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";

File diff suppressed because it is too large Load diff

View 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";

View file

@ -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;
}

View file

@ -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";

View file

@ -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);
}

View 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";

View file

@ -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

View file

@ -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));
}

View file

@ -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";

View file

@ -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());
}

View 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";

View file

@ -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
}

View file

@ -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);
}

View file

@ -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";

View file

@ -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));
}

View file

@ -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";

View file

@ -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());
}

View 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";

View file

@ -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
}

View file

@ -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();
}

View file

@ -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());
}

View file

@ -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";

View 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";

View 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";

View 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;
}

View 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";

View 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;

View 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..range1 (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";

View 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);
}

View 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);
}

View file

@ -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";
}

View file

@ -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";
}

View file

@ -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";
}

View 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..range1 (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";

View file

@ -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";
}

View 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
);
}

View 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);
}

View 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";

View 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";

View file

@ -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);
}

View 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";

View 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;
}

View 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>.

View file

@ -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";

View file

@ -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
);
}

View file

@ -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;
}

View file

@ -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
);
}

View file

@ -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
}

View file

@ -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());
}

View file

@ -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());
}

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -0,0 +1,3 @@
(int, int) get_stake_parameters() {
return (1000, 100);
}

View file

@ -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;
}

View file

@ -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.'
}

View file

@ -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());
}

View file

@ -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);
}

View 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";