├── .gitignore ├── README.md ├── coupons ├── README.md ├── contracts │ ├── helper.fc │ ├── imports │ │ └── stdlib.fc │ ├── multi_cheque.fc │ ├── one_time_cheque.fc │ ├── scheme.tlb │ ├── toncoin.fif │ └── toncoin_for_multi_cheque.fif ├── jest.config.ts ├── package-lock.json ├── package.json ├── scripts │ ├── claimMultiCheque.ts │ ├── claimOneTimeCheque.ts │ ├── deployMultiCheque.ts │ ├── deployOneTimeCheque.ts │ └── destroyMultiCheque.ts ├── tests │ ├── MultiCheque.spec.ts │ └── OneTimeCheque.spec.ts ├── tsconfig.json └── wrappers │ ├── Helper.compile.ts │ ├── MultiCheque.compile.ts │ ├── MultiCheque.ts │ ├── OneTimeCheque.compile.ts │ └── OneTimeCheque.ts ├── fungible-token ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── build │ └── .gitkeep ├── fift │ ├── burn.fif │ ├── deploy.fif │ ├── metadata.fif │ ├── minttokens.fif │ ├── ownership.fif │ └── utils │ │ ├── DeLabUtil.fif │ │ └── config.fif ├── func │ ├── common.fc │ ├── libs │ │ ├── delib.fc │ │ └── stdlib.fc │ ├── root.fc │ ├── utils │ │ ├── context.fc │ │ └── scheme.fc │ ├── wall.fc │ └── wallet │ │ └── shared.fc ├── sample.conf └── scheme.tlb └── jetton-pool ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── deploy.fif ├── func ├── libs │ ├── delib.fc │ └── stdlib.fc ├── main.fc ├── store.fc └── utils │ ├── consts.fc │ ├── context.fc │ └── scheme.fc └── sample.conf /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE 2 | .idea 3 | .vscode 4 | 5 | # MacOS General 6 | .DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | 10 | # Logs 11 | *.log 12 | 13 | # Other 14 | *-old 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DeLab Smart Contracts 2 | 3 | This monorepository consists of various smart contracts for the TON blockchain, developed by the DeLab. 4 | 5 | | Contract | Status | Description | 6 | | --------------------------------------| -------- | ------------ | 7 | | [`coupons`](./coupons/) | `publish`| | 8 | | [`fungible-token`](./fungible-token/) | `in dev` | | 9 | | [`jetton-pool`](./jetton-pool/) | `in dev` | | 10 | 11 | ## Security 12 | 13 | If you find a potential vulnerability or bug in the source code of these projects, please contact us: [@ivan_bridge](https://t.me/ivan_bridge). Thanks. 14 | 15 | # License 16 | 17 | The license under which a particular project is distributed is in the `LICENSE` file in the directory of each project. Pay attention to this. 18 | -------------------------------------------------------------------------------- /coupons/README.md: -------------------------------------------------------------------------------- 1 | # Coupons 2 | 3 | ## Project structure 4 | 5 | - `contracts` - source code of all the smart contracts of the project and their dependencies. 6 | - `wrappers` - wrapper classes (implementing `Contract` from ton-core) for the contracts, including any [de]serialization primitives and compilation functions. 7 | - `tests` - tests for the contracts. 8 | - `scripts` - scripts used by the project, mainly the deployment scripts. 9 | 10 | ## How to use 11 | 12 | ### Build 13 | 14 | `npx blueprint build` or `yarn blueprint build` 15 | 16 | ### Test 17 | 18 | `npx blueprint test` or `yarn blueprint test` 19 | 20 | ### Deploy or run another script 21 | 22 | `npx blueprint run` or `yarn blueprint run` 23 | 24 | ### Add a new contract 25 | 26 | `npx blueprint create ContractName` or `yarn blueprint create ContractName` 27 | 28 | # License 29 | MIT 30 | 31 | # Author 32 | 33 | https://github.com/AndreyBurnosov/cheque -------------------------------------------------------------------------------- /coupons/contracts/helper.fc: -------------------------------------------------------------------------------- 1 | #include "imports/stdlib.fc"; 2 | 3 | const int op::confirm = 0x93d37ba8; 4 | 5 | global slice user; 6 | global slice cheque; 7 | global int claimed; 8 | 9 | () load_data() impure { 10 | var ds = get_data().begin_parse(); 11 | 12 | user = ds~load_msg_addr(); 13 | cheque = ds~load_msg_addr(); 14 | claimed = ds~load_int(1); 15 | 16 | ds.end_parse(); 17 | } 18 | 19 | () save_data() impure { 20 | set_data(begin_cell() 21 | .store_slice(user) 22 | .store_slice(cheque) 23 | .store_int(claimed, 1) 24 | .end_cell() 25 | ); 26 | } 27 | 28 | () recv_internal(cell in_msg_full, slice in_msg) impure { 29 | load_data(); 30 | 31 | (_, slice sender) = in_msg_full.begin_parse().skip_bits(4).load_msg_addr(); 32 | 33 | throw_unless(700, equal_slices(cheque, sender)); 34 | 35 | throw_if(701, claimed); 36 | 37 | claimed = -1; 38 | 39 | send_raw_message(begin_cell() 40 | .store_uint(0x18, 6) 41 | .store_slice(cheque) 42 | .store_coins(0) 43 | .store_uint(0, 107) 44 | .store_uint(op::confirm, 32) 45 | .store_slice(user) 46 | .end_cell(), 64); 47 | 48 | save_data(); 49 | } 50 | -------------------------------------------------------------------------------- /coupons/contracts/imports/stdlib.fc: -------------------------------------------------------------------------------- 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 | () set_seed(int x) 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 | int equal_slices(slice a, slice b) asm "SDEQ"; 623 | 624 | ;;; Concatenates two builders 625 | builder store_builder(builder to, builder from) asm "STBR"; -------------------------------------------------------------------------------- /coupons/contracts/multi_cheque.fc: -------------------------------------------------------------------------------- 1 | #include "imports/stdlib.fc"; 2 | 3 | const int op::claim = 0x22356c66; 4 | const int op::confirm = 0x93d37ba8; 5 | const int op::destroy = 0x7ba45f85; 6 | 7 | 8 | global int public_key; 9 | global cont claim_cont; 10 | global int cheque_amount; 11 | global int activations; 12 | global int activatied; 13 | global cell helper_code; 14 | global slice owner_address; 15 | 16 | () load_data() impure { 17 | var ds = get_data().begin_parse(); 18 | 19 | public_key = ds~load_uint(256); 20 | cheque_amount = ds~load_coins(); 21 | owner_address = ds~load_msg_addr(); 22 | activations = ds~load_uint(64); 23 | ds~skip_bits(32); 24 | activatied = ds~load_uint(64); 25 | claim_cont = ds~load_ref().begin_parse().bless(); 26 | helper_code = ds~load_ref(); 27 | } 28 | 29 | () save_data() impure { 30 | set_data(begin_cell() 31 | .store_slice(get_data().begin_parse().skip_last_bits(64)) 32 | .store_uint(activatied, 64) 33 | .end_cell() 34 | ); 35 | } 36 | 37 | () claim (int cheque_amount, slice address, cont c) impure asm "EXECUTE"; 38 | 39 | cell calculate_init(slice address) { 40 | return begin_cell() 41 | .store_uint(6, 5) 42 | .store_ref(helper_code) 43 | .store_ref(begin_cell() 44 | .store_slice(address) 45 | .store_slice(my_address()) 46 | .store_uint(0, 1) 47 | .end_cell()) 48 | .end_cell(); 49 | } 50 | 51 | slice calculate_address(cell init){ 52 | return begin_cell() 53 | .store_uint(4, 3) 54 | .store_int(0, 8) 55 | .store_uint(cell_hash(init), 256) 56 | .end_cell().begin_parse(); 57 | } 58 | 59 | () recv_internal(cell in_msg_full, slice in_msg) impure { 60 | if (in_msg.slice_bits() < 32){ 61 | return (); 62 | } 63 | int op = in_msg~load_uint(32); 64 | (_, slice sender) = in_msg_full.begin_parse().skip_bits(4).load_msg_addr(); 65 | 66 | load_data(); 67 | 68 | if (op == op::claim){ 69 | slice signature = in_msg~load_bits(512); 70 | slice address = in_msg; 71 | 72 | throw_unless(700, check_signature(slice_hash(address), signature, public_key)); 73 | 74 | throw_unless(702, activatied < activations); 75 | 76 | cell init = calculate_init(address); 77 | 78 | slice addr = calculate_address(init); 79 | 80 | send_raw_message(begin_cell() 81 | .store_uint(0x18, 6) 82 | .store_slice(addr) 83 | .store_coins(0) 84 | .store_uint(6, 108) 85 | .store_ref(init) 86 | .end_cell(), 64); 87 | 88 | } elseif (op == op::confirm){ 89 | slice address = in_msg; 90 | 91 | cell init = calculate_init(address); 92 | 93 | slice addr = calculate_address(init); 94 | 95 | throw_unless(701, equal_slices(addr, sender)); 96 | 97 | claim(cheque_amount, address, claim_cont); 98 | 99 | activatied += 1; 100 | 101 | save_data(); 102 | } elseif (op == op::destroy) { 103 | activatied = activations; 104 | 105 | if equal_slices(sender, owner_address){ 106 | send_raw_message(begin_cell() 107 | .store_uint(0x18, 6) 108 | .store_slice(owner_address) 109 | .store_coins(0) 110 | .store_uint(0, 107) 111 | .end_cell(), 160); 112 | } 113 | } 114 | } 115 | 116 | (int) get_number_of_uses() method_id { 117 | load_data(); 118 | return activatied; 119 | } 120 | -------------------------------------------------------------------------------- /coupons/contracts/one_time_cheque.fc: -------------------------------------------------------------------------------- 1 | #include "imports/stdlib.fc"; 2 | 3 | const int op::claim = 0x79b0b258; 4 | 5 | global int public_key; 6 | global cont claim_cont; 7 | 8 | () load_data() impure { 9 | var ds = get_data().begin_parse(); 10 | 11 | public_key = ds~load_uint(256); 12 | claim_cont = ds~load_ref().begin_parse().bless(); 13 | } 14 | 15 | () claim (slice address, cont c) impure asm "EXECUTE"; 16 | 17 | () recv_internal() impure { } 18 | 19 | () recv_external(slice in_msg) impure { 20 | int op = in_msg~load_uint(32); 21 | 22 | load_data(); 23 | 24 | if (op == op::claim){ 25 | slice signature = in_msg~load_bits(512); 26 | slice address = in_msg; 27 | 28 | throw_unless(700, check_signature(slice_hash(address), signature, public_key)); 29 | 30 | accept_message(); 31 | 32 | claim(address, claim_cont); 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /coupons/contracts/scheme.tlb: -------------------------------------------------------------------------------- 1 | claim#79b0b258 signature:bits512 address:MsgAddressInt = ExternalMsgBody; 2 | 3 | claim#22356c66 signature:bits512 address:MsgAddressInt = InternalMsgBody; 4 | 5 | destroy#7ba45f85 = InternalMsgBody; 6 | 7 | confirm#93d37ba8 address:MsgAddressInt = InternalMsgBody; 8 | 9 | _ public_key:uint256 claim_cont:^Cell = OneTimeChequeStorage; 10 | 11 | _ public_key:uint256 cheque_amount:Coins owner_address:MsgAddressInt activations:uint64 activaited:uint64 claim_cont:^Cell helper_code:^Cell = MultiChequeStorage; 12 | 13 | _ user:MsgAddressInt cheque:MsgAddressInt claimed:Bool = HelperStorage; -------------------------------------------------------------------------------- /coupons/contracts/toncoin.fif: -------------------------------------------------------------------------------- 1 | "Asm.fif" include 2 | <{ 3 | 16 PUSHINT 4 | NEWC 5 | 6 STU 6 | STSLICE 7 | 0 PUSHINT 8 | STGRAMS 9 | 0 PUSHINT 10 | SWAP 11 | 107 STU 12 | ENDC 13 | 128 PUSHINT 14 | SENDRAWMSG 15 | }>c 16 | boc>B 17 | Bx. -------------------------------------------------------------------------------- /coupons/contracts/toncoin_for_multi_cheque.fif: -------------------------------------------------------------------------------- 1 | "Asm.fif" include 2 | <{ 3 | 16 PUSHINT 4 | NEWC 5 | 6 STU 6 | STSLICE 7 | SWAP 8 | STGRAMS 9 | 0 PUSHINT 10 | SWAP 11 | 107 STU 12 | ENDC 13 | 64 PUSHINT 14 | SENDRAWMSG 15 | }>c 16 | boc>B 17 | Bx. -------------------------------------------------------------------------------- /coupons/jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'jest'; 2 | 3 | const config: Config = { 4 | preset: 'ts-jest', 5 | testEnvironment: 'node', 6 | testPathIgnorePatterns: ['/node_modules/', '/dist/'], 7 | }; 8 | 9 | export default config; 10 | -------------------------------------------------------------------------------- /coupons/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cheque", 3 | "version": "0.0.1", 4 | "license": "MIT", 5 | "scripts": { 6 | "test": "jest" 7 | }, 8 | "devDependencies": { 9 | "@ton-community/blueprint": "^0.10.0", 10 | "@ton-community/sandbox": "^0.11.0", 11 | "@ton-community/test-utils": "^0.2.0", 12 | "@types/jest": "^29.5.0", 13 | "@types/node": "^20.2.5", 14 | "jest": "^29.5.0", 15 | "prettier": "^2.8.6", 16 | "ton": "^13.4.1", 17 | "ton-core": "^0.49.0", 18 | "ton-crypto": "^3.2.0", 19 | "ts-jest": "^29.0.5", 20 | "ts-node": "^10.9.1", 21 | "typescript": "^4.9.5" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /coupons/scripts/claimMultiCheque.ts: -------------------------------------------------------------------------------- 1 | import { Address, beginCell, Sender, toNano } from 'ton-core'; 2 | import { MultiCheque, ClaimFunctions } from '../wrappers/MultiCheque'; 3 | import { compile, NetworkProvider } from '@ton-community/blueprint'; 4 | import { keyPairFromSeed, sign, KeyPair, sha256 } from 'ton-crypto'; 5 | 6 | export async function run(provider: NetworkProvider) { 7 | const passwordString = 'qwerty'; 8 | const seed: Buffer = await sha256(passwordString); 9 | const keypair: KeyPair = keyPairFromSeed(seed); 10 | 11 | const address = Address.parse('EQDsD_def8Lmwk45z4UvkSuaDaJfXY8xg4l7XxIk9oOcPfRT'); 12 | 13 | const signature = sign(beginCell().storeAddress(address).endCell().hash(), keypair.secretKey); 14 | 15 | const multiCheque = provider.open( 16 | MultiCheque.createFromAddress(Address.parse('EQCYTPsNtmK116FSdRoHTFblQXMIVdi91YJ1twQXJEKU0JQh')) 17 | ); 18 | 19 | await multiCheque.sendClaim(provider.sender(), toNano('0.03'), { signature, address: address }); 20 | } 21 | -------------------------------------------------------------------------------- /coupons/scripts/claimOneTimeCheque.ts: -------------------------------------------------------------------------------- 1 | import { Address, Cell, beginCell, toNano } from 'ton-core'; 2 | import { OneTimeCheque, ClaimFunctions } from '../wrappers/OneTimeCheque'; 3 | import { compile, NetworkProvider } from '@ton-community/blueprint'; 4 | import { getSecureRandomBytes, keyPairFromSeed, sign, KeyPair, sha256 } from 'ton-crypto'; 5 | 6 | export async function run(provider: NetworkProvider) { 7 | const passwordString = 'qwerty'; 8 | const seed: Buffer = await sha256(passwordString); 9 | const keypair: KeyPair = keyPairFromSeed(seed); 10 | 11 | const address = Address.parse('EQDsD_def8Lmwk45z4UvkSuaDaJfXY8xg4l7XxIk9oOcPfRT'); 12 | 13 | const signature = sign(beginCell().storeAddress(address).endCell().hash(), keypair.secretKey); 14 | 15 | const oneTimeCheque = provider.open( 16 | OneTimeCheque.createFromAddress(Address.parse('EQCSu4NbkDtJYzDXrJCkYtIJrIYaxLMLI4VoPzcZ0Qz4sHdP')) 17 | ); 18 | 19 | await oneTimeCheque.sendClaim({ signature, address: address }); 20 | } 21 | -------------------------------------------------------------------------------- /coupons/scripts/deployMultiCheque.ts: -------------------------------------------------------------------------------- 1 | import { Address, toNano } from 'ton-core'; 2 | import { MultiCheque, ClaimFunctions } from '../wrappers/MultiCheque'; 3 | import { compile, NetworkProvider } from '@ton-community/blueprint'; 4 | import { keyPairFromSeed, KeyPair, sha256 } from 'ton-crypto'; 5 | 6 | export async function run(provider: NetworkProvider) { 7 | const passwordString = 'qwerty'; 8 | const seed: Buffer = await sha256(passwordString); 9 | const keypair: KeyPair = keyPairFromSeed(seed); 10 | 11 | const one_use_amount = toNano('0.5'); 12 | const number_of_uses = 2n; 13 | const amount = number_of_uses * one_use_amount + toNano('0.05'); 14 | 15 | const multiCheque = provider.open( 16 | MultiCheque.createFromConfig( 17 | { 18 | publicKey: keypair.publicKey, 19 | claimCont: ClaimFunctions.toncoin, 20 | chequeAmount: one_use_amount, 21 | activaitions: number_of_uses, 22 | helperCode: await compile('Helper'), 23 | ownerAddress: Address.parse('EQDsD_def8Lmwk45z4UvkSuaDaJfXY8xg4l7XxIk9oOcPfRT'), 24 | }, 25 | await compile('MultiCheque') 26 | ) 27 | ); 28 | await multiCheque.sendDeploy(provider.sender(), amount); 29 | 30 | await provider.waitForDeploy(multiCheque.address); 31 | } 32 | -------------------------------------------------------------------------------- /coupons/scripts/deployOneTimeCheque.ts: -------------------------------------------------------------------------------- 1 | import { toNano } from 'ton-core'; 2 | import { OneTimeCheque, ClaimFunctions } from '../wrappers/OneTimeCheque'; 3 | import { compile, NetworkProvider } from '@ton-community/blueprint'; 4 | import { keyPairFromSeed, KeyPair, sha256 } from 'ton-crypto'; 5 | 6 | export async function run(provider: NetworkProvider) { 7 | const passwordString = 'qwerty'; 8 | const seed: Buffer = await sha256(passwordString); 9 | const keypair: KeyPair = keyPairFromSeed(seed); 10 | 11 | const amount = toNano('0.1'); 12 | 13 | const oneTimeCheque = provider.open( 14 | OneTimeCheque.createFromConfig( 15 | { 16 | publicKey: keypair.publicKey, 17 | claimCont: ClaimFunctions.toncoin, 18 | }, 19 | await compile('OneTimeCheque') 20 | ) 21 | ); 22 | 23 | await oneTimeCheque.sendDeploy(provider.sender(), amount); 24 | 25 | await provider.waitForDeploy(oneTimeCheque.address); 26 | } 27 | -------------------------------------------------------------------------------- /coupons/scripts/destroyMultiCheque.ts: -------------------------------------------------------------------------------- 1 | import { Address, beginCell, Sender, toNano } from 'ton-core'; 2 | import { MultiCheque, ClaimFunctions } from '../wrappers/MultiCheque'; 3 | import { compile, NetworkProvider } from '@ton-community/blueprint'; 4 | import { keyPairFromSeed, sign, KeyPair, sha256 } from 'ton-crypto'; 5 | 6 | export async function run(provider: NetworkProvider) { 7 | const multiCheque = provider.open( 8 | MultiCheque.createFromAddress(Address.parse('EQB0pNuJjY59u3bYPbqQUVnhqT5z4taXBVwJoopoQpmyvIP7')) 9 | ); 10 | await multiCheque.sendDestroy(provider.sender(), toNano('0.03')); 11 | } 12 | -------------------------------------------------------------------------------- /coupons/tests/MultiCheque.spec.ts: -------------------------------------------------------------------------------- 1 | import { Blockchain, SandboxContract, TreasuryContract } from '@ton-community/sandbox'; 2 | import { Cell, beginCell, toNano } from 'ton-core'; 3 | import { MultiCheque, ClaimFunctions } from '../wrappers/MultiCheque'; 4 | import '@ton-community/test-utils'; 5 | import { compile } from '@ton-community/blueprint'; 6 | import { getSecureRandomBytes, keyPairFromSeed, sign, KeyPair } from 'ton-crypto'; 7 | import { randomAddress } from '@ton-community/test-utils'; 8 | 9 | describe('MultiCheque', () => { 10 | let code: Cell; 11 | 12 | beforeAll(async () => { 13 | code = await compile('MultiCheque'); 14 | }); 15 | 16 | let blockchain: Blockchain; 17 | let multiCheque: SandboxContract; 18 | let deployer: SandboxContract; 19 | 20 | beforeEach(async () => { 21 | blockchain = await Blockchain.create(); 22 | deployer = await blockchain.treasury('deployer'); 23 | }); 24 | 25 | it('should deploy simple cheque', async () => { 26 | const seed: Buffer = await getSecureRandomBytes(32); 27 | const keypair: KeyPair = keyPairFromSeed(seed); 28 | const owner_addr = randomAddress(); 29 | 30 | multiCheque = blockchain.openContract( 31 | MultiCheque.createFromConfig( 32 | { 33 | publicKey: keypair.publicKey, 34 | claimCont: ClaimFunctions.toncoin, 35 | ownerAddress: owner_addr, 36 | chequeAmount: toNano('1'), 37 | activaitions: 1n, 38 | helperCode: await compile('Helper'), 39 | }, 40 | code 41 | ) 42 | ); 43 | 44 | const deployResult = await multiCheque.sendDeploy(deployer.getSender(), toNano('1.05')); 45 | 46 | expect(deployResult.transactions).toHaveTransaction({ 47 | from: deployer.address, 48 | to: multiCheque.address, 49 | deploy: true, 50 | success: true, 51 | }); 52 | }); 53 | 54 | it('should claim simple cheque', async () => { 55 | const seed: Buffer = await getSecureRandomBytes(32); 56 | const keypair: KeyPair = keyPairFromSeed(seed); 57 | const owner_addr = randomAddress(); 58 | 59 | multiCheque = blockchain.openContract( 60 | MultiCheque.createFromConfig( 61 | { 62 | publicKey: keypair.publicKey, 63 | claimCont: ClaimFunctions.toncoin, 64 | ownerAddress: owner_addr, 65 | chequeAmount: toNano('1'), 66 | activaitions: 1n, 67 | helperCode: await compile('Helper'), 68 | }, 69 | code 70 | ) 71 | ); 72 | 73 | const addr = randomAddress(); 74 | 75 | const signature = sign(beginCell().storeAddress(addr).endCell().hash(), keypair.secretKey); 76 | 77 | await multiCheque.sendDeploy(deployer.getSender(), toNano('1.05')); 78 | 79 | const result = await multiCheque.sendClaim(deployer.getSender(), toNano('0.03'), { signature, address: addr }); 80 | 81 | expect(result.transactions).toHaveTransaction({ 82 | from: multiCheque.address, 83 | to: addr, 84 | }); 85 | 86 | const balance = (await blockchain.getContract(addr)).balance; 87 | expect(balance).toBeGreaterThan(toNano('1')); 88 | }); 89 | 90 | it('should not claim simple cheque (wrong signature)', async () => { 91 | const seed: Buffer = await getSecureRandomBytes(32); 92 | const keypair: KeyPair = keyPairFromSeed(seed); 93 | const owner_addr = randomAddress(); 94 | 95 | multiCheque = blockchain.openContract( 96 | MultiCheque.createFromConfig( 97 | { 98 | publicKey: keypair.publicKey, 99 | claimCont: ClaimFunctions.toncoin, 100 | ownerAddress: owner_addr, 101 | chequeAmount: toNano('1'), 102 | activaitions: 1n, 103 | helperCode: await compile('Helper'), 104 | }, 105 | code 106 | ) 107 | ); 108 | const addr = randomAddress(); 109 | const another = randomAddress(); 110 | 111 | const signature = sign(beginCell().storeAddress(another).endCell().hash(), keypair.secretKey); 112 | 113 | const result = await multiCheque.sendClaim(deployer.getSender(), toNano('0.03'), { signature, address: addr }); 114 | 115 | await multiCheque.sendDeploy(deployer.getSender(), toNano('1.05')); 116 | 117 | expect(result.transactions).toHaveTransaction({ 118 | exitCode: 700, 119 | }); 120 | const balance = (await blockchain.getContract(addr)).balance; 121 | expect(balance).toBeLessThanOrEqual(toNano('0.1')); 122 | }); 123 | 124 | it('should not claim simple cheque (usage limit)', async () => { 125 | const seed: Buffer = await getSecureRandomBytes(32); 126 | const keypair: KeyPair = keyPairFromSeed(seed); 127 | const owner_addr = randomAddress(); 128 | 129 | multiCheque = blockchain.openContract( 130 | MultiCheque.createFromConfig( 131 | { 132 | publicKey: keypair.publicKey, 133 | claimCont: ClaimFunctions.toncoin, 134 | ownerAddress: owner_addr, 135 | chequeAmount: toNano('1'), 136 | activaitions: 1n, 137 | helperCode: await compile('Helper'), 138 | }, 139 | code 140 | ) 141 | ); 142 | const addr = randomAddress(); 143 | 144 | const first_signature = sign(beginCell().storeAddress(addr).endCell().hash(), keypair.secretKey); 145 | 146 | await multiCheque.sendDeploy(deployer.getSender(), toNano('1.05')); 147 | const first_result = await multiCheque.sendClaim(deployer.getSender(), toNano('0.03'), { 148 | signature: first_signature, 149 | address: addr, 150 | }); 151 | 152 | expect(first_result.transactions).toHaveTransaction({ 153 | from: multiCheque.address, 154 | to: addr, 155 | }); 156 | 157 | const first_balance = (await blockchain.getContract(addr)).balance; 158 | 159 | expect(first_balance).toBeGreaterThan(toNano('1')); 160 | 161 | const another = randomAddress(); 162 | 163 | const signature = sign(beginCell().storeAddress(another).endCell().hash(), keypair.secretKey); 164 | 165 | const result = await multiCheque.sendClaim(deployer.getSender(), toNano('0.03'), { 166 | signature, 167 | address: another, 168 | }); 169 | 170 | expect(result.transactions).toHaveTransaction({ 171 | exitCode: 702, 172 | }); 173 | const balance = (await blockchain.getContract(another)).balance; 174 | expect(balance).toBeLessThanOrEqual(toNano('0.1')); 175 | }); 176 | 177 | it('should not claim simple cheque (dual use)', async () => { 178 | const seed: Buffer = await getSecureRandomBytes(32); 179 | const keypair: KeyPair = keyPairFromSeed(seed); 180 | const owner_addr = randomAddress(); 181 | 182 | multiCheque = blockchain.openContract( 183 | MultiCheque.createFromConfig( 184 | { 185 | publicKey: keypair.publicKey, 186 | claimCont: ClaimFunctions.toncoin, 187 | ownerAddress: owner_addr, 188 | chequeAmount: toNano('1'), 189 | activaitions: 2n, 190 | helperCode: await compile('Helper'), 191 | }, 192 | code 193 | ) 194 | ); 195 | const addr = randomAddress(); 196 | 197 | const signature = sign(beginCell().storeAddress(addr).endCell().hash(), keypair.secretKey); 198 | 199 | await multiCheque.sendDeploy(deployer.getSender(), toNano('2.05')); 200 | let result = await multiCheque.sendClaim(deployer.getSender(), toNano('0.03'), { signature, address: addr }); 201 | 202 | expect(result.transactions).toHaveTransaction({ 203 | from: multiCheque.address, 204 | to: addr, 205 | }); 206 | 207 | let balance = (await blockchain.getContract(addr)).balance; 208 | expect(balance).toBeGreaterThan(toNano('1')); 209 | await multiCheque.sendClaim(deployer.getSender(), toNano('0.03'), { signature, address: addr }); 210 | 211 | balance = (await blockchain.getContract(addr)).balance; 212 | expect(balance).toBeGreaterThan(toNano('1')); 213 | expect(balance).toBeLessThanOrEqual(toNano('1.05')); 214 | }); 215 | 216 | it('should claim simple cheque 200 times', async () => { 217 | const seed: Buffer = await getSecureRandomBytes(32); 218 | const keypair: KeyPair = keyPairFromSeed(seed); 219 | const owner_addr = randomAddress(); 220 | 221 | multiCheque = blockchain.openContract( 222 | MultiCheque.createFromConfig( 223 | { 224 | publicKey: keypair.publicKey, 225 | claimCont: ClaimFunctions.toncoin, 226 | ownerAddress: owner_addr, 227 | chequeAmount: toNano('1'), 228 | activaitions: 200n, 229 | helperCode: await compile('Helper'), 230 | }, 231 | code 232 | ) 233 | ); 234 | await multiCheque.sendDeploy(deployer.getSender(), toNano('200.05')); 235 | for (let i = 0; i < 200; i++) { 236 | let activaitions = await multiCheque.getUsage(); 237 | 238 | expect(activaitions).toEqual(BigInt(i)); 239 | 240 | const addr = randomAddress(); 241 | 242 | const signature = sign(beginCell().storeAddress(addr).endCell().hash(), keypair.secretKey); 243 | 244 | const result = await multiCheque.sendClaim(deployer.getSender(), toNano('0.03'), { 245 | signature, 246 | address: addr, 247 | }); 248 | 249 | expect(result.transactions).toHaveTransaction({ 250 | from: multiCheque.address, 251 | to: addr, 252 | }); 253 | 254 | const balance = (await blockchain.getContract(addr)).balance; 255 | expect(balance).toBeGreaterThan(toNano('1')); 256 | } 257 | 258 | const another = randomAddress(); 259 | 260 | const signature = sign(beginCell().storeAddress(another).endCell().hash(), keypair.secretKey); 261 | 262 | const result = await multiCheque.sendClaim(deployer.getSender(), toNano('0.03'), { 263 | signature, 264 | address: another, 265 | }); 266 | 267 | expect(result.transactions).toHaveTransaction({ 268 | exitCode: 702, 269 | }); 270 | 271 | const balance = (await blockchain.getContract(another)).balance; 272 | expect(balance).toBeLessThanOrEqual(toNano('0.1')); 273 | }); 274 | 275 | it('should destroy simple cheque', async () => { 276 | const seed: Buffer = await getSecureRandomBytes(32); 277 | const keypair: KeyPair = keyPairFromSeed(seed); 278 | const owner_addr = deployer.address; 279 | 280 | multiCheque = blockchain.openContract( 281 | MultiCheque.createFromConfig( 282 | { 283 | publicKey: keypair.publicKey, 284 | claimCont: ClaimFunctions.toncoin, 285 | ownerAddress: owner_addr, 286 | chequeAmount: toNano('1'), 287 | activaitions: 10n, 288 | helperCode: await compile('Helper'), 289 | }, 290 | code 291 | ) 292 | ); 293 | const addr = randomAddress(); 294 | 295 | const signature = sign(beginCell().storeAddress(addr).endCell().hash(), keypair.secretKey); 296 | 297 | await multiCheque.sendDeploy(deployer.getSender(), toNano('10.05')); 298 | 299 | const result = await multiCheque.sendClaim(deployer.getSender(), toNano('0.03'), { signature, address: addr }); 300 | 301 | expect(result.transactions).toHaveTransaction({ 302 | from: multiCheque.address, 303 | to: addr, 304 | }); 305 | 306 | const balance = (await blockchain.getContract(addr)).balance; 307 | expect(balance).toBeGreaterThan(toNano('1')); 308 | 309 | const result_destroy = await multiCheque.sendDestroy(deployer.getSender(), toNano('0.03')); 310 | 311 | expect(result_destroy.transactions).toHaveTransaction({ 312 | from: multiCheque.address, 313 | to: owner_addr, 314 | }); 315 | 316 | const owner_balance = (await blockchain.getContract(owner_addr)).balance; 317 | expect(owner_balance).toBeGreaterThan(toNano('9')); 318 | }); 319 | }); 320 | -------------------------------------------------------------------------------- /coupons/tests/OneTimeCheque.spec.ts: -------------------------------------------------------------------------------- 1 | import { Blockchain, SandboxContract, TreasuryContract } from '@ton-community/sandbox'; 2 | import { Cell, beginCell, toNano } from 'ton-core'; 3 | import { OneTimeCheque, ClaimFunctions } from '../wrappers/OneTimeCheque'; 4 | import '@ton-community/test-utils'; 5 | import { compile } from '@ton-community/blueprint'; 6 | import { getSecureRandomBytes, keyPairFromSeed, sign, KeyPair } from 'ton-crypto'; 7 | import { randomAddress } from '@ton-community/test-utils'; 8 | 9 | describe('OneTimeCheque', () => { 10 | let code: Cell; 11 | 12 | beforeAll(async () => { 13 | code = await compile('OneTimeCheque'); 14 | }); 15 | 16 | let blockchain: Blockchain; 17 | let oneTimeCheque: SandboxContract; 18 | let deployer: SandboxContract; 19 | 20 | beforeEach(async () => { 21 | blockchain = await Blockchain.create(); 22 | deployer = await blockchain.treasury('deployer'); 23 | }); 24 | 25 | it('should deploy simple cheque', async () => { 26 | const seed: Buffer = await getSecureRandomBytes(32); 27 | const keypair: KeyPair = keyPairFromSeed(seed); 28 | 29 | oneTimeCheque = blockchain.openContract( 30 | OneTimeCheque.createFromConfig( 31 | { 32 | publicKey: keypair.publicKey, 33 | claimCont: ClaimFunctions.toncoin, 34 | }, 35 | code 36 | ) 37 | ); 38 | 39 | const deployResult = await oneTimeCheque.sendDeploy(deployer.getSender(), toNano('0.05')); 40 | 41 | expect(deployResult.transactions).toHaveTransaction({ 42 | from: deployer.address, 43 | to: oneTimeCheque.address, 44 | deploy: true, 45 | success: true, 46 | }); 47 | }); 48 | 49 | it('should claim simple cheque', async () => { 50 | const seed: Buffer = await getSecureRandomBytes(32); 51 | const keypair: KeyPair = keyPairFromSeed(seed); 52 | 53 | oneTimeCheque = blockchain.openContract( 54 | OneTimeCheque.createFromConfig( 55 | { 56 | publicKey: keypair.publicKey, 57 | claimCont: ClaimFunctions.toncoin, 58 | }, 59 | code 60 | ) 61 | ); 62 | const addr = randomAddress(); 63 | 64 | const signature = sign(beginCell().storeAddress(addr).endCell().hash(), keypair.secretKey); 65 | 66 | await oneTimeCheque.sendDeploy(deployer.getSender(), toNano('1')); 67 | const result = await oneTimeCheque.sendClaim({ signature, address: addr }); 68 | 69 | expect(result.transactions).toHaveTransaction({ 70 | from: oneTimeCheque.address, 71 | to: addr, 72 | }); 73 | 74 | const balance = (await blockchain.getContract(addr)).balance; 75 | expect(balance).toBeGreaterThan(toNano('0.9')); 76 | expect(balance).toBeLessThanOrEqual(toNano('1')); 77 | }); 78 | 79 | it('should not claim simple cheque', async () => { 80 | const seed: Buffer = await getSecureRandomBytes(32); 81 | const keypair: KeyPair = keyPairFromSeed(seed); 82 | 83 | oneTimeCheque = blockchain.openContract( 84 | OneTimeCheque.createFromConfig( 85 | { 86 | publicKey: keypair.publicKey, 87 | claimCont: ClaimFunctions.toncoin, 88 | }, 89 | code 90 | ) 91 | ); 92 | const addr = randomAddress(); 93 | const another = randomAddress(); 94 | 95 | const signature = sign(beginCell().storeAddress(another).endCell().hash(), keypair.secretKey); 96 | 97 | await oneTimeCheque.sendDeploy(deployer.getSender(), toNano('1')); 98 | 99 | await expect(oneTimeCheque.sendClaim({ signature: signature, address: addr })).rejects.toThrow(); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /coupons/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "outDir": "dist", 5 | "module": "commonjs", 6 | "declaration": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "skipLibCheck": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /coupons/wrappers/Helper.compile.ts: -------------------------------------------------------------------------------- 1 | import { CompilerConfig } from '@ton-community/blueprint'; 2 | 3 | export const compile: CompilerConfig = { 4 | lang: 'func', 5 | targets: ['contracts/helper.fc'], 6 | }; 7 | -------------------------------------------------------------------------------- /coupons/wrappers/MultiCheque.compile.ts: -------------------------------------------------------------------------------- 1 | import { CompilerConfig } from '@ton-community/blueprint'; 2 | 3 | export const compile: CompilerConfig = { 4 | lang: 'func', 5 | targets: ['contracts/multi_cheque.fc'], 6 | }; 7 | -------------------------------------------------------------------------------- /coupons/wrappers/MultiCheque.ts: -------------------------------------------------------------------------------- 1 | import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode } from 'ton-core'; 2 | 3 | export type MultiChequeConfig = { 4 | publicKey: Buffer; 5 | claimCont: Cell; 6 | activaitions: bigint; 7 | chequeAmount: bigint; 8 | ownerAddress: Address; 9 | helperCode: Cell; 10 | }; 11 | 12 | export function multiChequeConfigToCell(config: MultiChequeConfig): Cell { 13 | return beginCell() 14 | .storeBuffer(config.publicKey) 15 | .storeCoins(config.chequeAmount) 16 | .storeAddress(config.ownerAddress) 17 | .storeUint(config.activaitions, 64) 18 | .storeUint(BigInt(Math.floor(Math.random() * 1e9)), 32) 19 | .storeUint(0n, 64) 20 | .storeRef(config.claimCont) 21 | .storeRef(config.helperCode) 22 | .endCell(); 23 | } 24 | 25 | export const Opcodes = { 26 | claim: 0x22356c66, 27 | destroy: 0x7ba45f85, 28 | }; 29 | 30 | export const ClaimFunctions = { 31 | toncoin: Cell.fromBoc(Buffer.from('B5EE9C720101010100140000248010C8CB05CE01FA027001CB6AC98040FB00', 'hex'))[0], 32 | }; 33 | 34 | export class MultiCheque implements Contract { 35 | constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {} 36 | 37 | static createFromAddress(address: Address) { 38 | return new MultiCheque(address); 39 | } 40 | 41 | static createFromConfig(config: MultiChequeConfig, code: Cell, workchain = 0) { 42 | const data = multiChequeConfigToCell(config); 43 | const init = { code, data }; 44 | return new MultiCheque(contractAddress(workchain, init), init); 45 | } 46 | 47 | async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) { 48 | await provider.internal(via, { 49 | value, 50 | sendMode: SendMode.PAY_GAS_SEPARATELY, 51 | body: beginCell().endCell(), 52 | }); 53 | } 54 | 55 | async sendClaim( 56 | provider: ContractProvider, 57 | via: Sender, 58 | value: bigint, 59 | opts: { 60 | signature: Buffer; 61 | address: Address; 62 | } 63 | ) { 64 | await provider.internal(via, { 65 | value, 66 | sendMode: SendMode.PAY_GAS_SEPARATELY, 67 | body: beginCell() 68 | .storeUint(Opcodes.claim, 32) 69 | .storeBuffer(opts.signature) 70 | .storeAddress(opts.address) 71 | .endCell(), 72 | }); 73 | } 74 | 75 | async sendDestroy(provider: ContractProvider, via: Sender, value: bigint) { 76 | await provider.internal(via, { 77 | value, 78 | sendMode: SendMode.PAY_GAS_SEPARATELY, 79 | body: beginCell().storeUint(Opcodes.destroy, 32).endCell(), 80 | }); 81 | } 82 | 83 | async getUsage(provider: ContractProvider): Promise { 84 | return (await provider.get('get_number_of_uses', [])).stack.readBigNumber(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /coupons/wrappers/OneTimeCheque.compile.ts: -------------------------------------------------------------------------------- 1 | import { CompilerConfig } from '@ton-community/blueprint'; 2 | 3 | export const compile: CompilerConfig = { 4 | lang: 'func', 5 | targets: ['contracts/one_time_cheque.fc'], 6 | }; 7 | -------------------------------------------------------------------------------- /coupons/wrappers/OneTimeCheque.ts: -------------------------------------------------------------------------------- 1 | import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode } from 'ton-core'; 2 | 3 | export type OneTimeChequeConfig = { 4 | publicKey: Buffer; 5 | claimCont: Cell; 6 | }; 7 | 8 | export function oneTimeChequeConfigToCell(config: OneTimeChequeConfig): Cell { 9 | return beginCell() 10 | .storeBuffer(config.publicKey) 11 | .storeRef(config.claimCont) 12 | .storeUint(BigInt(Math.floor(Math.random() * 1e9)), 32) 13 | .endCell(); 14 | } 15 | 16 | export const Opcodes = { 17 | claim: 0x79b0b258, 18 | }; 19 | 20 | export const ClaimFunctions = { 21 | toncoin: Cell.fromBoc(Buffer.from('B5EE9C720101010100150000268010C8CB05CE70FA027001CB6AC9810080FB00', 'hex'))[0], 22 | }; 23 | 24 | export class OneTimeCheque implements Contract { 25 | constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {} 26 | 27 | static createFromAddress(address: Address) { 28 | return new OneTimeCheque(address); 29 | } 30 | 31 | static createFromConfig(config: OneTimeChequeConfig, code: Cell, workchain = 0) { 32 | const data = oneTimeChequeConfigToCell(config); 33 | const init = { code, data }; 34 | return new OneTimeCheque(contractAddress(workchain, init), init); 35 | } 36 | 37 | async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) { 38 | await provider.internal(via, { 39 | value, 40 | sendMode: SendMode.PAY_GAS_SEPARATELY, 41 | body: beginCell().endCell(), 42 | }); 43 | } 44 | 45 | async sendClaim( 46 | provider: ContractProvider, 47 | opts: { 48 | signature: Buffer; 49 | address: Address; 50 | } 51 | ) { 52 | await provider.external( 53 | beginCell().storeUint(Opcodes.claim, 32).storeBuffer(opts.signature).storeAddress(opts.address).endCell() 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /fungible-token/.gitignore: -------------------------------------------------------------------------------- 1 | # build 2 | build/* 3 | !build/.gitkeep 4 | 5 | # OS generated files 6 | .DS_Store 7 | .DS_Store? 8 | 9 | # other 10 | *.boc 11 | *.conf 12 | *.log 13 | !sample.conf 14 | -------------------------------------------------------------------------------- /fungible-token/Makefile: -------------------------------------------------------------------------------- 1 | #!make 2 | 3 | build: clean 4 | - func -S -o build/root.code.fif -W build/root.code.boc func/root.fc 5 | - func -S -o build/wall.code.fif -W build/wall.code.boc func/wall.fc 6 | 7 | - fift build/root.code.fif 8 | - fift build/wall.code.fif 9 | 10 | clean: 11 | - rm -rf build/*code* 12 | 13 | tlbcrc: 14 | - @tlbcrc scheme.tlb -f "\ 15 | query::internal_transfer,\ 16 | query::transfer,\ 17 | query::transfer_notification,\ 18 | query::wall_burn,\ 19 | query::root_constructor,\ 20 | query::root_burn_notification,\ 21 | query::root_mint,\ 22 | query::root_set_metadata,\ 23 | query::root_transfer_ownership,\ 24 | query::provide_wallet_address,\ 25 | response::take_wallet_address" 26 | -------------------------------------------------------------------------------- /fungible-token/README.md: -------------------------------------------------------------------------------- 1 | # DeLab TON fungible-token implementation 2 | 3 | > :warning: IN ACTIVE DEVELOPMENT. DO NOT USE IN PRODUCTION! 4 | 5 | ## Project structure 6 | 7 | | Path | Description | 8 | | ------------------------------- | ------------ | 9 | | [`./func/`](./func/) | FunC code | 10 | | [`./fift/`](./fift/) | Fift scripts | 11 | | [`./scheme.tlb`](./scheme.tlb) | TL-B scheme | 12 | 13 | ## Deploy process 14 | 15 | 1. Build smart contracts 16 | ``` 17 | make 18 | ``` 19 | 20 | 2. Create a config based on `sample.conf` 21 | 22 | 3. Run deploy fift script (`--help` for more info) 23 | ``` 24 | fift -s ./fift/deploy.fif deploy.conf ./build/out 25 | ``` 26 | 27 | 4. Deploy into blockchain 28 | 29 | Send an internal message to smart contract address with StateInit and body, 30 | which were generated in the previous step. 31 | 32 | ## Standards coverage 33 | 34 | [**Fungible tokens (Jettons) standard [TEP74]**](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md) 35 | 36 | | method | tag | 37 | | ------------------------- | ------------ | 38 | | `transfer` | `0x0f8a7ea5` | 39 | | `transfer_notification` | `0x7362d09c` | 40 | 41 | Get-methods such as `get_wallet_data` and `get_jetton_data` also implemented. 42 | 43 | [**Discoverable Jettons Wallets [TEP89]**](https://github.com/ton-blockchain/TEPs/blob/master/text/0089-jetton-wallet-discovery.md) 44 | 45 | | method | tag | 46 | | ------------------------- | ------------ | 47 | | `provide_wallet_address` | `0x2c76b973` | 48 | | `take_wallet_address` | `0xd1735400` | 49 | 50 | ## License 51 | `GNU GENERAL PUBLIC LICENSE Version 3` 52 | -------------------------------------------------------------------------------- /fungible-token/build/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delab-team/contracts/78a2047a16591be0cfbd3d54f6970d16afced8ae/fungible-token/build/.gitkeep -------------------------------------------------------------------------------- /fungible-token/fift/burn.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | "GetOpt.fif" include 4 | 5 | { show-options-help 1 halt } : usage 6 | begin-options 7 | " " +cr 8 | +" " +cr 9 | +"Creates a message body for burning tokens and saves it to .boc file" +cr +tab 10 | 11 | disable-digit-options generic-help-setopt 12 | "r" "--resp-to" { =: resp-to } short-long-option-arg 13 | " "+tab +"Sets the response_address (base64 addr) (00 by default)" option-help 14 | 15 | "h" "--help" { usage } short-long-option 16 | " "+tab +tab +"Shows a help message" option-help 17 | parse-options 18 | 19 | $# 2 <> ' usage if 20 | 21 | $1 parse-int =: burn-amount 22 | $2 =: save-file 23 | 24 | =: msg-body 32 | 33 | ."----------------------------------------------------------------" cr 34 | msg-body 2 boc+>B save-file +".boc" tuck B>file 35 | ."contract msg body saved to: '" type ."' file" cr 36 | ."----------------------------------------------------------------" cr 37 | -------------------------------------------------------------------------------- /fungible-token/fift/deploy.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | "GetOpt.fif" include 4 | "utils/DeLabUtil.fif" include 5 | 6 | // defaults 7 | true =: mintable 8 | 0 =: init-supply 9 | 1 =: fwd-amount 10 | "" =: comment 11 | 12 | { show-options-help 1 halt } : usage 13 | begin-options 14 | " " +cr 15 | +" " +cr 16 | +"Creates the fungible-token contract deploy body & state_init " 17 | +"than saves it to .init.boc, .body.boc files" +cr +tab 18 | 19 | disable-digit-options generic-help-setopt 20 | "n" "--no-mintable" { false =: mintable } short-long-option 21 | " "+tab +"Sets mintable flag to false" option-help 22 | "i" "--init-supply" { parse-int =: init-supply } short-long-option-arg 23 | "Sets an initial supply of the token in nano-tokens (" init-supply (.) $+ +" by default)" option-help 24 | "m" "--mint-to" { parse-smc-addr drop 2=: mint-to } short-long-option-arg 25 | " "+tab +"Sets the initial supply recipient (base64 addr) (INITIALIZER by default)" option-help 26 | "r" "--resp-to" { parse-smc-addr drop 2=: resp-to } short-long-option-arg 27 | " "+tab +"Sets the response_address (base64 addr) (INITIALIZER by default)" option-help 28 | "a" "--fwd-amount" { $>GR =: fwd-amount } short-long-option-arg 29 | "Sets transfer_notification amount in TONs (" fwd-amount (.GR) $+ +" by default)" option-help 30 | "b" "--fwd-body" { =: fwd-body-boc-file } short-long-option-arg 31 | " "+tab +"Sets transfer_notification body, path to boc file" +cr +tab option-help 32 | "c" "--comment" { =: comment } short-long-option-arg 33 | "Sets the comment to be sent in the transfer_notification" option-help 34 | 35 | "h" "--help" { usage } short-long-option 36 | " "+tab +tab +"Shows a help message" option-help 37 | parse-options 38 | 39 | $# 2 <> ' usage if 40 | 41 | 0 =: workchain-id 42 | $1 =: config-file 43 | $2 =: save-file 44 | 45 | def? fwd-body-boc-file { @' fwd-body-boc-file file>B B>boc } { 46 | comment $len 0 = { b{} s>c } { comment simple-transfer-body } cond 47 | } cond =: fwd-body-cell 48 | 49 | "utils/config.fif" include 50 | 51 | "build/wall.code.boc" file>B B>boc =: wall-code 52 | "build/root.code.boc" file>B B>boc =: root-code 53 | 54 | =: init-storage 59 | 60 | { 67 | true 1 i, // mint_msg 68 | ref, 77 | } { false 1 i, } cond 78 | b> =: msg-body 79 | 80 | =: state-init 86 | 87 | ."----------------------------------------------------------------" cr 88 | state-init hashu workchain-id swap 2=: smc-addr 89 | ."new contract address: " smc-addr 4 .Addr cr cr 90 | 91 | state-init 2 boc+>B save-file +".init.boc" tuck B>file 92 | ."contract state init saved to: '" type ."' file" cr 93 | msg-body 2 boc+>B save-file +".body.boc" tuck B>file 94 | ."contract msg body saved to: '" type ."' file" cr 95 | ."----------------------------------------------------------------" cr 96 | -------------------------------------------------------------------------------- /fungible-token/fift/metadata.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | "GetOpt.fif" include 4 | "utils/DeLabUtil.fif" include 5 | 6 | { show-options-help 1 halt } : usage 7 | begin-options 8 | " " +cr 9 | +" " +cr 10 | +"Creates a message body to change token metadata and saves it to .boc file" +cr +tab 11 | disable-digit-options generic-help-setopt 12 | "h" "--help" { usage } short-long-option 13 | " "+tab +tab +"Shows a help message" option-help 14 | parse-options 15 | 16 | $# 2 <> ' usage if 17 | 18 | $1 =: config-file 19 | $2 =: save-file 20 | 21 | "utils/config.fif" include 22 | 23 | =: msg-body 28 | 29 | ."----------------------------------------------------------------" cr 30 | msg-body 2 boc+>B save-file +".boc" tuck B>file 31 | ."contract msg body saved to: '" type ."' file" cr 32 | ."----------------------------------------------------------------" cr 33 | -------------------------------------------------------------------------------- /fungible-token/fift/minttokens.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | "GetOpt.fif" include 4 | 5 | 1 =: fwd-amount 6 | "" =: comment 7 | 8 | { show-options-help 1 halt } : usage 9 | begin-options 10 | " " +cr 11 | +" " +cr 12 | +"Creates a message body for minting new tokens and saves it to .boc file" +cr +tab 13 | 14 | disable-digit-options generic-help-setopt 15 | "r" "--resp-to" { parse-smc-addr drop 2=: resp-to } short-long-option-arg 16 | " "+tab +"Sets the response_address (base64 addr) (mint-to-addr by default)" option-help 17 | "a" "--fwd-amount" { $>GR =: fwd-amount } short-long-option-arg 18 | "Sets transfer_notification amount in TONs (" fwd-amount (.GR) $+ +" by default)" option-help 19 | "b" "--fwd-body" { =: fwd-body-boc-file } short-long-option-arg 20 | " "+tab +"Sets transfer_notification body, path to boc file" option-help 21 | "c" "--comment" { =: comment } short-long-option-arg 22 | " "+tab +"Sets the comment to be sent in the transfer_notification" option-help 23 | 24 | "h" "--help" { usage } short-long-option 25 | " "+tab +tab +"Shows a help message" option-help 26 | parse-options 27 | 28 | $# 3 <> ' usage if 29 | 30 | $1 parse-smc-addr drop 2=: mint-to-addr 31 | $2 parse-int =: mint-amount 32 | $3 =: save-file 33 | 34 | def? fwd-body-boc-file { @' fwd-body-boc-file file>B B>boc } { 35 | comment $len 0 = { b{} s>c } { comment simple-transfer-body } cond 36 | } cond =: fwd-body-cell 37 | 38 | =: msg-body 50 | 51 | ."----------------------------------------------------------------" cr 52 | msg-body 2 boc+>B save-file +".boc" tuck B>file 53 | ."contract msg body saved to: '" type ."' file" cr 54 | ."----------------------------------------------------------------" cr 55 | -------------------------------------------------------------------------------- /fungible-token/fift/ownership.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | "GetOpt.fif" include 4 | 5 | { show-options-help 1 halt } : usage 6 | begin-options 7 | " <>" +cr 8 | +" " +cr 9 | +"Creates a message body to transfer token ownership and saves it to .boc file. "+cr 10 | +"Use 00 as argument for transfering ownership to add_none$00." +cr +tab 11 | disable-digit-options generic-help-setopt 12 | "h" "--help" { usage } short-long-option 13 | " "+tab +tab +"Shows a help message" option-help 14 | parse-options 15 | 16 | $# 2 <> ' usage if 17 | 18 | $1 =: new-owner-address 19 | $2 =: save-file 20 | 21 | =: msg-body 27 | 28 | ."----------------------------------------------------------------" cr 29 | msg-body 2 boc+>B save-file +".boc" tuck B>file 30 | ."contract msg body saved to: '" type ."' file" cr 31 | ."----------------------------------------------------------------" cr 32 | -------------------------------------------------------------------------------- /fungible-token/fift/utils/DeLabUtil.fif: -------------------------------------------------------------------------------- 1 | library DeLabUtil // DeLab TON Blockchain Fift Library 2 | 3 | // -------------------------- jetton utils ------------------------------ 4 | 0x6105d6cc76af400325e94d588ce511be5bfdbb73b437dc51eca43917d7a43e3d // kimage 5 | 0xc9046f7a37ad0ea7cee73355984fa5428982f8b37c8f7bcec91f7ac71a7cd104 // kdescr 6 | 0xee80fd2f1e03480e2282363596ee752d7bb27f50776b95086a0279189675923e // kdecimals 7 | 0xb76a7ca153c24671658335bbd08946350ffc621fa1c516e7123095d4ffd5c581 // ksymbol 8 | 0x82a3537ff0dbce7eec35d69edc3a189ee6f17d82f353a553f9aa96cb0be3ce89 // kname 9 | 5 tuple =: metadata-keys 10 | 11 | // Serializes onchain jetton metadata 12 | // and stores it as ref into Builder b 13 | // ( b name symbol decimals descr image -- b' ) 14 | { 0 { 15 | dup 0 = { dictnew } { swap } cond 16 | ref, b> 17 | ref, } : Onchmd, 20 | // ---------------------------------------------------------------------- 21 | -------------------------------------------------------------------------------- /fungible-token/fift/utils/config.fif: -------------------------------------------------------------------------------- 1 | // parse config 2 | { bl word parse-smc-addr drop 2=: _initializer } : INITIALIZER: 3 | { 124 word =: _name } : NAME: 4 | { 124 word =: _symbol } : SYMBOL: 5 | { bl word =: _decimals } : DECIMALS: 6 | { 124 word =: _descr } : DESCRIPTION: 7 | { bl word =: _image } : IMAGE: 8 | 9 | config-file include 10 | // ------------ 11 | -------------------------------------------------------------------------------- /fungible-token/func/common.fc: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0; 2 | 3 | const int CONF::WC = 0; 4 | 5 | const int ERR::CONSRTUCTOR = 1000; 6 | const int ERR::ADDR_FORMAT = 1001; 7 | const int ERR::ACCESS = 1002; 8 | const int ERR::INVALID_AMOUNT = 1003; 9 | const int ERR::INVALID_FWDBOD = 1004; 10 | const int ERR::NOT_ENOUGH = 709; 11 | const int ERR::NOT_FOUND = 0xffff; 12 | 13 | const int ROOT::TARGET = 100000000; ;; 0.1 ton 14 | const int WALL::TARGET = 20000000; ;; 0.02 ton 15 | const int CONF::MAX_GAS = 20000000; ;; 0.02 ton 16 | -------------------------------------------------------------------------------- /fungible-token/func/libs/delib.fc: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0; 2 | 3 | ;; FunC stdlib is required 4 | 5 | ;; converts a slice into cell 6 | cell delib::stc(slice s) asm "NEWC SWAP STSLICER ENDC"; 7 | 8 | slice delib::ES() asm " PUSHREF"; ;; creates an empty cell 10 | 11 | slice delib::addr_none() asm "b{00} PUSHSLICE"; 12 | 13 | ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress 14 | const int delib::flags_bounce_false = 0x10; ;; b'010000' -> hex -> 0x10 15 | const int delib::flags_bounce_true = 0x18; ;; b'011000' -> hex -> 0x10 16 | 17 | builder delib::begin_int_msg(int flags, slice addr, int amount) inline { 18 | return begin_cell() 19 | .store_uint(flags, 6) 20 | .store_slice(addr) 21 | .store_grams(amount) 22 | .store_uint(0, 1 + 4 + 4 + 64 + 32); 23 | ;; 1 zeros (ExtraCurrencyCollection) 24 | ;; 4 zeros (ihr_fee:Grams) 25 | ;; 4 zeros (fwd_fee:Grams) 26 | ;; 64 zeros (created_lt:uint64) 27 | ;; 32 zeros (created_at:uint32) 28 | } 29 | 30 | builder delib::store_either_cell(builder b, cell c) inline { 31 | slice cs = c.begin_parse(); 32 | if (b.builder_bits() + cs.slice_bits() > 1023) { 33 | return b.store_int(true, 1).store_ref(c); 34 | } 35 | 36 | return b.store_int(false, 1).store_slice(cs); 37 | } 38 | 39 | (slice, (cell)) delib::load_either_cell(slice s) inline { 40 | if (s~load_int(1)) { 41 | cell c = s~load_ref(); 42 | return (s, (c)); 43 | } 44 | 45 | return (delib::ES(), (delib::stc(s))); 46 | } 47 | 48 | cell delib::int_msg(slice dest, int bounce, int amount, cell body, cell init) inline_ref { 49 | int flags = bounce ? delib::flags_bounce_true : delib::flags_bounce_false; 50 | builder msg = delib::begin_int_msg(flags, dest, amount); 51 | 52 | if (begin_parse(init).slice_bits() > 0) { 53 | msg = msg.store_uint(3, 2).store_ref(init); ;; b'11' 54 | } else { 55 | msg = msg.store_int(false, 1); ;; b'0' 56 | } 57 | 58 | msg = msg.delib::store_either_cell(body); ;; body:(Either X ^X) 59 | return msg.end_cell(); 60 | } 61 | 62 | cell delib::basic_state_init(cell code, cell data) inline { 63 | return begin_cell() 64 | .store_uint(0, 2) ;; split_depth:(Maybe (## 5)) 65 | .store_dict(code) ;; special:(Maybe TickTock) 66 | .store_dict(data) ;; code:(Maybe ^Cell) 67 | .store_uint(0, 1) ;; data:(Maybe ^Cell) 68 | .end_cell(); 69 | } 70 | 71 | slice delib::addrstd_by_state(int workchain, cell state_init) inline { 72 | builder addr = begin_cell() 73 | .store_uint(4, 3) ;; addr_std$10 without AnyCast 74 | .store_int(workchain, 8) ;; workchain 75 | .store_uint(cell_hash(state_init), 256); 76 | 77 | return addr.end_cell().begin_parse(); 78 | } 79 | 80 | int delib::is_wc_eq?(int wc, slice addr) asm "REWRITESTDADDR DROP EQUAL"; 81 | int delib::is_addr_none?(slice address) asm "b{00} PUSHSLICE SDEQ"; 82 | int delib::is_addr_std_no_anycast?(slice address) asm "3 PLDU 4 PUSHINT EQUAL"; 83 | 84 | () delib::force_addr(slice addr, int chain, int can_be_none?, int err) impure inline { 85 | int is_none? = addr.delib::is_addr_none?(); 86 | if (is_none? & can_be_none?) { return (); } 87 | 88 | throw_if(err, is_none?); 89 | throw_unless(err, addr.delib::is_addr_std_no_anycast?()); 90 | throw_unless(err, delib::is_wc_eq?(chain, addr)); 91 | } 92 | -------------------------------------------------------------------------------- /fungible-token/func/libs/stdlib.fc: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /fungible-token/func/root.fc: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0; 2 | 3 | #include "libs/stdlib.fc"; 4 | #include "libs/delib.fc"; 5 | #include "utils/scheme.fc"; 6 | #include "utils/context.fc"; 7 | #include "wallet/shared.fc"; 8 | #include "common.fc"; 9 | 10 | global int self::inited?; 11 | global slice self::owner_address; 12 | global int self::mintable?; 13 | global int self::total_supply; 14 | global cell self::metadata; 15 | global cell self::wallet_code; 16 | 17 | () load_data() impure inline_ref { 18 | slice data = get_data().begin_parse(); 19 | 20 | self::inited? = data~load_int(1); 21 | self::owner_address = data~load_msg_addr(); 22 | self::mintable? = data~load_int(1); 23 | self::total_supply = data~load_coins(); 24 | self::metadata = data~load_ref(); 25 | self::wallet_code = data~load_ref(); 26 | } 27 | 28 | () save_data() impure inline { 29 | builder data = begin_cell() 30 | .store_int(self::inited?, 1) ;; + stc 1 bit 31 | .store_slice(self::owner_address) ;; + max 267 bit (addr_std$10 without Anycast) 32 | .store_int(self::mintable?, 1) ;; + stc 1 bit 33 | .store_coins(self::total_supply) ;; + max 124 bit 34 | .store_ref(self::metadata) ;; + stc 1 ref 35 | .store_ref(self::wallet_code); ;; + stc 1 ref 36 | 37 | ;; save maximum 393 and 2 refs cell to storage 38 | set_data(data.end_cell()); 39 | } 40 | 41 | ;; private function to create a mint msg and perform a few checks 42 | cell _mint(slice mint_msg, int add_req_amount) impure inline { 43 | (int amount, slice mint_to_address, slice response_destination, 44 | int forward_ton_amount, slice forward_payload) = mint_msg.unpack_mint_msg(); 45 | throw_unless(ERR::INVALID_FWDBOD, slice_bits(forward_payload) >= 1); 46 | 47 | ;; 48 | ;; -()> this -(internal_transfer)-> wall -(transfer_notification)-> wall owner (if fwd amount) 49 | ;; -(excesses)-> response_destination (optional) 50 | int fwd_count = forward_ton_amount ? 2 : 1; 51 | throw_unless(ERR::NOT_ENOUGH, msg::value > 52 | (CONF::MAX_GAS * 2) + (msg::fwd_fee * fwd_count) 53 | + forward_ton_amount + add_req_amount); 54 | 55 | throw_unless(ERR::INVALID_AMOUNT, amount > 0); 56 | delib::force_addr(mint_to_address, CONF::WC, false, ERR::ADDR_FORMAT); 57 | delib::force_addr(response_destination, CONF::WC, true, ERR::ADDR_FORMAT); 58 | 59 | cell body = pack_internal_transfer(msg::query_id, amount, msg::sender, 60 | response_destination, forward_ton_amount, forward_payload); 61 | 62 | self::total_supply += amount; 63 | return wallet::buildmsg(CONF::WC, self::wallet_code, mint_to_address, my_address(), body); 64 | } 65 | 66 | ;; contract constructor (can be called only once) 67 | int constructor(slice msg_body) impure inline_ref { 68 | slice ds = get_data().begin_parse(); 69 | throw_if(ERR::CONSRTUCTOR, ds~load_int(1)); 70 | throw_unless(ERR::ACCESS, equal_slice_bits(msg::sender, ds~load_msg_addr())); 71 | 72 | (int mintable, cell metadata, cell wallet_code, cell mint_msg) 73 | = msg_body.unpack_root_constructor(); 74 | 75 | self::inited? = true; 76 | self::mintable? = mintable; 77 | self::total_supply = 0; 78 | self::owner_address = msg::sender; 79 | self::metadata = metadata; 80 | self::wallet_code = wallet_code; 81 | 82 | if (~ cell_null?(mint_msg)) { 83 | cell msg = _mint(mint_msg.begin_parse(), ROOT::TARGET + WALL::TARGET); 84 | send_raw_message(msg, MSGFLAG::ALL_NOT_RESERVED); 85 | return true; 86 | } 87 | 88 | throw_unless(ERR::NOT_ENOUGH, msg::value > msg::fwd_fee + CONF::MAX_GAS + ROOT::TARGET); 89 | ctx::send_excesses(msg::sender, MSGFLAG::ALL_NOT_RESERVED + MSGFLAG::IGNORE_ERRORS); 90 | return true; 91 | } 92 | 93 | ;; only owner function to mint new tokens 94 | int mint(slice msg_body) impure inline_ref { 95 | ctx::only_owner(ERR::ACCESS, self::owner_address); 96 | send_raw_message(_mint(msg_body, WALL::TARGET), MSGFLAG::ALL_NOT_RESERVED); 97 | return true; 98 | } 99 | 100 | ;; only owner function to change token metadata 101 | int set_metadata(slice msg_body) impure inline_ref { 102 | ctx::only_owner(ERR::ACCESS, self::owner_address); 103 | 104 | self::metadata = msg_body.preload_ref(); 105 | ctx::send_excesses(msg::sender, MSGFLAG::ALL_NOT_RESERVED + MSGFLAG::IGNORE_ERRORS); 106 | return true; 107 | } 108 | 109 | ;; only owner function to transfer ownership 110 | int transfer_ownership(slice msg_body) impure inline_ref { 111 | ctx::only_owner(ERR::ACCESS, self::owner_address); 112 | 113 | slice new_owner = msg_body~load_msg_addr(); 114 | delib::force_addr(new_owner, CONF::WC, true, ERR::ADDR_FORMAT); 115 | 116 | self::owner_address = new_owner; 117 | ctx::send_excesses(msg::sender, MSGFLAG::ALL_NOT_RESERVED + MSGFLAG::IGNORE_ERRORS); 118 | return true; 119 | } 120 | 121 | ;; function to accept burn_notification from wall contract 122 | int burn_notification(slice msg_body) impure inline_ref { 123 | (int amount, slice sender, slice response_address) = msg_body.unpack_root_burn_notification(); 124 | 125 | cell state_init = wallet::state_init(self::wallet_code, sender, my_address()); 126 | slice wallet_address = delib::addrstd_by_state(CONF::WC, state_init); 127 | 128 | throw_unless(ERR::ACCESS, equal_slice_bits(msg::sender, wallet_address)); 129 | self::total_supply -= amount; 130 | 131 | if (~ response_address.delib::is_addr_none?()) { 132 | ctx::send_excesses(response_address, MSGFLAG::ALL_NOT_RESERVED + MSGFLAG::IGNORE_ERRORS); 133 | } 134 | 135 | return true; 136 | } 137 | 138 | ;; handler for TEP89 139 | int provide_wallet_address(slice msg_body) impure inline_ref { 140 | throw_unless(ERR::NOT_ENOUGH, msg::value > msg::fwd_fee + CONF::MAX_GAS); 141 | 142 | (slice owner_address, int include?) = msg_body.unpack_provide_wallet_address(); 143 | cell included = include? ? delib::stc(owner_address) : null(); 144 | 145 | cell state_init = wallet::state_init(self::wallet_code, owner_address, my_address()); 146 | slice wallet_address = delib::addrstd_by_state(CONF::WC, state_init); 147 | 148 | cell body = pack_take_wallet_address(msg::query_id, wallet_address, included); 149 | cell msg = delib::int_msg(msg::sender, true, 0, body, delib::EC()); 150 | 151 | send_raw_message(msg, MSGFLAG::ALL_NOT_RESERVED); 152 | return true; 153 | } 154 | 155 | ;; bounced messages handler 156 | int on_bounce(slice msg_body) impure inline_ref { 157 | throw_unless(ERR::NOT_FOUND, msg::op == op::wallet::internal_transfer); 158 | 159 | load_data(); 160 | self::total_supply -= msg_body~load_coins(); ;; subtract the unminted tokens 161 | 162 | ;; we send excesses to the owner because only he could call the new tokens mintion 163 | ctx::send_excesses(self::owner_address, MSGFLAG::ALL_NOT_RESERVED + MSGFLAG::IGNORE_ERRORS); 164 | return true; 165 | } 166 | 167 | ;; function selector by msg::op 168 | int selector(slice msg_body) impure inline { 169 | if (msg::bounced?) { return (on_bounce(msg_body)); } 170 | if (msg::op == op::root::constructor) { return constructor(msg_body); } 171 | 172 | load_data(); 173 | if (msg::op == op::root::mint) { return mint(msg_body); } 174 | if (msg::op == op::root::set_metadata) { return set_metadata(msg_body); } 175 | if (msg::op == op::root::transfer_ownership) { return transfer_ownership(msg_body); } 176 | if (msg::op == op::root::burn_notification) { return burn_notification(msg_body); } 177 | if (msg::op == op::root::provide_wallet_address) { return provide_wallet_address(msg_body); } 178 | 179 | return false; 180 | } 181 | 182 | ;; internal message entry point 183 | () recv_internal(int balance, int value, cell msg, slice msg_body) impure { 184 | throw_if(0, msg_body.slice_empty?() | msg_body.slice_bits() < 32 + 64); 185 | 186 | msg_body~ctx::load(msg, balance, value); 187 | delib::force_addr(msg::sender, CONF::WC, false, ERR::ADDR_FORMAT); 188 | 189 | ctx::reserve(ROOT::TARGET); 190 | if (selector(msg_body)) { return (save_data()); } 191 | 192 | throw(ERR::NOT_FOUND); ;; not found 193 | } 194 | 195 | ;; according to TEP74 (Fungible tokens (Jettons) standard) 196 | (int, int, slice, cell, cell) get_jetton_data() method_id { 197 | load_data(); 198 | 199 | return (self::total_supply, self::mintable?, self::owner_address, 200 | self::metadata, self::wallet_code); 201 | } 202 | 203 | ;; according to TEP74 (Fungible tokens (Jettons) standard) 204 | slice get_wallet_address(slice owner) method_id { 205 | load_data(); 206 | cell state_init = wallet::state_init(self::wallet_code, owner, my_address()); 207 | return delib::addrstd_by_state(CONF::WC, state_init); 208 | } 209 | -------------------------------------------------------------------------------- /fungible-token/func/utils/context.fc: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0; 2 | 3 | global int msg::op; 4 | global int msg::query_id; 5 | global int msg::bounced?; 6 | 7 | global slice msg::sender; 8 | global int msg::balance; 9 | global int msg::value; 10 | global int msg::fwd_fee; 11 | 12 | ;; ---------------- msg flags ---------------- 13 | const int MSGFLAG::SENDER_PAYS_FEES = 1; 14 | const int MSGFLAG::IGNORE_ERRORS = 2; 15 | const int MSGFLAG::DESTROY_IF_ZERO = 32; 16 | const int MSGFLAG::REMAINING_GAS = 64; 17 | const int MSGFLAG::ALL_NOT_RESERVED = 128; 18 | ;; ------------------------------------------- 19 | 20 | (slice, ()) ctx::load(slice body, cell msg, int balance, int value) impure inline { 21 | slice cs = msg.begin_parse(); 22 | 23 | int bounced? = cs~load_uint(4) & 1; 24 | slice sender = cs~load_msg_addr(); 25 | 26 | cs~load_msg_addr(); ;; skip dst 27 | cs~load_coins(); ;; skip value 28 | cs~skip_bits(1); ;; skip extracurrency collection 29 | cs~load_coins(); ;; skip ihr_fee 30 | 31 | if (bounced?) { body~skip_bits(32); } 32 | 33 | msg::op = body~load_uint(32); 34 | msg::query_id = body~load_uint(64); 35 | msg::bounced? = bounced?; 36 | 37 | msg::sender = sender; 38 | msg::balance = balance; 39 | msg::value = value; 40 | msg::fwd_fee = muldiv(cs~load_coins(), 3, 2); 41 | 42 | return (body, ()); 43 | } 44 | 45 | const int op::general::excesses = 0xd53276db; 46 | 47 | () ctx::send_excesses(slice to, int flag) impure inline { 48 | cell body = begin_cell() 49 | .store_uint(op::general::excesses , 32) 50 | .store_uint(msg::query_id, 64) 51 | .end_cell(); 52 | 53 | send_raw_message(delib::int_msg(to, false, 0, body, delib::EC()), flag); 54 | } 55 | 56 | () ctx::only_owner(int err, slice owner_address) impure inline { 57 | throw_unless(err, equal_slice_bits(msg::sender, owner_address)); 58 | } 59 | 60 | () ctx::reserve(int target) impure inline { 61 | raw_reserve(max(msg::balance - msg::value, target), 0); 62 | } 63 | -------------------------------------------------------------------------------- /fungible-token/func/utils/scheme.fc: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0; 2 | 3 | const int op::root::constructor = 0x24b81ac7; 4 | const int op::root::mint = 0x6f813cd5; 5 | const int op::root::set_metadata = 0x3611ae36; 6 | const int op::root::transfer_ownership = 0x45f53e63; 7 | const int op::root::burn_notification = 0x3dba5544; 8 | 9 | const int op::root::provide_wallet_address = 0x2c76b973; ;; TEP89 10 | const int op::root::take_wallet_address = 0xd1735400; ;; TEP89 11 | 12 | const int op::wallet::internal_transfer = 0x178d4519; 13 | const int op::wallet::transfer = 0x0f8a7ea5; ;; TEP74 14 | const int op::wallet::burn = 0x2b6406ac; 15 | 16 | const int op::general::transfer_notification = 0x7362d09c; ;; TEP74 17 | 18 | cell pack_internal_transfer(int query_id, int amount, slice from, 19 | slice response_address, int forward_ton_amount, 20 | slice forward_payload) inline { 21 | 22 | return begin_cell() 23 | .store_uint(op::wallet::internal_transfer, 32) 24 | .store_uint(query_id, 64) 25 | .store_coins(amount) 26 | .store_slice(from) 27 | .store_slice(response_address) 28 | .store_coins(forward_ton_amount) 29 | .store_slice(forward_payload) 30 | .end_cell(); 31 | } 32 | 33 | ;; op and query_id must be loaded before 34 | (int, slice, slice, int, slice) unpack_internal_transfer(slice s) inline { 35 | int amount = s~load_coins(); 36 | slice from = s~load_msg_addr(); 37 | slice response_address = s~load_msg_addr(); 38 | int forward_ton_amount = s~load_coins(); 39 | slice forward_payload = s; 40 | 41 | return (amount, from, response_address, 42 | forward_ton_amount, forward_payload); 43 | } 44 | 45 | cell pack_transfer_notification(int query_id, int amount, slice sender, 46 | slice forward_payload) inline { 47 | 48 | builder body = begin_cell() 49 | .store_uint(op::general::transfer_notification, 32) 50 | .store_uint(query_id, 64) 51 | .store_coins(amount) 52 | .store_slice(sender) 53 | .store_slice(forward_payload); 54 | 55 | return body.end_cell(); 56 | } 57 | 58 | ;; op and query_id must be loaded before 59 | (int, slice, slice, int, slice) unpack_transfer_query(slice s) inline { 60 | int amount = s~load_coins(); 61 | slice destination = s~load_msg_addr(); 62 | slice response_destination = s~load_msg_addr(); 63 | cell custom_payload = s~load_maybe_ref(); ;; ignored 64 | int forward_ton_amount = s~load_coins(); 65 | slice forward_payload = s; 66 | 67 | return (amount, destination, response_destination, forward_ton_amount, forward_payload); 68 | } 69 | 70 | ;; op and query_id must be loaded before 71 | (int, slice, slice, int, slice) unpack_mint_msg(slice s) inline { 72 | int amount = s~load_coins(); 73 | slice mint_to_address = s~load_msg_addr(); 74 | slice response_destination = s~load_msg_addr(); 75 | int forward_ton_amount = s~load_coins(); 76 | slice forward_payload = s; 77 | 78 | return (amount, mint_to_address, response_destination, 79 | forward_ton_amount, forward_payload); 80 | } 81 | 82 | ;; op and query_id must be loaded before 83 | (int, cell, cell, cell) unpack_root_constructor(slice s) inline { 84 | int mintable = s~load_int(1); 85 | cell metadata = s~load_ref(); 86 | cell wallet_code = s~load_ref(); 87 | cell mint_msg = s~load_maybe_ref(); 88 | 89 | return (mintable, metadata, wallet_code, mint_msg); 90 | } 91 | 92 | ;; op and query_id must be loaded before 93 | (int, slice, slice) unpack_root_burn_notification(slice s) inline { 94 | int amount = s~load_coins(); 95 | slice sender = s~load_msg_addr(); 96 | slice response_address = s~load_msg_addr(); 97 | 98 | return (amount, sender, response_address); 99 | } 100 | 101 | cell pack_root_burn_notification(int query_id, int amount, 102 | slice sender, slice response_address) inline { 103 | return begin_cell() 104 | .store_uint(op::root::burn_notification, 32) 105 | .store_uint(query_id, 64) 106 | .store_coins(amount) 107 | .store_slice(sender) 108 | .store_slice(response_address) 109 | .end_cell(); 110 | } 111 | 112 | ;; op and query_id must be loaded before 113 | (int, slice) unpack_wall_burn(slice s) inline { 114 | int amount = s~load_coins(); 115 | slice response_destination = s~load_msg_addr(); 116 | 117 | return (amount, response_destination); 118 | } 119 | 120 | ;; op and query_id must be loaded before 121 | (slice, int) unpack_provide_wallet_address(slice s) inline { 122 | slice owner_address = s~load_msg_addr(); 123 | int include_address = s~load_int(1); 124 | 125 | return (owner_address, include_address); 126 | } 127 | 128 | cell pack_take_wallet_address(int query_id, slice wallet_address, cell owner_address_cell) inline { 129 | return begin_cell() 130 | .store_uint(op::root::take_wallet_address, 32) 131 | .store_uint(query_id, 64) 132 | .store_slice(wallet_address) 133 | .store_maybe_ref(owner_address_cell) 134 | .end_cell(); 135 | } 136 | -------------------------------------------------------------------------------- /fungible-token/func/wall.fc: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0; 2 | 3 | #include "libs/stdlib.fc"; 4 | #include "libs/delib.fc"; 5 | #include "utils/scheme.fc"; 6 | #include "utils/context.fc"; 7 | #include "wallet/shared.fc"; 8 | #include "common.fc"; 9 | 10 | global int self::balance; 11 | global slice self::owner_address; 12 | global slice self::root_address; 13 | global cell self::wallet_code; 14 | 15 | () load_data() impure inline_ref { 16 | slice data = get_data().begin_parse(); 17 | 18 | self::balance = data~load_coins(); 19 | self::owner_address = data~load_msg_addr(); 20 | self::root_address = data~load_msg_addr(); 21 | self::wallet_code = data~load_ref(); 22 | } 23 | 24 | () save_data() impure inline_ref { 25 | cell data = wallet::pack_data( 26 | self::balance, 27 | self::owner_address, 28 | self::root_address, 29 | self::wallet_code 30 | ); 31 | 32 | set_data(data); 33 | } 34 | 35 | int receive_tokens(slice msg_body) impure inline_ref { 36 | (int amount, slice from, slice response_address, 37 | int forward_ton_amount, slice forward_payload) = msg_body.unpack_internal_transfer(); 38 | 39 | cell state_init = wallet::state_init(self::wallet_code, from, self::root_address); 40 | slice wallet_address = delib::addrstd_by_state(CONF::WC, state_init); 41 | 42 | int root? = equal_slice_bits(msg::sender, self::root_address); 43 | int wall? = equal_slice_bits(msg::sender, wallet_address); 44 | throw_unless(ERR::ACCESS, root? | wall?); 45 | 46 | self::balance += amount; 47 | 48 | if (forward_ton_amount > 0) { 49 | cell body = pack_transfer_notification(msg::query_id, amount, from, forward_payload); 50 | cell msg = delib::int_msg(self::owner_address, false, forward_ton_amount, body, delib::EC()); 51 | send_raw_message(msg, MSGFLAG::SENDER_PAYS_FEES); 52 | } 53 | 54 | if (~ response_address.delib::is_addr_none?()) { 55 | ctx::send_excesses(response_address, MSGFLAG::ALL_NOT_RESERVED + MSGFLAG::IGNORE_ERRORS); 56 | } 57 | 58 | return true; 59 | } 60 | 61 | int send_tokens(slice msg_body) impure inline_ref { 62 | ctx::only_owner(ERR::ACCESS, self::owner_address); 63 | 64 | (int amount, slice destination, slice response_destination, 65 | int forward_ton_amount, slice forward_payload) = msg_body.unpack_transfer_query(); 66 | throw_unless(ERR::INVALID_FWDBOD, slice_bits(forward_payload) >= 1); 67 | 68 | int fwd_count = forward_ton_amount ? 2 : 1; 69 | throw_unless(ERR::NOT_ENOUGH, msg::value > 70 | (CONF::MAX_GAS * 2) + (msg::fwd_fee * fwd_count) 71 | + forward_ton_amount + WALL::TARGET); 72 | 73 | delib::force_addr(destination, CONF::WC, false, ERR::ADDR_FORMAT); ;; can't be addr_none 74 | delib::force_addr(response_destination, CONF::WC, true, ERR::ADDR_FORMAT); ;; can be addr_none 75 | 76 | throw_unless(ERR::INVALID_AMOUNT, (self::balance > 0) & (self::balance >= amount)); 77 | self::balance -= amount; 78 | 79 | cell body = pack_internal_transfer(msg::query_id, amount, self::owner_address, 80 | response_destination, forward_ton_amount, forward_payload); 81 | 82 | cell msg = wallet::buildmsg(CONF::WC, self::wallet_code, destination, self::root_address, body); 83 | send_raw_message(msg, MSGFLAG::ALL_NOT_RESERVED); 84 | return true; 85 | } 86 | 87 | int burn_tokens(slice msg_body) impure inline_ref { 88 | ctx::only_owner(ERR::ACCESS, self::owner_address); 89 | (int amount, slice response_destination) = msg_body.unpack_wall_burn(); 90 | 91 | throw_unless(ERR::INVALID_AMOUNT, (self::balance > 0) & (self::balance >= amount)); 92 | throw_unless(ERR::NOT_ENOUGH, msg::value > msg::fwd_fee + (CONF::MAX_GAS * 2)); 93 | delib::force_addr(response_destination, CONF::WC, true, ERR::ADDR_FORMAT); 94 | 95 | self::balance -= amount; 96 | 97 | cell body = pack_root_burn_notification(msg::query_id, amount, msg::sender, response_destination); 98 | cell msg = delib::int_msg(self::root_address, true, 0, body, delib::EC()); 99 | send_raw_message(msg, MSGFLAG::ALL_NOT_RESERVED); 100 | 101 | return true; 102 | } 103 | 104 | ;; bounced messages handler 105 | int on_bounce(slice msg_body) impure inline_ref { 106 | throw_unless(ERR::NOT_FOUND, 107 | (msg::op == op::wallet::internal_transfer) | 108 | (msg::op == op::root::burn_notification)); 109 | 110 | load_data(); 111 | self::balance += msg_body~load_coins(); 112 | ctx::send_excesses(self::owner_address, MSGFLAG::ALL_NOT_RESERVED + MSGFLAG::IGNORE_ERRORS); 113 | return true; 114 | } 115 | 116 | ;; function selector by msg::op 117 | int selector(slice msg_body) impure inline { 118 | if (msg::bounced?) { return (on_bounce(msg_body)); } 119 | 120 | load_data(); 121 | if (msg::op == op::wallet::internal_transfer) { return receive_tokens(msg_body); } 122 | if (msg::op == op::wallet::transfer) { return send_tokens(msg_body); } 123 | if (msg::op == op::wallet::burn) { return burn_tokens(msg_body); } 124 | 125 | return false; 126 | } 127 | 128 | ;; internal message entry point 129 | () recv_internal(int balance, int value, cell msg, slice msg_body) impure { 130 | throw_if(0, msg_body.slice_empty?() | msg_body.slice_bits() < 32 + 64); 131 | 132 | msg_body~ctx::load(msg, balance, value); 133 | delib::force_addr(msg::sender, CONF::WC, false, ERR::ADDR_FORMAT); 134 | 135 | ctx::reserve(WALL::TARGET); 136 | if (selector(msg_body)) { return (save_data()); } 137 | 138 | throw(0xffff); ;; not found 139 | } 140 | 141 | ;; according to TEP74 (Fungible tokens (Jettons) standard) 142 | (int, slice, slice, cell) get_wallet_data() method_id { 143 | load_data(); 144 | return (self::balance, self::owner_address, self::root_address, self::wallet_code); 145 | } 146 | -------------------------------------------------------------------------------- /fungible-token/func/wallet/shared.fc: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0; 2 | 3 | cell wallet::pack_data(int balance, slice owner, slice root, cell code) inline { 4 | builder data = begin_cell() 5 | .store_coins(balance) 6 | .store_slice(owner) 7 | .store_slice(root) 8 | .store_ref(code); 9 | 10 | return data.end_cell(); 11 | } 12 | 13 | cell wallet::state_init(cell code, slice owner, slice root) inline { 14 | return delib::basic_state_init(code, wallet::pack_data(0, owner, root, code)); 15 | } 16 | 17 | cell wallet::intmsg(slice address, cell state_init, cell body) inline { 18 | cell msg = delib::int_msg(address, true, 0, body, state_init); 19 | return msg; 20 | } 21 | 22 | cell wallet::buildmsg(int wc, cell code, slice owner, slice root, cell body) inline { 23 | cell state_init = wallet::state_init(code, owner, root); 24 | return wallet::intmsg(delib::addrstd_by_state(wc, state_init), state_init, body); 25 | } 26 | -------------------------------------------------------------------------------- /fungible-token/sample.conf: -------------------------------------------------------------------------------- 1 | INITIALIZER: EQBOh1HYD13tb6TKkox5iniM_8vOKNJ1gZNQ305Y8XEVyg3X // replace with your address 2 | 3 | NAME: Token A| 4 | SYMBOL: TTA| 5 | DECIMALS: 9 6 | DESCRIPTION: Some testnet token| 7 | IMAGE: ipfs://bafkreicwu5getztwleuqlmhucc5m4wfnymmon3wiszhgsdgekiub2beizi 8 | -------------------------------------------------------------------------------- /fungible-token/scheme.tlb: -------------------------------------------------------------------------------- 1 | bit$_ (## 1) = Bit; 2 | 3 | bool_false$0 = Bool; 4 | bool_true$1 = Bool; 5 | 6 | bool_false$0 = BoolFalse; 7 | bool_true$1 = BoolTrue; 8 | 9 | nothing$0 {X:Type} = Maybe X; 10 | just$1 {X:Type} value:X = Maybe X; 11 | 12 | left$0 {X:Type} {Y:Type} value:X = Either X Y; 13 | right$1 {X:Type} {Y:Type} value:Y = Either X Y; 14 | 15 | // ---------------------------------------------------------------- 16 | // ordinary Hashmap / HashmapE, with fixed length keys (FROM hashmap.tlb) 17 | // https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb 18 | hm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l n) 19 | {n = (~m) + l} node:(HashmapNode m X) = Hashmap n X; 20 | 21 | hmn_leaf#_ {X:Type} value:X = HashmapNode 0 X; 22 | hmn_fork#_ {n:#} {X:Type} left:^(Hashmap n X) 23 | right:^(Hashmap n X) = HashmapNode (n + 1) X; 24 | 25 | hml_short$0 {m:#} {n:#} len:(Unary ~n) {n <= m} s:(n * Bit) = HmLabel ~n m; 26 | hml_long$10 {m:#} n:(#<= m) s:(n * Bit) = HmLabel ~n m; 27 | hml_same$11 {m:#} v:Bit n:(#<= m) = HmLabel ~n m; 28 | 29 | unary_zero$0 = Unary ~0; 30 | unary_succ$1 {n:#} x:(Unary ~n) = Unary ~(n + 1); 31 | 32 | hme_empty$0 {n:#} {X:Type} = HashmapE n X; 33 | hme_root$1 {n:#} {X:Type} root:^(Hashmap n X) = HashmapE n X; 34 | // ---------------------------------------------------------------- 35 | 36 | addr_none$00 = MsgAddressExt; 37 | addr_extern$01 len:(## 9) external_address:(bits len) = MsgAddressExt; 38 | anycast_info$_ depth:(#<= 30) { depth >= 1 } rewrite_pfx:(bits depth) = Anycast; 39 | addr_std$10 anycast:(Maybe Anycast) workchain_id:int8 address:bits256 = MsgAddressInt; 40 | addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) workchain_id:int32 address:(bits addr_len) = MsgAddressInt; 41 | 42 | _ _:MsgAddressInt = MsgAddress; 43 | _ _:MsgAddressExt = MsgAddress; 44 | 45 | var_uint$_ {n:#} len:(#< n) value:(uint (len * 8)) = VarUInteger n; 46 | nanograms$_ amount:(VarUInteger 16) = Coins; 47 | 48 | // TEP64 (https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md) 49 | tail#_ {bn:#} b:(bits bn) = SnakeData ~0; 50 | cons#_ {bn:#} {n:#} b:(bits bn) next:^(SnakeData ~n) = SnakeData ~(n + 1); 51 | 52 | chunked_data#_ data:(HashmapE 32 ^(SnakeData ~0)) = ChunkedData; 53 | text#_ {n:#} data:(SnakeData ~n) = Text; 54 | snake#00 {n:#} data:(SnakeData ~n) = ContentData; 55 | chunks#01 data:ChunkedData = ContentData; 56 | onchain#00 data:(HashmapE 256 ^ContentData) = FullContent; 57 | offchain#01 uri:Text = FullContent; 58 | // ------------------------------------------------------------------------------------------------ 59 | 60 | internal_transfer query_id:uint64 amount:(VarUInteger 16) from:MsgAddress 61 | response_address:MsgAddress 62 | forward_ton_amount:(VarUInteger 16) 63 | forward_payload:(Either Cell ^Cell) 64 | = InternalMsgBody; 65 | 66 | // TEP74 (https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md) 67 | transfer query_id:uint64 amount:(VarUInteger 16) destination:MsgAddress 68 | response_destination:MsgAddress custom_payload:(Maybe ^Cell) 69 | forward_ton_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell) 70 | = InternalMsgBody; 71 | 72 | transfer_notification query_id:uint64 amount:(VarUInteger 16) 73 | sender:MsgAddress forward_payload:(Either Cell ^Cell) 74 | = InternalMsgBody; 75 | // ------------------------------------------------------------------------------------------------ 76 | 77 | // TEP89 (https://github.com/ton-blockchain/TEPs/blob/master/text/0089-jetton-wallet-discovery.md) 78 | provide_wallet_address query_id:uint64 owner_address:MsgAddress include_address:Bool = InternalMsgBody; 79 | take_wallet_address query_id:uint64 wallet_address:MsgAddress owner_address:(Maybe ^MsgAddress) = InternalMsgBody; 80 | // ------------------------------------------------------------------------------------------------ 81 | 82 | wall_burn query_id:uint64 amount:Coins response_destination:MsgAddress = InternalMsgBody; 83 | 84 | _ amount:Coins mint_to_address:MsgAddress response_destination:MsgAddress 85 | forward_ton_amount:Coins forward_payload:(Either Cell ^Cell) = MintMsg; 86 | 87 | root_constructor query_id:uint64 mintable:Bool metadata:^FullContent 88 | wallet_code:^Cell mint_msg:(Maybe ^MintMsg) = InternalMsgBody; 89 | 90 | root_burn_notification query_id:uint64 amount:Coins 91 | sender:MsgAddress response_address:MsgAddress = RootInternalMsgBody; 92 | 93 | root_mint query_id:uint64 _:MintMsg = InternalMsgBody; 94 | root_set_metadata query_id:uint64 new_metadata:^FullContent = InternalMsgBody; 95 | root_transfer_ownership query_id:uint64 new_owner:MsgAddress = RootInternalMsgBody; 96 | -------------------------------------------------------------------------------- /jetton-pool/.gitignore: -------------------------------------------------------------------------------- 1 | # build 2 | build/* 3 | !build/.gitkeep 4 | 5 | # OS generated files 6 | .DS_Store 7 | .DS_Store? 8 | 9 | # other 10 | *.boc 11 | !shared/* 12 | *.conf 13 | *.log 14 | *test* 15 | !sample.conf 16 | -------------------------------------------------------------------------------- /jetton-pool/Makefile: -------------------------------------------------------------------------------- 1 | #!make 2 | 3 | build: clean 4 | func -S -o build/main.code.fif -W build/main.code.boc func/main.fc 5 | fift build/main.code.fif 6 | 7 | func -S -o build/store.code.fif -W build/store.code.boc func/store.fc 8 | fift build/store.code.fif 9 | 10 | clean: 11 | @rm -rf build/*code* 12 | -------------------------------------------------------------------------------- /jetton-pool/README.md: -------------------------------------------------------------------------------- 1 | # DeLab Jetton Pool 2 | 3 | > :warning: IN DEVELOPMENT. DO NOT USE IN PRODUCTION! 4 | 5 | The Jetton Pool TON smart contract is designed to create farm pools to distribute a specified income in `B tokens` as a reward for blocking liquidity in other `A tokens` (liquidity mining). 6 | 7 | ## Deploy 8 | 9 | 1. `make build` 10 | 2. `fift -s deploy.fif sample.conf build/out` 11 | 3. Send internal message with body and state init 12 | 13 | ## License 14 | 15 | `GNU GENERAL PUBLIC LICENSE Version 3 or later` 16 | `SPDX-License-Identifier: GPL-3.0-or-later` 17 | -------------------------------------------------------------------------------- /jetton-pool/deploy.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | "GetOpt.fif" include 4 | 5 | "build/main.code.boc" file>B B>boc =: pool-code 6 | "build/store.code.boc" file>B B>boc =: stor-code 7 | 8 | { show-options-help 1 halt } : usage 9 | begin-options " " +cr +cr 10 | +"Creates the jetton-pool contract deploy state init and body," +cr 11 | +"than saves it to .init.boc, .body.boc" +cr +tab 12 | 13 | disable-digit-options generic-help-setopt 14 | "h" "--help" { usage } short-long-option 15 | "Shows a help message" option-help 16 | parse-options 17 | 18 | $# 2 <> ' usage if 19 | 20 | 0 =: workchain-id 21 | $1 =: config-file 22 | $2 =: save-file 23 | 24 | { bl word parse-smc-addr drop 2=: initializer } : INITIALIZER: 25 | { bl word parse-smc-addr drop 2=: token-a-addr } : TOKEN_A: 26 | { bl word parse-smc-addr drop 2=: token-b-addr } : TOKEN_B: 27 | { bl word parse-int =: reward-amount } : REWARD_AMOUNT: 28 | { bl word parse-int =: reward-interv } : REWARD_INTERV: 29 | 30 | config-file include 31 | 32 | =: msg-bodycc 40 | 41 | // init storage (inited?, initializer, salt) 42 | =: init-storage 43 | =: state-init 44 | 45 | state-init hashu workchain-id swap .addr cr 46 | state-init 2 boc+>B save-file +".init.boc" tuck B>file 47 | msg-bodycc 2 boc+>B save-file +".body.boc" tuck B>file 48 | ."body and state init saved to: " type ."; " type cr 49 | -------------------------------------------------------------------------------- /jetton-pool/func/libs/delib.fc: -------------------------------------------------------------------------------- 1 | {- 2 | This file is part of DeLab jetton-pool. 3 | 4 | DeLab jetton-pool is free software: you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as 6 | published by the Free Software Foundation, either version 3 of 7 | the License, or (at your option) any later version. 8 | 9 | DeLab jetton-pool is distributed in the hope that it will be 10 | useful, but WITHOUT ANY WARRANTY; without even the implied 11 | warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | See the GNU General Public License for more details. 13 | 14 | Copyright (C) 2022 DeLab Team. 15 | -} 16 | 17 | #pragma version ^0.3.0; 18 | 19 | #include "./stdlib.fc"; ;; func stdlib is required 20 | 21 | slice delib::ES() asm " PUSHREF"; ;; creates an empty cell 23 | cell delib::stc(slice s) asm "NEWC SWAP STSLICER ENDC"; ;; converts a slice into cell 24 | slice delib::addr_none() asm "b{00} PUSHSLICE"; ;; creates an addr_none$00 slice 25 | 26 | ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress 27 | const int delib::flags_bounce_false = 0x10; ;; b'010000' -> hex -> 0x10 28 | const int delib::flags_bounce_true = 0x18; ;; b'011000' -> hex -> 0x10 29 | 30 | builder delib::begin_int_msg(int flags, slice addr, int amount) inline { 31 | return begin_cell() 32 | .store_uint(flags, 6) 33 | .store_slice(addr) 34 | .store_grams(amount) 35 | .store_uint(0, 1 + 4 + 4 + 64 + 32); 36 | ;; 1 zeros (ExtraCurrencyCollection) 37 | ;; 4 zeros (ihr_fee:Grams) 38 | ;; 4 zeros (fwd_fee:Grams) 39 | ;; 64 zeros (created_lt:uint64) 40 | ;; 32 zeros (created_at:uint32) 41 | } 42 | 43 | builder delib::store_either_cell(builder b, cell c) inline { 44 | slice cs = c.begin_parse(); 45 | return (b.builder_bits() + cs.slice_bits() > 1023) 46 | ? b.store_int(true, 1).store_ref(c) 47 | : b.store_int(false, 1).store_slice(cs); 48 | } 49 | 50 | (slice, (cell)) delib::load_either_cell(slice s) inline { 51 | cell loaded = s~load_int(1) ? s~load_ref() : delib::stc(s); 52 | return (s, (loaded)); 53 | } 54 | 55 | cell delib::int_msg(slice dest, int bounce, int amount, cell body, cell init) inline { 56 | int flags = bounce ? delib::flags_bounce_true : delib::flags_bounce_false; 57 | 58 | builder msg = delib::begin_int_msg(flags, dest, amount); 59 | msg = cell_null?(init) ? msg.store_int(false, 1) : msg.store_uint(3, 2).store_ref(init); 60 | msg = cell_null?(body) ? msg.store_int(false, 1) : msg.delib::store_either_cell(body); 61 | 62 | return msg.end_cell(); 63 | } 64 | 65 | cell delib::basic_state_init(cell code, cell data) inline { 66 | return begin_cell() 67 | .store_uint(0, 2) ;; split_depth:(Maybe (## 5)) 68 | .store_dict(code) ;; special:(Maybe TickTock) 69 | .store_dict(data) ;; code:(Maybe ^Cell) 70 | .store_uint(0, 1) ;; data:(Maybe ^Cell) 71 | .end_cell(); 72 | } 73 | 74 | slice delib::addrstd_by_state(int workchain, cell state_init) inline { 75 | builder addr = begin_cell() 76 | .store_uint(4, 3) ;; addr_std$10 without AnyCast 77 | .store_int(workchain, 8) ;; workchain 78 | .store_uint(cell_hash(state_init), 256); 79 | 80 | return addr.end_cell().begin_parse(); 81 | } 82 | 83 | int delib::is_wc_eq?(int wc, slice addr) asm "REWRITESTDADDR DROP EQUAL"; 84 | int delib::is_addr_none?(slice address) asm "b{00} PUSHSLICE SDEQ"; 85 | int delib::is_addr_std_no_anycast?(slice address) asm "3 PLDU 4 PUSHINT EQUAL"; 86 | 87 | () delib::force_addr(slice addr, int chain, int can_be_none?, int err) impure inline { 88 | int is_none? = addr.delib::is_addr_none?(); 89 | if (is_none? & can_be_none?) { return (); } 90 | 91 | throw_if(err, is_none?); 92 | throw_unless(err, addr.delib::is_addr_std_no_anycast?()); 93 | throw_unless(err, delib::is_wc_eq?(chain, addr)); 94 | } 95 | -------------------------------------------------------------------------------- /jetton-pool/func/main.fc: -------------------------------------------------------------------------------- 1 | {- 2 | This file is part of DeLab jetton-pool. 3 | 4 | DeLab jetton-pool is free software: you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as 6 | published by the Free Software Foundation, either version 3 of 7 | the License, or (at your option) any later version. 8 | 9 | DeLab jetton-pool is distributed in the hope that it will be 10 | useful, but WITHOUT ANY WARRANTY; without even the implied 11 | warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | See the GNU General Public License for more details. 13 | 14 | Copyright (C) 2022 DeLab Team. 15 | -} 16 | 17 | #pragma version ^0.3.0; 18 | 19 | #include "libs/stdlib.fc"; 20 | #include "libs/delib.fc"; 21 | #include "utils/consts.fc"; 22 | #include "utils/scheme.fc"; 23 | #include "utils/context.fc"; 24 | 25 | global int self::inited?; 26 | global slice self::initializer; 27 | global int self::token_a_wall_set?; 28 | global int self::token_b_wall_set?; 29 | global int self::reward_amount; 30 | global int self::last_reward_ts; 31 | global int self::reward_interval; 32 | 33 | global slice self::token_a_root; 34 | global slice self::token_b_root; 35 | 36 | global slice self::token_a_wall; 37 | global slice self::token_b_wall; 38 | 39 | global int self::token_b_balance; 40 | global int self::token_b_shared; 41 | global int self::token_a_staked; 42 | 43 | global cell slef::store_contract_code; 44 | 45 | () load_data() impure inline_ref { 46 | slice data = get_data().begin_parse(); 47 | 48 | int inited? = data~load_int(1); 49 | throw_unless(ERR::CONSRTUCTOR, inited?); 50 | 51 | self::inited? = inited?; 52 | self::initializer = data~load_msg_addr(); 53 | self::token_a_wall_set? = data~load_int(1); 54 | self::token_b_wall_set? = data~load_int(1); 55 | self::reward_amount = data~load_coins(); 56 | self::last_reward_ts = data~load_uint(32); 57 | self::reward_interval = data~load_uint(32); 58 | 59 | slice roots = data~load_ref().begin_parse(); 60 | self::token_a_root = roots~load_msg_addr(); 61 | self::token_b_root = roots~load_msg_addr(); 62 | 63 | slice walls = data~load_ref().begin_parse(); 64 | self::token_a_wall = walls~load_msg_addr(); 65 | self::token_b_wall = walls~load_msg_addr(); 66 | 67 | slice balances = data~load_ref().begin_parse(); 68 | self::token_b_balance = balances~load_coins(); 69 | self::token_b_shared = balances~load_coins(); 70 | self::token_a_staked = balances~load_coins(); 71 | 72 | cell slef::store_contract_code = data~load_ref(); 73 | } 74 | 75 | () save_data() impure inline_ref { 76 | builder data = begin_cell() 77 | .store_int(self::inited?, 1) 78 | .store_slice(self::initializer) 79 | .store_int(self::token_a_wall_set?, 1) 80 | .store_int(self::token_b_wall_set?, 1) 81 | .store_coins(self::reward_amount) 82 | .store_uint(self::last_reward_ts, 32) 83 | .store_uint(self::reward_interval, 32); 84 | 85 | builder roots = begin_cell() 86 | .store_slice(self::token_a_root) 87 | .store_slice(self::token_b_root); 88 | 89 | builder walls = begin_cell() 90 | .store_slice(self::token_a_wall) 91 | .store_slice(self::token_b_wall); 92 | 93 | builder balances = begin_cell() 94 | .store_coins(self::token_b_balance) 95 | .store_coins(self::token_b_shared) 96 | .store_coins(self::token_a_staked); 97 | 98 | data = data.store_ref(roots.end_cell()); 99 | data = data.store_ref(walls.end_cell()); 100 | data = data.store_ref(balances.end_cell()); 101 | data = data.store_ref(slef::store_contract_code); 102 | 103 | set_data(data.end_cell()); 104 | } 105 | 106 | cell token_transfer(int amount, slice to_address, slice response_address, 107 | slice token_wall, int ton_amount) inline { 108 | 109 | cell body = pack_simple_token_transfer(msg::query_id, amount, to_address, 110 | response_address, CONF::TOKEN_FWD_AMOUNT); 111 | 112 | return delib::int_msg(token_wall, false, ton_amount, body, null()); 113 | } 114 | 115 | () throw_tokens_128(slice to, slice token_wall, int amount) impure inline { 116 | cell msg = token_transfer(amount, to, to, token_wall, 0); 117 | send_raw_message(msg, MSG::ALL_NOT_RESERVED + MSG::IGNORE_ERRORS); 118 | 119 | commit(); 120 | throw(ERR::NOT_ENOUGH); 121 | } 122 | 123 | (slice, cell) calc_store_contract(slice staker_address) inline { 124 | cell contract_data = pack_store_data(my_address(), staker_address); 125 | cell contract_init = delib::basic_state_init(slef::store_contract_code, contract_data); 126 | return (delib::addrstd_by_state(CONF::WC, contract_init), contract_init); 127 | } 128 | 129 | () auth_by_store_contract(slice staker_address) impure inline { 130 | (slice store_address, _) = calc_store_contract(staker_address); 131 | throw_unless(ERR::ACCESS, equal_slice_bits(msg::sender, store_address)); 132 | } 133 | 134 | int calc_to_share() inline { 135 | int time_diif = now() - self::last_reward_ts; 136 | int to_share = muldiv(time_diif, self::reward_amount, self::reward_interval); 137 | return min(to_share, self::token_b_balance); 138 | } 139 | 140 | ;; contract constructor (can be called only once) 141 | () constructor(slice msg_body) impure inline_ref { 142 | slice ds = get_data().begin_parse(); 143 | 144 | throw_if(ERR::CONSRTUCTOR, ds~load_int(1)); 145 | throw_unless(ERR::ACCESS, equal_slice_bits(msg::sender, ds~load_msg_addr())); 146 | 147 | ;; -------------------------------------------------------------------------- 148 | ;; this |--(provide_wallet)-> root_a -(take_wallet)-> this -?(excesses)-> initializer 149 | ;; |--(provide_wallet)-> root_b -(take_wallet)-> this -?(excesses)-> initializer 150 | ;; |-?(excesses)-> initializer 151 | ;; -------------------------------------------------------------------------- 152 | throw_unless(ERR::NOT_ENOUGH, msg::value > MAIN::TARGET + 153 | (msg::fwd_fee * 4) + (CONF::MAX_GAS * 5)); 154 | 155 | (slice token_a, slice token_b, int reward_amount, 156 | int reward_interval, cell store_contract_code) = msg_body.unpack_constructor(); 157 | 158 | delib::force_addr(token_a, CONF::WC, false, ERR::ADDR_FORMAT); 159 | delib::force_addr(token_b, CONF::WC, false, ERR::ADDR_FORMAT); 160 | 161 | int tval = CONF::MAX_GAS + msg::fwd_fee; 162 | cell body = pack_provide_wallet(msg::query_id, my_address()); 163 | send_raw_message(delib::int_msg(token_a, false, tval, body, null()), MSG::SENDER_PAYS_FEES); 164 | send_raw_message(delib::int_msg(token_b, false, tval, body, null()), MSG::SENDER_PAYS_FEES); 165 | 166 | self::inited? = true; 167 | self::initializer = msg::sender; 168 | self::token_a_wall_set? = false; 169 | self::token_b_wall_set? = false; 170 | self::reward_amount = reward_amount; 171 | self::last_reward_ts = 0; 172 | self::reward_interval = reward_interval; 173 | 174 | self::token_a_root = token_a; 175 | self::token_b_root = token_b; 176 | 177 | self::token_a_wall = delib::addr_none(); 178 | self::token_b_wall = delib::addr_none(); 179 | 180 | self::token_b_balance = 0; 181 | self::token_b_shared = 0; 182 | self::token_a_staked = 0; 183 | 184 | slef::store_contract_code = store_contract_code; 185 | 186 | ctx::send_excesses(self::initializer, MSG::ALL_NOT_RESERVED + MSG::IGNORE_ERRORS); 187 | } 188 | 189 | ;; function that accepts incoming token transfer 190 | () accept_tokens(slice msg_body) impure inline_ref { 191 | int token_a? = equal_slice_bits(msg::sender, self::token_a_wall); 192 | int token_b? = equal_slice_bits(msg::sender, self::token_b_wall); 193 | throw_unless(ERR::ACCESS, token_a? | token_b?); 194 | 195 | (int amount, slice sender) = (msg_body~load_coins(), msg_body~load_msg_addr()); 196 | 197 | if (token_a?) { ;; accept stake request 198 | ;; this -(get_store_data)-> store -(take_recv_data)-> this -(next_stake)-> 199 | ;; -> store -(end_stake)-> this -?(excesses)-> initializer 200 | if (msg::value < ((CONF::MAX_GAS * 5) + (msg::fwd_fee * 4)) ) { 201 | throw_tokens_128(sender, self::token_a_wall, amount); 202 | } 203 | 204 | (slice store_address, cell store_init) = calc_store_contract(sender); 205 | cell body = pack_get_store_data(msg::query_id, callback::store::next_stake, 206 | begin_cell().store_coins(amount)); 207 | 208 | cell msg = delib::int_msg(store_address, false, 0, body, store_init); 209 | send_raw_message(msg, MSG::ALL_NOT_RESERVED); 210 | } 211 | 212 | if (token_b?) { ;; just top up the pool balance with b tokens 213 | self::token_b_balance += amount; 214 | ctx::send_excesses(sender, MSG::ALL_NOT_RESERVED + MSG::IGNORE_ERRORS); 215 | } 216 | } 217 | 218 | ;; function that sets addresses of token wallets 219 | () take_wallet(slice msg_body) impure inline_ref { 220 | int ok? = false; 221 | 222 | if (equal_slice_bits(msg::sender, self::token_a_root)) { 223 | self::token_a_wall = msg_body~load_msg_addr(); 224 | self::token_a_wall_set? = true; 225 | ok? = true; 226 | } 227 | 228 | if (equal_slice_bits(msg::sender, self::token_b_root)) { 229 | self::token_b_wall = msg_body~load_msg_addr(); 230 | self::token_b_wall_set? = true; 231 | ok? = true; 232 | } 233 | 234 | throw_unless(ERR::ACCESS, ok?); 235 | ctx::send_excesses(self::initializer, MSG::ALL_NOT_RESERVED + MSG::IGNORE_ERRORS); 236 | } 237 | 238 | ;; next_stake handler 239 | () next_stake(slice msg_body) impure inline_ref { 240 | slice staker_address = msg_body~load_msg_addr(); 241 | auth_by_store_contract(staker_address); 242 | 243 | (int staker::stamp, int staker::staked_a, int staker::reward_debt) 244 | = unpack_staker_info(msg_body~load_ref().begin_parse()); 245 | 246 | int to_stake = msg_body~load_coins(); 247 | 248 | if (staker::staked_a > 0) { 249 | int actual_fees = self::token_b_shared - staker::stamp; 250 | int reward = muldiv(actual_fees, staker::staked_a, self::token_a_staked); 251 | staker::reward_debt += reward; 252 | } 253 | 254 | self::token_a_staked += to_stake; 255 | staker::staked_a += to_stake; 256 | staker::stamp = self::token_b_shared; 257 | 258 | if (self::last_reward_ts == 0) { 259 | self::last_reward_ts = now(); 260 | } 261 | 262 | builder fwd = begin_cell().store_slice(staker_address); 263 | cell info = pack_staker_info(staker::stamp, staker::staked_a, staker::reward_debt); 264 | cell body = pack_store_ext_data(msg::query_id, info, callback::store::end_stake_or_unstake, fwd); 265 | cell msg = delib::int_msg(msg::sender, false, 0, body, null()); 266 | send_raw_message(msg, MSG::ALL_NOT_RESERVED); 267 | } 268 | 269 | ;; end_stake_or_unstake handler 270 | () end_stake_or_unstake(slice msg_body) impure inline_ref { 271 | slice staker_address = msg_body~load_msg_addr(); 272 | auth_by_store_contract(staker_address); 273 | 274 | ctx::send_excesses(msg_body~load_msg_addr(), MSG::ALL_NOT_RESERVED + MSG::IGNORE_ERRORS); 275 | } 276 | 277 | () unstake(slice msg_body) impure inline_ref { 278 | ;; this -(get_store_data)-> store -(take_recv_data)-> this -(next_unstake)-> 279 | ;; |-> store -(end_stake)-> this -?(excesses)-> initializer 280 | ;; |-> ({transfer_chain} * 2) 281 | int basic_chain = (CONF::MAX_GAS * 5) + (msg::fwd_fee * 4); 282 | int transfer_chain = CONF::TOKEN_FWD_AMOUNT + (msg::fwd_fee * 3) + (CONF::MAX_GAS * 2); 283 | throw_unless(ERR::NOT_ENOUGH, msg::value > basic_chain + (transfer_chain * 2)); 284 | 285 | int unstake_amount = msg_body~load_coins(); 286 | 287 | (slice store_address, _) = calc_store_contract(msg::sender); 288 | builder fwd_body = begin_cell().store_coins(unstake_amount).store_coins(transfer_chain); 289 | cell body = pack_get_store_data(msg::query_id, callback::store::next_unstake, fwd_body); 290 | 291 | cell msg = delib::int_msg(store_address, false, 0, body, null()); 292 | send_raw_message(msg, MSG::ALL_NOT_RESERVED); 293 | } 294 | 295 | () next_unstake(slice msg_body) impure inline_ref { 296 | slice staker = msg_body~load_msg_addr(); 297 | auth_by_store_contract(staker); 298 | 299 | (int staker::stamp, int staker::staked_a, int staker::reward_debt) 300 | = unpack_staker_info(msg_body~load_ref().begin_parse()); 301 | 302 | (int uamount, int transfer_chain) = (msg_body~load_coins(), msg_body~load_coins()); 303 | throw_unless(ERR::NOT_ENOUGH_STAKE, staker::staked_a >= uamount); 304 | 305 | int to_share = calc_to_share(); 306 | self::token_b_shared += to_share; 307 | self::token_b_balance -= to_share; 308 | 309 | int actual_fees = self::token_b_shared - staker::stamp; 310 | int reward = muldiv(actual_fees, uamount, self::token_a_staked); 311 | 312 | self::token_a_staked -= uamount; 313 | self::token_b_shared -= reward; 314 | 315 | cell bodya = token_transfer(uamount, staker, staker, self::token_a_wall, transfer_chain); 316 | cell bodyb = token_transfer(reward, staker, staker, self::token_b_wall, transfer_chain); 317 | send_raw_message(bodya, MSG::SENDER_PAYS_FEES); 318 | send_raw_message(bodyb, MSG::SENDER_PAYS_FEES); 319 | 320 | staker::stamp = self::token_b_shared; 321 | staker::staked_a -= uamount; 322 | staker::reward_debt = 0; 323 | 324 | builder fwd = begin_cell().store_slice(staker); 325 | cell info = pack_staker_info(staker::stamp, staker::staked_a, staker::reward_debt); 326 | cell body = pack_store_ext_data(msg::query_id, info, callback::store::end_stake_or_unstake, fwd); 327 | cell msg = delib::int_msg(msg::sender, false, 0, body, null()); 328 | 329 | send_raw_message(msg, MSG::ALL_NOT_RESERVED); 330 | } 331 | 332 | ;; function selector by msg::op 333 | () callselector(slice msg_body) impure inline_ref { 334 | throw_if(0, msg::op == 0 | msg::bounced?); ;; ignore simple transfers or bounced 335 | if (msg::op == op::main::constructor) { return constructor(msg_body); } 336 | 337 | load_data(); 338 | if (msg::op == op::main::unstake) { return unstake(msg_body); } 339 | if (msg::op == op::general::transfer_notification) { return accept_tokens(msg_body); } 340 | if (msg::op == op::root::take_wallet_address) { return take_wallet(msg_body); } 341 | 342 | if (msg::op == callback::store::next_stake) { return next_stake(msg_body); } 343 | if (msg::op == callback::store::next_unstake) { return next_unstake(msg_body); } 344 | if (msg::op == callback::store::end_stake_or_unstake) { return end_stake_or_unstake(msg_body); } 345 | 346 | throw(0xffff); ;; not found 347 | } 348 | 349 | ;; internal message entry point 350 | () recv_internal(int balance, int value, cell msg, slice msg_body) impure { 351 | throw_if(0, msg_body.slice_empty?() | msg_body.slice_bits() < 32 + 64); 352 | 353 | msg_body~ctx::load(msg, balance, value); 354 | delib::force_addr(msg::sender, CONF::WC, false, ERR::ADDR_FORMAT); 355 | 356 | ctx::reserve(MAIN::TARGET); 357 | callselector(msg_body); 358 | save_data(); 359 | } 360 | 361 | (int, int, int, int, int, int, int, int) get_pool_info() method_id { 362 | load_data(); 363 | int actual_token_b_shared = self::token_a_staked > 0 364 | ? self::token_b_shared + calc_to_share() : 0; 365 | 366 | return (self::inited? & self::token_a_wall_set? & self::token_b_wall_set?, 367 | self::reward_amount, 368 | self::last_reward_ts, 369 | self::reward_interval, 370 | self::token_b_balance, 371 | actual_token_b_shared, ;; actual 372 | self::token_b_shared, ;; from storage 373 | self::token_a_staked); 374 | } 375 | 376 | ([slice, slice], [slice, slice]) get_token_addresses() method_id { 377 | load_data(); 378 | return ([self::token_a_root, self::token_a_wall], 379 | [self::token_b_root, self::token_b_wall]); 380 | } 381 | 382 | (slice) calc_store_by_staker(slice staker_address) method_id { 383 | load_data(); 384 | (slice store_address, _) = calc_store_contract(staker_address); 385 | return store_address; 386 | } 387 | 388 | (int, int, int) parse_staker_info(slice info) method_id { 389 | return unpack_staker_info(info); ;; (stamp, staked_a, reward_debt) 390 | } 391 | -------------------------------------------------------------------------------- /jetton-pool/func/store.fc: -------------------------------------------------------------------------------- 1 | {- 2 | This file is part of DeLab jetton-pool. 3 | 4 | DeLab jetton-pool is free software: you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as 6 | published by the Free Software Foundation, either version 3 of 7 | the License, or (at your option) any later version. 8 | 9 | DeLab jetton-pool is distributed in the hope that it will be 10 | useful, but WITHOUT ANY WARRANTY; without even the implied 11 | warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | See the GNU General Public License for more details. 13 | 14 | Copyright (C) 2022 DeLab Team. 15 | -} 16 | 17 | #pragma version ^0.3.0; 18 | 19 | #include "libs/stdlib.fc"; 20 | #include "libs/delib.fc"; 21 | #include "utils/consts.fc"; 22 | #include "utils/context.fc"; 23 | 24 | global slice self::main_address; 25 | global slice self::staker_address; 26 | global cell self::stored_data; 27 | 28 | () load_data() impure inline { 29 | slice data = get_data().begin_parse(); 30 | 31 | self::main_address = data~load_msg_addr(); 32 | self::staker_address = data~load_msg_addr(); 33 | self::stored_data = data~load_ref(); 34 | } 35 | 36 | () save_data() impure inline { 37 | builder data = begin_cell() 38 | .store_slice(self::main_address) 39 | .store_slice(self::staker_address) 40 | .store_ref(self::stored_data); 41 | 42 | set_data(data.end_cell()); 43 | } 44 | 45 | () store_ext_data(slice msg_body) impure inline { 46 | int fwd_op = msg_body~load_uint(32); 47 | self::stored_data = msg_body~load_ref(); 48 | 49 | builder body = begin_cell() 50 | .store_uint(fwd_op, 32) 51 | .store_uint(msg::query_id, 64) 52 | .store_slice(self::staker_address) 53 | .store_slice(msg_body); 54 | 55 | cell msg = delib::int_msg(msg::sender, false, 0, body.end_cell(), null()); 56 | send_raw_message(msg, MSG::ALL_NOT_RESERVED); 57 | } 58 | 59 | () get_store_data(slice msg_body) impure inline { 60 | int fwd_op = msg_body~load_uint(32); 61 | 62 | builder body = begin_cell() 63 | .store_uint(fwd_op, 32) 64 | .store_uint(msg::query_id, 64) 65 | .store_slice(self::staker_address) 66 | .store_ref(self::stored_data) 67 | .store_slice(msg_body); 68 | 69 | cell msg = delib::int_msg(msg::sender, false, 0, body.end_cell(), null()); 70 | send_raw_message(msg, MSG::ALL_NOT_RESERVED); 71 | } 72 | 73 | ;; function selector by msg::op 74 | () callselector(slice msg_body) impure inline_ref { 75 | throw_if(0, msg::op == 0 | msg::bounced?); ;; ignore simple transfers or bounced 76 | 77 | load_data(); 78 | throw_unless(ERR::ACCESS, equal_slice_bits(msg::sender, self::main_address)); ;; auth 79 | 80 | if (msg::op == op::store::store_ext_data) { return store_ext_data(msg_body); } 81 | if (msg::op == op::store::get_store_data) { return get_store_data(msg_body); } 82 | 83 | throw(0xffff); ;; not found 84 | } 85 | 86 | ;; internal message entry point 87 | () recv_internal(int balance, int value, cell msg, slice msg_body) impure { 88 | throw_if(0, msg_body.slice_empty?() | msg_body.slice_bits() < 32 + 64); 89 | 90 | msg_body~ctx::load(msg, balance, value); 91 | delib::force_addr(msg::sender, CONF::WC, false, ERR::ADDR_FORMAT); 92 | 93 | ctx::reserve(STORE::TARGET); 94 | callselector(msg_body); 95 | save_data(); 96 | } 97 | 98 | (slice, slice, cell) get_stored_data() method_id { 99 | load_data(); 100 | return (self::main_address, self::staker_address, self::stored_data); 101 | } 102 | -------------------------------------------------------------------------------- /jetton-pool/func/utils/consts.fc: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0; 2 | 3 | const int op::main::constructor = 0xe1; 4 | const int op::main::unstake = 0xe2; 5 | 6 | const int op::root::provide_wallet_address = 0x2c76b973; ;; TEP89 7 | const int op::root::take_wallet_address = 0xd1735400; ;; TEP89 8 | 9 | const int op::wallet::transfer = 0x0f8a7ea5; ;; TEP74 10 | 11 | const int op::store::store_ext_data = 0xb1; 12 | const int op::store::get_store_data = 0xb2; 13 | const int op::store::take_recv_data = 0xb3; 14 | const int op::store::store_callback = 0xb4; 15 | 16 | const int callback::store::next_stake = 0xb5; 17 | const int callback::store::next_unstake = 0xb6; 18 | const int callback::store::end_stake_or_unstake = 0xb7; 19 | 20 | const int op::general::transfer_notification = 0x7362d09c; ;; TEP74 21 | const int op::general::excesses = 0xd53276db; 22 | 23 | ;; other 24 | const int STORE::TARGET = 100000000; ;; 0.1 ton 25 | 26 | const int ERR::CONSRTUCTOR = 1000; 27 | const int ERR::ADDR_FORMAT = 1001; 28 | const int ERR::ACCESS = 1002; 29 | const int ERR::NOT_ENOUGH_STAKE = 1003; 30 | const int ERR::NOT_ENOUGH = 709; 31 | 32 | const int MAIN::TARGET = 500000000; ;; 0.5 ton 33 | 34 | const int CONF::TOKEN_FWD_AMOUNT = 1; 35 | const int CONF::MAX_GAS = 50000000; ;; 0.05 ton 36 | const int CONF::WC = 0; 37 | 38 | const int MSG::SENDER_PAYS_FEES = 1; 39 | const int MSG::IGNORE_ERRORS = 2; 40 | const int MSG::DESTROY_IF_ZERO = 32; 41 | const int MSG::REMAINING_GAS = 64; 42 | const int MSG::ALL_NOT_RESERVED = 128; 43 | -------------------------------------------------------------------------------- /jetton-pool/func/utils/context.fc: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0; 2 | 3 | #include "../libs/stdlib.fc"; 4 | #include "../libs/delib.fc"; 5 | 6 | global int msg::op; 7 | global int msg::query_id; 8 | global int msg::bounced?; 9 | 10 | global slice msg::sender; 11 | global int msg::balance; 12 | global int msg::value; 13 | global int msg::fwd_fee; 14 | 15 | (slice, ()) ctx::load(slice body, cell msg, int balance, int value) impure inline { 16 | slice cs = msg.begin_parse(); 17 | 18 | int bounced? = cs~load_uint(4) & 1; 19 | slice sender = cs~load_msg_addr(); 20 | 21 | cs~load_msg_addr(); ;; skip dst 22 | cs~load_coins(); ;; skip value 23 | cs~skip_bits(1); ;; skip extracurrency collection 24 | cs~load_coins(); ;; skip ihr_fee 25 | 26 | if (bounced?) { body~skip_bits(32); } 27 | 28 | msg::op = body~load_uint(32); 29 | msg::query_id = body~load_uint(64); 30 | msg::bounced? = bounced?; 31 | 32 | msg::sender = sender; 33 | msg::balance = balance; 34 | msg::value = value; 35 | msg::fwd_fee = muldiv(cs~load_coins(), 3, 2); 36 | 37 | return (body, ()); 38 | } 39 | 40 | () ctx::send_excesses(slice to, int flag) impure inline { 41 | cell body = begin_cell() 42 | .store_uint(op::general::excesses , 32) 43 | .store_uint(msg::query_id, 64) 44 | .end_cell(); 45 | 46 | send_raw_message(delib::int_msg(to, false, 0, body, null()), flag); 47 | } 48 | 49 | () ctx::only_owner(int err, slice owner_address) impure inline { 50 | throw_unless(err, equal_slice_bits(msg::sender, owner_address)); 51 | } 52 | 53 | () ctx::reserve(int target) impure inline { 54 | raw_reserve(max(msg::balance - msg::value, target), 0); 55 | } 56 | -------------------------------------------------------------------------------- /jetton-pool/func/utils/scheme.fc: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0; 2 | 3 | #include "../libs/stdlib.fc"; 4 | #include "../libs/delib.fc"; 5 | 6 | ;; op and query_id must be loaded before 7 | (slice, slice, int, int, cell) unpack_constructor(slice s) inline { 8 | slice token_a_address = s~load_msg_addr(); 9 | slice token_b_address = s~load_msg_addr(); 10 | int reward_amount = s~load_coins(); 11 | int reward_interval = s~load_uint(32); 12 | cell store_contract_code = s~load_ref(); 13 | 14 | return (token_a_address, token_b_address, reward_amount, 15 | reward_interval, store_contract_code); 16 | } 17 | 18 | cell pack_provide_wallet(int query_id, slice owner_address) inline { 19 | return begin_cell() 20 | .store_uint(op::root::provide_wallet_address, 32) 21 | .store_uint(query_id, 64) 22 | .store_slice(owner_address) 23 | .store_int(false, 1) ;; include_address 24 | .end_cell(); 25 | } 26 | 27 | cell pack_simple_token_transfer(int query_id, int token_amount, slice to_address, 28 | slice response_address, int fwd_amount) inline { 29 | return begin_cell() 30 | .store_uint(op::wallet::transfer, 32) 31 | .store_uint(query_id, 64) 32 | .store_coins(token_amount) 33 | .store_slice(to_address) 34 | .store_slice(response_address) 35 | .store_int(false, 1) ;; custom_payload 36 | .store_coins(fwd_amount) ;; fwd_amount 37 | .store_int(false, 1) ;; either fwd_body 38 | .end_cell(); 39 | } 40 | 41 | cell pack_staker_info(int stamp, int staked_a, int reward_debt) inline { 42 | return begin_cell() 43 | .store_coins(stamp) 44 | .store_coins(staked_a) 45 | .store_coins(reward_debt) 46 | .end_cell(); 47 | } 48 | 49 | (int, int, int) unpack_staker_info(slice s) inline { 50 | int stamp = s~load_coins(); 51 | int staked_a = s~load_coins(); 52 | int reward_debt = s~load_coins(); 53 | 54 | return (stamp, staked_a, reward_debt); 55 | } 56 | 57 | cell pack_store_data(slice main_address, slice staker_address) inline { 58 | return begin_cell() 59 | .store_slice(main_address) 60 | .store_slice(staker_address) 61 | .store_ref(pack_staker_info(0, 0, 0)) 62 | .end_cell(); 63 | } 64 | 65 | cell pack_get_store_data(int query_id, int fwd_op, builder fwd_payload) inline { 66 | return begin_cell() 67 | .store_uint(op::store::get_store_data, 32) 68 | .store_uint(query_id, 64) 69 | .store_uint(fwd_op, 32) 70 | .store_builder(fwd_payload) 71 | .end_cell(); 72 | } 73 | 74 | cell pack_store_ext_data(int query_id, cell ext_data, int fwd_op, builder fwd_payload) inline { 75 | return begin_cell() 76 | .store_uint(op::store::store_ext_data, 32) 77 | .store_uint(query_id, 64) 78 | .store_uint(fwd_op, 32) 79 | .store_ref(ext_data) 80 | .store_builder(fwd_payload) 81 | .end_cell(); 82 | } 83 | -------------------------------------------------------------------------------- /jetton-pool/sample.conf: -------------------------------------------------------------------------------- 1 | INITIALIZER: EQBkBpjhbH4vO7GdfbSkV5KsSMNjxOdpdrEKt-Vws5MVCKnp 2 | TOKEN_A: EQDCiLkQKpNOGpNDX2oZRHDaNAVZqV6cmBbGgsz_IhUUnYOZ 3 | TOKEN_B: EQCe026X84xst_fnZyLWETJMNAsGMUsB8eVNH0wbla0SY2WK 4 | 5 | REWARD_AMOUNT: 100000000000 6 | REWARD_INTERV: 3600 7 | --------------------------------------------------------------------------------