├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── c ├── README.md ├── example-1-hello-world │ ├── README.md │ ├── hello_world.abi │ └── hello_world.c ├── example-10-setcode │ ├── README │ ├── setcode.abi │ ├── setcode.c │ └── setcode_new.c ├── example-2-echo │ ├── README.md │ ├── echo.abi │ └── echo.c ├── example-3-transfer-80000001 │ ├── README.md │ ├── transfer_80000001.abi │ └── transfer_80000001.c ├── example-4-piggybank │ ├── README.md │ ├── piggybank.abi │ └── piggybank.c ├── example-5-wallet │ ├── README.md │ ├── wallet.abi │ └── wallet.c ├── example-6-message-exchange │ ├── README.md │ ├── receiver.abi │ ├── receiver.c │ ├── sender.abi │ └── sender.c ├── example-7-loan │ ├── README │ ├── bank.abi │ ├── bank.c │ ├── client.abi │ ├── client.c │ ├── common.c │ └── common.h └── example-8-gramgiver │ ├── README.md │ ├── client.abi │ ├── client.c │ ├── giver.abi │ └── giver.c ├── cpp ├── Authorization │ ├── HelloWorld.cpp │ ├── HelloWorld.hpp │ └── README.md ├── Giver │ ├── Client.cpp │ ├── Client.hpp │ ├── Giver.cpp │ ├── Giver.hpp │ └── README.md ├── HelloWorld │ ├── HelloWorld.cpp │ ├── HelloWorld.hpp │ └── README.md ├── Kamikaze │ ├── Kamikaze.cpp │ ├── Kamikaze.hpp │ └── README.md ├── Piggybank │ ├── PiggyOwner.cpp │ ├── PiggyOwner.hpp │ ├── PiggyStranger.cpp │ ├── PiggyStranger.hpp │ ├── Piggybank.cpp │ ├── Piggybank.hpp │ └── README.md ├── README.md ├── TUTORIAL.md └── Wallet │ ├── README.md │ ├── Wallet.cpp │ └── Wallet.hpp └── solidity ├── 10_Wallet.sol ├── 11_ContractDeployer.sol ├── 11_SimpleContract.sol ├── 11_Waller_no_constructor.sol ├── 12_BadContract.sol ├── 12_NewVersion.sol ├── 13_BankCollector.sol ├── 13_BankCollectorClient.sol ├── 13_Interfaces.sol ├── 14_CustomReplayProtection.sol ├── 15_MessageReceiver.sol ├── 15_MessageSender.sol ├── 16_onBounceHandler.sol ├── 17_ContractProducer.md ├── 17_ContractProducer.sol ├── 17_SimpleWallet.sol ├── 17_low_level.md ├── 18_Interfaces.sol ├── 18_OrderClient.sol ├── 18_OrderDatabase.sol ├── 19_Casino.sol ├── 19_CasinoClient.sol ├── 19_CasinoInterfaces.sol ├── 19_CasinoOwner.sol ├── 1_Accumulator.sol ├── 1_Accumulator_no_ctor.sol ├── 20_bomber.sol ├── 20_interface.sol ├── 20_sink.sol ├── 21_self_deploy.sol ├── 22_sender.sol ├── 22_sink.sol ├── 23_rawReserve.sol ├── 23_sender.sol ├── 24_Client.sol ├── 24_IClient.sol ├── 24_ISquareProvider.sol ├── 24_LengthProvider.sol ├── 24_SquareProvider.sol ├── 24_WidthProvider.sol ├── 25_Config.md ├── 25_Config.sol ├── 2_StorageClient.sol ├── 2_UintStorage.sol ├── 3_Borrower.sol ├── 3_Loaner.sol ├── 4.1_CentralBank.sol ├── 4.1_CurrencyExchange.sol ├── 4_CentralBank.sol ├── 4_CurrencyExchange.sol ├── 4_interfaces.sol ├── 5_Bank.sol ├── 5_BankClient.sol ├── 5_BankClientInterfaces.sol ├── 6_DBClientInterface.sol ├── 6_DataBase.sol ├── 6_DataBaseClient.sol ├── 7_CrashContract.sol ├── 7_Giver.sol ├── 8_Heir.sol ├── 8_Kamikaze.sol ├── 9_PiggyBank.sol ├── 9_PiggyBank_Owner.sol ├── 9_PiggyBank_Stranger.sol └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sample smart-contracts 2 | 3 | ## [Sample contracts in Solidity](https://github.com/everx-labs/samples/tree/master/solidity) 4 | ## [Sample contracts in C (deprecated)](https://github.com/everx-labs/samples/tree/master/c) 5 | ## [Sample contracts in C++ (deprecated)](https://github.com/everx-labs/samples/tree/master/cpp) 6 | 7 | ## Compilers and tools in open source 8 | 9 | - [Solidity compiler](https://github.com/everx-labs/TON-Solidity-Compiler) 10 | - [C/C++ LLVM-based compiler (deprecated)](https://github.com/everx-labs/TON-Compiler) 11 | -------------------------------------------------------------------------------- /c/README.md: -------------------------------------------------------------------------------- 1 | # Deprecated 2 | # C contract samples for TON 3 | This directory contains contracts that demonstrate how to use C to write contracts for TON blockchain. There is a separate subfolder for each file and its description. 4 | 5 | ## Prerequisites 6 | TVM Toolchain for C to build and test contracts locally. It includes: 7 | * Clang for TVM available in Node SE and at [https://github.com/everx-labs/TON-Compiler](https://github.com/everx-labs/TON-Compiler) 8 | * C runtime library distributed with Clang [https://github.com/everx-labs/TON-Compiler/blob/master/stdlib/stdlib_c.tvm](https://github.com/everx-labs/TON-Compiler/blob/master/stdlib/stdlib_c.tvm) 9 | * TON SDK for C distributed with Clang [https://github.com/everx-labs/TON-Compiler/tree/master/stdlib/ton-sdk](https://github.com/everx-labs/TON-Compiler/tree/master/stdlib/ton-sdk) 10 | * ABI parser tool distributed with Clang [https://github.com/everx-labs/TON-Compiler/blob/master/stdlib/abi_parser.py](https://github.com/everx-labs/TON-Compiler/blob/master/stdlib/abi_parser.py) 11 | * Assembler & linker tool, tvm_linker; currently available as a binary in Node SE. 12 | 13 | To deploy contracts in testnet, you also need Lite Client tool available at [http://test.ton.org](http://test.ton.org) 14 | 15 | ## Example of usage 16 | 17 | Check our [youtube tutorial](https://www.youtube.com/watch?v=Srfor1s1eLM). 18 | 19 | We use example-4-piggybank to show the building process. 20 | It is assumed that tools are located in the 'PATH'. 21 | 1. Generate wrappers and headers from contracts' ABI. 22 | ``` 23 | cd example-4-piggybank 24 | abi_parser.py piggybank 25 | ``` 26 | 27 | 2. Compile the contract sources. 28 | ``` 29 | clang -target tvm -S -O3 *.c -I/path/to/stdlib 30 | ``` 31 | 32 | If Clang fails to recognize the TVM target, make sure that you are using Clang for TVM, not the system Clang. 33 | 34 | 3. Compile the SDK sources. 35 | ``` 36 | clang -target tvm -S -O3 /path/to/ton-std/*.c -I/path/to/stdlib 37 | ``` 38 | 39 | 4. Merge the assemblies together ('tvm_linker' does not support multiple inputs now). 40 | ``` 41 | cat *.s > piggybank.combined.s 42 | ``` 43 | 44 | 5. Assemble and link the contract. 45 | ``` 46 | tvm_linker compile piggybank.combined.s --abi-json piggybank.abi --lib /path/to/stdlib_c.tvm 47 | ``` 48 | 49 | The linker produces a .tvc file. Its name corresponds to the contract address. 50 | Test the contract locally. 51 | You can invoke a public function specified in the ABI and see the output using 'tvm_linker' tool: 52 | ``` 53 | tvm_linker test --abi-json piggybank.abi --abi-method initialize_target --abi-params "{\"target\":\"100\"}" 54 | ``` 55 | 56 | To learn more about ABI, refer to [https://docs.ton.dev/86757ecb2/p/15062d](https://docs.ton.dev/86757ecb2/p/15062d) 57 | 58 | ## Contract deployment 59 | Contract deploy guidelines do not depend on the language. You can use the one at [https://github.com/everx-labs/samples/tree/master/solidity](https://github.com/everx-labs/samples/tree/master/solidity). 60 | 61 | ## C language limitations and performance issues 62 | * Each contract has a limited gas supply; once it is exceeded, a contract terminates with an error. 63 | * TVM does not use float point arithmetic, so float point operations result in an error. 64 | * Contrary to the C specification, unsigned integer overflow can be expected causing an exception. 65 | * TVM has a stack, but no memory. Currently it is emulated via dictionaries in the runtime. Accessing dictionaries consumes a lot of gas, so we strongly discourage the use of globals and getting variable addresses. 66 | * TVM uses 257-bits wide SMR numbers representation. All numeric types are 257-bit wide, and 1 byte = 257 bit. So we suggest making no assumptions about behavior of implementation-defined features of C and C++. 67 | 68 | ## Unsupported features 69 | * C and C++ standard libraries (partial support is planned) 70 | * Exception handling 71 | * Pointers to functions and virtual methods 72 | * Free store memory allocation / deallocation 73 | 74 | ## Getting support 75 | Texts, videos and samples illustrating how to use the compiler will soon appear at [https://ton.dev/](https://ton.dev/) and [Youtube channel](https://www.youtube.com/channel/UC9kJ6DKaxSxk6T3lEGdq-Gg). Stay tuned. 76 | You can also get support in [TON Dev Telegram channel](https://t.me/tondev_en). 77 | In case you found a bug, raise an issue in the repository. Please attach the source file, the command to reproduce the failure and your machine description. 78 | -------------------------------------------------------------------------------- /c/example-1-hello-world/README.md: -------------------------------------------------------------------------------- 1 | # Hello, world! 2 | 3 | This contract adds 2 and 2 and returns the sum as an external message. 4 | 5 | ## Methods 6 | 7 | ### Method `compute` 8 | #### Input values 9 | None 10 | #### Output value 11 | * ABI type: `uint64` 12 | * C type: `unsigned` 13 | * Value description: 4 (sum of 2 and 2) 14 | #### Notes 15 | To call this method, send a message to the contract. 16 | All messages are built by `tvm_linker`. 17 | To build a message, invoke the following command: 18 | 19 | tvm_linker message -w 0 --abi_file hello_world.abi 20 | --abi_method compute --abi_params "{}" 21 | 22 | The parameters of the command have the following meaning: 23 | 24 | `` stands for the contract account number. Thos 256-bit number in hex format, 25 | file `.tvc` with the compiled contract generated by the compiler at deploy; it has to be stored in the current directory. 26 | 27 | * `-w` parameter specifies the message workchain number. It should be 0. 28 | 29 | * `--abi_file` parameter should point to ABI interface of the contract. If you are 30 | working in the `hello_world_build.c` subdirectory , it should point to the parent catalog: 31 | `--abi_file ../hello_world.abi`. 32 | 33 | * `--abi_method` is used to specify the name of the method to be called. In this example 34 | it is `compute`. 35 | 36 | * `--abi_params` is used to specify parameters for the method. Since the `compute` method has no 37 | parameters, empty curve braces are specified here. The braces should be 38 | quoted to be properly understood by shell. 39 | 40 | After the command is called, an `xxx_msg_body.boc` file is created with the compiled 41 | message. This file is to be sent to the node. 42 | 43 | ## Persistent data 44 | 45 | None 46 | -------------------------------------------------------------------------------- /c/example-1-hello-world/hello_world.abi: -------------------------------------------------------------------------------- 1 | { 2 | "ABI version" : 1, 3 | "functions" : [{ 4 | "name": "compute", 5 | "inputs": [], 6 | "outputs": [{ "name": "result", "type": "uint64" }] 7 | }, 8 | { 9 | "name": "constructor", 10 | "inputs": [], 11 | "outputs": [] 12 | }] 13 | } 14 | -------------------------------------------------------------------------------- /c/example-1-hello-world/hello_world.c: -------------------------------------------------------------------------------- 1 | // hello_world.h file is automatically generated according to ABI each 2 | // time before compilation by deploy script. 3 | #include "hello_world.h" 4 | 5 | // Function "compute" is a public method -- it is declared in ABI, and its 6 | // name is fixed in an external interface to the contract. However, public 7 | // methods calling conventions are quite different from C conventions, 8 | // they are defined in toolchain and ABI docs. For example, they accept a 9 | // slice instead of values on stack, the arguments must be deserialized by 10 | // special library funcitons, and so on. 11 | // 12 | // So, the public functions are defined in hello_world_wrapper.c file, 13 | // auto generated by the toolchain. These functions are wrappers around 14 | // real contract functions and along with implementation calls contain code 15 | // for parameter deserialization and result serialization. 16 | // 17 | // The contract programmer deals with implementation functions only (those 18 | // with suffix _Impl added to their names). These functions should be 19 | // defined according to ABI definitions, their prototypes are declared in 20 | // auto-generated hello_world.h file. 21 | unsigned compute_Impl () { 22 | tvm_accept(); 23 | 24 | // The return value is packed into an external message and sent at the exit 25 | // from the method. Unfortunately, now blockchain does not have instruments 26 | // to see it. 27 | return 2 + 2; 28 | } 29 | 30 | // Contract constructor implementation 31 | void constructor_Impl () { 32 | tvm_accept(); 33 | } 34 | -------------------------------------------------------------------------------- /c/example-10-setcode/README: -------------------------------------------------------------------------------- 1 | Original code of a contract can be replaced by the SETCODE instruction. 2 | 3 | The intended sequence of actions to demonstrate code replacement: 4 | 1. Deploy setcode.c contract. 5 | 2. Call setcode() method passing binary code of setcode_new.c as parameter. 6 | 3. Observe the value computed by run() method has changed. 7 | -------------------------------------------------------------------------------- /c/example-10-setcode/setcode.abi: -------------------------------------------------------------------------------- 1 | { 2 | "ABI version": 1, 3 | "functions": [ 4 | { "name": "constructor", "inputs": [], "outputs": [] }, 5 | { "name": "run", "inputs": [], "outputs": [{ "name": "computed", "type": "uint32" }] }, 6 | { "name": "setcode", "inputs": [{ "name": "code", "type": "cell" }], "outputs": [] } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /c/example-10-setcode/setcode.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | volatile int timestamp_persistent; 4 | 5 | void constructor_Impl() { 6 | tvm_assert(timestamp_persistent == 0, 100); 7 | __builtin_tvm_accept(); 8 | timestamp_persistent = __builtin_tvm_getglobal(12); 9 | } 10 | 11 | void default_replay_protect() { 12 | int timestamp = __builtin_tvm_getglobal(12); 13 | tvm_assert(timestamp_persistent < timestamp, 101); 14 | __builtin_tvm_accept(); 15 | timestamp_persistent = timestamp; 16 | } 17 | 18 | int run_Impl() { 19 | default_replay_protect(); 20 | return 0xbeefcafe; 21 | } 22 | 23 | void setcode_Impl(__tvm_cell c) { 24 | default_replay_protect(); 25 | __builtin_tvm_setcode(c); 26 | } 27 | -------------------------------------------------------------------------------- /c/example-10-setcode/setcode_new.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | volatile int timestamp_persistent; 4 | 5 | void constructor_Impl() { 6 | tvm_assert(timestamp_persistent == 0, 100); 7 | __builtin_tvm_accept(); 8 | timestamp_persistent = __builtin_tvm_getglobal(12); 9 | } 10 | 11 | void default_replay_protect() { 12 | int timestamp = __builtin_tvm_getglobal(12); 13 | tvm_assert(timestamp_persistent < timestamp, 101); 14 | __builtin_tvm_accept(); 15 | timestamp_persistent = timestamp; 16 | } 17 | 18 | int run_Impl() { 19 | default_replay_protect(); 20 | return 0xfeedc0de; 21 | } 22 | 23 | void setcode_Impl(__tvm_cell c) { 24 | default_replay_protect(); 25 | __builtin_tvm_setcode(c); 26 | } 27 | -------------------------------------------------------------------------------- /c/example-2-echo/README.md: -------------------------------------------------------------------------------- 1 | # Echo contract 2 | 3 | This contract just keeps the last input value in a persistent variable, 4 | so one may check the results of `compute` method invocation in account 5 | state by analyzing `getaccount` command output. 6 | 7 | This is the second contract in the samples, look into `hello_world` 8 | contract source code and readme for additional info. 9 | 10 | ## Methods 11 | 12 | ### Method `compute` 13 | #### Input values 14 | ##### Argument `x` 15 | * ABI type: `uint64` 16 | * C type: `unsigned` 17 | * Description: value to be copied into persistent memory and 18 | returned in external message. 19 | 20 | #### Output values 21 | * ABI type: `uint64` 22 | * C type: `unsigned` 23 | * Description: equal to the input value `x`. 24 | 25 | #### Notes 26 | This method has input value, so `--abi-params` argument should be specified 27 | differently (in comparison with `tvm_linker` invocation for `compute` method 28 | in `hello_world` contract): 29 | 30 | ... --abi-params "{\"x\":}" ... 31 | 32 | Here, `` is the value for the field — an integer number in decimal format. 33 | The field name is specified in screened quotes to be properly transferred to linker. 34 | 35 | ## Persistent data 36 | ### Field `x_persistent` 37 | * C type: `unsigned` 38 | * Description: keeps the argument, passed to the last 39 | invocation of `compute`. 40 | -------------------------------------------------------------------------------- /c/example-2-echo/echo.abi: -------------------------------------------------------------------------------- 1 | { 2 | "ABI version" : 1, 3 | "functions" : [{ 4 | "name": "compute", 5 | "inputs": [{ "name": "x", "type": "uint64" }], 6 | "outputs": [{ "name": "result", "type": "uint64" }] 7 | }, 8 | { 9 | "name": "constructor", 10 | "inputs": [], 11 | "outputs": [] 12 | }] 13 | } 14 | -------------------------------------------------------------------------------- /c/example-2-echo/echo.c: -------------------------------------------------------------------------------- 1 | #include "echo.h" 2 | 3 | // Variable x_persistent has _persistent suffix. This suffix instructs the 4 | // compiler to keep the variable value in blockchain between contract 5 | // methods invocations. Initialization takes place at deploy. 6 | int x_persistent = 0; 7 | 8 | // This method is declared in the ABI as receiving and returning uint64 value. 9 | // However, all values are stored as 256-bit variables in TON VM. 10 | // conversion from unit64 to 256-bit unsigned integer 11 | // and back is carried out in wrapper by the compute() function. 12 | unsigned compute_Impl (unsigned x) { 13 | tvm_accept(); 14 | // The function argument is copied into persistent storage and 15 | // contents of x_persistent can be analyzed manually after the method 16 | // invocation. 17 | x_persistent = x; 18 | 19 | // Except for their save/restore semantics, persistent variables are not 20 | // different from other variables. 21 | return x_persistent; 22 | } 23 | 24 | // Contract constructor implementation. 25 | void constructor_Impl () { 26 | tvm_accept(); 27 | } 28 | -------------------------------------------------------------------------------- /c/example-3-transfer-80000001/README.md: -------------------------------------------------------------------------------- 1 | # Transfer 80000001 2 | 3 | This contract sends 0xAAAA nanograms (43960 in decimal) to 0x80000001 account number hardcoded in the contract. 4 | This contract shows how to create and send internal messages. 5 | 6 | ## Methods 7 | 8 | ### Method `compute` 9 | #### Input values 10 | None 11 | 12 | #### Output values 13 | None 14 | 15 | #### Notes 16 | This method does not return any values, so it does not send external 17 | messages. However, it sends an internal message to 0x80000001 account. 18 | In order to properly test its execution, one can check balance of 19 | 0x80000001 account before and after the method invocation. 20 | 21 | When the method is called for the first time for a specific node, the 0x80000001 account is likely to be nonexistent. In this case this message creates the account with about 0xAAAA nanograms 22 | on the balance (a bit less, since message transfer is not free). 23 | 24 | To check that, use the `getaccount` command of test-lite-client (unluckily, 25 | you cannot skip these 56 leading zeroes): 26 | 27 | getaccount 0:0000000000000000000000000000000000000000000000000000000080000001 28 | 29 | ## Persistent data 30 | None 31 | -------------------------------------------------------------------------------- /c/example-3-transfer-80000001/transfer_80000001.abi: -------------------------------------------------------------------------------- 1 | { 2 | "ABI version": 1, 3 | "functions": [{ 4 | "name": "transfer", 5 | "inputs": [], 6 | "outputs": [] 7 | }, 8 | { 9 | "name": "constructor", 10 | "inputs": [], 11 | "outputs": [] 12 | }] 13 | } 14 | -------------------------------------------------------------------------------- /c/example-3-transfer-80000001/transfer_80000001.c: -------------------------------------------------------------------------------- 1 | #include "transfer_80000001.h" 2 | #include "ton-sdk/tvm.h" 3 | #include "ton-sdk/messages.h" 4 | 5 | void transfer_Impl () { 6 | tvm_accept(); 7 | 8 | // MsgAddressInt structure corresponds to the structure of the same name 9 | // from TON blockchain document. 10 | MsgAddressInt dest; 11 | 12 | // Anycast messages are not supported in current C SDK, so specify 0 in 13 | // this field. 14 | dest.anycast = 0; 15 | 16 | // We work with workchain 0. 17 | dest.workchain_id = 0; 18 | 19 | // Destination address: unsigned ints in TVM are 256-bits, so a contract 20 | // address easily fits into unsigned. 21 | dest.address = 0x80000001; 22 | 23 | // Call library function that builds an empty internal message (internal 24 | // message with empty payload). Note that the MsgAddressInt structure is 25 | // passed by pointer. The message is built in a working slice stored 26 | // deep in the standard library. You cannot work with its value directly, 27 | // but you can serialize values to it and deserialize values from it. 28 | // 29 | // The build_internal_message function in its turn empties the working 30 | // slice, and then performs several serializations, thus preparing 31 | // an internal message. 32 | build_internal_message (&dest, 0xAAAA); 33 | 34 | // Call library function that sends a message generated in a work slice. 35 | send_raw_message (MSG_PAY_FEES_SEPARATELY); 36 | } 37 | 38 | void constructor_Impl () { 39 | tvm_accept(); 40 | } 41 | -------------------------------------------------------------------------------- /c/example-4-piggybank/README.md: -------------------------------------------------------------------------------- 1 | # Piggybank 2 | 3 | This contract has two methods. The first method (`initialize_target`) 4 | specifies the target amount of money that should be collected by 5 | the contract. 6 | 7 | The second method (`transfer`) transfers almost all the money from 8 | the account (reduced by fixed `MESSAGE_COST` amount, a fixed constant in 9 | the contract C file) when the contract accumulates the target amount of money or more. 10 | 11 | ## Methods 12 | 13 | ### Method `initialize_target` 14 | #### Input values 15 | ##### Argument `target` 16 | * ABI type: `uint64` 17 | * C type: `unsigned` 18 | * Description: specifies target amount of money. 19 | 20 | #### Output values 21 | None 22 | 23 | #### Notes 24 | This method initializes the contract with the target amount of money. 25 | The method may be called again with a greater target amount to 26 | increase it. However, if the new target amount is smaller than the 27 | previous one, the method will fail. 28 | 29 | ### Method `transfer` 30 | #### Input values 31 | ##### Argument `destination_account` 32 | * ABI type: `uint256` 33 | * C type: `unsigned` 34 | * Description: specifies the account for funds transfer. 35 | 36 | #### Output values 37 | None 38 | 39 | #### Notes 40 | This method transfers all funds from the contract (minus technical `MESSAGE_COST`, 41 | needed to pay for the money transfer and the contract execution) to the specified 42 | destination account. 43 | 44 | ## Persistent data 45 | ### Field `target_persistent` 46 | * C type: `unsigned` 47 | * Description: keeps the target amount of money. This amount may not be smaller than 48 | `MESSAGE_COST`. 49 | -------------------------------------------------------------------------------- /c/example-4-piggybank/piggybank.abi: -------------------------------------------------------------------------------- 1 | { 2 | "ABI version": 1, 3 | "functions": [{ 4 | "name": "initialize_target", 5 | "inputs": [{"name":"target", "type":"uint64"}], 6 | "outputs": [] 7 | }, 8 | { 9 | "name": "transfer", 10 | "inputs": [{"name":"destination_account", "type":"uint256"}], 11 | "outputs": [] 12 | }, 13 | { 14 | "name": "constructor", 15 | "inputs": [], 16 | "outputs": [] 17 | }] 18 | } 19 | -------------------------------------------------------------------------------- /c/example-4-piggybank/piggybank.c: -------------------------------------------------------------------------------- 1 | #include "ton-sdk/tvm.h" 2 | #include "ton-sdk/messages.h" 3 | #include "ton-sdk/smart-contract-info.h" 4 | 5 | // Custom exception declaration 6 | 7 | // This exception is thrown when the account is required to 8 | // transfer money before the target amount is accumulated. 9 | TVM_CUSTOM_EXCEPTION (NOT_ENOUGH_MONEY, 61); 10 | 11 | // Target amount initialization may be performed only once: 12 | // otherwise one could easily evade "target" amount limitation. 13 | TVM_CUSTOM_EXCEPTION (ALREADY_INITIALIZED, 62); 14 | 15 | enum { MESSAGE_COST = 10000000 }; 16 | 17 | // Target amount of money: piggybank ensures that the contract 18 | // owner accumulates this amount before they can spend the 19 | // money. 20 | int target_persistent = MESSAGE_COST; 21 | 22 | void constructor_Impl () { 23 | tvm_accept(); 24 | } 25 | 26 | // Used to specify the target amount of money in nanograms. 27 | // The money cannot be decreased: if method initialize_limit 28 | // was invoked with a larger value, its re-initialization with 29 | // smaller value results in error. 30 | void initialize_target_Impl (unsigned target) { 31 | tvm_assert (target_persistent > target, ALREADY_INITIALIZED); 32 | tvm_accept(); 33 | target_persistent = target; 34 | } 35 | 36 | // Main piggybank method: withdraw all money from the piggybank 37 | // account. Can be activated if the account accumulated the 38 | // "target" amount of money. If not, the method throws 39 | // NOT_ENOUGH_MONEY exception. 40 | void transfer_Impl (unsigned destination_account) { 41 | tvm_accept(); 42 | // Check that we have collected enough money to transfer it. 43 | // Info about the current account state (including current contract 44 | // gram balance) is stored in SmartContractInfo structure - 45 | // see TON blockchain docs for more info. 46 | SmartContractInfo sc_info = get_SmartContractInfo(); 47 | int balance = sc_info.balance_remaining.grams.amount.value; 48 | tvm_assert (balance >= target_persistent, NOT_ENOUGH_MONEY); 49 | 50 | //Once we collected enough money and can retrieve 51 | // it! In this case, send the message to the contract to 52 | // transfer the money to the destination address. 53 | MsgAddressInt destination = 54 | build_msg_address_int (0, destination_account); 55 | build_internal_message (&destination, balance - MESSAGE_COST); 56 | send_raw_message (MSG_PAY_FEES_SEPARATELY); 57 | } 58 | -------------------------------------------------------------------------------- /c/example-5-wallet/README.md: -------------------------------------------------------------------------------- 1 | # Wallet 2 | 3 | This contract has two methods. The first method (`constructor`) 4 | has no formal parameters, but gets public signature of the message (using 5 | tvm_sender_pubkey() function), and stores it in a persistent variable. 6 | 7 | The second method (`sendTransaction`) transfers the specified 8 | amount of money from the account to another account. 9 | 10 | This contract demonstrates work with public signatures of the messages; 11 | also this is a direct analog of Wallet contract in Solidity. 12 | 13 | ## Methods 14 | 15 | ### Method `constructor` 16 | #### Input values 17 | None 18 | 19 | #### Output values 20 | None 21 | 22 | #### Notes 23 | This method initializes the contract with the public signature of the 24 | message: if the signature is absent (equals to zero), the method throws 25 | an exception. Otherwise, the signature owner becomes the owner of the 26 | wallet. 27 | 28 | If the contract is already initialized (owner_persistent field is 29 | not zero, that is populated), the method throws an exception. 30 | 31 | ### Method `sendTransaction` 32 | #### Input values 33 | ##### Argument `dest` 34 | * ABI type: `uint256` 35 | * C type: `unsigned` 36 | * Description: specifies destination account for funds transfer 37 | 38 | ##### Argument `value` 39 | * ABI type: `uint128` 40 | * C type: `unsigned` 41 | * Description: money (in nanograms) to be transferred 42 | 43 | ##### Argument `bounce` 44 | * ABI type: `uint1` 45 | * C type: `unsigned` 46 | * Description: flags that the transfer message should bounce, if the 47 | destination account does not exist. 48 | 49 | #### Output values 50 | None 51 | 52 | #### Notes 53 | This method transfers `value` nanograms from the contract to the 54 | `dest` account. Various conditions are checked, among them: 55 | - the account balance should greater than `value`; 56 | - the message should be signed by the wallet owner. 57 | 58 | ## Persistent data 59 | ### Field `owner_persistent` 60 | * C type: `unsigned` 61 | * Description: keeps the contract owner signature. 62 | -------------------------------------------------------------------------------- /c/example-5-wallet/wallet.abi: -------------------------------------------------------------------------------- 1 | { 2 | "ABI version": 1, 3 | "functions": [{ 4 | "name": "constructor", 5 | "inputs": [], 6 | "outputs": [] 7 | }, 8 | { 9 | "name": "sendTransaction", 10 | "inputs": [{"name":"dest", "type":"uint256"}, 11 | {"name":"value", "type":"uint128"}, 12 | {"name":"bounce", "type":"uint1"}], 13 | "outputs": [] 14 | }] 15 | } 16 | -------------------------------------------------------------------------------- /c/example-5-wallet/wallet.c: -------------------------------------------------------------------------------- 1 | #include "ton-sdk/tvm.h" 2 | #include "ton-sdk/messages.h" 3 | #include "ton-sdk/smart-contract-info.h" 4 | 5 | unsigned owner_persistent = 0; 6 | 7 | TVM_CUSTOM_EXCEPTION (MESSAGE_SENDER_NOT_OWNER, 100); 8 | TVM_CUSTOM_EXCEPTION (LIMIT_IS_OVERRUN, 101); 9 | TVM_CUSTOM_EXCEPTION (INVALID_TRANSFER_VALUE, 102); 10 | TVM_CUSTOM_EXCEPTION (DESTINATION_ADDRESS_ZERO, 103); 11 | TVM_CUSTOM_EXCEPTION (MESSAGE_MUST_BE_SIGNED, 104); 12 | TVM_CUSTOM_EXCEPTION (ALREADY_INITIALIZED, 105); 13 | 14 | void constructor_Impl () { 15 | ACCEPT(); 16 | unsigned owner = tvm_sender_pubkey (); 17 | tvm_assert (owner != 0, MESSAGE_MUST_BE_SIGNED); 18 | tvm_assert (owner_persistent == 0, ALREADY_INITIALIZED); 19 | owner_persistent = owner; 20 | } 21 | 22 | void sendTransaction_Impl (unsigned dest, unsigned value, unsigned bounce) { 23 | ACCEPT(); 24 | 25 | SmartContractInfo sc_info = get_SmartContractInfo(); 26 | int balance = sc_info.balance_remaining.grams.amount.value; 27 | tvm_assert (value > 0 && value < balance, INVALID_TRANSFER_VALUE); 28 | 29 | tvm_assert (tvm_sender_pubkey() != 0, MESSAGE_MUST_BE_SIGNED); 30 | tvm_assert (tvm_sender_pubkey() == owner_persistent, MESSAGE_SENDER_NOT_OWNER); 31 | tvm_assert (dest != 0, DESTINATION_ADDRESS_ZERO); 32 | 33 | MsgAddressInt destination = build_msg_address_int (0, dest); 34 | build_internal_message_bounce (&destination, value, bounce); 35 | send_raw_message (0); 36 | } 37 | -------------------------------------------------------------------------------- /c/example-6-message-exchange/README.md: -------------------------------------------------------------------------------- 1 | # Remote procedure call 2 | 3 | There are two contracts in this subdirectory - the sender and the receiver. 4 | The sender has a method `sendTo` to call a method remoteMethod of the contract specified by the given address. 5 | The receiver accepts the incoming message and stores the incoming value in value_persistent. 6 | 7 | ## Methods 8 | 9 | ### Method `sendTo` (`sender.c`) 10 | #### Input values 11 | ##### Argument `remoteContractAddress` 12 | * ABI type: `address`. 13 | * C type: `MsgAddressInt`. 14 | * Description: specifies the address of the contract to call. 15 | 16 | #### Output values 17 | None 18 | 19 | #### Notes 20 | Sends a message with value `42` encoded in body the public method `remoteProcedure` of the contract with a given address. 21 | It's assumed that `remoteProcedure` takes a single uint64 value. 22 | 23 | ### Method `remoteProcedure` (`reciever.c`). 24 | #### Input values 25 | ##### Argument `value` 26 | * ABI type: `uint64` 27 | * C type: `unsigned` 28 | * Description: the incoming value. 29 | 30 | #### Output values 31 | None 32 | 33 | #### Notes 34 | Store the incoming value in value_persistent. 35 | 36 | ## Persistent data 37 | ### Field `value_persistent` (`callee.c`) 38 | * C type: `unsigned` 39 | * Description: the value received from the caller. 40 | -------------------------------------------------------------------------------- /c/example-6-message-exchange/receiver.abi: -------------------------------------------------------------------------------- 1 | { 2 | "ABI version": 1, 3 | "functions": [ 4 | { 5 | "name": "remoteProcedure", 6 | "inputs": [{"name":"value","type":"uint64"}], 7 | "outputs": [] 8 | }, 9 | { 10 | "name": "getValue", 11 | "inputs": [], 12 | "outputs": [{ "name": "result", "type": "uint64" }] 13 | }, 14 | { 15 | "name": "getCaller", 16 | "inputs": [], 17 | "outputs": [{ "name": "result", "type": "uint256" }] 18 | }, 19 | { 20 | "name": "constructor", 21 | "inputs": [], 22 | "outputs": [] 23 | } 24 | ], 25 | "events": [], 26 | "data": [] 27 | } 28 | -------------------------------------------------------------------------------- /c/example-6-message-exchange/receiver.c: -------------------------------------------------------------------------------- 1 | #include "ton-sdk/tvm.h" 2 | #include "ton-sdk/messages.h" 3 | #include "ton-sdk/smart-contract-info.h" 4 | 5 | unsigned value_persistent = 0; 6 | unsigned caller_persistent = 77; 7 | 8 | // Implementation of the contract's constructor. 9 | void constructor_Impl () { 10 | tvm_accept(); 11 | } 12 | 13 | void remoteProcedure_Impl(unsigned value) { 14 | tvm_accept(); 15 | value_persistent = value; 16 | caller_persistent = get_sender_address().address; 17 | } 18 | 19 | unsigned getValue_Impl() { 20 | tvm_accept(); 21 | return value_persistent; 22 | } 23 | 24 | unsigned getCaller_Impl() { 25 | tvm_accept(); 26 | return caller_persistent; 27 | } 28 | -------------------------------------------------------------------------------- /c/example-6-message-exchange/sender.abi: -------------------------------------------------------------------------------- 1 | { 2 | "ABI version": 1, 3 | "functions": [ 4 | { 5 | "name": "sendTo", 6 | "inputs": [ 7 | {"name":"remoteContractAddress","type":"address"} 8 | ], 9 | "outputs": [] 10 | }, 11 | { 12 | "name": "remoteProcedure", 13 | "inputs": [ 14 | {"name":"value","type":"uint64"} 15 | ], 16 | "outputs": [] 17 | }, 18 | { 19 | "name": "constructor", 20 | "inputs": [], 21 | "outputs": [] 22 | } 23 | ], 24 | "events": [], 25 | "data": [] 26 | } 27 | -------------------------------------------------------------------------------- /c/example-6-message-exchange/sender.c: -------------------------------------------------------------------------------- 1 | #include "ton-sdk/tvm.h" 2 | #include "ton-sdk/messages.h" 3 | #include "ton-sdk/smart-contract-info.h" 4 | 5 | #include "sender.h" 6 | 7 | #define TON_STRUCT_NAME Message_uint64 8 | #define TON_STRUCT \ 9 | FIELD_UNSIGNED(rpaddr, 32) \ 10 | FIELD_UNSIGNED(value, 64) 11 | #include "ton-sdk/define-ton-struct-header.inc" 12 | #define TON_STRUCT_NAME Message_uint64 13 | #define TON_STRUCT \ 14 | FIELD_UNSIGNED(rpaddr, 32) \ 15 | FIELD_UNSIGNED(value, 64) 16 | #include "ton-sdk/define-ton-struct-c.inc" 17 | 18 | void remoteProcedure(); 19 | void remoteProcedure_Impl(unsigned value) {} 20 | 21 | void build_internal_uint64_message (MsgAddressInt* dest, unsigned balance, unsigned data) { 22 | build_internal_message_bounce(dest, balance, 0); 23 | Message_uint64 msg = {(unsigned) &remoteProcedure, data}; 24 | __tvm_builder builder = __builtin_tvm_cast_to_builder(__builtin_tvm_getglobal(6)); 25 | Serialize_Message_uint64_Impl(&builder, &msg); 26 | __builtin_tvm_setglobal(6, __builtin_tvm_cast_from_builder(builder)); 27 | } 28 | 29 | // Number of remote procedure calls. 30 | int numCalls_persistent; 31 | 32 | // Implementation of the contract's constructor. 33 | void constructor_Impl () { 34 | tvm_accept(); 35 | } 36 | 37 | void sendTo_Impl(MsgAddressInt remoteContractAddr) { 38 | tvm_accept(); 39 | // increment the counter 40 | numCalls_persistent++; 41 | // call remote contract function with parameter 42 | build_internal_uint64_message (&remoteContractAddr, 100000000, 42); 43 | send_raw_message (MSG_PAY_FEES_SEPARATELY); 44 | } 45 | -------------------------------------------------------------------------------- /c/example-7-loan/README: -------------------------------------------------------------------------------- 1 | The bank contract keeps per-client information (available credit) 2 | in the map_persistent dictionary. 3 | 4 | A client calls the bank to get a credit. 5 | 6 | Interaction sequence: 7 | 1. Setting up the bank: 8 | - call constructor, 9 | - call set_allowance method for each client address. 10 | 2. Running client contract(s): 11 | - call get_my_credit method passing the bank address, 12 | - expect a credit_persistent update after the bank calls back. 13 | -------------------------------------------------------------------------------- /c/example-7-loan/bank.abi: -------------------------------------------------------------------------------- 1 | { 2 | "ABI version": 1, 3 | "functions": [{ 4 | "name": "constructor", 5 | "inputs": [], 6 | "outputs": [] 7 | }, 8 | { 9 | "name": "set_allowance", 10 | "inputs": [{ "name": "client", "type": "address" }, 11 | { "name": "amount", "type": "uint64" }], 12 | "outputs": [] 13 | }, 14 | { 15 | "name": "get_credit", 16 | "inputs": [], 17 | "outputs": [] 18 | }, 19 | { 20 | "name": "get_credit_callback", 21 | "inputs": [{ "name": "balance", "type": "uint64" }], 22 | "outputs": [] 23 | }] 24 | } 25 | -------------------------------------------------------------------------------- /c/example-7-loan/bank.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | // Declaring client's remote method 4 | extern void get_credit_callback(int); 5 | void get_credit_callback_Impl(int balance) { } 6 | 7 | __tvm_cell map_persistent; 8 | 9 | void constructor_Impl() { 10 | tvm_accept(); 11 | 12 | // Construct empty dict 13 | map_persistent = __builtin_tvm_endc(__builtin_tvm_stdict( 14 | __builtin_tvm_newdict(), __builtin_tvm_newc())); 15 | } 16 | 17 | void set_allowance_Impl(MsgAddressInt addr, int amount) { 18 | tvm_accept(); 19 | 20 | // Wrap amount into slice s 21 | __tvm_builder b = __builtin_tvm_newc(); 22 | b = __builtin_tvm_sti(amount, b, 64); 23 | __tvm_cell c = __builtin_tvm_endc(b); 24 | __tvm_slice s = __builtin_tvm_ctos(c); 25 | 26 | // Load dict d from persistent memory 27 | __tvm_cell d = __builtin_tvm_plddict(__builtin_tvm_ctos(map_persistent)); 28 | // Update it with (addr -> s) pair 29 | d = __builtin_tvm_dictuset(s, addr.address, d, 256); 30 | // Store dict back 31 | map_persistent = 32 | __builtin_tvm_endc(__builtin_tvm_stdict(d, __builtin_tvm_newc())); 33 | } 34 | 35 | TVM_CUSTOM_EXCEPTION(INVALID_ADDRESS, 101); 36 | 37 | void get_credit_Impl() { 38 | tvm_accept(); 39 | 40 | MsgAddressInt sender = get_sender_address(); 41 | tvm_assert(sender.anycast == 0, INVALID_ADDRESS); 42 | tvm_assert(sender.workchain_id == 0, INVALID_ADDRESS); 43 | 44 | // Load dict d from persistent memory 45 | __tvm_cell d = __builtin_tvm_plddict(__builtin_tvm_ctos(map_persistent)); 46 | 47 | // Try getting value by the address key 48 | struct __attribute__((tvm_tuple)) { __tvm_slice s; int found; } st = 49 | __builtin_tvm_dictuget(sender.address, d, 256); 50 | // Bail out if key doesn't exist 51 | tvm_assert(st.found, INVALID_ADDRESS); 52 | 53 | // Unwrap amount 54 | int amount = __builtin_tvm_pldu(st.s, 64); 55 | 56 | send_message(sender, (unsigned)get_credit_callback, amount); 57 | } 58 | -------------------------------------------------------------------------------- /c/example-7-loan/client.abi: -------------------------------------------------------------------------------- 1 | { 2 | "ABI version": 1, 3 | "functions": [{ 4 | "name": "constructor", 5 | "inputs": [], 6 | "outputs": [] 7 | }, 8 | { 9 | "name": "get_my_credit", 10 | "inputs": [{ "name": "bank", "type": "address" }], 11 | "outputs": [] 12 | }, 13 | { 14 | "name": "get_credit_callback", 15 | "inputs": [{ "name": "balance", "type": "uint64" }], 16 | "outputs": [] 17 | }, 18 | { 19 | "name": "get_credit", "inputs": [], "outputs": [] 20 | }] 21 | } 22 | -------------------------------------------------------------------------------- /c/example-7-loan/client.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | // Declaring bank's remote method 4 | extern void get_credit(); 5 | void get_credit_Impl() { } 6 | 7 | volatile int credit_persistent; 8 | 9 | void constructor_Impl() { 10 | tvm_accept(); 11 | } 12 | 13 | // A client calls this method, 14 | void get_my_credit_Impl(MsgAddressInt bank) { 15 | tvm_accept(); 16 | send_message(bank, (unsigned)get_credit, 0); 17 | } 18 | 19 | // and a bank calls this one in response. 20 | void get_credit_callback_Impl(unsigned balance) { 21 | tvm_accept(); 22 | credit_persistent = balance; 23 | } 24 | -------------------------------------------------------------------------------- /c/example-7-loan/common.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | #define TON_STRUCT_NAME Message_uint64 4 | #define TON_STRUCT \ 5 | FIELD_CONSTANT_UNSIGNED(zero, 0, 1) \ 6 | FIELD_UNSIGNED(ihr_disabled, 1) \ 7 | FIELD_UNSIGNED(bounce, 1) \ 8 | FIELD_UNSIGNED(bounced, 1) \ 9 | FIELD_COMPLEX(src, MsgAddressInt) \ 10 | FIELD_COMPLEX(dst, MsgAddressInt) \ 11 | FIELD_COMPLEX(val, CurrencyCollection) \ 12 | FIELD_COMPLEX(ihr_fee, Grams) \ 13 | FIELD_COMPLEX(fwd_fee, Grams) \ 14 | FIELD_UNSIGNED(created_lt, 64) \ 15 | FIELD_UNSIGNED(created_at, 32) \ 16 | FIELD_UNSIGNED(init, 1) \ 17 | FIELD_UNSIGNED(body, 1) \ 18 | FIELD_UNSIGNED(rpaddr, 32) \ 19 | FIELD_UNSIGNED(value, 64) 20 | #include "ton-sdk/define-ton-struct-c.inc" 21 | 22 | #undef TON_STRUCT_NAME 23 | #undef TON_STRUCT 24 | 25 | void send_message(MsgAddressInt dest, unsigned rpaddr, unsigned value) { 26 | const unsigned fee = 10000000; 27 | Grams val_grams; 28 | val_grams.amount.len = tonstdlib_ubytesize(fee); 29 | val_grams.amount.value = fee; 30 | 31 | CurrencyCollection val; 32 | val.grams = val_grams; 33 | val.other = 0; 34 | 35 | Grams zero_grams; 36 | zero_grams.amount.len = 0; 37 | zero_grams.amount.value = 0; 38 | 39 | Message_uint64 msg; 40 | msg.ihr_disabled = 0; 41 | msg.bounce = 0; 42 | msg.bounced = 0; 43 | msg.src = dest; 44 | msg.dst = dest; 45 | msg.val = val; 46 | msg.ihr_fee = zero_grams; 47 | msg.fwd_fee = zero_grams; 48 | msg.created_lt = 0; 49 | msg.created_at = 0; 50 | 51 | msg.init = 0; 52 | msg.body = 0; 53 | msg.rpaddr = rpaddr; 54 | msg.value = value; 55 | 56 | Serialize_Message_uint64(&msg); 57 | send_raw_message(MSG_NO_FLAGS); 58 | } 59 | -------------------------------------------------------------------------------- /c/example-7-loan/common.h: -------------------------------------------------------------------------------- 1 | #include "ton-sdk/tvm.h" 2 | #include "ton-sdk/messages.h" 3 | #include "ton-sdk/smart-contract-info.h" 4 | 5 | #define TON_STRUCT_NAME Message_uint64 6 | #define TON_STRUCT \ 7 | FIELD_CONSTANT_UNSIGNED(zero, 0, 1) \ 8 | FIELD_UNSIGNED(ihr_disabled, 1) \ 9 | FIELD_UNSIGNED(bounce, 1) \ 10 | FIELD_UNSIGNED(bounced, 1) \ 11 | FIELD_COMPLEX(src, MsgAddressInt) \ 12 | FIELD_COMPLEX(dst, MsgAddressInt) \ 13 | FIELD_COMPLEX(val, CurrencyCollection) \ 14 | FIELD_COMPLEX(ihr_fee, Grams) \ 15 | FIELD_COMPLEX(fwd_fee, Grams) \ 16 | FIELD_UNSIGNED(created_lt, 64) \ 17 | FIELD_UNSIGNED(created_at, 32) \ 18 | FIELD_UNSIGNED(init, 1) \ 19 | FIELD_UNSIGNED(body, 1) \ 20 | FIELD_UNSIGNED(rpaddr, 32) \ 21 | FIELD_UNSIGNED(value, 64) 22 | #include "ton-sdk/define-ton-struct-header.inc" 23 | 24 | #undef TON_STUCT_NAME 25 | #undef TON_STRUCT 26 | 27 | void send_message(MsgAddressInt dest, unsigned rpaddr, unsigned value); 28 | -------------------------------------------------------------------------------- /c/example-8-gramgiver/README.md: -------------------------------------------------------------------------------- 1 | # Gram giver example 2 | 3 | There are two contracts in this repository - the client and the giver. 4 | The client requests grams by calling `requestGrams` method, specifying the address of the giver contract and the amount of grams it needs. 5 | The giver receives the request in `getGrams` method and transfers grams to the sender. 6 | 7 | ## Methods 8 | 9 | ### Method `requestGrams` (`client.c`) 10 | #### Input values 11 | ##### Argument `remoteContractAddress` 12 | * ABI type: `address`. 13 | * C type: `MsgAddressInt`. 14 | * Description: specifies the address of the contract to call. 15 | 16 | ##### Argument `value` 17 | * ABI type: `uint64`. 18 | * C type: `unsigned`. 19 | * Description: number of nanograms to be requested. 20 | 21 | #### Output values 22 | None 23 | 24 | #### Notes 25 | Sends a message with `value` encoded in body the public method `getGrams` of the contract with a given address. 26 | It's assumed that `getGrams` takes a single uint64 value. 27 | 28 | ### Method `getGrams` (`giver.c`). 29 | #### Input values 30 | ##### Argument `value` 31 | * ABI type: `uint64` 32 | * C type: `unsigned` 33 | * Description: number of grams requested. 34 | 35 | #### Output values 36 | None 37 | 38 | #### Notes 39 | Sends `value` nanograms to the address where the incoming message came from. 40 | 41 | ## Persistent data 42 | ### Field `numCalls_persistent` (`client.c`) 43 | * C type: `unsigned` 44 | * Description: number of requests to giver. 45 | -------------------------------------------------------------------------------- /c/example-8-gramgiver/client.abi: -------------------------------------------------------------------------------- 1 | { 2 | "ABI version": 1, 3 | "functions": [ 4 | { 5 | "name": "requestGrams", 6 | "inputs": [ 7 | {"name":"remoteContractAddress","type":"address"}, 8 | {"name":"value", "type":"uint64"} 9 | ], 10 | "outputs": [] 11 | }, 12 | { 13 | "name": "getGrams", 14 | "inputs": [ 15 | {"name":"value","type":"uint64"} 16 | ], 17 | "outputs": [] 18 | }, 19 | { 20 | "name": "constructor", 21 | "inputs": [], 22 | "outputs": [] 23 | } 24 | ], 25 | "events": [], 26 | "data": [] 27 | } 28 | -------------------------------------------------------------------------------- /c/example-8-gramgiver/client.c: -------------------------------------------------------------------------------- 1 | #include "ton-sdk/tvm.h" 2 | #include "ton-sdk/messages.h" 3 | #include "ton-sdk/smart-contract-info.h" 4 | 5 | #include "client.h" 6 | 7 | #define TON_STRUCT_NAME Message_uint64 8 | #define TON_STRUCT \ 9 | FIELD_UNSIGNED(rpaddr, 32) \ 10 | FIELD_UNSIGNED(value, 64) 11 | #include "ton-sdk/define-ton-struct-header.inc" 12 | #define TON_STRUCT_NAME Message_uint64 13 | #define TON_STRUCT \ 14 | FIELD_UNSIGNED(rpaddr, 32) \ 15 | FIELD_UNSIGNED(value, 64) 16 | #include "ton-sdk/define-ton-struct-c.inc" 17 | 18 | void getGrams(); 19 | void getGrams_Impl(unsigned value) {} 20 | 21 | void build_internal_uint64_message (MsgAddressInt* dest, unsigned data) { 22 | build_internal_message_bounce(dest, 0, 0); 23 | Message_uint64 msg = {(unsigned) &getGrams, data}; 24 | __tvm_builder builder = __builtin_tvm_cast_to_builder(__builtin_tvm_getglobal(6)); 25 | Serialize_Message_uint64_Impl(&builder, &msg); 26 | __builtin_tvm_setglobal(6, __builtin_tvm_cast_from_builder(builder)); 27 | } 28 | 29 | // Number of remote procedure calls. 30 | int numCalls_persistent; 31 | 32 | // Implementation of the contract's constructor. 33 | void constructor_Impl () { 34 | tvm_accept(); 35 | } 36 | 37 | void requestGrams_Impl(MsgAddressInt remoteContractAddr, unsigned value) { 38 | tvm_accept(); 39 | // increment the counter 40 | numCalls_persistent++; 41 | // call remote contract function with parameter 42 | build_internal_uint64_message (&remoteContractAddr, value); 43 | send_raw_message (MSG_PAY_FEES_SEPARATELY); 44 | } 45 | -------------------------------------------------------------------------------- /c/example-8-gramgiver/giver.abi: -------------------------------------------------------------------------------- 1 | { 2 | "ABI version": 1, 3 | "functions": [ 4 | { 5 | "name": "getGrams", 6 | "inputs": [{"name":"value","type":"uint64"}], 7 | "outputs": [] 8 | }, 9 | { 10 | "name": "constructor", 11 | "inputs": [], 12 | "outputs": [] 13 | } 14 | ], 15 | "events": [], 16 | "data": [] 17 | } 18 | -------------------------------------------------------------------------------- /c/example-8-gramgiver/giver.c: -------------------------------------------------------------------------------- 1 | #include "ton-sdk/tvm.h" 2 | #include "ton-sdk/messages.h" 3 | #include "ton-sdk/smart-contract-info.h" 4 | 5 | // Implementation of the contract's constructor. 6 | void constructor_Impl () { 7 | tvm_accept(); 8 | } 9 | 10 | #ifndef NODE_RUN 11 | #include "client-address-workaround.h" 12 | #endif 13 | 14 | void getGrams_Impl(unsigned value) { 15 | tvm_accept(); 16 | #ifdef NODE_RUN 17 | // This is how sender's address should be obtained when running on a node. 18 | MsgAddressInt sender = get_sender_address(); 19 | #else 20 | // And this is a workaround for running the test in a limited environment, 21 | // i.e. by means of tvm_linker test command. 22 | MsgAddressInt sender = { 0, 0, client_address }; 23 | #endif 24 | build_internal_message(&sender, value); 25 | send_raw_message(MSG_PAY_FEES_SEPARATELY); 26 | } 27 | -------------------------------------------------------------------------------- /cpp/Authorization/HelloWorld.cpp: -------------------------------------------------------------------------------- 1 | #include "HelloWorld.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace tvm::schema; 8 | using namespace tvm; 9 | 10 | class HelloWorld final : public smart_interface, 11 | public DHelloWorld { 12 | public: 13 | // The compiler reads the key from the incoming message and stores is in 14 | // pubkey_. So the key is available in a public method via tvm_pubkey(). 15 | unsigned pubkey_ = 0; 16 | __always_inline void set_tvm_pubkey(unsigned pubkey) { pubkey_ = pubkey; } 17 | __always_inline unsigned tvm_pubkey() const { return pubkey_; } 18 | 19 | /// Deploy the contract. 20 | __always_inline void constructor() final { ownerKey = tvm_pubkey(); } 21 | __always_inline uint_t<8> hello_world() final { 22 | require(tvm_pubkey() == ownerKey, 101); 23 | tvm_accept(); 24 | return uint_t<8>(42); 25 | }; 26 | // Function is called in case of unparsed or unsupported func_id 27 | static __always_inline int _fallback(cell msg, slice msg_body) { return 0; }; 28 | 29 | }; 30 | DEFINE_JSON_ABI(IHelloWorld, DHelloWorld, EHelloWorld); 31 | 32 | // ----------------------------- Main entry functions ---------------------- // 33 | DEFAULT_MAIN_ENTRY_FUNCTIONS(HelloWorld, IHelloWorld, DHelloWorld, 1800) 34 | -------------------------------------------------------------------------------- /cpp/Authorization/HelloWorld.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace tvm { namespace schema { 6 | 7 | // Hello world interface 8 | struct IHelloWorld { 9 | // Handle external messages only 10 | __attribute__((external)) 11 | void constructor() = 1; 12 | 13 | // Handle external messages only 14 | __attribute__((external, noaccept)) 15 | uint_t<8> hello_world() = 2; 16 | }; 17 | 18 | // Hello world persistent data 19 | struct DHelloWorld { 20 | uint_t<256> ownerKey; 21 | }; 22 | 23 | // Hello world events 24 | struct EHelloWorld {}; 25 | 26 | }} // namespace tvm::schema 27 | -------------------------------------------------------------------------------- /cpp/Authorization/README.md: -------------------------------------------------------------------------------- 1 | In case you don't find the example self-explainatory, please refer to [C++ Tutorial](https://github.com/everx-labs/samples/blob/master/cpp/HelloWorld) which contain extensive description on what is a contract in C++ and how this contract work. 2 | -------------------------------------------------------------------------------- /cpp/Giver/Client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "Client.hpp" 6 | #include "Giver.hpp" 7 | #include 8 | 9 | using namespace tvm::schema; 10 | using namespace tvm; 11 | 12 | /// Get money from giver and report the incoming value to the happy owner. 13 | class Client final : public smart_interface, public DClient { 14 | public: 15 | 16 | /// Run on contract's deploy. Do nothing but accepts the incoming message. 17 | __always_inline void constructor() final; 18 | /// Request money from the giver contract. 19 | /// Get money from giver. 20 | /// Call method give of a giver contract deployed at address \p giver, 21 | /// request \p balance nanograms. 22 | __always_inline void get_money(lazy giver, 23 | uint_t<256> balance) final; 24 | /// Report current balance with an external message. 25 | /// The giver calls this method in addition to sending the requested number 26 | /// of grams. 27 | __always_inline uint_t<256> receive_and_report() final; 28 | 29 | // Function is called in case of unparsed or unsupported func_id 30 | static __always_inline int _fallback(cell msg, slice msg_body); 31 | }; 32 | DEFINE_JSON_ABI(IClient, DClient, EClient); 33 | 34 | // -------------------------- Public calls --------------------------------- // 35 | void Client::constructor() { 36 | } 37 | 38 | void Client::get_money(lazy giver, uint_t<256> balance) { 39 | auto handle = contract_handle(giver); 40 | handle(Grams(10000000)).call<&IGiver::give>(balance); 41 | } 42 | 43 | uint_t<256> Client::receive_and_report() { 44 | return uint_t<256>(int_msg().unpack().value()); 45 | } 46 | 47 | // Fallback function 48 | int Client::_fallback(cell msg, slice msg_body) { 49 | tvm_accept(); 50 | return 0; 51 | } 52 | 53 | // ----------------------------- Main entry functions ---------------------- // 54 | DEFAULT_MAIN_ENTRY_FUNCTIONS(Client, IClient, DClient, 1800) 55 | -------------------------------------------------------------------------------- /cpp/Giver/Client.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace tvm { namespace schema { 6 | 7 | // Piggybank interface 8 | __interface IClient { 9 | __attribute__((internal, external)) 10 | void constructor() = 1; 11 | 12 | __attribute__((external)) 13 | void get_money(lazy giver, uint_t<256> balance) = 2; 14 | 15 | __attribute__((internal)) 16 | uint_t<256> receive_and_report() = 3; 17 | }; 18 | 19 | // Piggybank owner's persistent data 20 | struct DClient { 21 | uint_t<1> x; 22 | }; 23 | 24 | // Piggybank owner's events 25 | struct EClient {}; 26 | 27 | }} // namespace tvm::schema 28 | -------------------------------------------------------------------------------- /cpp/Giver/Giver.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "Giver.hpp" 6 | #include "Client.hpp" 7 | #include 8 | 9 | using namespace tvm::schema; 10 | using namespace tvm; 11 | 12 | /// Gives money to everyone in need. 13 | class Giver final : public smart_interface, public DGiver { 14 | public: 15 | struct error_code : tvm::error_code { 16 | static constexpr int not_enough_balance = 101; 17 | }; 18 | 19 | __always_inline void constructor() final; 20 | __always_inline void give(uint_t<256> value) final; 21 | 22 | // Function is called in case of unparsed or unsupported func_id 23 | static __always_inline int _fallback(cell msg, slice msg_body); 24 | }; 25 | DEFINE_JSON_ABI(IGiver, DGiver, EGiver); 26 | 27 | // -------------------------- Public calls --------------------------------- // 28 | void Giver::constructor() { 29 | } 30 | 31 | void Giver::give(uint_t<256> value) { 32 | auto sender = int_msg().unpack().int_sender(); 33 | auto handle = contract_handle(sender); 34 | handle(Grams(value.get())).call<&IClient::receive_and_report>(); 35 | } 36 | 37 | // Fallback function 38 | int Giver::_fallback(cell msg, slice msg_body) { 39 | tvm_accept(); 40 | return 0; 41 | } 42 | 43 | // ----------------------------- Main entry functions ---------------------- // 44 | DEFAULT_MAIN_ENTRY_FUNCTIONS(Giver, IGiver, DGiver, 1800) 45 | -------------------------------------------------------------------------------- /cpp/Giver/Giver.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace tvm { namespace schema { 6 | 7 | // Piggybank interface 8 | struct IGiver { 9 | __attribute__((internal, external)) 10 | void constructor() = 1; 11 | 12 | __attribute__((internal)) 13 | void give(uint_t<256> value) = 2; 14 | }; 15 | 16 | // Giver persistent data 17 | struct DGiver { 18 | // At the moment contract's persistend data has to have at least one field. 19 | uint_t<1> x; 20 | }; 21 | 22 | // Giver events 23 | struct EGiver {}; 24 | 25 | }} // namespace tvm::schema 26 | -------------------------------------------------------------------------------- /cpp/Giver/README.md: -------------------------------------------------------------------------------- 1 | # Giver 2 | 3 | The example demonstrates how to asynchronously call a method of a contract from another contract. 4 | Client contract asks for money from a giver one by calling its `give` method, which in turn calls `receive_and_report` method of the client. 5 | 6 | ## Methods 7 | * `Client::get_money(lazy giver, uint_t<256> balance)` - calls `give` method of the contract deployed at `giver` address. Pass balance as the argument of the call. 8 | * `Client::receive_and_report()` - send an external message displaying the balance of the incoming message. 9 | * `Giver::give(uint_t<256> value)` - call `receive_and_report` method of the sender. 10 | -------------------------------------------------------------------------------- /cpp/HelloWorld/HelloWorld.cpp: -------------------------------------------------------------------------------- 1 | #include "HelloWorld.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace tvm::schema; 8 | using namespace tvm; 9 | 10 | class HelloWorld final : public smart_interface, 11 | public DHelloWorld { 12 | public: 13 | /// Deploy the contract. 14 | __always_inline void constructor() final {} 15 | __always_inline uint_t<8> hello_world() final {return uint_t<8>(42);}; 16 | 17 | // Function is called in case of unparsed or unsupported func_id 18 | static __always_inline int _fallback(cell msg, slice msg_body) { return 0; }; 19 | }; 20 | DEFINE_JSON_ABI(IHelloWorld, DHelloWorld, EHelloWorld); 21 | 22 | // ----------------------------- Main entry functions ---------------------- // 23 | DEFAULT_MAIN_ENTRY_FUNCTIONS(HelloWorld, IHelloWorld, DHelloWorld, 1800) 24 | -------------------------------------------------------------------------------- /cpp/HelloWorld/HelloWorld.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace tvm { namespace schema { 6 | 7 | // Hello world interface 8 | struct IHelloWorld { 9 | // Handle external messages only 10 | __attribute__((external)) 11 | void constructor() = 1; 12 | 13 | // Handle external messages only 14 | __attribute__((external)) 15 | uint_t<8> hello_world() = 2; 16 | }; 17 | 18 | // Hello world persistent data 19 | struct DHelloWorld { 20 | uint_t<1> x; 21 | }; 22 | 23 | // Hello world events 24 | struct EHelloWorld {}; 25 | 26 | }} // namespace tvm::schema 27 | -------------------------------------------------------------------------------- /cpp/HelloWorld/README.md: -------------------------------------------------------------------------------- 1 | In case you don't find the example self-explainatory, please refer to [C++ Tutorial](https://github.com/everx-labs/samples/blob/master/cpp/HelloWorld) which contain extensive description on what is a contract in C++ and how this contract work. 2 | -------------------------------------------------------------------------------- /cpp/Kamikaze/Kamikaze.cpp: -------------------------------------------------------------------------------- 1 | #include "Kamikaze.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace tvm::schema; 9 | using namespace tvm; 10 | 11 | /// Demonstrate how to self-destruct a deployed contract 12 | class Kamikaze final : public smart_interface, 13 | public DKamikaze { 14 | public: 15 | struct error_code : tvm::error_code { 16 | static constexpr int wrong_owner = 101; 17 | }; 18 | /// Deploy the contract and store the owner's key. 19 | __always_inline void constructor() final; 20 | /// When called by the owner self-destroy and send all outstanding balance 21 | /// to \p addr. 22 | __always_inline void selfDestruct(lazy addr) final; 23 | 24 | // Function is called in case of unparsed or unsupported func_id 25 | static __always_inline int _fallback(cell msg, slice msg_body); 26 | 27 | // Field and methods to work with the public key of the incoming message. 28 | // The compiler reads the key from the incoming message and stores is in 29 | // pubkey_. So the key is available in a public method via tvm_pubkey(). 30 | unsigned pubkey_ = 0; 31 | __always_inline void set_tvm_pubkey(unsigned pubkey) { pubkey_ = pubkey; } 32 | __always_inline unsigned tvm_pubkey() const { return pubkey_; } 33 | }; 34 | DEFINE_JSON_ABI(IKamikaze, DKamikaze, EKamikaze); 35 | 36 | // -------------------------- Public calls --------------------------------- // 37 | void Kamikaze::constructor() { ownerKey = tvm_pubkey(); } 38 | 39 | void Kamikaze::selfDestruct(lazy addr) { 40 | require(tvm_pubkey() == ownerKey, error_code::wrong_owner); 41 | tvm_accept(); 42 | // Send outstanding balance and delete the contract from the network. 43 | tvm_transfer(addr, 0, false, SEND_ALL_GAS | DELETE_ME_IF_I_AM_EMPTY, 44 | builder().endc()); 45 | } 46 | 47 | // Fallback function 48 | int Kamikaze::_fallback(cell msg, slice msg_body) { 49 | tvm_accept(); 50 | return 0; 51 | } 52 | 53 | // ----------------------------- Main entry functions ---------------------- // 54 | DEFAULT_MAIN_ENTRY_FUNCTIONS(Kamikaze, IKamikaze, DKamikaze, 1800) 55 | -------------------------------------------------------------------------------- /cpp/Kamikaze/Kamikaze.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace tvm { namespace schema { 6 | 7 | // Kamikaze interface 8 | struct IKamikaze { 9 | __attribute__((external)) 10 | void constructor() = 1; 11 | 12 | // Handle an external message and require tvm_accept to be called explicitly. 13 | __attribute__((external, noaccept)) 14 | void selfDestruct(lazy addr) = 2; 15 | }; 16 | 17 | // Kamikaze persistent data 18 | struct DKamikaze { 19 | // Public key of the owner. 20 | uint_t<256> ownerKey; 21 | }; 22 | 23 | // Kamikaze events 24 | struct EKamikaze {}; 25 | 26 | }} // namespace tvm::schema 27 | -------------------------------------------------------------------------------- /cpp/Kamikaze/README.md: -------------------------------------------------------------------------------- 1 | # Kamikaze 2 | 3 | The example demonstrates how a contract can be destroyed. 4 | To change the code of a deployed contract one can call `tvm_setcode` method and pass a cell with new code to it. In particular if to pass an empty cell, the contract will be destroyed. 5 | 6 | ## Methods 7 | This contract has three methods. 8 | * `Kamikaze::constructor()` - method run on the contract's deploy, store the owner's key in the persistent storage. 9 | * `Kamikaze::selfDestruct(lazy addr)` - send outstanding balance to the specified address and destoy itself. 10 | 11 | ## Persistent data 12 | * uint256 `owner` - contract's owner key. 13 | -------------------------------------------------------------------------------- /cpp/Piggybank/PiggyOwner.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "PiggyOwner.hpp" 6 | #include "Piggybank.hpp" 7 | #include 8 | 9 | using namespace tvm::schema; 10 | using namespace tvm; 11 | 12 | /// The contract which owns a piggybank. It can both deposit money to and 13 | /// them from the bank. 14 | class PiggyOwner final : public smart_interface, 15 | public DPiggyOwner { 16 | public: 17 | 18 | /// Deploy the contract, do nothing aside from that. 19 | __always_inline void constructor() final; 20 | /// Asynchroniously call deposit method of Piggybank contract deployed 21 | /// at \p bank, send \p balance nanograms to it. 22 | __always_inline void deposit(lazy bank, 23 | uint_t<256> balance) final; 24 | /// Asynchroniously call withdraw method of Piggybank contract deployed 25 | /// at \p bank. 26 | __always_inline void withdraw(lazy bank) final; 27 | 28 | // Function is called in case of unparsed or unsupported func_id 29 | static __always_inline int _fallback(cell msg, slice msg_body); 30 | }; 31 | DEFINE_JSON_ABI(IPiggyOwner, DPiggyOwner, EPiggyOwner); 32 | 33 | // -------------------------- Public calls --------------------------------- // 34 | void PiggyOwner::constructor() { 35 | } 36 | 37 | void PiggyOwner::deposit(lazy bank, uint_t<256> balance) { 38 | contract_handle(bank)(balance.get()).call<&IPiggybank::deposit>(); 39 | } 40 | 41 | void PiggyOwner::withdraw(lazy bank) { 42 | contract_handle(bank)().call<&IPiggybank::withdraw>(); 43 | } 44 | 45 | // Fallback function 46 | int PiggyOwner::_fallback(cell msg, slice msg_body) { 47 | tvm_accept(); 48 | return 0; 49 | } 50 | 51 | // ----------------------------- Main entry functions ---------------------- // 52 | DEFAULT_MAIN_ENTRY_FUNCTIONS(PiggyOwner, IPiggyOwner, DPiggyOwner, 1800) 53 | -------------------------------------------------------------------------------- /cpp/Piggybank/PiggyOwner.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace tvm { namespace schema { 6 | 7 | // Piggybank owner's interface 8 | struct IPiggyOwner { 9 | __attribute__((internal, external)) 10 | void constructor() = 1; 11 | 12 | __attribute__((external)) 13 | void deposit(lazy bank, uint_t<256> value) = 2; 14 | 15 | __attribute__((external)) 16 | void withdraw(lazy bank) = 3; 17 | }; 18 | 19 | // Piggybank owner's persistent data 20 | struct DPiggyOwner { 21 | // At the moment a contract implemented using smart interface has to have 22 | // at least one field in persistent data. 23 | uint_t<1> x; 24 | }; 25 | 26 | // Piggybank owner's events 27 | struct EPiggyOwner {}; 28 | 29 | }} // namespace tvm::schema 30 | -------------------------------------------------------------------------------- /cpp/Piggybank/PiggyStranger.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "PiggyStranger.hpp" 6 | #include "Piggybank.hpp" 7 | #include 8 | 9 | using namespace tvm::schema; 10 | using namespace tvm; 11 | 12 | class PiggyStranger final : public smart_interface, 13 | public DPiggyStranger { 14 | public: 15 | 16 | /// Deploy the contract. Do nothing aside from that. 17 | __always_inline void constructor() final; 18 | /// Call deposit method of Piggybank contract deployed at \p bank and send 19 | /// \p balance nanograms. 20 | __always_inline void deposit(lazy bank, 21 | uint_t<256> balance) final; 22 | 23 | // Function is called in case of unparsed or unsupported func_id 24 | static __always_inline int _fallback(cell msg, slice msg_body); 25 | }; 26 | DEFINE_JSON_ABI(IPiggyStranger, DPiggyStranger, EPiggyStranger); 27 | 28 | // -------------------------- Public calls --------------------------------- // 29 | void PiggyStranger::constructor() { 30 | } 31 | 32 | void PiggyStranger::deposit(lazy bank, uint_t<256> balance) { 33 | contract_handle(bank)(balance.get()).call<&IPiggybank::deposit>(); 34 | } 35 | 36 | // Fallback function 37 | int PiggyStranger::_fallback(cell msg, slice msg_body) { 38 | tvm_accept(); 39 | return 0; 40 | } 41 | 42 | // ----------------------------- Main entry functions ---------------------- // 43 | DEFAULT_MAIN_ENTRY_FUNCTIONS(PiggyStranger, IPiggyStranger, 44 | DPiggyStranger, 1800) 45 | -------------------------------------------------------------------------------- /cpp/Piggybank/PiggyStranger.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace tvm { namespace schema { 6 | 7 | // Piggybank stranger's interface 8 | struct IPiggyStranger { 9 | __attribute__((internal, external)) 10 | void constructor() = 1; 11 | 12 | __attribute__((external)) 13 | void deposit(lazy bank, uint_t<256> value) = 2; 14 | }; 15 | 16 | // Piggybank stranger's persistent data 17 | struct DPiggyStranger { 18 | // At the moment a contract implemented using smart interface has to have 19 | // at least one field in persistent data. 20 | uint_t<1> x; 21 | }; 22 | 23 | // Piggybank stranger's events 24 | struct EPiggyStranger {}; 25 | 26 | }} // namespace tvm::schema 27 | -------------------------------------------------------------------------------- /cpp/Piggybank/Piggybank.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "Piggybank.hpp" 5 | #include 6 | 7 | using namespace tvm::schema; 8 | using namespace tvm; 9 | 10 | /// A contract which receives money from other contracts, and once the limit is 11 | /// reached a dedicated contract called the piggy bank owner is able to withdraw 12 | /// the savings. 13 | class Piggybank final : public smart_interface, public DPiggybank { 14 | public: 15 | struct error_code : tvm::error_code { 16 | static constexpr int wrong_owner = 101; 17 | static constexpr int not_enough_balance = 102; 18 | }; 19 | 20 | /// Deploy the contract and store \p pb_owner and \p pb_limit in 21 | /// persistent memory. 22 | __always_inline void constructor(lazy pb_owner, 23 | uint_t<256> pb_limit) final; 24 | /// When other contract calls deposit, the money it attached to the message 25 | /// count towards the limit. 26 | __always_inline void deposit() final; 27 | /// If called by the owner and savings exceeded the limit, send them 28 | /// to the caller. 29 | __always_inline void withdraw() final; 30 | /// Auxiliary method to get current amount of savings. 31 | __always_inline uint_t<256> get_balance() final; 32 | 33 | // Function is called in case of unparsed or unsupported func_id 34 | static __always_inline int _fallback(cell msg, slice msg_body); 35 | }; 36 | DEFINE_JSON_ABI(IPiggybank, DPiggybank, EPiggybank); 37 | 38 | // -------------------------- Public calls --------------------------------- // 39 | void Piggybank::constructor(lazy pb_owner, uint_t<256> pb_limit) { 40 | owner = pb_owner; 41 | limit = pb_limit; 42 | balance = 0; 43 | } 44 | 45 | void Piggybank::deposit() { 46 | balance += int_msg().unpack().value(); 47 | } 48 | 49 | void Piggybank::withdraw() { 50 | auto sender = int_msg().unpack().int_sender(); 51 | 52 | require(sender.raw_slice() == owner.raw_slice(), error_code::wrong_owner); 53 | require(balance >= limit, error_code::not_enough_balance); 54 | 55 | tvm_transfer(sender, balance.get(), false); 56 | balance = 0; 57 | } 58 | 59 | uint_t<256> Piggybank::get_balance() { 60 | return balance; 61 | } 62 | 63 | // Fallback function 64 | int Piggybank::_fallback(cell msg, slice msg_body) { 65 | tvm_accept(); 66 | return 0; 67 | } 68 | 69 | // ----------------------------- Main entry functions ---------------------- // 70 | DEFAULT_MAIN_ENTRY_FUNCTIONS(Piggybank, IPiggybank, DPiggybank, 1800) 71 | -------------------------------------------------------------------------------- /cpp/Piggybank/Piggybank.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace tvm { namespace schema { 6 | 7 | // Piggybank interface 8 | struct IPiggybank { 9 | __attribute__((internal, external)) 10 | void constructor(lazy owner, uint_t<256> limit) = 1; 11 | 12 | __attribute__((internal)) 13 | void deposit() = 2; 14 | 15 | __attribute__((internal)) 16 | void withdraw() = 3; 17 | 18 | __attribute__((getter)) 19 | uint_t<256> get_balance() = 4; 20 | }; 21 | 22 | // Piggybank persistent data 23 | struct DPiggybank { 24 | lazy owner; 25 | uint_t<256> limit; 26 | uint_t<256> balance; 27 | }; 28 | 29 | // Piggybank events 30 | struct EPiggybank {}; 31 | 32 | }} // namespace tvm::schema 33 | -------------------------------------------------------------------------------- /cpp/Piggybank/README.md: -------------------------------------------------------------------------------- 1 | # Piggybank 2 | 3 | This example demonstrate message exchange between contracts. 4 | * `Piggybank` is the contract which accumulate savings. 5 | * `PiggyStranger` is the contract that can only deposit grams to `Piggybank`. 6 | * `PiggyOwner` is the contract that can deposit grams to `Piggybank` and withdraw all the saving when the saving goal is reached. 7 | 8 | ## Methods 9 | The contract has the following methods: 10 | * `Piggybank::constructor(lazy owner, uint_t<256> limit)` - method run on the contract's deploy. It stores an address of the contract which is treated as PiggyOwner. It also stores goal for saving money (`limit`). 11 | * `Piggybank::deposit()`. The method is supposed to be called by any contract (in particular `PiggyOwner` or `PiggyStranger`) to contribute to savings. Unlike a simple incoming message with balance, deposit call count the incoming money towards the saving goal. 12 | * `Piggybank::withdraw()`. The method is supposed to be called by the owner contract, otherwise the exception is thrown. If saving reach the goal, send them to the caller. 13 | * `Piggybank::get_balance()` - show the current amount of savings. 14 | * `PiggyOwner::deposit(lazy bank, uint_t<256> balance)`. Call `deposit` method of the contract deployed at `bank` address (the contract is expected to be `Piggybank`) and send `balance` nanograms to it. 15 | * `Piggybank::withdraw(lazy bank)`. Call `withdraw` method of the contract deployed at `bank` address (the contract is expected to be `Piggybank`). 16 | * `PiggyStranger::deposit(lazy bank, uint_t<256> balance)`. Call `deposit` method of the contract deployed at `bank` address (the contract is expected to be `Piggybank`) and send `balance` nanograms to it. 17 | -------------------------------------------------------------------------------- /cpp/README.md: -------------------------------------------------------------------------------- 1 | # Deprecated 2 | 3 | ## Prerequisites 4 | 5 | To compile a contract, you first need to get the following libraries and tools. 6 | - [C++ for TVM compiler, C++ runtime, C++ SDK libraries, C++ driver](https://github.com/everx-labs/TON-Compiler). All libraries and tools are in the same repository. 7 | - [TVM linker](https://github.com/everx-labs/TVM-linker/). 8 | - [TONOS CLI](https://github.com/everx-labs/tonos-cli). 9 | 10 | ## Contract in C++ 11 | 12 | A contract in C++ might be written using any C++ standard supported by Clang-7. So a developer could use most of the features available in the most recent C++17 standard. However since blockchain is the domain, C++ was not initially intended for contracts and has some limitations: 13 | - All the arithmetic is 257-bits wide, sizeof(char) == sizeof() == 1 byte == 257 bits. 14 | - TVM doesn't support float point arithmetic, so compilation of a contract utilizing float or double type might end up with an internal compiler error. 15 | - TVM has very poor performance when emulating pointers to functions, so a program which _after optimization_ still has calls by a pointer will not compile. So we strongly recommend to use coding style which could be characterized as C with classes and templates with several exceptions that are shown in the examples from this repository. 16 | - Exception handling is not currently supported. 17 | 18 | Aside from that, we introduced some extensions into the language to deal with the blockchain specifics: 19 | 20 | ### Language extensions 21 | 22 | - GNU attributes to mark public methods of a contract (see any example from this repo for details). 23 | - `auto [=a] = expression;`. Structure binding extension allowing to assign to already declared variable a. Note that any mix of new and existing variables is allowed in a structure binding (e.g. `auto [=a, b, c, =d] = expression;` defines `b` and `c` and assigns to `a` and `d`). 24 | - `smart_interface` template class and macro to generate contract ABI (see any example from this repo for details). 25 | - `void foo() = n` in a pure virtual function declaration is used to designate the public method id in a contract. 26 | 27 | Note that C++ compiler is under active development and we aim to improve diagnostic, but at the moment compilation might end up with an internal compiler error. In such case please refer to the aforementioned guidelines and ask for help in the community chats. 28 | 29 | ## C++ compilation workflow 30 | 31 | To compile a contract in C++ perform the following steps. 32 | 1. Extract the contract ABI (alternatively it could be written by hands, but it duplicates information about the contract interface which is bug prone). To extract the ABI, run 33 | ``` 34 | clang++ -target tvm --sysroot Contract.cpp -export-json-abi -o Contract.abi 35 | ``` 36 | 37 | 2. Compile the contract and link it 38 | ``` 39 | export TVM_LINKER= 40 | export TVM_INCLUDE_PATH= 41 | export TVM_LIBRARY_PATH= 42 | --abi Contract.abi Contract.cpp --include $TVM_INCLUDE_PATH 43 | ``` 44 | 45 | If the compilation is successful, `*.tvc` file is created in the directory where `tvm-build++` was run. 46 | 47 | ## Contract deployment 48 | 49 | Here we describe a contract deployment to the TON Blockchain Test Network (testnet) using `tonlabs-cli` tool. The following commands must be run from the directory where tonlabs-cli is located. 50 | 51 | 0. Add tonos-cli build directory to PATH 52 | ``` 53 | export PATH=/path/to/tonos-cli/target// 54 | ``` 55 | 56 | 1. Generate contract address 57 | ``` 58 | tonos-cli genaddr Contract.tvc Contract.abi --genkey key 59 | ``` 60 | 61 | The command generates and print lists all possible addresses of the contract: 62 | ``` 63 | Raw address: 64 | testnet: 65 | Non-bounceable address (for init): 66 | Bounceable address (for later access): 67 | mainnet: 68 | Non-bounceable address (for init): 69 | Bounceable address (for later access): 70 | ``` 71 | 72 | Later we will use `` of the contract. Note that `--genkey` (or `--setkey`) is mandatory, it generates a key file which is used for every interaction with the contract made by an offchain application. TVM supports unauthorized external (i.e. from outside of the blockchain) method calls, but C++ currently doesn't. 73 | 74 | 2. Prepare the account 75 | The contract is going to store its code and data in the blockchain, but it costs money. So we must transfer some coins to the future contract address before deploying the contract. Some of the ways to get test coins are covered in **Getting test coins**. 76 | 77 | 3. Deploy the contract 78 | ``` 79 | tonos-cli deploy --abi Contract.abi Contract.tvc '{}' --sign key 80 | ``` 81 | Note that call arguments are supposed to be pairs of `"parameter":value` in JSON format. For instance, `'{"a":5, "b":6}'`calls the constructor with a = 5 and b = 6. Note that the parameter names must match ones specified in the contract ABI. 82 | 83 | 4. Run a method 84 | Method's running is syntactically similar to deploying, but the name of the method should also be specified. 85 | ``` 86 | tonos-cli call --abi Contract.abi '{}' --sign key 87 | ``` 88 | Note that a contract method might also be called internally (i.e. by another contract), see [Giver](https://github.com/everx-labs/samples/blob/master/cpp/Giver) example to learn more. 89 | 90 | ## Getting test Coins 91 | 92 | At present, we can describe two ways to obtain test coins: 93 | 94 | ### 1) Ask your friend to send test coins to your address. 95 | If you have a friend who works with TON smart contracts they may have some extra test coins and a contract with transfer function (e.g. it could be a Wallet contract, which can send test coins to a given address). In this situation, you can ask them to send some test coins to your address. 96 | 97 | ### 2) Use giver contract. 98 | If you know that there is a giver contract in your TON network and you know its address and possibly keys you can ask giver to transfer some test coins to your address. 99 | 100 | 2.1) If you need to use keys with your giver save them into 2 files: 101 | **secret.key** - 64 bytes concatenation of secret key and public key; 102 | **public.key** - 32 bytes of public key. 103 | 104 | 2.2) Use tvm_linker to create message that we will call giver's function. Use giver's abi to create this message (in this example giver's abi was saved to file giver.abi.json and function name and arguments were taken from it). 105 | 106 | ``` 107 | tvm_linker message -w 0 --abi-json giver.abi.json --abi-method 'sendTransaction' --abi-params '{"dest":"0:","value":"","bounce":"false"}' --setkey secret.key 108 | ``` 109 | 110 | \ - giver contract address in HEX format without workchain id. 111 | The command generates **.boc** file with name \<\*-msg-body.boc\>. 112 | 113 | 2.3) Use lite_client to send **.boc** file obtained on step **2.2**. 114 | Run lite_client: 115 | 116 | ``` 117 | lite-client -C ton-lite-client-test1.config.json 118 | ``` 119 | 120 | Send out **.boc** file. 121 | 122 | ``` 123 | sendfile > 124 | ``` 125 | 2.4) Check whether the balance replenishment was successful: 126 | We can perform this check in different ways: 127 | 128 | 2.4.1) Using ton.live service: 129 | 130 | Go to https://net.ton.live/accounts?section=details&id=: using your contract address. 131 | 132 | 2.4.2) Using GraphQL: 133 | 134 | Go to https://net.ton.live/ Open Playground and run this code: 135 | 136 | ``` 137 | { 138 | accounts 139 | (filter:{id:{eq:"0:"}}) 140 | { 141 | acc_type 142 | balance 143 | code 144 | } 145 | } 146 | ``` 147 | 148 | In case of success, you get the similar code: 149 | ``` 150 | { 151 | "data": { 152 | "accounts": [ 153 | { 154 | "acc_type": 0, 155 | "balance": "", 156 | "code": null 157 | } 158 | ] 159 | } 160 | } 161 | ``` 162 | 163 | 2.4.3) Using lite_client: 164 | 165 | Run lite_client and execute the following command: 166 | 167 | ``` 168 | getaccount 0: 169 | ``` 170 | 171 | If everything is OK, you get the following output: 172 | 173 | ``` 174 | ... 175 | value:(currencies 176 | grams:(nanograms 177 | amount:(var_uint len:5 value:)) 178 | ... 179 | ``` 180 | 181 | ## Contract examples 182 | 183 | The list of C++ examples is provided below. If you just started learning C++ for TVM we recommend to start with the [Tutorial](https://github.com/everx-labs/samples/blob/master/cpp/TUTORIAL.md) and then to study the examples in order. If you are familiar with TVM and smart contracts, however, jump to the contract you are interested in. 184 | 185 | 1) [Hello, world!](https://github.com/everx-labs/samples/blob/master/cpp/HelloWorld): Introduces general concepts of the contract development. 186 | 187 | This example is a part of [C++ Tutorial](https://github.com/everx-labs/samples/blob/master/cpp/TUTORIAL.md) which is a step by step guidance on how to create your first contract. 188 | 189 | 2) [Authorization](https://github.com/everx-labs/samples/blob/master/cpp/Authorization): Demonstrate a message signature check. 190 | 191 | This example is a part of [C++ Tutorial](https://github.com/everx-labs/samples/blob/master/cpp/TUTORIAL.md). The example extends [Hello, world!](https://github.com/everx-labs/samples/blob/master/cpp/HelloWorld) example by introducing signature cheching to prevent spam attack on a contract and make it run out of money. 192 | 193 | 3) [Giver](https://github.com/everx-labs/samples/blob/master/cpp/Giver): The contract sends the requested amount of money. 194 | 195 | This example is a part of [C++ Tutorial](https://github.com/everx-labs/samples/blob/master/cpp/TUTORIAL.md). It shows how to call a public method with parameters from another contract. 196 | 197 | 4) [Wallet](https://github.com/everx-labs/samples/blob/master/cpp/Wallet): Simple contract to hold and spend money. 198 | 199 | 5) [Piggybank](https://github.com/everx-labs/samples/blob/master/cpp/Piggybank): Contract for savings. 200 | 201 | The example consist of three contracts which exchange messages between each other. It shows simplest form of an internal call of a public method. It also show how a mechanism of internal authorization might work. 202 | 203 | 6) [Kamikaze](https://github.com/everx-labs/samples/blob/master/cpp/Kamikaze): The example shows how a contract could be deleted from the network. 204 | -------------------------------------------------------------------------------- /cpp/Wallet/README.md: -------------------------------------------------------------------------------- 1 | # Wallet 2 | 3 | This example is a simple wallet contract which allows to transfer grams to the specified address. Only the wallet's owner and optionaly thusted 3rd party contract is allowed to do such a transfer. 4 | 5 | ## Methods 6 | This contract has four methods. 7 | * `Wallet::constructor()` - method run on the contract's deploy, store the owner's key in the persistent storage. 8 | * `Wallet::set_subscription(MsgAddressInt address)` - specify the address of the trusted contract, thus grant to the contract authority to send money. 9 | * `MsgAddress Wallet::get_subscription()` - get subscription contract address. 10 | * `Wallet::send_transaction(MsgAddressInt dest, uint128 value, bool bounce)` - send `value` nanograms to address `dest`. 11 | 12 | ## Persistent data 13 | * uint256 `owner` - contract's owner public key. 14 | * MsgAddressInt `subscription` - subscription contract address. 15 | * uint64 `timestamp` - last time stamp value for replay protection check (hidden). 16 | -------------------------------------------------------------------------------- /cpp/Wallet/Wallet.cpp: -------------------------------------------------------------------------------- 1 | #include "Wallet.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace tvm::schema; 8 | using namespace tvm; 9 | 10 | class Wallet final : public smart_interface, public DWallet { 11 | public: 12 | struct error_code : tvm::error_code { 13 | static constexpr int wrong_owner = 101; 14 | static constexpr int not_enough_balance = 102; 15 | }; 16 | 17 | // Public methods 18 | __always_inline void constructor() final; 19 | __always_inline void set_subscription_account(MsgAddressInt addr) final; 20 | __always_inline MsgAddressInt get_subscription_account() final; 21 | __always_inline void send_transaction(MsgAddressInt dest, uint_t<128> value, 22 | bool_t bounce) final; 23 | 24 | // Function is called in case of unparsed or unsupported func_id 25 | static __always_inline int _fallback(cell msg, slice msg_body); 26 | 27 | // Field and methods to work with the public key of the incoming message. 28 | // The compiler reads the key from the incoming message and stores is in 29 | // pubkey_. So the key is available in a public method via tvm_pubkey(). 30 | unsigned pubkey_ = 0; 31 | __always_inline void set_tvm_pubkey(unsigned pubkey) { pubkey_ = pubkey; } 32 | __always_inline unsigned tvm_pubkey() const { return pubkey_; } 33 | }; 34 | 35 | // ABI file generating macro. 36 | // To generate ABI run clang with -export-json-abi argument 37 | DEFINE_JSON_ABI(IWallet, DWallet, EWallet); 38 | 39 | // -------------------------- Public calls --------------------------------- // 40 | /// Store owner's key. 41 | // Note that signature checking is auto-generated. 42 | void Wallet::constructor() { ownerKey = tvm_pubkey(); } 43 | 44 | /// Allow \p addr to request \p send_transacton. 45 | void Wallet::set_subscription_account(MsgAddressInt addr) { 46 | require(tvm_pubkey() == ownerKey, error_code::wrong_owner); 47 | subscriber = addr; 48 | } 49 | 50 | MsgAddressInt Wallet::get_subscription_account() { return subscriber; } 51 | 52 | /// Send \p value grams to \p dest. 53 | void Wallet::send_transaction(MsgAddressInt dest, uint_t<128> value, 54 | bool_t bounce) { 55 | require(tvm_pubkey() == ownerKey || 56 | int_msg().unpack().int_sender()() == subscriber, 57 | error_code::wrong_owner); 58 | 59 | int balance = smart_contract_info::balance_remaining(); 60 | require(value.get() > 0 && value.get() < balance, 61 | error_code::not_enough_balance); 62 | 63 | tvm_transfer(dest, value.get(), bounce.get()); 64 | } 65 | 66 | /// Fallback function which is called when function id is not parsed or wrong. 67 | int Wallet::_fallback(cell msg, slice msg_body) { 68 | tvm_accept(); 69 | return 0; 70 | } 71 | 72 | // ----------------------------- Main entry functions ---------------------- // 73 | // Main entry function are auto generated. 74 | DEFAULT_MAIN_ENTRY_FUNCTIONS(Wallet, IWallet, DWallet, 75 | 1800 /* interval for timestamp replay protection 76 | in seconds */) 77 | -------------------------------------------------------------------------------- /cpp/Wallet/Wallet.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace tvm { namespace schema { 6 | 7 | // Wallet interface 8 | // It supposed to be passed as an argument to smart_interface template class. 9 | // The compiler auto generates essential logic for each public method of 10 | // the class and the class is supposed to be an interface (i.e. has only 11 | // pure virtual public methods and no data). 12 | // - incoming message parsing and extracting arguments. 13 | // - return value serializing and sending via an external message. 14 | // - timestamp-based replay protection check. 15 | // - signature checking (for an external call). 16 | // - accepting a message. 17 | struct IWallet { 18 | __attribute__((external)) // Only awailable from an offchain application. 19 | void constructor() = 1 /* Function ID */; 20 | 21 | __attribute__((external)) // Only awailable from an offchain application. 22 | void set_subscription_account(MsgAddressInt addr) = 2; 23 | 24 | __attribute__((getter)) // Supposed to be run locally for free. 25 | MsgAddressInt get_subscription_account() = 3; 26 | 27 | __attribute__((internal, external)) 28 | // Available both from a contract and from an off-chain application. 29 | void send_transaction(MsgAddressInt dest, 30 | uint_t<128> value, 31 | bool_t bounce) = 4; 32 | }; 33 | 34 | // Wallet persistent data 35 | struct DWallet { 36 | uint256 ownerKey; // owner's key 37 | MsgAddressInt subscriber; // trusted contract address 38 | // timestamp for replay protection is auto-generated 39 | }; 40 | 41 | // Wallet events 42 | struct EWallet {}; 43 | 44 | }} // namespace tvm::schema 45 | -------------------------------------------------------------------------------- /solidity/10_Wallet.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | /// @title Simple wallet 5 | contract Wallet { 6 | /* 7 | Exception codes: 8 | 100 - message sender is not a wallet owner. 9 | 101 - invalid transfer value. 10 | */ 11 | 12 | /// @dev Contract constructor. 13 | constructor() { 14 | // check that contract's public key is set 15 | require(tvm.pubkey() != 0, 101); 16 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 17 | require(msg.pubkey() == tvm.pubkey(), 102); 18 | tvm.accept(); 19 | } 20 | 21 | // Modifier that allows function to accept external call only if it was signed 22 | // with contract owner's public key. 23 | modifier checkOwnerAndAccept { 24 | // Check that inbound message was signed with owner's public key. 25 | // Runtime function that obtains sender's public key. 26 | require(msg.pubkey() == tvm.pubkey(), 100); 27 | 28 | // Runtime function that allows contract to process inbound messages spending 29 | // its own resources (it's necessary if contract should process all inbound messages, 30 | // not only those that carry value with them). 31 | tvm.accept(); 32 | _; 33 | } 34 | 35 | /// @dev Allows to transfer tons to the destination account. 36 | /// @param dest Transfer target address. 37 | /// @param value Nanoevers value to transfer. 38 | /// @param bounce Flag that enables bounce message in case of target contract error. 39 | function sendTransaction(address dest, coins value, bool bounce) external view checkOwnerAndAccept { 40 | // Runtime function that allows to make a transfer with arbitrary settings. 41 | dest.transfer(value, bounce, 0); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /solidity/11_ContractDeployer.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | import "11_SimpleContract.sol"; 5 | import "11_Waller_no_constructor.sol"; 6 | 7 | contract ContractDeployer { 8 | 9 | // addresses of deployed contracts 10 | address[] public contracts; 11 | 12 | constructor() { 13 | // check that contract's public key is set 14 | require(tvm.pubkey() != 0, 101); 15 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 16 | require(msg.pubkey() == tvm.pubkey(), 102); 17 | tvm.accept(); 18 | } 19 | 20 | // Modifier that allows public function to accept external calls only from the contract owner. 21 | modifier checkOwnerAndAccept { 22 | require(msg.pubkey() == tvm.pubkey(), 102); 23 | tvm.accept(); 24 | _; 25 | } 26 | 27 | // The first option of contract deployment. 28 | function deployFromCodeAndData( 29 | TvmCell code, 30 | TvmCell data, 31 | coins initialBalance, 32 | uint paramA, 33 | uint32 paramB 34 | ) 35 | public 36 | checkOwnerAndAccept 37 | { 38 | // Runtime function to generate StateInit from code and data cells. 39 | TvmCell stateInit = abi.encodeStateInit(code, data); 40 | 41 | address addr = new SimpleContract{stateInit: stateInit, value: initialBalance}(paramA, paramB); 42 | 43 | // save address 44 | contracts.push(addr); 45 | } 46 | 47 | // The second option of contract deployment 48 | function deployWithMsgBody( 49 | TvmCell stateInit, 50 | int8 wid, 51 | coins initialBalance, 52 | TvmCell payload 53 | ) 54 | public 55 | checkOwnerAndAccept 56 | { 57 | // Runtime function to deploy contract with prepared msg body for constructor call. 58 | address addr = address.makeAddrStd(wid, tvm.hash(stateInit)); 59 | addr.transfer({stateInit: stateInit, body: payload, value: initialBalance}); 60 | 61 | // save address 62 | contracts.push(addr); 63 | } 64 | 65 | // The third option of contract deployment 66 | function deployNoConstructor(TvmCell code) external checkOwnerAndAccept { 67 | TvmCell data = abi.encodeData({ 68 | contr: Wallet, 69 | varInit: { 70 | m_creator: address(this) 71 | }, 72 | pubkey: 0x3f82435f2bd40915c28f56d3c2f07af4108931ae8bf1ca9403dcf77d96250827 73 | }); 74 | TvmCell stateInit = abi.encodeStateInit(code, data); 75 | // Get address of new contracts 76 | address addr = address.makeAddrStd(0, tvm.hash(stateInit)); 77 | // Deploy the contract (that has no constructor) by calling `sendCoins` function 78 | Wallet(addr).sendCoins{stateInit: stateInit, value: 2 ever}(address(0x12345), 1 ever, false); 79 | 80 | // save address 81 | contracts.push(addr); 82 | } 83 | 84 | function deployNoConstructor2(TvmCell code) public checkOwnerAndAccept { 85 | TvmCell data = abi.encodeData({ 86 | contr: Wallet, 87 | varInit: { 88 | m_creator: address(this) 89 | }, 90 | pubkey: 0x3f82435f2bd40915c28f56d3c2f07af4108931ae8bf1ca9403dcf77d96250828 91 | }); 92 | TvmCell stateInit = abi.encodeStateInit(code, data); 93 | // Deploy the contract (that has no constructor) by calling `receive` function 94 | address addr = address.makeAddrStd(0, tvm.hash(stateInit)); 95 | addr.transfer({stateInit: stateInit, value: 1 ever}); 96 | 97 | // save address 98 | contracts.push(addr); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /solidity/11_SimpleContract.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | contract SimpleContract { 5 | 6 | uint public m_a; 7 | uint32 public m_b; 8 | 9 | constructor(uint a, uint32 b) { 10 | // check that contract's public key is set 11 | require(tvm.pubkey() != 0, 101); 12 | 13 | // NOTE: To protect from deploying this contract by hacker it's good idea to check msg.sender. See 17_SimpleWallet.sol 14 | tvm.accept(); 15 | m_a = a; 16 | m_b = b; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /solidity/11_Waller_no_constructor.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | contract Wallet { 5 | 6 | uint m_counter; 7 | uint m_receiveCounter; 8 | address static m_creator; 9 | 10 | modifier checkOwnerAndAccept { 11 | require(msg.sender == m_creator, 100); 12 | tvm.accept(); 13 | _; 14 | } 15 | 16 | function sendCoins(address dest, coins value, bool bounce) external checkOwnerAndAccept { 17 | ++m_counter; 18 | dest.transfer(value, bounce, 0); 19 | } 20 | 21 | receive() external { 22 | ++m_receiveCounter; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /solidity/12_BadContract.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | contract ContractWithBug { 5 | // Version of the contract 6 | uint public m_version = 1; 7 | uint public m_value; 8 | 9 | constructor() { 10 | // check that contract's public key is set 11 | require(tvm.pubkey() != 0, 101); 12 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 13 | require(msg.pubkey() == tvm.pubkey(), 102); 14 | tvm.accept(); 15 | } 16 | 17 | function setValue(uint a, uint b) external checkPubkeyAndAccept { 18 | m_value = a + b; // there is bug. It will be fixed in next version of contract. See 12_NewVersion.sol 19 | } 20 | 21 | // Modifier that allows public function to be called only by message signed with owner's pubkey. 22 | modifier checkPubkeyAndAccept { 23 | require(msg.pubkey() == tvm.pubkey(), 102); 24 | tvm.accept(); 25 | _; 26 | } 27 | 28 | // Function that changes the code of the contract. 29 | function updateContractCode(TvmCell newcode) external view checkPubkeyAndAccept { 30 | // Runtime function that creates an output action that would change this 31 | // smart contract code to that given by cell newcode. 32 | tvm.setcode(newcode); 33 | // Runtime function that replaces current code (in register C3) of the contract with newcode. 34 | // It needs to call new `onCodeUpgrade` function 35 | tvm.setCurrentCode(newcode); 36 | // store all state variable to cell 37 | TvmCell stateVars = abi.encode(m_version, m_value); 38 | // Call function onCodeUpgrade of the 'new' code. 39 | onCodeUpgrade(stateVars); 40 | } 41 | 42 | // This function will never be called. But it must be defined. 43 | function onCodeUpgrade(TvmCell stateVars) private pure { 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /solidity/12_NewVersion.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | contract GoodContract { 5 | // Version of the contract 6 | uint public m_version; // this value is set in 'onCodeUpgrade' function 7 | uint public m_value; 8 | mapping(uint => uint) m_map; // we added this new contract's state variable 9 | 10 | constructor () { 11 | tvm.accept(); 12 | } 13 | 14 | function setValue(uint a, uint b) external checkPubkeyAndAccept { 15 | m_value = a * b; // Bug has been fixed here =) 16 | } 17 | 18 | // Modifier that allows public function to be called only by message signed with owner's pubkey. 19 | modifier checkPubkeyAndAccept { 20 | require(msg.pubkey() == tvm.pubkey(), 102); 21 | tvm.accept(); 22 | _; 23 | } 24 | 25 | // Function that changes the code of current contract. 26 | function updateContractCode(TvmCell newcode) external checkPubkeyAndAccept { 27 | // Runtime function that creates an output action that would change this 28 | // smart contract code to that given by cell newcode. 29 | tvm.setcode(newcode); 30 | // Runtime function that replaces current code (in register C3) of the contract with newcode. 31 | // It needs to call new `onCodeUpgrade` function 32 | tvm.setCurrentCode(newcode); 33 | TvmCell stateVars = abi.encode(m_version, m_value, m_map); 34 | // Call function onCodeUpgrade of the 'new' code. 35 | onCodeUpgrade(stateVars); 36 | } 37 | 38 | // This function will be called from old contract. 39 | // This function must have same signature as in old contract. 40 | function onCodeUpgrade(TvmCell stateVars) private { 41 | // in new contract we added new state variable. So we must reset storage 42 | tvm.resetStorage(); 43 | 44 | // initialize state variables 45 | (m_version, m_value) = abi.decode(stateVars, (uint, uint)); 46 | ++m_version; 47 | m_map[100] = 200; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /solidity/13_BankCollector.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | import "13_Interfaces.sol"; 5 | 6 | // The contract allows to store information about bank clients and iterate over them to filter clients. 7 | contract BankCollector is IBankCollector { 8 | 9 | // Expiration period. 10 | uint32 constant EXPIRATION_PERIOD = 1 days; 11 | 12 | // State variable storing client information. 13 | mapping(address => ClientInfo) clientDB; 14 | 15 | constructor() { 16 | // check that contract's public key is set 17 | require(tvm.pubkey() != 0, 101); 18 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 19 | require(msg.pubkey() == tvm.pubkey(), 102); 20 | tvm.accept(); 21 | } 22 | 23 | modifier onlyOwner { 24 | require(msg.pubkey() == tvm.pubkey(), 102); 25 | tvm.accept(); 26 | _; 27 | } 28 | 29 | // Struct for storing the credit information. 30 | struct ClientInfo { 31 | coins debtAmount; 32 | uint32 expiredTimestamp; 33 | } 34 | 35 | // Add client to database. 36 | function addClient(address addr, coins debtAmount) external onlyOwner { 37 | // Mapping member function to obtain value from mapping if it exists. 38 | optional(ClientInfo) info = clientDB.fetch(addr); 39 | if (info.hasValue()) { 40 | ClientInfo i = info.get(); 41 | i.debtAmount += debtAmount; 42 | clientDB[addr] = i; 43 | } else { 44 | clientDB[addr] = ClientInfo(debtAmount, block.timestamp + EXPIRATION_PERIOD); 45 | } 46 | } 47 | 48 | // Function for client to get his debt amount. 49 | function getDebtAmount() external override { 50 | // Mapping member function to obtain value from mapping if it exists. 51 | optional(ClientInfo) info = clientDB.fetch(msg.sender); 52 | if (info.hasValue()) { 53 | IBankClient(msg.sender).setDebtAmount(info.get().debtAmount); 54 | } else { 55 | IBankClient(msg.sender).setDebtAmount(228); 56 | } 57 | } 58 | 59 | // Function for client to return debt. 60 | function receivePayment() external override { 61 | address addr = msg.sender; 62 | // Mapping member function to obtain value from mapping if it exists. 63 | optional(ClientInfo) info = clientDB.fetch(addr); 64 | if (info.hasValue()) { 65 | ClientInfo i = info.get(); 66 | if (i.debtAmount <= msg.value) { 67 | delete clientDB[addr]; 68 | } else { 69 | i.debtAmount -= msg.value; 70 | clientDB[addr] = i; 71 | } 72 | } 73 | } 74 | 75 | // Function to demand all expired debts. 76 | function demandExpiredDebts() external view onlyOwner { 77 | // Mapping member function to obtain minimal key and associated value from mapping if it exists. 78 | optional(address, ClientInfo) client = clientDB.min(); 79 | while (client.hasValue()) { 80 | (address addr, ClientInfo info) = client.get(); 81 | if (info.expiredTimestamp <= block.timestamp) 82 | IBankClient(addr).demandDebt(info.debtAmount); 83 | // Mapping member function to obtain next key and associated value from mapping if it exists. 84 | client = clientDB.next(addr); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /solidity/13_BankCollectorClient.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | import "13_Interfaces.sol"; 5 | 6 | // This contract implements 'IBankClient' interface. 7 | contract BankClient is IBankClient { 8 | 9 | address public bankCollector; 10 | uint public debtAmount; 11 | 12 | constructor(address _bankCollector) { 13 | // check that contract's public key is set 14 | require(tvm.pubkey() != 0, 101); 15 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 16 | require(msg.pubkey() == tvm.pubkey(), 102); 17 | tvm.accept(); 18 | bankCollector = _bankCollector; 19 | } 20 | 21 | // Modifier that allows public function to be called only by message signed with owner's pubkey. 22 | modifier onlyOwnerAndAccept { 23 | require(msg.pubkey() == tvm.pubkey(), 102); 24 | tvm.accept(); 25 | _; 26 | } 27 | 28 | // Modifier that allows public function to accept external calls only from bank collecor. 29 | modifier onlyCollector { 30 | // Runtime function to obtain message sender address. 31 | require(msg.sender == bankCollector, 101); 32 | 33 | // Runtime function that allows contract to process inbound messages spending 34 | // its own resources (it's necessary if contract should process all inbound messages, 35 | // not only those that carry value with them). 36 | tvm.accept(); 37 | _; 38 | } 39 | 40 | function demandDebt(coins amount) external override onlyCollector { 41 | IBankCollector(msg.sender).receivePayment{value: amount}(); 42 | } 43 | 44 | function obtainDebtAmount() external view onlyOwnerAndAccept { 45 | IBankCollector(bankCollector).getDebtAmount{value: 0.5 ever}(); 46 | } 47 | 48 | function setDebtAmount(coins amount) external override onlyCollector { 49 | debtAmount = amount; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /solidity/13_Interfaces.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | // Interface to the bank client. 5 | interface IBankClient { 6 | function demandDebt(coins amount) external; 7 | function setDebtAmount(coins amount) external; 8 | } 9 | 10 | // Interface to the bank collector. 11 | interface IBankCollector { 12 | function receivePayment() external; 13 | function getDebtAmount() external; 14 | } 15 | -------------------------------------------------------------------------------- /solidity/14_CustomReplayProtection.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | 3 | // AbiHeader section allows to define which fields are expected to be in the header of inbound message. 4 | // This fields must be read in the replay protection function. 5 | pragma AbiHeader expire; 6 | 7 | // This contract demonstrates custom replay protection functionality. 8 | contract CustomReplaySample { 9 | struct MessageInfo { 10 | uint256 messageHash; 11 | uint32 expireAt; 12 | } 13 | 14 | // All accepted messages: expire => (messageHash => flag) 15 | mapping(uint32 => mapping(uint256 => bool)) messages; 16 | // Iteration count for cleaning mapping `messages` 17 | uint8 constant MAX_CLEANUP_ITERATIONS = 20; 18 | // Information about the last message 19 | MessageInfo lastMessage; 20 | // Dummy variable to demonstrate contract functionality. 21 | uint value; 22 | 23 | constructor() { 24 | // check that contract's public key is set 25 | require(tvm.pubkey() != 0, 101); 26 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 27 | require(msg.pubkey() == tvm.pubkey(), 104); 28 | tvm.accept(); 29 | } 30 | 31 | // Checks condition and then a function body 32 | modifier onlyOwner { 33 | require(msg.pubkey() == tvm.pubkey(), 104); 34 | _; 35 | } 36 | 37 | // Accepts the message and then calls a function body 38 | modifier accept { 39 | tvm.accept(); 40 | _; 41 | } 42 | 43 | // Saves the message and then a function body 44 | modifier saveMessage { 45 | messages[lastMessage.expireAt][lastMessage.messageHash] = true; 46 | _; 47 | } 48 | 49 | // Colls a function body and then gc() 50 | modifier clear { 51 | _; 52 | gc(); 53 | } 54 | 55 | // Function with predefined name which is used to replace custom replay protection. 56 | function afterSignatureCheck(TvmSlice body, TvmCell message) private inline returns (TvmSlice) { 57 | body.load(uint64); // The first 64 bits contain timestamp which is usually used to differentiate messages. 58 | 59 | // check expireAt 60 | uint32 expireAt = body.load(uint32); 61 | require(expireAt > block.timestamp, 101); // Check whether the message is not expired. 62 | require(expireAt < block.timestamp + 5 minutes, 102); // Check whether expireAt is not too huge. 63 | 64 | // Check whether the message is not expired and then save (messageHash, expireAt) in the state variable 65 | uint messageHash = tvm.hash(message); 66 | optional(mapping(uint256 => bool)) m = messages.fetch(expireAt); 67 | require(!m.hasValue() || !m.get()[messageHash], 103); 68 | lastMessage = MessageInfo({messageHash: messageHash, expireAt: expireAt}); 69 | 70 | // After reading message headers this function must return the rest of the body slice. 71 | return body; 72 | } 73 | 74 | /// Delete expired messages from `messages`. 75 | function gc() private { 76 | uint counter = 0; 77 | for ((uint32 expireAt, mapping(uint256 => bool) m) : messages) { 78 | m; // suspend compilation warning 79 | if (counter >= MAX_CLEANUP_ITERATIONS) { 80 | break; 81 | } 82 | counter++; 83 | if (expireAt <= block.timestamp) { 84 | delete messages[expireAt]; 85 | } else { 86 | break; 87 | } 88 | } 89 | } 90 | 91 | // Dummy function to demonstrate contract functionality. 92 | // Note: execution order of the function 93 | // 1) calling the function `afterSignatureCheck` 94 | // 2) calling the modifier `onlyOwner` 95 | // 3) calling the modifier `accept` 96 | // 4) calling the modifier `saveMessage` 97 | // 5) calling the function (`storeValue`) body 98 | // 6) calling the modifier clear 99 | function storeValue(uint newValue) external onlyOwner accept saveMessage clear { 100 | value = newValue; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /solidity/15_MessageReceiver.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | 3 | // Contract that parses the argument passed to his function. 4 | contract MessageReceiver { 5 | 6 | // State variable storing the number of times 'receiveMessage' function was called. 7 | uint public counter; 8 | 9 | constructor() { 10 | // check that contract's public key is set 11 | require(tvm.pubkey() != 0, 101); 12 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 13 | require(msg.pubkey() == tvm.pubkey(), 102); 14 | tvm.accept(); 15 | } 16 | 17 | // Function to be called from another contract. This function gets TvmCell argument and parses data from it. 18 | // Reserved keyword "functionID" allows to set function identifier manually. 19 | function receiveMessage(TvmCell cell) external functionID(0x12345678) { 20 | // Function toSlice allows to convert cell into slice. 21 | TvmSlice slice = cell.toSlice(); 22 | // Function size() returns size of data bits and number of refs in slice. 23 | (uint16 bits, uint8 refs) = slice.size(); 24 | require(bits == 0, 101); 25 | require(refs == 1, 102); 26 | // Function loadRefAsSlice() allows to load a cell from slice ref and convert it into a slice. 27 | TvmSlice slice2 = slice.loadRefAsSlice(); 28 | require(slice2.bits() == 267 + 16 + 8 + 11, 103); 29 | // Function load() allows to load arbitrary types from slice. 30 | (address addr, uint16 val0, uint8 val1) = slice2.load(address, uint16, uint8); 31 | require(addr.value == 123, 104); 32 | require(val0 == 123, 105); 33 | require(val1 == 123, 106); 34 | // Function loadUint() allows to load an unsigned integer of arbitrary bitsize. 35 | uint val2 = slice2.loadUint(11); 36 | require(val2 == 123, 107); 37 | counter++; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /solidity/15_MessageSender.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | 3 | // Contract that constructs message to call another contract function and sends it. 4 | contract MessageSender { 5 | 6 | // State variable storing the number of times 'sendMessage' function was called. 7 | uint public counter; 8 | 9 | constructor() { 10 | // check that contract's public key is set 11 | require(tvm.pubkey() != 0, 101); 12 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 13 | require(msg.pubkey() == tvm.pubkey(), 102); 14 | tvm.accept(); 15 | } 16 | 17 | modifier onlyOwnerAndAccept { 18 | require(msg.pubkey() == tvm.pubkey(), 102); 19 | tvm.accept(); 20 | _; 21 | } 22 | 23 | function sendMessage(address anotherContract) external onlyOwnerAndAccept { 24 | // Create an object of TVM type Builder. 25 | TvmBuilder builder; 26 | 27 | // Construct an object of type Message X, according to the TL-B scheme 28 | // described in spec. 29 | 30 | // currencies$_ grams:Grams other:ExtraCurrencyCollection 31 | // = CurrencyCollection; 32 | // 33 | // int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool 34 | // src:MsgAddress dest:MsgAddressInt 35 | // value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams 36 | // created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; 37 | // 38 | // message$_ {X:Type} info:CommonMsgInfoRelaxed 39 | // init:(Maybe (Either StateInit ^StateInit)) 40 | // body:(Either X ^X) = Message X; 41 | 42 | // Function storeUint() allows to store unsigned integer of arbitrary size in the builder. 43 | builder.storeUint(0, 1); // int_msg_info$0 - constant value 44 | builder.storeUint(1, 1); // ihr_disabled - true (currently disabled) 45 | builder.storeUint(1, 1); // bounce - true (we want this message to bounce to the sender 46 | // in case of error) 47 | builder.storeUint(0, 1); // bounced - false (this message is not bounced) 48 | 49 | builder.storeUint(0, 2); // src:MsgAddress we store addr_none$00 50 | // because blockchain software will replace 51 | // it with the current smart-contract address 52 | 53 | // Function store() allows to store variable of arbitrary type in the builder. 54 | builder.store(anotherContract); // dest:MsgAddressInt 55 | 56 | builder.storeUint(0x3989680, 28); // value:CurrencyCollection : grams:Grams - we attach 0.01 evers to the message, 57 | // which is equivalent to the 10,000 gas units in the base workchain. 58 | 59 | builder.storeUint(0, 1); // value:CurrencyCollection : other:ExtraCurrencyCollection - we store 0, because 60 | // we don't attach any other currencies. 61 | 62 | // In the next 4 fields we store zeroes, because blockchain software will replace them 63 | // with the correct values after this function finishes execution. 64 | builder.storeUint(0, 4); // ihr_fee:Grams 65 | builder.storeUint(0, 4); // fwd_fee:Grams 66 | builder.storeUint(0, 64); // created_lt:uint64 67 | builder.storeUint(0, 32); // created_at:uint32 68 | 69 | builder.storeUint(0, 1); // init:(Maybe (Either StateInit ^StateInit)) - we store 0, because we don't attach 70 | // initial state of a contract. 71 | 72 | builder.storeUint(0, 1); // body:(Either X ^X) - we store zero, because body of the message is stored in the 73 | // current cell, not in the ref. 74 | 75 | // Filling message body 76 | builder.store(uint32(0x12345678)); // store functionID of the receiving contract function to process this message. 77 | 78 | // Attach the cell with fixed value, that will be checked in the receiving contract. 79 | TvmBuilder builder2; 80 | builder2.store(address.makeAddrStd(0, 123), uint16(123), uint8(123)); 81 | builder2.storeUint(123, 11); 82 | 83 | TvmBuilder builder3; 84 | 85 | // Function storeRef() allows to store a builder in the ref of the builder. 86 | builder3.storeRef(builder2); 87 | 88 | builder.storeRef(builder3); 89 | 90 | // Function toCell() allows to finish builder and convert it to a cell. 91 | TvmCell message = builder.toCell(); 92 | tvm.sendrawmsg(message, 1); 93 | 94 | // increment the counter 95 | counter++; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /solidity/16_onBounceHandler.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | 3 | // Interface of the contract we want to interact with. 4 | interface AnotherContract { 5 | function receiveMoney(uint128 value) external; 6 | function receiveValues(uint16 value1, bool value2, uint64 value3) external; 7 | } 8 | 9 | // Contract that can handle errors during intercontract communication. 10 | contract MyContract { 11 | 12 | // Number of onBounce function calls 13 | uint public bounceCounter; 14 | 15 | // Saved arguments of function calls which were handled with failure. 16 | uint16 public invalidValue1; 17 | bool public invalidValue2; 18 | uint64 public invalidValue3; 19 | uint128 public invalidMoneyAmount; 20 | 21 | constructor() { 22 | // check that contract's public key is set 23 | require(tvm.pubkey() != 0, 101); 24 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 25 | require(msg.pubkey() == tvm.pubkey(), 102); 26 | tvm.accept(); 27 | } 28 | 29 | modifier onlyOwnerAndAccept { 30 | require(msg.pubkey() == tvm.pubkey(), 102); 31 | tvm.accept(); 32 | _; 33 | } 34 | 35 | // Function onBounce is executed on inbound messages with set flag. This function can not be called by 36 | // external/internal message 37 | // This function takes the body of the message as an argument. 38 | onBounce(TvmSlice slice) external { 39 | // The full original message in the first reference 40 | slice = slice.loadRef().toSlice(); 41 | 42 | // Increase the counter. 43 | bounceCounter++; 44 | 45 | // Start decoding the message. First 32 bits store the function id. 46 | uint32 functionId = slice.load(uint32); 47 | 48 | // Api function abi.functionId() allows to calculate function id by function name. 49 | if (functionId == abi.functionId(AnotherContract.receiveMoney)) { 50 | //Function decodeFunctionParams() allows to load function parameters from the slice. 51 | // After decoding we store the arguments of the function in the state variables. 52 | invalidMoneyAmount = abi.decodeFunctionParams(AnotherContract.receiveMoney, slice); 53 | } else if (functionId == abi.functionId(AnotherContract.receiveValues)) { 54 | (invalidValue1, invalidValue2, invalidValue3) = abi.decodeFunctionParams(AnotherContract.receiveValues, slice); 55 | } 56 | } 57 | 58 | // Function that calls another contract function and attaches some currency to the call. 59 | function sendMoney(address dest, coins amount) external view onlyOwnerAndAccept { 60 | AnotherContract(dest).receiveMoney{value: amount}(amount); 61 | } 62 | 63 | // Function that calls another contract function with arbitrary arguments. 64 | function sendValues(address dest, uint16 value1, bool value2, uint64 value3) external view onlyOwnerAndAccept { 65 | AnotherContract(dest).receiveValues(value1, value2, value3); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /solidity/17_ContractProducer.md: -------------------------------------------------------------------------------- 1 | # Deploy contracts from the contract 2 | 3 | ## Introduction 4 | 5 | Convenient way to deploy contracts from the contract is using `new ContractName{value: ..., ...}(arg0, arg1, ...)` construction. Obligatory call options for such construction: 6 | * Either `stateInit` or `code` must be set. Option `stateInit` is an initial state of the contract that contains `code` and `data`. Option `code` contains the code of the contract. 7 | * `value` - funds that will be transferred to the deployed address. 8 | 9 | Also, [another options](https://github.com/everx-labs/TVM-Solidity-Compiler/blob/master/API.md#deploy-via-new) can be set. 10 | 11 | It needs a solidity source code (*.sol file) and `stateInit` or `code` in base64 to deploy a contract from the contract. Solidity source code should be imported into the contract that will deploy new contract. `.code` file should be compiled with [tvm_linker](https://github.com/everx-labs/TVM-linker) to get `stateInit`/`code` in base64 of new contract. 12 | 13 | Let's consider how deploy a [SimpleWallet](https://github.com/everx-labs/samples/blob/master/solidity/17_SimpleWallet.sol) contract from the [WalletProducer](https://github.com/everx-labs/samples/blob/master/solidity/17_ContractProducer.sol) contract. In `WalletProducer` contract there are two public functions that deploy contracts: 14 | 1. `deployWalletUsingCode` - uses `code` to deploy a contract. 15 | 2. `deployWalletUsingStateInit` - uses `stateInit` to deploy a contract. 16 | 17 | **Note**: linker creates a `*.tvc` file. This file represents `stateInit`. `stateInit` has such fields as `code`, `data` etc. In first case we create `stateInit` onchain in smart contract's construction `new SimpleWallet{code: ..., ...}(...)`. In second case we create `stateInit` offchain using ever-cli to set public key and static variables in the `data` field of the `stateInit`. 18 | 19 | ## 1. Compiling and deploying the contract deployer (`WalletProducer`) 20 | 21 | ```bash 22 | # Generate 17_ContractProducer.code and 17_ContractProducer.abi.json 23 | solc 17_ContractProducer.sol 24 | 25 | # Generate the tvc file 26 | tvm_linker compile 17_ContractProducer.code --lib stdlib_sol.tvm -o 17_ContractProducer.tvc 27 | 28 | # Generate the keypair, set public key for ContractProducer contract and obtain contract address 29 | ever-cli genaddr --save --genkey key0.key 17_ContractProducer.tvc 17_ContractProducer.abi.json 30 | ``` 31 | 32 | **Note**: `--genkey` option can be changed to `--setkey` if you have a file with a keypair and want to use it. 33 | Last command updates the 17_ContractProducer.tvc file and prints the address of the contract (it's just hash of the updated file). Let's denote address of the WalletProducer as ``. Then send funds on `` address from your wallet or giver and deploy the `WalletProducer` contract: 34 | 35 | ```bash 36 | ever-cli deploy --sign key.key --abi 17_ContractProducer.abi.json 17_ContractProducer.tvc '{}' 37 | ``` 38 | 39 | ## 2. Compiling and linking `SimpleWallet` contract 40 | 41 | ```bash 42 | solc 17_SimpleWallet.sol 43 | 44 | tvm_linker compile 17_SimpleWallet.code --lib stdlib_sol.tvm -o 17_SimpleWallet.tvc 45 | ``` 46 | 47 | Last command generates the `17_SimpleWallet.tvc` file. 48 | 49 | ## 3. Deploying the `SimpleWallet` 50 | 51 | ### 3.1 Using `code` to deploy a contract from the contract 52 | 53 | Get `code` from `17_SimpleWallet.tvc` file: 54 | 55 | ```bash 56 | tvm_linker decode --tvc 17_SimpleWallet.tvc 57 | ``` 58 | 59 | Copy the `code` section from the linker output. Let's denote it as ``. 60 | 61 | Generate 2 public keys required to deploy a `SimpleWallet` contract: 62 | 63 | ```bash 64 | # Generate a seed phrase (it is used in other commands and denoted as 65 | ever-cli genphrase 66 | 67 | # Genrate a keypair from the seed phrase and save it to the file 68 | ever-cli getkeypair key1.key "" 69 | 70 | ever-cli genphrase 71 | ever-cli getkeypair key2.key "" 72 | ``` 73 | 74 | After this commands the keypair file can be opened and a public key can be copied from it to use later. 75 | Let's denote this 2 public keys as `` and ``. 76 | Example of the file with a keypair: 77 | 78 | ```json 79 | { 80 | "public": "03a4d60fc3d67eebb3a0267f172439f6ae0cfcfab4a0a44c0fe8a3c92b97d3dc", 81 | "secret": "7539d54d6ece6f727dc64268dd2e3975765b230e7d56ea5afa2b39eb62ff9d9d" 82 | } 83 | ``` 84 | 85 | Call function `deployWalletUsingCode` to deploy a new `SimpleWallet` contract: 86 | 87 | ```bash 88 | ever-cli call --sign key0.key --abi 17_ContractProducer.abi.json \ 89 | deployWalletUsingCode \ 90 | '{"walletCode":"","publicKey0":"0x","publicKey1":"0x"}' 91 | ``` 92 | 93 | This call returns address of the new `SimpleWallet` contract (It's denoted as ``). Then the new contract is successfully deployed, that can be checked by obtaining it's account state with `ever-cli`: 94 | 95 | ```bash 96 | ever-cli account 97 | ``` 98 | 99 | Example of the last command output: 100 | 101 | ``` 102 | acc_type: Active 103 | balance: 981590000 nanoton 104 | last_paid: 1632420089 105 | last_trans_lt: 0x17 106 | data(boc): b5ee9c720101020100b00001d103a4d60fc3d67eebb3a0267f172439f6ae0cfcfab4a0a44c0fe8a3c92b97d3dc0000000000000000800000000000000000000000000000000000000000000000000000000000001681d26b07e1eb3f75d9d0133f8b921cfb57067e7d5a50522607f451e495cbe9ee4001008300000000000000000000000000000000000000000000000000000000000000008009249c823901fed37b652b1eca5aeaef2aec694675317807a891fc3e90bf005c70 107 | code_hash: b4ac0a0f61ffddc1db5cec6c66abade1064b12dc528795699bc7cd0791b7868e 108 | ``` 109 | 110 | `SimpleWallet` can be called now using its abi: 111 | 112 | ```bash 113 | ever-cli call --abi 17_SimpleWallet.abi.json --sign key1.key sendTransaction '{"destination":"","value":"100_000_000","bounce":"false","flag":1}' 114 | ever-cli call --abi 17_SimpleWallet.abi.json --sign key2.key sendTransaction '{"destination":"","value":"100_000_000","bounce":"false","flag":1}' 115 | ``` 116 | 117 | ### 3.2 Using `stateInit` to deploy a contract from the contract 118 | 119 | Let's set a public key and static variables in the `data` field of the `SimpleWallet` contract `stateInit` struct. 120 | 121 | ```bash 122 | ever-cli genaddr --setkey key1.key 17_SimpleWallet.tvc 17_SimpleWallet.abi.json --save --data '{"m_id":"444", "m_creator":""}' 123 | ``` 124 | 125 | `*.tvc` file represents contract's `stateInit`. Get `stateInit` in base64: 126 | 127 | ```bash 128 | base64 -w 0 17_SimpleWallet.tvc 129 | ``` 130 | 131 | Let's denote output of the last command as ``. 132 | Call function `deployWalletUsingStateInit` to deploy a new `SimpleWallet` contract: 133 | 134 | ```bash 135 | ever-cli call --sign key0.key --abi 17_ContractProducer.abi.json deployWalletUsingStateInit '{"stateInit":"","publicKey1":"0x"}' 136 | ``` 137 | 138 | This call returns address of the new `SimpleWallet` contract. Then the new contract is successfully deployed, that can be checked as in previous section. 139 | 140 | # See also: 141 | 142 | * [Low level constructor message structure](https://github.com/everx-labs/samples/blob/master/solidity/17_low_level.md) 143 | -------------------------------------------------------------------------------- /solidity/17_ContractProducer.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | import "17_SimpleWallet.sol"; 5 | 6 | // Contract deployed wallet contracts. 7 | contract WalletProducer { 8 | 9 | // Number of deployed contracts. 10 | uint public m_deployedNumber = 0; 11 | 12 | constructor() { 13 | // Check that contract's public key is set. 14 | require(tvm.pubkey() != 0, 101); 15 | // Check that constructor is called by owner (message is signed by correct public key). 16 | require(tvm.pubkey() == msg.pubkey(), 102); 17 | 18 | tvm.accept(); 19 | } 20 | 21 | modifier checkOwnerAndAccept { 22 | require(tvm.pubkey() == msg.pubkey()); 23 | tvm.accept(); 24 | _; 25 | } 26 | 27 | // Function deploys a wallet contract with specified public key 28 | function deployWalletUsingCode(TvmCell walletCode, uint256 publicKey0, uint256 publicKey1) 29 | public 30 | checkOwnerAndAccept 31 | returns (address newWallet) 32 | { 33 | uint id = m_deployedNumber; 34 | uint n = 10; 35 | newWallet = new SimpleWallet{ 36 | // code of the new contract 37 | code: walletCode, 38 | // value sent to the new contract 39 | value: 1 ever, 40 | // contract's public key (in the new contract it can be obtained by calling 'tvm.pubkey()') 41 | pubkey: publicKey0, 42 | // New contract public variables initialization 43 | varInit: { 44 | m_id: id, 45 | m_creator: address(this) 46 | } 47 | }(n, publicKey1); // 'n' and 'publicKey1' are parameters of the constructor. 48 | 49 | ++m_deployedNumber; 50 | } 51 | 52 | function deployWalletUsingStateInit(TvmCell stateInit, uint256 publicKey1) 53 | public 54 | checkOwnerAndAccept 55 | returns (address newWallet) 56 | { 57 | uint n = 10; 58 | newWallet = new SimpleWallet{ 59 | // stateInit of the new contract 60 | stateInit: stateInit, 61 | // value sent to the new contract 62 | value: 1 ever 63 | }(n, publicKey1); // 'n' and 'publicKey1' are parameters of the constructor. 64 | 65 | ++m_deployedNumber; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /solidity/17_SimpleWallet.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | pragma AbiHeader pubkey; 4 | 5 | // Simple wallet for 2 owners. 6 | contract SimpleWallet { 7 | 8 | // Some magic number (Just example of a non static variable, that is calculated in the constructor) 9 | uint public m_sum; 10 | // pubkey of second owner 11 | uint256 m_secondPubkey; 12 | 13 | /* 14 | * static variables 15 | */ 16 | // id - sequence number of wallet that was created by `WalletProducer` contract 17 | uint static public m_id; 18 | // Address of contract created this contract. 19 | address static public m_creator; 20 | 21 | // This constructor is called by internal message 22 | constructor(uint n, uint256 secondPubkey) { 23 | // Note: here static variables are already set. 24 | 25 | // Check that constructor is called by creator. It's need to check that contract is not deployd by hacker 26 | // which can set his constructor parameters. It's possible because public variables is used to calculate 27 | // address of new account, but constructor parameters - no. 28 | require(msg.sender == m_creator, 103); 29 | 30 | // check that contract's public key is set 31 | require(tvm.pubkey() != 0, 101); 32 | // check that second public key is set 33 | require(secondPubkey != 0, 102); 34 | 35 | for (uint i = 0; i < n; ++i) { 36 | m_sum += i; 37 | } 38 | 39 | // It's good idea to mark 'm_secondPubkey' as public variable. But for example it's not public. 40 | m_secondPubkey = secondPubkey; 41 | } 42 | 43 | modifier onlyOwnerAndAccept { 44 | // Check that the function was called by some owner by external message and this message is signed. 45 | require( 46 | msg.pubkey() == tvm.pubkey() || // check that message is signed by first owner 47 | msg.pubkey() == m_secondPubkey // check that message is signed by second owner 48 | ); 49 | tvm.accept(); 50 | _; 51 | } 52 | 53 | // Function to make an arbitrary transfer. Called by external message 54 | function sendTransaction(address destination, coins value, bool bounce, uint8 flag) 55 | public view 56 | onlyOwnerAndAccept 57 | { 58 | // Perform transfer. 59 | destination.transfer(value, bounce, flag); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /solidity/17_low_level.md: -------------------------------------------------------------------------------- 1 | ## Constructor message structure 2 | 3 | All messages in TON have type **Message X**. Spec describes this type with this TL-B scheme: 4 | 5 | ```TL-B 6 | message$_ {X:Type} info:CommonMsgInfo 7 | init:(Maybe (Either StateInit ^StateInit)) 8 | body:(Either X ^X) = Message X; 9 | ``` 10 | 11 | CommonMsgInfo structure has 3 types: 12 | 13 | * Internal message info; 14 | * External inbound message info; 15 | * External outbound message info. 16 | 17 | To deploy a new contract from the contract we need to send an internal message. TL-B scheme for CommonMsgInfo of an internal message: 18 | 19 | ```TL-B 20 | int_msg_info$0 ihr_disabled:Bool bounce:Bool src:MsgAddressInt dest:MsgAddressInt 21 | value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams created_lt:uint64 22 | created_at:uint32 = CommonMsgInfo; 23 | ``` 24 | 25 | CommonMsgInfo contains structures, that are described by the following TL-B schemes: 26 | 27 | ```TL-B 28 | nothing$0 {X:Type} = Maybe X; 29 | just$1 {X:Type} value:X = Maybe X; 30 | left$0 {X:Type} {Y:Type} value:X = Either X Y; 31 | right$1 {X:Type} {Y:Type} value:Y = Either X Y; 32 | 33 | anycast_info$_ depth:(## 5) rewrite_pfx:(depth * Bit) = Anycast; 34 | 35 | addr_none$00 = MsgAddressExt; 36 | 37 | addr_std$10 anycast:(Maybe Anycast) 38 | workchain_id:int8 address:uint256 = MsgAddressInt; 39 | 40 | var_uint$_ {n:#} len:(#< n) value:(uint (len * 8)) 41 | = VarUInteger n; 42 | nanograms$_ amount:(VarUInteger 16) = Grams; 43 | 44 | extra_currencies$_ dict:(HashmapE 32 (VarUInteger 32)) 45 | = ExtraCurrencyCollection; 46 | currencies$_ grams:Grams other:ExtraCurrencyCollection 47 | = CurrencyCollection; 48 | ``` 49 | 50 | When we deploy a contract we need to attach a stateInit of that contract, but we use tvm_linker to obtain it, that's why we don't need to construct it. 51 | 52 | In case of deployment via **new** we also pass arguments to the constructor of the contract. That's why we need to attach constructor call as the body of the message. To do it we need to store **constructor** function identifier and encode it's parameters. 53 | 54 | In binary form the whole constructor message look like this: 55 | 56 | ```TVM_Message 57 | ---CommonMsgInfo--- 58 | 0 - int_msg_info$0 - constant value 59 | 1 - ihr_disabled - true (currently disabled) 60 | 1 - bounce - true (we want this message to bounce to the sender in case of error) 61 | 0 - bounced - false (this message is not bounced) 62 | 63 | 00 - src:MsgAddress we store addr_none$00 because blockchain software will replace 64 | it with the current smart-contract address 65 | 66 | - dest:MsgAddressInt: 67 | 10 - addr_std$10 - constant value 68 | 00000000 - workchain_id:int8 - store zero 69 | hash(stateInit) - address:uint256 - address of the contract is equal to hash of the stateInit 70 | 71 | - value:CurrencyCollection: (for example we will store 10_000_000 nanograms) 72 | - grams:Grams 73 | 0011 - len (because 10_000_000 < 2^(3*8)) 74 | x989680 - value (3*8 bits) 75 | 0 - other:ExtraCurrencyCollection (we don't attach any other currencies) 76 | 77 | - In the next 4 fields we store zeroes, because blockchain software will replace them 78 | with the correct values after this function finishes execution. 79 | 0000 - ihr_fee:Grams 80 | 0000 - fwd_fee:Grams 81 | x0000000000000000 - created_lt:uint64 82 | x00000000 - created_at:uint32 83 | ------------------ 84 | 85 | ---stateInit--- 86 | 1 - Maybe - 1 because we attach a stateInit 87 | 1 - Either StateInit ^StateInit - 1 because we store stateInit in a ref 88 | - Store stateInit in a ref of 89 | --------------- 90 | 91 | ---body--- 92 | 0/1 - Maybe: 0 if store body in current cell, otherwise 1 93 | constructorID - uint32 constructor identifier value 94 | 95 | ---------- 96 | ``` 97 | -------------------------------------------------------------------------------- /solidity/18_Interfaces.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | 3 | // Interface of the database contract. 4 | interface IOrderDatabase { 5 | function createAnOrder(uint amount, uint32 duration) external; 6 | } 7 | 8 | // Interface of the client contract. 9 | interface IClient { 10 | function setOrderKey(uint40 key) external; 11 | } -------------------------------------------------------------------------------- /solidity/18_OrderClient.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | 3 | import "18_Interfaces.sol"; 4 | 5 | // Contract that can create new orders in OrderDatabase contract. 6 | contract Client is IClient { 7 | 8 | // State variables: 9 | uint40 public orderKey; // Key of the last order in database. 10 | uint public counter; // Internal order counter. 11 | address database; 12 | 13 | constructor(address _database) { 14 | // check that contract's public key is set 15 | require(tvm.pubkey() != 0, 101); 16 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 17 | require(msg.pubkey() == tvm.pubkey(), 102); 18 | tvm.accept(); 19 | database = _database; 20 | } 21 | 22 | modifier onlyOwnerAndAccept { 23 | require(msg.pubkey() == tvm.pubkey(), 102); 24 | tvm.accept(); 25 | _; 26 | } 27 | 28 | modifier onlyDatabase { 29 | require(msg.sender == database); 30 | _; 31 | } 32 | 33 | // Function that calls database to create an order. 34 | function createAnOrder(uint amount, uint32 duration) external onlyOwnerAndAccept { 35 | IOrderDatabase(database).createAnOrder{value: 1 ever}(amount, duration); 36 | counter++; 37 | } 38 | 39 | // Callback function to set key of the last order. 40 | function setOrderKey(uint40 key) external override onlyDatabase { 41 | orderKey = key; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /solidity/18_OrderDatabase.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | 3 | import "18_Interfaces.sol"; 4 | 5 | // Contract that demonstrates usage of new mapping functions. 6 | contract OrderDatabase is IOrderDatabase { 7 | 8 | // Struct to store order information. 9 | struct Order { 10 | address client; 11 | uint amount; 12 | uint32 orderDuration; 13 | uint32 createdAt; 14 | } 15 | 16 | // State variable storing database of orders. 17 | mapping (uint40 => Order[]) database; 18 | 19 | // Exception codes: 20 | uint16 constant MESSAGE_SENDER_IS_NOT_THE_OWNER = 101; 21 | uint16 constant MAPPING_REPLACE_ERROR = 102; 22 | uint16 constant PLAIN_TRANSFERS_ARE_FORBIDDEN = 103; 23 | uint16 constant NO_PUBKEY = 104; 24 | 25 | // Constructor function initializes the database with an array of orders. 26 | constructor(Order[] initialOrders, uint32 duration) { 27 | require(tvm.pubkey() != 0, NO_PUBKEY); 28 | require(tvm.pubkey() == msg.pubkey(), MESSAGE_SENDER_IS_NOT_THE_OWNER); 29 | tvm.accept(); 30 | uint40 key = block.timestamp + duration; 31 | 32 | // Mapping function 'add(keyArg, valueArg)' sets the value associated with keyArg only if the keyArg does not exist in mapping. 33 | database.add(key, initialOrders); 34 | } 35 | 36 | // Modifier that allows function to accept calls only from the contract owner. 37 | modifier checkOwnerAndAccept { 38 | // Check that message was signed with contracts key. 39 | require(tvm.pubkey() == msg.pubkey(), MESSAGE_SENDER_IS_NOT_THE_OWNER); 40 | tvm.accept(); 41 | _; 42 | } 43 | 44 | // Internal inline function that removes expired orders from the database. 45 | // Keyword 'inline' means that this function is not called but it's code is inserted in place of call. 46 | function removeExpiredOrders() inline private { 47 | // Mapping function 'prevOrEq(keyArg)' computes the maximal key in mapping that is lexicographically less or equal to argument keyArg 48 | // and returns that key, associated value and status flag. 49 | optional(uint40, Order[]) val = database.prevOrEq(block.timestamp); 50 | 51 | while (val.hasValue()) { 52 | (uint40 expire, ) = val.get(); 53 | // Remove entity from mapping. 54 | delete database[expire]; 55 | 56 | // Obtain next array of expired orders. 57 | val = database.prevOrEq(block.timestamp); 58 | } 59 | } 60 | 61 | // Public function to create an order. 62 | function createAnOrder(uint amount, uint32 duration) external override { 63 | // Remove expired orders. 64 | removeExpiredOrders(); 65 | 66 | // Create new order. 67 | Order newOrder = Order(msg.sender, amount, duration, block.timestamp); 68 | Order[] orders; 69 | orders.push(newOrder); 70 | 71 | // Calculate expiration timestamp. 72 | uint40 key = block.timestamp + duration; 73 | 74 | // Mapping function 'getAdd(keyArg, valueArg)' sets the value associated with keyArg, but only if keyArg does not exist in 75 | // the mapping. Otherwise returns the old value without changing the dictionary. 76 | optional(Order[]) old_orders = database.getAdd(key, orders); 77 | if (old_orders.hasValue()) { 78 | Order[] orders_array = old_orders.get(); 79 | orders_array.push(newOrder); 80 | 81 | // Mapping function 'replace(keyArg, valueArg)' sets the value associated with keyArg only if keyArg exists in mapping. 82 | bool status = database.replace(key, orders_array); 83 | 84 | // Throw exception in case of unexpected error. 85 | require(status, MAPPING_REPLACE_ERROR); 86 | } 87 | 88 | // Call callback function to send order key to the client. 89 | IClient(msg.sender).setOrderKey(key); 90 | } 91 | 92 | // Function to get the first not expired order. 93 | function getNextOrder() external checkOwnerAndAccept returns (bool exists, Order nextOrder) { 94 | // Remove expired transactions. 95 | removeExpiredOrders(); 96 | 97 | // Obtain current timestamp. 98 | uint40 curTime = block.timestamp; 99 | 100 | // Mapping function 'nextOrEq(keyArg)' computes the minimal key in mapping that is lexicographically greater or equal to argument keyArg 101 | // and returns that key, associated value and status flag. 102 | optional(uint40, Order[]) n_orders = database.nextOrEq(curTime); 103 | if (n_orders.hasValue()) { 104 | (uint40 expire, Order[] orders) = n_orders.get(); 105 | // Get the last order from the array. 106 | nextOrder = orders[orders.length - 1]; 107 | 108 | // Set the return flag. 109 | exists = true; 110 | 111 | // Delete the las order from the array. 112 | orders.pop(); 113 | 114 | if (orders.length == 0) 115 | // If the array is empty, remove entity from the mapping. 116 | delete database[expire]; 117 | else 118 | // Replace value in the database. 119 | database.replace(expire, orders); 120 | } 121 | } 122 | 123 | // Function that allows owner to change the database. 124 | function setEntity(bool replace, uint40 key, Order[] newOrders) 125 | public 126 | checkOwnerAndAccept 127 | returns (bool status, Order[] oldOrders) 128 | { 129 | // Remove expired transactions. 130 | removeExpiredOrders(); 131 | 132 | // 'replace' flag set means that owner wants to replace the value, if it is not set and key doesn't exist in the database 133 | // false status should be returned and database shouldn't be changed. 134 | if (replace) { 135 | // Mapping function 'getReplace(keyArg, valueArg)' sets the value associated with keyArg, but only if keyArg exists in 136 | // the mapping. On success returns the old value, otherwise returns a null value. 137 | optional(Order[]) orders = database.getReplace(key, newOrders); 138 | status = orders.hasValue(); 139 | if (status) { 140 | oldOrders = orders.get(); 141 | } 142 | } else { 143 | // If 'replace' is not set, contract just has to set the value and return the old value in case it existed. 144 | 145 | // Mapping function 'getSet(keyArg, valueArg)' sets the value associated with keyArg and returns the old value associated 146 | // with keyArg, if it exists, otherwise returns a null value. 147 | optional(Order[]) orders = database.getSet(key, newOrders); 148 | status = orders.hasValue(); 149 | if (status) { 150 | oldOrders = orders.get(); 151 | } 152 | } 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /solidity/19_CasinoClient.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | 3 | import "19_CasinoInterfaces.sol"; 4 | 5 | /// @title Casino owner smart contract. 6 | 7 | contract CasinoClient is ICasinoClient { 8 | 9 | // State variables: 10 | address m_casino; 11 | uint8 public m_lastCode; 12 | uint128 public m_lastComment; 13 | 14 | constructor(address casino) { 15 | require(tvm.pubkey() != 0, 103); 16 | require(msg.pubkey() == tvm.pubkey(), 102); 17 | tvm.accept(); 18 | 19 | m_casino = casino; 20 | } 21 | 22 | // Callback function to get answer from the casino. 23 | function receiveAnswer(uint8 code, uint128 comment) external override { 24 | require(msg.sender == m_casino, 101); 25 | tvm.accept(); 26 | m_lastCode = code; 27 | m_lastComment = comment; 28 | } 29 | 30 | modifier onlyOwner { 31 | require(msg.pubkey() == tvm.pubkey(), 102); 32 | tvm.accept(); 33 | _; 34 | } 35 | 36 | function setCasino(address casino) external onlyOwner { 37 | m_casino = casino; 38 | } 39 | 40 | function bet(coins value, uint8 number) external view onlyOwner { 41 | ICasino(m_casino).singleBet{value: value}(number); 42 | } 43 | 44 | function betDozen(coins value, uint8 number) external view onlyOwner { 45 | ICasino(m_casino).dozenBet{value: value}(number); 46 | } 47 | 48 | function betColumn(coins value, uint8 number) external view onlyOwner { 49 | ICasino(m_casino).columnBet{value: value}(number); 50 | } 51 | 52 | function betColor(coins value, bool isRed) external view onlyOwner { 53 | ICasino(m_casino).colorBet{value: value}(isRed); 54 | } 55 | 56 | function betGreatSmall(coins value, bool isGreat) external view onlyOwner { 57 | ICasino(m_casino).greatSmallBet{value: value}(isGreat); 58 | } 59 | 60 | function betParity(coins value, bool isEven) external view onlyOwner { 61 | ICasino(m_casino).parityBet{value: value}(isEven); 62 | } 63 | 64 | receive() external pure {} 65 | } 66 | 67 | 68 | -------------------------------------------------------------------------------- /solidity/19_CasinoInterfaces.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | 3 | // Interface of the Casino contract. 4 | interface ICasino { 5 | function singleBet(uint8 number) external view; 6 | function dozenBet(uint8 number) external view; 7 | function columnBet(uint8 number) external view; 8 | function greatSmallBet(bool isGreat) external view; 9 | function parityBet(bool isEven) external view; 10 | function colorBet(bool isRed) external view; 11 | function getSeed() external view; 12 | function withdrawBenefits() external view; 13 | function receiveFunds() external pure; 14 | } 15 | 16 | interface ICasinoClient { 17 | function receiveAnswer(uint8 code, uint128 comment) external; 18 | } 19 | 20 | interface ICasinoOwner { 21 | function returnSeed(uint seed) external; 22 | } 23 | -------------------------------------------------------------------------------- /solidity/19_CasinoOwner.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | 3 | import "19_CasinoInterfaces.sol"; 4 | 5 | /// @title Casino owner smart contract. 6 | 7 | contract CasinoOwner is ICasinoOwner { 8 | 9 | // State variables: 10 | uint public m_casinoSeed; // modifier public creates a getter function with the same name as a state variable. 11 | address m_casino; 12 | 13 | constructor(address casino) { 14 | require(tvm.pubkey() != 0, 103); 15 | require(msg.pubkey() == tvm.pubkey(), 102); 16 | tvm.accept(); 17 | 18 | m_casino = casino; 19 | } 20 | 21 | // Callback function to get seed value from the casino. 22 | function returnSeed(uint seed) external override { 23 | require(msg.sender == m_casino, 101); 24 | tvm.accept(); 25 | m_casinoSeed = seed; 26 | } 27 | 28 | modifier onlyOwner { 29 | require(msg.pubkey() == tvm.pubkey(), 102); 30 | tvm.accept(); 31 | _; 32 | } 33 | 34 | function updateSeed() external view onlyOwner { 35 | ICasino(m_casino).getSeed(); 36 | } 37 | 38 | function withdrawBenefits() external view onlyOwner { 39 | ICasino(m_casino).withdrawBenefits(); 40 | } 41 | 42 | function replenishCasino(coins value) external view onlyOwner { 43 | ICasino(m_casino).receiveFunds{value: value}(); 44 | } 45 | 46 | receive() external pure {} 47 | } 48 | -------------------------------------------------------------------------------- /solidity/1_Accumulator.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | contract Accumulator { 5 | 6 | // State variable storing the sum of arguments that were passed to function 'add', 7 | uint public sum; 8 | 9 | constructor() { 10 | // check that contract's public key is set 11 | require(tvm.pubkey() != 0, 101); 12 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 13 | require(msg.pubkey() == tvm.pubkey(), 102); 14 | tvm.accept(); 15 | } 16 | 17 | // Modifier that allows to accept some external messages 18 | modifier checkOwnerAndAccept { 19 | // Check that message was signed with contracts key. 20 | require(msg.pubkey() == tvm.pubkey(), 102); 21 | tvm.accept(); 22 | _; 23 | } 24 | 25 | // Function that adds its argument to the state variable. 26 | function add(uint delta) external checkOwnerAndAccept { 27 | sum += delta; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /solidity/1_Accumulator_no_ctor.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | contract Accumulator { 5 | 6 | // State variable storing the sum of arguments that were passed to function 'add', 7 | uint public sum; 8 | 9 | // Modifier that allows to accept some external messages 10 | modifier checkOwnerAndAccept { 11 | // Check that message was signed with contracts key. 12 | require(msg.pubkey() == tvm.pubkey(), 102); 13 | tvm.accept(); 14 | _; 15 | } 16 | 17 | // Function that adds its argument to the state variable. 18 | function add(uint delta) external checkOwnerAndAccept { 19 | sum += delta; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /solidity/20_bomber.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | import "20_interface.sol"; 5 | 6 | contract Bomber is IBomber { 7 | 8 | uint constant param0 = 10; 9 | uint constant param1 = 100; 10 | 11 | mapping(uint => uint) map; 12 | 13 | constructor () { 14 | tvm.accept(); 15 | } 16 | 17 | modifier onlyOwner { 18 | // Check that function is called by external message 19 | require(tvm.pubkey() != 0 && tvm.pubkey() == msg.pubkey()); 20 | tvm.accept(); 21 | _; 22 | } 23 | 24 | function testSend0(address addr) external view onlyOwner { 25 | // Bomber sends 1 ever but Sink will get less than 1 ever (~ 0.998 ever). 26 | // Because forward fee is subtracted from 1 ever 27 | ISink(addr).receive0{value: 1 ever, flag: 0}(param0, param1); 28 | } 29 | 30 | function testSend1(address addr) external view onlyOwner { 31 | // Bomber sends 1 ever and Sink will get 1 ever exactly. 32 | // Forward fee is subtracted from balance of this contract. 33 | ISink(addr).receive0{value: 1 ever, flag: 1}(param0, param1); 34 | } 35 | 36 | function testSend128(address addr) external view onlyOwner { 37 | // Bomber sends all its balance and Sink will get all that funds minus forward fee. 38 | // The Bomber's balance will be equal to zero. 39 | ISink(addr).receive0{value: 0, flag: 128}(param0, param1); 40 | // Note: parameter "value" is ignored by the virtual machine. It can be set to any value, for example zero. 41 | } 42 | 43 | function testSend160(address addr) external view onlyOwner { 44 | // Bomber sends all its balance and Sink will get all that funds minus forward fee. 45 | // The Bomber's balance will be equal to zero and the contract will be destroyed. 46 | ISink(addr).receive0{value: 0, flag: 128 + 32}(param0, param1); 47 | // Note: parameter "value" is ignored by the virtual machine. It can be set to any value, for example zero. 48 | // Note: After destroying the contract can be redeployed on the same address. 49 | } 50 | 51 | // The function can be called only by internal message (in function there is no `tvm.accept()`) 52 | function testValue0Flag64() external override { 53 | // This function was called by internal message. In this function works with mapping. In this case amount of 54 | // used gas depends on size of the mapping. Caller doesn't know how much value should be attached to cover gas. 55 | // So, caller can attach some big value and this contract will return change. 56 | map[rnd.next()] = rnd.next(); 57 | // Return change. 58 | // Forward fee is subtracted from change. See also function `testSend0`. 59 | ISink(msg.sender).receive0{value: 0, flag: 64}(param0, param1); 60 | } 61 | 62 | function testValue1Flag64() external override { 63 | map[rnd.next()] = rnd.next(); 64 | // Returns change and sends 1 ever. 65 | // Forward fee is subtracted from (change + 1 ever). See also function `testSend0`. 66 | ISink(msg.sender).receive0{value: 1 ever, flag: 64}(param0, param1); 67 | } 68 | 69 | function testValue1Flag65() external override { 70 | map[rnd.next()] = rnd.next(); 71 | // Returns change and sends 1 ever. 72 | // Forward fee is subtracted from Bomber's balance. See also function `testSend1` 73 | ISink(msg.sender).receive0{value: 1 ever, flag: 64 + 1}(param0, param1); 74 | } 75 | 76 | function testFlag2(address addr) external view onlyOwner { 77 | // Contract sends 3 messages with values: 1 ever, 1e9 ever and 1 ever. 78 | // Let contract has balance equal to 5 ever. Then it's obvious that it can't send 1e9 ever. It should cause fail 79 | // of action phase. But the second message has flag: 2. It means that any errors arising while processing this 80 | // message during the action phase should be ignored. 81 | // That's why contract will send successfully the first and third messages. The second message will be ignored. 82 | ISink(addr).receive0{value: 1 ever, flag: 0}(param0, param1); 83 | ISink(addr).receive0{value: 1e9 ever, flag: 2}(param0, param1); 84 | ISink(addr).receive0{value: 1 ever, flag: 0}(param0, param1); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /solidity/20_interface.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | interface ISink { 5 | function receive0(uint _a, uint _b) external; 6 | } 7 | 8 | interface IBomber { 9 | function testValue0Flag64() external; 10 | function testValue1Flag64() external; 11 | function testValue1Flag65() external; 12 | } -------------------------------------------------------------------------------- /solidity/20_sink.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | import "20_interface.sol"; 5 | 6 | contract Sink is ISink { 7 | 8 | uint public counter = 0; 9 | uint a; 10 | uint b; 11 | 12 | constructor () { 13 | tvm.accept(); 14 | } 15 | 16 | receive() external { 17 | ++counter; 18 | } 19 | 20 | function receive0(uint _a, uint _b) external override { 21 | ++counter; 22 | a = _a; 23 | b = _b; 24 | } 25 | 26 | modifier onlyOwner { 27 | require(tvm.pubkey() != 0 && tvm.pubkey() == msg.pubkey()); 28 | tvm.accept(); 29 | _; 30 | } 31 | 32 | function testFlag64(address addr, uint mode) external view onlyOwner { 33 | if (mode == 0) 34 | IBomber(addr).testValue0Flag64{value: 1 ever}(); 35 | else if (mode == 1) 36 | IBomber(addr).testValue1Flag64{value: 1 ever}(); 37 | else if (mode == 2) 38 | IBomber(addr).testValue1Flag65{value: 1 ever}(); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /solidity/21_self_deploy.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | // This sample shows how the contract can deploy another contract of the same type 5 | 6 | contract SelfDeployer { 7 | 8 | uint static m_value; 9 | address static m_parent; 10 | 11 | uint m_depth; 12 | mapping(address => bool) m_chilred; 13 | 14 | constructor(uint _depth) { 15 | require( 16 | (tvm.pubkey() != 0 && tvm.pubkey() == msg.pubkey()) || 17 | (msg.sender == m_parent) 18 | ); 19 | tvm.accept(); 20 | m_depth = _depth; 21 | } 22 | 23 | modifier onlyOwner { 24 | require(tvm.pubkey() != 0 && tvm.pubkey() == msg.pubkey()); 25 | tvm.accept(); 26 | _; 27 | } 28 | 29 | function deploy(uint _value) onlyOwner external returns (address addr) { 30 | TvmCell code = tvm.code(); 31 | addr = new SelfDeployer{ 32 | value: 2 ever, 33 | code: code, 34 | varInit: { 35 | m_value: _value, 36 | m_parent: address(this) 37 | } 38 | }(m_depth + 1); 39 | m_chilred[addr] = true; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /solidity/22_sender.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | import "22_sink.sol"; 5 | 6 | contract Sender { 7 | 8 | uint public m_value; 9 | 10 | constructor() onlyOwnerAccept { 11 | } 12 | 13 | modifier onlyOwnerAccept { 14 | require(tvm.pubkey() != 0 && tvm.pubkey() == msg.pubkey()); 15 | tvm.accept(); 16 | _; 17 | } 18 | 19 | function testSend(address dest) onlyOwnerAccept external view { 20 | // generates cell which contains message which calls another contract by internal outbound message 21 | TvmCell message = abi.encodeIntMsg({ 22 | dest: dest, 23 | value: 1 ever, 24 | call: {Sink.inc, 5, 22}, 25 | bounce: true 26 | }); 27 | 28 | for (int i = 0; i < 10; ++i) { 29 | tvm.sendrawmsg(message, 0); 30 | } 31 | } 32 | 33 | function testResponsibleSend(address dest) onlyOwnerAccept external view { 34 | TvmCell message = abi.encodeIntMsg({ 35 | dest: dest, 36 | value: 1 ever, 37 | call: {Sink.incAndGetCount, Sender.onReceiveCount, 15, 22}, // here we must set callback function 'onReceiveCount' 38 | // because function `incAndGetCount` is responsible 39 | bounce: true 40 | }); 41 | 42 | tvm.sendrawmsg(message, 0); 43 | } 44 | 45 | function onReceiveCount(uint count) external { 46 | // just save received value in state variable 47 | m_value = count; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /solidity/22_sink.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | contract Sink { 5 | 6 | uint public m_counter; 7 | uint public m_value; 8 | 9 | constructor() onlyOwnerAccept { 10 | } 11 | 12 | modifier onlyOwnerAccept { 13 | require(tvm.pubkey() != 0 && tvm.pubkey() == msg.pubkey()); 14 | tvm.accept(); 15 | _; 16 | } 17 | 18 | function inc(uint delta, uint value) external { 19 | m_counter += delta; 20 | m_value = value; 21 | } 22 | 23 | function incAndGetCount(uint delta, uint value) external responsible returns (uint) { 24 | m_counter += delta; 25 | m_value = value; 26 | return{value: 0, bounce: false, flag: 64} m_counter; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /solidity/23_rawReserve.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | contract Reserve { 5 | 6 | mapping(uint => uint) m_map; 7 | 8 | constructor() onlyOwnerAccept { 9 | } 10 | 11 | modifier onlyOwnerAccept { 12 | require(tvm.pubkey() != 0 && tvm.pubkey() == msg.pubkey()); 13 | tvm.accept(); 14 | _; 15 | } 16 | 17 | function doHardWork() private { 18 | for (uint i = 0; i < 50; ++i) { 19 | m_map[i] = i * i; 20 | } 21 | } 22 | 23 | function reserve0() external { 24 | doHardWork(); 25 | // Contract reserves exactly 1 ever. 26 | // If contract balance is less than 1 ever, an exception is thrown at the action phase. 27 | tvm.rawReserve(1 ever, 0); 28 | msg.sender.transfer({value: 0, flag: 128}); // sends all rest balance 29 | // after a successful call of `reserve0` contract's balance will equal to 1 ever 30 | } 31 | 32 | function reserve1() external { 33 | doHardWork(); 34 | // Contract reserves all but 1 ever from the remaining balance of the account. 35 | // If contract balance is less than 1 ever, an exception is thrown at the action phase. 36 | tvm.rawReserve(1 ever, 1); 37 | msg.sender.transfer({value: 0, flag: 128}); 38 | // Let's consider that the contract had balance equal to 5 ever before the function `reserve1` 39 | // was called by an internal message with value of 0.5 ever. 40 | // `doHardWork` function call consumes gas, which is roughly equal to 0.2 ever in workchain. 41 | // After compute phase contract's balance will be approximately equal to 5 + 0.5 - 0.2 = 5.3 ever. 42 | // `rawReserve` will reserve 5.3 - 1 = 4.3 ever. 43 | // `msg.sender.transfer` will send `all_balance` - `reserved_value` = 5.3 - 4.3 = 1 ever 44 | // Finally, after a successful call of `reserve1` contract's balance will be approximately equal to 4.3 ever 45 | } 46 | 47 | function reserve2() external { 48 | doHardWork(); 49 | // contract reserves at most 1 ever. Never throws exceptions 50 | tvm.rawReserve(1 ever, 0 + 2); 51 | msg.sender.transfer({value: 0, flag: 128}); // sends all rest balance 52 | // after a successful call of `reserve2` contract's balance will be less than or equal to 1 ever 53 | } 54 | 55 | function reserve3() external { 56 | doHardWork(); 57 | // Contract reserves all but 1 ever from the remaining balance of the account 58 | // or 0 ever if remaining balance less than 1 ever. 59 | // Never throws exceptions. 60 | tvm.rawReserve(1 ever, 1 + 2); 61 | msg.sender.transfer({value: 0, flag: 128}); 62 | } 63 | 64 | function reserve12() external { 65 | doHardWork(); 66 | tvm.rawReserve(0.3 ever, 4 + 8); 67 | msg.sender.transfer({value: 0, flag: 128}); 68 | // Let's consider that the contract had balance equal to 5 ever before the function `reserve4` 69 | // was called by an internal message with value of 0.5 ever. 70 | // It means that original_balance (see API.md for details) is approximately equal to 5 ever. 71 | // `tvm.rawReserve` will reserve 5 - 0.3 = 4.7 evers 72 | // Throws an exception if at the action phase there is not enough funds. 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /solidity/23_sender.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | import "23_rawReserve.sol"; 5 | 6 | contract MyContract { 7 | 8 | constructor () { 9 | tvm.accept(); 10 | } 11 | 12 | modifier onlyOwnerAccept { 13 | require(tvm.pubkey() != 0 && tvm.pubkey() == msg.pubkey()); 14 | tvm.accept(); 15 | _; 16 | } 17 | 18 | function transfer(address addr, coins value) external view onlyOwnerAccept { 19 | addr.transfer({value: value, flag: 1}); 20 | } 21 | 22 | function send(address addr, uint mode) external view onlyOwnerAccept { 23 | if (mode == 0) { 24 | Reserve(addr).reserve0{value: 0.5 ever, flag: 1}(); 25 | } else if (mode == 1) { 26 | Reserve(addr).reserve1{value: 0.5 ever, flag: 1}(); 27 | } else if (mode == 2) { 28 | Reserve(addr).reserve2{value: 0.5 ever, flag: 1}(); 29 | } else if (mode == 3) { 30 | Reserve(addr).reserve3{value: 0.5 ever, flag: 1}(); 31 | } else if (mode == 4) { 32 | Reserve(addr).reserve12{value: 0.5 ever, flag: 1}(); 33 | } else { 34 | revert(101); 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /solidity/24_Client.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | import "24_IClient.sol"; 4 | import "24_ISquareProvider.sol"; 5 | 6 | contract Client is IClient { 7 | ISquareProvider m_provider; 8 | uint64 public m_square; 9 | 10 | constructor(ISquareProvider provider) { 11 | require(tvm.pubkey() != 0, 100); 12 | require(msg.pubkey() == tvm.pubkey(), 101); 13 | tvm.accept(); 14 | m_provider = provider; 15 | } 16 | 17 | function getRectangleSquare() external view { 18 | require(msg.pubkey() == tvm.pubkey(), 101); 19 | tvm.accept(); 20 | m_provider.startGettingRectangleSquare{value: 5 ever}(); 21 | } 22 | 23 | function setRectangleSquare(uint64 square) external override { 24 | require(msg.sender == m_provider, 101); 25 | m_square = square; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /solidity/24_IClient.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | interface IClient { 5 | function setRectangleSquare(uint64 square) external; 6 | } 7 | -------------------------------------------------------------------------------- /solidity/24_ISquareProvider.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | interface ISquareProvider { 5 | function startGettingRectangleSquare() external; 6 | } 7 | -------------------------------------------------------------------------------- /solidity/24_LengthProvider.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | contract LengthProvider { 5 | constructor () { 6 | tvm.accept(); 7 | } 8 | 9 | function getLength(uint64 id) pure external responsible returns(uint64, uint32) { 10 | return{value: 0, bounce: false, flag: 64}(id, 43); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /solidity/24_SquareProvider.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | import "24_IClient.sol"; 4 | import "24_ISquareProvider.sol"; 5 | import "24_LengthProvider.sol"; 6 | import "24_WidthProvider.sol"; 7 | 8 | struct Context { 9 | optional(uint32) width; 10 | optional(uint32) length; 11 | IClient client; 12 | } 13 | 14 | contract SquareProvider is ISquareProvider { 15 | WidthProvider m_widthProvider; // contract address that returns width of rectangle 16 | LengthProvider m_lengthProvider; // contract address that returns length of rectangle 17 | uint64 m_idRequest = 0; // sequence number for requests 18 | mapping(uint64 => Context) m_context; // for storing context of requests 19 | uint constant FEE = 2 ever; // some fee for storing context, calling another contracts, etc. Must be calculated more precisely 20 | uint constant MAX_TRANS_FEE = 0.5 ever; // max possible fee for `startGettingRectangleSquare` function. May be calculated more precisely 21 | 22 | constructor(address widthProvider, address lengthProvider) { 23 | require(tvm.pubkey() != 0, 100); 24 | require(msg.pubkey() == tvm.pubkey(), 101); 25 | tvm.accept(); 26 | m_widthProvider = WidthProvider(widthProvider); 27 | m_lengthProvider = LengthProvider(lengthProvider); 28 | } 29 | 30 | function startGettingRectangleSquare() external override { 31 | // check if msg.value is enough for successful completion computing phase and reserving (on action phase) 32 | require(msg.value >= MAX_TRANS_FEE + FEE, 102); 33 | 34 | // these 2 lines have such effect: balance of the contract will be equal (original_balance + FEE) 35 | // and the sender will get the change from MAX_TRANS_FEE 36 | tvm.rawReserve(FEE, 4); // reserve original_balance + FEE. 37 | msg.sender.transfer({value: 0, flag: 128}); // return change 38 | 39 | // create and save context of call 40 | uint64 id = m_idRequest; 41 | ++m_idRequest; 42 | m_context[id] = Context(null, null, IClient(msg.sender)); 43 | 44 | m_widthProvider.getWidth{value: 0.5 ever, callback: SquareProvider.onGetWidth}(id); 45 | m_lengthProvider.getLength{value: 0.5 ever, callback: SquareProvider.onGetLength}(id); 46 | } 47 | 48 | function onGetWidth(uint64 id, uint32 width) external { 49 | require(msg.sender == m_widthProvider); 50 | 51 | Context con = m_context.at(id); 52 | con.width = width; 53 | updateOrReturnValue(id, con); 54 | } 55 | 56 | function onGetLength(uint64 id, uint32 length) external { 57 | require(msg.sender == m_lengthProvider); 58 | 59 | Context con = m_context.at(id); 60 | con.length = length; 61 | updateOrReturnValue(id, con); 62 | } 63 | 64 | function updateOrReturnValue(uint64 id, Context con) private { 65 | if (!con.width.hasValue() || !con.length.hasValue()) { 66 | m_context[id] = con; 67 | return ; 68 | } 69 | 70 | // delete context and return value to client 71 | delete m_context[id]; 72 | uint64 square = uint64(con.width.get()) * con.length.get(); 73 | con.client.setRectangleSquare{value: 0.5 ever}(square); 74 | } 75 | } 76 | 77 | -------------------------------------------------------------------------------- /solidity/24_WidthProvider.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | contract WidthProvider { 5 | constructor () { 6 | tvm.accept(); 7 | } 8 | 9 | function getWidth(uint64 id) pure external responsible returns(uint64, uint32) { 10 | return{value: 0, bounce: false, flag: 64}(id, 43); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /solidity/25_Config.md: -------------------------------------------------------------------------------- 1 | # Function arguments specification 2 | 3 | Sometimes it can be not obvious in which way function arguments should be specified, 4 | especially if it is a large structure with different and complex fields. 5 | It is generally described in [abi doc](https://github.com/everx-labs/ever-abi/blob/master/docs/ABI_2.1_spec.md). 6 | And this example was made to help users clear this moment. 7 | 8 | To deploy this sample contract user should call ever-cli command with such example parameters: 9 | 10 | ```bash 11 | $ ever-cli deploy --sign keys/key0 --wc 0 --abi 25_Config.abi.json 25_Config.tvc '{ 12 | "initial_config":{ 13 | "_bool":true, 14 | "_i256":1, 15 | "_u256":2, 16 | "_u8":3, 17 | "_i16":4, 18 | "_i7":5, 19 | "_u53":6, 20 | "dest":"0:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", 21 | "cell":"te6ccgEBAQEAAgAAAA==", 22 | "map":{"0":"0:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef","1":"0:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdee"} 23 | } 24 | }' 25 | ``` 26 | 27 | And the same way to call function: 28 | 29 | ```bash 30 | $ ever-cli -j call --sign keys/key0 --abi 25_Config.abi.json 0:08ce0221bce8fd710225470610ccaec8617aff7fb074b76edb51f1ed009f0b3d change_config '{ 31 | "new_config":{ 32 | "_bool":"false", 33 | "_i256":"0x10", 34 | "_u256":"0x20", 35 | "_u8":"0x30", 36 | "_i16":"0x40", 37 | "_i7":"0x33", 38 | "_u53":"0x60", 39 | "dest":"0:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", 40 | "cell":"", 41 | "map":{} 42 | } 43 | }' 44 | ``` 45 | 46 | **Note**: One of the best ways to discover the right specification of arguments is to inspect return of the function 47 | with the same return types: 48 | 49 | ```bash 50 | $ ever-cli -j run --abi ../samples/25_Config.abi.json 0:d8782a0a8725f8015a09eaa850cdddf3e9c26e1f94e2ce73187534d01d348ee3 config {} 51 | { 52 | "config": { 53 | "_bool": true, 54 | "_i256": "1", 55 | "_u256": "0x0000000000000000000000000000000000000000000000000000000000000002", 56 | "_u8": "3", 57 | "_i16": "4", 58 | "_i7": "5", 59 | "_u53": "6", 60 | "dest": "0:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", 61 | "cell": "te6ccgEBAQEAAgAAAA==", 62 | "map": { 63 | "0": "0:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", 64 | "1": "0:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdee" 65 | } 66 | } 67 | } 68 | ``` -------------------------------------------------------------------------------- /solidity/25_Config.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | // Example of a structure with different fields 5 | struct Config { 6 | // boolean field 7 | bool _bool; 8 | // integer fields 9 | int _i256; 10 | uint _u256; 11 | uint8 _u8; 12 | int16 _i16; 13 | int7 _i7; 14 | uint53 _u53; 15 | // address type 16 | address dest; 17 | // cell type 18 | TvmCell cell; 19 | // mapping type 20 | mapping(uint128 => address) map; 21 | } 22 | 23 | contract ConfigContract { 24 | 25 | // State variable that stores current config 26 | Config public config; 27 | 28 | // Constructor function that initializes config 29 | constructor(Config initial_config) { 30 | // check that contract's public key is set 31 | require(tvm.pubkey() != 0, 101); 32 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 33 | require(msg.pubkey() == tvm.pubkey(), 102); 34 | tvm.accept(); 35 | // store the initial config 36 | config = initial_config; 37 | } 38 | 39 | function change_config(Config new_config) external { 40 | // Check that message was signed with contracts key. 41 | require(msg.pubkey() == tvm.pubkey(), 102); 42 | tvm.accept(); 43 | config = new_config; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /solidity/2_StorageClient.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | // import interface 'Storage' 5 | import "2_UintStorage.sol"; 6 | 7 | // This contract calls the remote contract function with parameter to store a uint value in the remote contract's 8 | // persistent memory. 9 | contract StorageClient { 10 | 11 | constructor() { 12 | // check that contract's public key is set 13 | require(tvm.pubkey() != 0, 101); 14 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 15 | require(msg.pubkey() == tvm.pubkey(), 102); 16 | tvm.accept(); 17 | } 18 | 19 | modifier checkOwnerAndAccept { 20 | // Check that message was signed with contracts key. 21 | require(msg.pubkey() == tvm.pubkey(), 102); 22 | tvm.accept(); 23 | _; 24 | } 25 | 26 | function store(Storage storageAddress, uint value) external view checkOwnerAndAccept { 27 | // Call the remote contract function with parameter. 28 | storageAddress.storeValue(value); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /solidity/2_UintStorage.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | // Remote contract interface. 5 | interface Storage { 6 | function storeValue(uint value) external; 7 | } 8 | 9 | // This contract implements 'Storage' interface. 10 | contract UintStorage is Storage { 11 | 12 | // State variables: 13 | uint public value; // storage for a uint value; 14 | address public clientAddress; // last caller address. 15 | 16 | constructor () { 17 | tvm.accept(); 18 | } 19 | 20 | // This function can be called only by another contract. There is no 'tvm.accept()' 21 | function storeValue(uint v) external override { 22 | // save parameter v to contract's state variable 23 | value = v; 24 | // save address of callee 25 | clientAddress = msg.sender; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /solidity/3_Borrower.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | import "3_Loaner.sol"; 5 | 6 | // This contract calls the remote contract function with parameter to ask remote contract to transfer 7 | // of currency. 8 | contract Borrower { 9 | 10 | constructor() { 11 | // check that contract's public key is set 12 | require(tvm.pubkey() != 0, 101); 13 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 14 | require(msg.pubkey() == tvm.pubkey(), 102); 15 | tvm.accept(); 16 | } 17 | 18 | modifier checkOwnerAndAccept { 19 | // Check that message was signed with contracts key. 20 | require(msg.pubkey() == tvm.pubkey(), 102); 21 | tvm.accept(); 22 | _; 23 | } 24 | 25 | function askForALoan(Loaner loanerAddress, coins amount) external view checkOwnerAndAccept { 26 | // Call the remote contract function with parameter. 27 | loanerAddress.borrow(amount); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /solidity/3_Loaner.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | interface Loaner { 5 | function borrow(coins amount) external; 6 | } 7 | 8 | // This contract implements 'Loaner' interface. 9 | contract LoanerContract is Loaner { 10 | 11 | constructor () { 12 | tvm.accept(); 13 | } 14 | 15 | // A function to be called from another contract 16 | // This function receives parameter 'amount' from another contract and 17 | // transfers 'amount' of currency to the caller. 18 | function borrow(coins amount) external override { 19 | // Before 'accept' here can be some checks (e.g: check that msg.sender is in while list) 20 | tvm.accept(); 21 | 22 | msg.sender.transfer(amount); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /solidity/4.1_CentralBank.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | // This contract implements 'IBank' interface. 5 | contract CentralBank { 6 | 7 | constructor () { 8 | tvm.accept(); 9 | } 10 | 11 | // This function receives the code of currency and returns to the sender exchange rate 12 | // via callback function. 13 | function getExchangeRate(uint16 currency) external pure responsible returns (uint32) { 14 | // To convert one currency to another we just multiply by 16. Here maybe more complex logic. 15 | uint32 ec = 16 * uint32(currency); 16 | return{value: 0, bounce: false, flag: 64} ec; 17 | // options {value: 0, flag: 64} mean returning change 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /solidity/4.1_CurrencyExchange.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | import "4.1_CentralBank.sol"; 5 | 6 | // This contract implements 'ICurrencyExchange' interface. 7 | // The contract calls remote bank contract to get the exchange rate via callback function calling. 8 | contract CurrencyExchange { 9 | 10 | // State variable storing the exchange rate. 11 | uint32 public exchangeRate; 12 | 13 | constructor() { 14 | // check that contract's public key is set 15 | require(tvm.pubkey() != 0, 101); 16 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 17 | require(msg.pubkey() == tvm.pubkey(), 102); 18 | tvm.accept(); 19 | } 20 | 21 | modifier checkOwnerAndAccept { 22 | // Check that message was signed with contracts key. 23 | require(msg.pubkey() == tvm.pubkey(), 102); 24 | tvm.accept(); 25 | _; 26 | } 27 | 28 | // This function gets an address of the contract and code of the currency , 29 | // casts the address to IRemoteContract interface and calls 30 | // function 'GetExchangeRate' with parameter . 31 | function updateExchangeRate(address bankAddress, uint16 currency) external view checkOwnerAndAccept { 32 | CentralBank(bankAddress).getExchangeRate{value: 1 ever, callback: CurrencyExchange.setExchangeRate}(currency); 33 | } 34 | 35 | // A callback function to set exchangeRate. 36 | function setExchangeRate(uint32 er) external { 37 | // save parameter er in state variable 'exchangeRate'. 38 | exchangeRate = er; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /solidity/4_CentralBank.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | import "4_interfaces.sol"; 4 | 5 | // This contract implements 'IBank' interface. 6 | contract CentralBank is ICentralBank { 7 | 8 | constructor () { 9 | tvm.accept(); 10 | } 11 | 12 | // This function receives the code of currency and returns to the sender exchange rate 13 | // via calling a callback function. 14 | function getExchangeRate(uint16 currency) external override { 15 | // Cast address of caller to ICurrencyExchange interface and call its 'setExchangeRate' function. 16 | // To convert one currency to another we just multiply by 16. Here maybe more complex logic. 17 | ICurrencyExchange(msg.sender).setExchangeRate(16 * currency); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /solidity/4_CurrencyExchange.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | import "4_interfaces.sol"; 5 | 6 | // This contract implements 'ICurrencyExchange' interface. 7 | // The contract calls remote bank contract to get the exchange rate via callback function calling. 8 | contract CurrencyExchange is ICurrencyExchange { 9 | 10 | // State variable storing the exchange rate. 11 | uint32 public exchangeRate; 12 | 13 | constructor() { 14 | // check that contract's public key is set 15 | require(tvm.pubkey() != 0, 101); 16 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 17 | require(msg.pubkey() == tvm.pubkey(), 102); 18 | tvm.accept(); 19 | } 20 | 21 | modifier checkOwnerAndAccept { 22 | // Check that message was signed with contracts key. 23 | require(msg.pubkey() == tvm.pubkey(), 102); 24 | tvm.accept(); 25 | _; 26 | } 27 | 28 | // This function gets an address of the contract and code of the currency , 29 | // casts the address to IRemoteContract interface and calls 30 | // function 'GetExchangeRate' with parameter . 31 | function updateExchangeRate(address bankAddress, uint16 currency) external view checkOwnerAndAccept { 32 | ICentralBank(bankAddress).getExchangeRate(currency); 33 | } 34 | 35 | // A callback function to set exchangeRate. 36 | function setExchangeRate(uint32 er) external override { 37 | // save parameter er in state variable 'exchangeRate'. 38 | exchangeRate = er; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /solidity/4_interfaces.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | interface ICentralBank { 5 | function getExchangeRate(uint16 code) external; 6 | } 7 | 8 | interface ICurrencyExchange { 9 | function setExchangeRate(uint32 n_exchangeRate) external; 10 | } -------------------------------------------------------------------------------- /solidity/5_Bank.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | // Import the interface file 5 | import "5_BankClientInterfaces.sol"; 6 | 7 | // This contract implements 'IBank' interface. 8 | // The contract allows to store credit limits in mapping and give to the caller it's credit limits. 9 | contract Bank is IBank { 10 | 11 | // Struct for storing the credit information. 12 | struct CreditInfo { 13 | uint allowed; 14 | uint used; 15 | } 16 | 17 | // State variable storing a credit information for addresses. 18 | mapping(address => CreditInfo) clientDB; 19 | 20 | constructor() { 21 | // check that contract's public key is set 22 | require(tvm.pubkey() != 0, 101); 23 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 24 | require(msg.pubkey() == tvm.pubkey(), 102); 25 | tvm.accept(); 26 | } 27 | 28 | modifier checkOwnerAndAccept { 29 | // Check that message was signed with contracts key. 30 | require(msg.pubkey() == tvm.pubkey(), 102); 31 | tvm.accept(); 32 | _; 33 | } 34 | 35 | // Set credit limit for the address. 36 | function setAllowance(address bankClientAddress, uint amount) external checkOwnerAndAccept { 37 | // Store allowed credit limit for the address in state variable mapping. 38 | clientDB[bankClientAddress].allowed = amount; 39 | } 40 | 41 | // Get allowed credit limit for the caller. 42 | function getCreditLimit() external override { 43 | // Cast caller to IMyContractCallback and invoke callback function 44 | // with value obtained from state variable mapping. 45 | CreditInfo borrowerInfo = clientDB[msg.sender]; 46 | IBankClient(msg.sender).setCreditLimit(borrowerInfo.allowed - borrowerInfo.used); 47 | } 48 | 49 | // This function checks whether message sender's available limit could be loaned 50 | // and sends currency. 51 | function loan(coins amount) external override { 52 | CreditInfo borrowerInfo = clientDB[msg.sender]; 53 | if (borrowerInfo.used + amount > borrowerInfo.allowed) { 54 | IBankClient(msg.sender).refusalCallback(borrowerInfo.allowed - borrowerInfo.used); 55 | } else { 56 | // '{value: amount}' allows to attach arbitrary amount of currency to the message 57 | // if it is not set amount would be set to 10 000 000 nanoever 58 | IBankClient(msg.sender).receiveLoan{value: amount}(borrowerInfo.used + amount); 59 | clientDB[msg.sender].used += amount; 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /solidity/5_BankClient.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | // Import the interface file 5 | import "5_BankClientInterfaces.sol"; 6 | 7 | // This contract implements 'IBankClient' interface. 8 | contract BankClient is IBankClient { 9 | 10 | uint public creditLimit = 0; // allowed credit limit; 11 | uint public totalDebt = 0; // contract total debt; 12 | uint public balance = 0; // contract balance; 13 | uint public value = 0; // inbound message value. 14 | 15 | constructor() { 16 | // check that contract's public key is set 17 | require(tvm.pubkey() != 0, 101); 18 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 19 | require(msg.pubkey() == tvm.pubkey(), 102); 20 | tvm.accept(); 21 | } 22 | 23 | modifier checkOwnerAndAccept { 24 | // Check that message was signed with contracts key. 25 | require(msg.pubkey() == tvm.pubkey(), 102); 26 | tvm.accept(); 27 | _; 28 | } 29 | 30 | // This function calls a remote IBank contract to get the credit limit. 31 | function getMyCreditLimit(IBank bank) external view checkOwnerAndAccept { 32 | // Call remote contract function. 33 | bank.getCreditLimit(); 34 | } 35 | 36 | // A callback function to set the credit limit. 37 | function setCreditLimit(uint limit) external override { 38 | // Save the credit limit (received from another contract) in the state variable. 39 | creditLimit = limit; 40 | } 41 | 42 | //This function calls bank contract to ask for a loan. 43 | function askForALoan(IBank bank, coins amount) external checkOwnerAndAccept { 44 | balance = address(this).balance; 45 | bank.loan(amount); 46 | } 47 | 48 | // A callback function to receive requested loan. Function receives the total debt as an argument. 49 | function receiveLoan(uint n_totalDebt) external override { 50 | value = msg.value; 51 | uint n_balance = address(this).balance; 52 | require(n_balance > balance); 53 | balance = n_balance; 54 | totalDebt = n_totalDebt; 55 | } 56 | 57 | // A callback function to indicate refuse of the loan request. Function receives available limit as an argument. 58 | function refusalCallback(uint availableLimit) external override { 59 | creditLimit = availableLimit; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /solidity/5_BankClientInterfaces.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | //Contract interface file 5 | 6 | interface IBank { 7 | function getCreditLimit() external; 8 | function loan(coins amount) external; 9 | } 10 | 11 | interface IBankClient { 12 | function setCreditLimit(uint limit) external; 13 | function refusalCallback(uint remainingLimit) external; 14 | function receiveLoan(uint totalDebt) external; 15 | } 16 | -------------------------------------------------------------------------------- /solidity/6_DBClientInterface.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | struct MyStruct { 5 | uint ID; 6 | uint value; 7 | } 8 | 9 | // Interface for a database client. 10 | interface IDataBaseClient { 11 | function receiveArray(uint64[] arr) external; 12 | function receiveFiveArrays(uint256[] a0, uint256[] a1, uint256[] a2, uint256[] a3, uint256[] a4) external; 13 | function receiveFiveUint256(uint256 a0, uint256 a1, uint256 a2, uint256 a3, uint256 a4) external; 14 | function receiveStructArray(MyStruct[] arr) external; 15 | } 16 | -------------------------------------------------------------------------------- /solidity/6_DataBase.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | import "6_DBClientInterface.sol"; 5 | 6 | // This contract demonstrates how to send different amounts of information to another contract. 7 | contract DataBase { 8 | 9 | uint64 constant ATTACHED_VALUE = 1 ever; 10 | 11 | constructor () { 12 | tvm.accept(); 13 | } 14 | 15 | // Modifier that allows public function to accept all external messages. 16 | modifier alwaysAccept { 17 | // Runtime function that allows contract to process inbound messages spending 18 | // its own resources (it's necessary if contract should process all inbound messages, 19 | // not only those that carry value with them). 20 | tvm.accept(); 21 | _; 22 | } 23 | 24 | // Contract can store or generate different coefficients and provide them 25 | // upon request as an array of integers. 26 | // Function sends an array of uint64 with size 'count' to the contract 27 | // with address 'receiver'. 28 | function sendArray(address receiver, uint64 count) external pure alwaysAccept { 29 | uint64[] arr = new uint64[](count); 30 | for (uint64 i = 0; i < count; i++) { 31 | arr[i] = i + 1; 32 | } 33 | IDataBaseClient(receiver).receiveArray{value: ATTACHED_VALUE}(arr); 34 | } 35 | 36 | // Function sends five arrays of uint to the contract with address 'receiver'. 37 | function sendFiveArrays(address receiver) external pure alwaysAccept { 38 | uint[] arr0 = [uint(1)]; 39 | uint[] arr1 = [uint(2)]; 40 | uint[] arr2 = [uint(3)]; 41 | uint[] arr3 = [uint(4)]; 42 | uint[] arr4 = [uint(5)]; 43 | IDataBaseClient(receiver).receiveFiveArrays{value: ATTACHED_VALUE}(arr0, arr1, arr2, arr3, arr4); 44 | } 45 | 46 | // Function sends five uint256 to the contract with address 'receiver'. 47 | function sendFiveUint256(address receiver) external pure alwaysAccept { 48 | uint256 a0 = 5; 49 | uint256 a1 = 4; 50 | uint256 a2 = 3; 51 | uint256 a3 = 2; 52 | uint256 a4 = 1; 53 | IDataBaseClient(receiver).receiveFiveUint256{value: ATTACHED_VALUE}(a0, a1, a2, a3, a4); 54 | } 55 | 56 | // Private function to create an array of structures, that will be sent to another contract. 57 | function createStructArray() private pure returns (MyStruct[]) { 58 | MyStruct[] arr = [ 59 | MyStruct({ 60 | ID: 1, 61 | value: 2 62 | }), 63 | MyStruct({ 64 | ID: 3, 65 | value: 4 66 | }) 67 | ]; 68 | return arr; 69 | } 70 | 71 | // Function sends an array of structures to the contract with address 'receiver'. 72 | function sendStructArray(address receiver) external pure alwaysAccept { 73 | MyStruct[] arr = createStructArray(); 74 | IDataBaseClient(receiver).receiveStructArray{value: ATTACHED_VALUE}(arr); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /solidity/6_DataBaseClient.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | import "6_DBClientInterface.sol"; 5 | 6 | // This contract implements IDataBaseClient interface. 7 | contract DataBaseClient is IDataBaseClient { 8 | // State variable storing the number of receive* functions were called. 9 | uint public receiptCounter = 0; 10 | 11 | // State variable that can be used to check received values. 12 | uint public checkSum = 0; 13 | 14 | constructor () { 15 | tvm.accept(); 16 | } 17 | 18 | // Function receives an array of uint64 values. 19 | function receiveArray(uint64[] arr) external override { 20 | uint sum = 0; 21 | uint len = arr.length; 22 | for (uint i = 0; i < len; i++) { 23 | sum += arr[i]; 24 | } 25 | checkSum = sum; 26 | receiptCounter++; 27 | } 28 | 29 | // Function receives five arrays of uint values. 30 | function receiveFiveArrays(uint256[] a0, uint256[] a1, uint256[] a2, uint256[] a3, uint256[] a4) external override { 31 | checkSum = a0[0]; 32 | checkSum = (checkSum << 4) + a1[0]; 33 | checkSum = (checkSum << 4) + a2[0]; 34 | checkSum = (checkSum << 4) + a3[0]; 35 | checkSum = (checkSum << 4) + a4[0]; 36 | receiptCounter++; 37 | } 38 | 39 | // Function receives five uint256 values. 40 | function receiveFiveUint256(uint256 a0, uint256 a1, uint256 a2, uint256 a3, uint256 a4) external override { 41 | checkSum = a0; 42 | checkSum = (checkSum << 4) + a1; 43 | checkSum = (checkSum << 4) + a2; 44 | checkSum = (checkSum << 4) + a3; 45 | checkSum = (checkSum << 4) + a4; 46 | receiptCounter++; 47 | } 48 | 49 | // Function receives an array of structures. 50 | function receiveStructArray(MyStruct[] arr) external override { 51 | checkSum = arr[0].ID * 1_000 + arr[0].value * 100 + 52 | arr[1].ID * 10 + arr[1].value; 53 | receiptCounter++; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /solidity/7_CrashContract.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | // This contract is used to emulate currency transfer destination contract, 5 | // it can accept incoming transfer via receive function or emulate crash in function doCrash(). 6 | contract CrashContract { 7 | 8 | uint public counter = 0; 9 | 10 | constructor () { 11 | tvm.accept(); 12 | } 13 | 14 | receive() external { 15 | ++counter; 16 | } 17 | 18 | // Function that just throws an exception 19 | function doCrash() external pure { 20 | revert(101); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /solidity/7_Giver.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | import "7_CrashContract.sol"; 5 | 6 | interface AbstractContract { 7 | function receiveTransfer(uint64 number) pure external; 8 | } 9 | 10 | //This contract allows to perform different kinds of currency transactions and control the result using the fallback function. 11 | contract Giver { 12 | 13 | // State variable storing the number of times receive/fallback/onBounce function was called. 14 | uint public counter = 0; 15 | 16 | constructor() { 17 | // check that contract's public key is set 18 | require(tvm.pubkey() != 0, 101); 19 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 20 | require(msg.pubkey() == tvm.pubkey(), 102); 21 | tvm.accept(); 22 | } 23 | 24 | modifier checkOwnerAndAccept { 25 | // Check that message was signed with contracts key. 26 | require(msg.pubkey() == tvm.pubkey(), 102); 27 | tvm.accept(); 28 | _; 29 | } 30 | 31 | onBounce(TvmSlice /*slice*/) external { 32 | ++counter; 33 | } 34 | 35 | // This function can transfer currency to an existing contract with fallback 36 | // function. 37 | function transferToAddress(address destination, coins value) external view checkOwnerAndAccept { 38 | destination.transfer(value); 39 | } 40 | 41 | // This function calls an AbstractContract which would case a crash and call of onBounce function. 42 | function transferToAbstractContract(address destination, coins amount) external view checkOwnerAndAccept { 43 | AbstractContract(destination).receiveTransfer{value: amount}(123); 44 | } 45 | 46 | // This function call a CrashContract's function which would cause a crash during transaction 47 | // and call of onBounce function. 48 | function transferToCrashContract(address destination, coins amount) external view checkOwnerAndAccept { 49 | CrashContract(destination).doCrash{value: amount}(); 50 | } 51 | 52 | // Function which allows to make a transfer to an arbitrary address. 53 | function transferToAddress2(address destination, coins value, bool bounce, uint16 flag) 54 | view 55 | public 56 | checkOwnerAndAccept 57 | { 58 | // Runtime function that allows to make a transfer with arbitrary settings 59 | // and can be used to send tons to non-existing address. 60 | destination.transfer(value, bounce, flag); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /solidity/8_Heir.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | // The contract receives all the balance of Kamikaze contract after its self-destruction. 5 | contract Heir { 6 | 7 | // State variable storing the number of times receive was called. 8 | uint public heritageCounter; 9 | 10 | constructor () { 11 | tvm.accept(); 12 | } 13 | 14 | // Receive function that will be called after Kamikaze contract self-destruction. 15 | receive() external { 16 | heritageCounter++; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /solidity/8_Kamikaze.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | // This sample demonstrates usage of selfdestruct function. 5 | contract Kamikaze { 6 | 7 | // Constructor saves the owner's public key in the state variable. 8 | constructor() { 9 | // check that contract's public key is set 10 | require(tvm.pubkey() != 0, 101); 11 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 12 | require(msg.pubkey() == tvm.pubkey(), 102); 13 | tvm.accept(); 14 | } 15 | 16 | modifier checkOwnerAndAccept { 17 | // Check that message was signed with contracts key. 18 | require(msg.pubkey() == tvm.pubkey(), 102); 19 | tvm.accept(); 20 | _; 21 | } 22 | 23 | // Due to the modifier checkOwnerAndAccept function sendAllMoney can be 24 | // called only by the owner of the contract. 25 | function sendAllMoney(address dest) external checkOwnerAndAccept { 26 | selfdestruct(dest); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /solidity/9_PiggyBank.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | contract PiggyBank { 5 | // contract owner's address; 6 | address public owner; 7 | // piggybank's minimal limit to withdraw; 8 | uint public limit; 9 | // piggybank's deposit balance. 10 | coins public balance; 11 | 12 | // Constructor saves the address of the contract owner in a state variable and 13 | // initializes the limit and the balance. 14 | constructor(address own, uint lim) { 15 | // check that contract's public key is set 16 | require(tvm.pubkey() != 0, 101); 17 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 18 | require(msg.pubkey() == tvm.pubkey(), 102); 19 | tvm.accept(); 20 | owner = own; 21 | limit = lim; 22 | balance = 0; 23 | } 24 | 25 | // Modifier that allows public function to be called only when the limit is reached. 26 | modifier checkBalance() { 27 | require(balance >= limit, 103); 28 | _; 29 | } 30 | 31 | // Modifier that allows public function to be called only from the owners address. 32 | modifier checkSenderIsOwner { 33 | require(msg.sender == owner, 101); 34 | _; 35 | } 36 | 37 | // Function that can be called by anyone. 38 | function deposit() external { 39 | balance += msg.value; 40 | } 41 | 42 | // Function that can be called only by the owner after reaching the limit. 43 | function withdraw() external checkSenderIsOwner checkBalance { 44 | tvm.accept(); 45 | msg.sender.transfer(balance); 46 | balance = 0; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /solidity/9_PiggyBank_Owner.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | import "9_PiggyBank.sol"; 5 | 6 | // This contract describes the owner of PiggyBank who can add to deposit and withdraw deposit. 7 | contract Owner { 8 | 9 | constructor() { 10 | // check that contract's public key is set 11 | require(tvm.pubkey() != 0, 101); 12 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 13 | require(msg.pubkey() == tvm.pubkey(), 102); 14 | tvm.accept(); 15 | } 16 | 17 | modifier onlyOwner { 18 | // Check that message was signed with contracts key. 19 | require(msg.pubkey() == tvm.pubkey(), 102); 20 | _; 21 | } 22 | 23 | // Function to deposit money to piggy bank. 24 | function addToDeposit(PiggyBank bankAddress, coins amount) external view onlyOwner { 25 | tvm.accept(); 26 | bankAddress.deposit{value: amount}(); 27 | } 28 | 29 | // Function to withdraw money from piggy bank. 30 | function withdrawDeposit(PiggyBank bankAddress) external view onlyOwner { 31 | tvm.accept(); 32 | bankAddress.withdraw(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /solidity/9_PiggyBank_Stranger.sol: -------------------------------------------------------------------------------- 1 | pragma tvm-solidity >= 0.72.0; 2 | pragma AbiHeader expire; 3 | 4 | import "9_PiggyBank.sol"; 5 | 6 | // This contract describes the Stranger who can add to deposit of PiggyBank but can't withdraw deposit. 7 | contract Stranger { 8 | 9 | constructor() { 10 | // check that contract's public key is set 11 | require(tvm.pubkey() != 0, 101); 12 | // Check that message has signature (msg.pubkey() is not zero) and message is signed with the owner's private key 13 | require(msg.pubkey() == tvm.pubkey(), 102); 14 | tvm.accept(); 15 | } 16 | 17 | modifier onlyOwner { 18 | // Check that message was signed with contracts key. 19 | require(msg.pubkey() == tvm.pubkey(), 102); 20 | _; 21 | } 22 | 23 | // Function to deposit money to piggy bank. 24 | function addToDeposit(PiggyBank bankAddress, coins amount) external view onlyOwner { 25 | tvm.accept(); 26 | bankAddress.deposit{value: amount}(); 27 | } 28 | 29 | // Function to withdraw money from piggy bank. 30 | function withdrawDeposit(PiggyBank bankAddress) external view onlyOwner { 31 | tvm.accept(); 32 | bankAddress.withdraw(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /solidity/README.md: -------------------------------------------------------------------------------- 1 | ## Solidity contract workflow 2 | 3 | Links: 4 | * [How to write and deploy contact](https://docs.ton.dev/86757ecb2/p/950f8a-write-smart-contract-in-solidity/t/832d9e). 5 | * [How to deploy contract from contract](https://github.com/everx-labs/samples/blob/master/solidity/17_ContractProducer.md). 6 | 7 | ## Contract examples 8 | 9 | This set of smart-contract samples illustrates common functionality of smart-contracts developed in 10 | Solidity, starting with very basic and gradually evolving into code snippets, which may come handy 11 | in production smart-contracts. 12 | 13 | #### 1. [Accumulator](https://github.com/everx-labs/samples/blob/master/solidity/1_Accumulator.sol): persistent storage 14 | Smart-contracts deployed to the blockchain store their state variables in a persistent storage. 15 | Call `Accumulator.add(uint value)`. It adds `value` to its state variable `sum`. 16 | Resulting state of the account can be examined by conventional means. 17 | 18 | #### 2. [StorageClient](https://github.com/everx-labs/samples/blob/master/solidity/2_StorageClient.sol): calling another [contract](https://github.com/everx-labs/samples/blob/master/solidity/2_UintStorage.sol) 19 | 20 | Contracts can also call other remote contracts. Call `StorageClient.store(Storage storageAddress)` to 21 | invoke a public function of another contract. The remote contract `UintStorage` saves the integer 22 | value of the argument and the caller address in its state variables. 23 | 24 | #### 3. [Borrower](https://github.com/everx-labs/samples/blob/master/solidity/3_Borrower.sol): transfer 25 | 26 | This sample demonstrates how currency transfer works. Call `Borrower.askForALoan(Loaner loanerAddress, uint amount)`. 27 | This requests `amount` of currency from the contract deployed at the specified address. 28 | The remote contract [LoanerContract](https://github.com/everx-labs/samples/blob/master/solidity/3_Loaner.sol) 29 | transfers `amount` of currency to the caller via `msg.sender.transfer(amount)`. 30 | Each contract has an internal transaction counter. The counter value increases with each transaction 31 | and is stored in the persistent memory. 32 | 33 | #### 4. [CurrencyExchange](https://github.com/everx-labs/samples/blob/master/solidity/4_CurrencyExchange.sol): callback implementation 34 | 35 | Call `CurrencyExchange.updateExchangeRate(address bankAddress, uint16 code)`. This function allows 36 | interacting with a remote contract by calling its function: `ICentralBank.GetExchangeRate(uint16 code)`. 37 | The remote contract [CentralBank](https://github.com/everx-labs/samples/blob/master/solidity/4_CentralBank.sol) 38 | obtains caller's address via `msg.sender` and performs a callback. 39 | 40 | #### 5. [Bank](https://github.com/everx-labs/samples/blob/master/solidity/5_Bank.sol): loan interaction between Bank and [BankClient](https://github.com/everx-labs/samples/blob/master/solidity/5_BankClient.sol) 41 | 42 | Call `Bank.setAllowance(address bankClientAddress, uint amount)`. 43 | Bank stores information about loan allowances and current debts for different contracts. This data 44 | is recorded in the following state variable: `mapping(address => CreditInfo) clientDB;` 45 | A contract owner is supposed to call the `setAllowance()` function to specify limits. 46 | 47 | [BankClient](https://github.com/everx-labs/samples/blob/master/solidity/5_BankClient.sol) is a client 48 | that can interact with Bank. 49 | 50 | Call `BankClient.getMyCredit(IBank bank)`. 51 | This function calls the remote contract Bank to receive allowed credit limit via Bank invoking the 52 | callback function `setCreditLimit(uint limit)`. 53 | 54 | Call `BankClient.askForALoan(IBank bank, uint amount)`. 55 | This function call the remote contract Bank to get an amount of credit. According to the current 56 | credit info of the BankClient contract Bank will approve the credit via calling the callback 57 | function "receiveLoan(uint n_totalDebt)" or refuse the credit via calling the callback function 58 | `refusalCallback(uint availableLimit)`. 59 | **receiveLoan** function also obtains balance of the contract via **address(this).balance** and 60 | balance of the inbound message via **msg.value** and saves them in state variables. 61 | **refusalCallback** function saves the argument (available credit limit) in the state variable. 62 | 63 | #### 6. [DataBase](https://github.com/everx-labs/samples/blob/master/solidity/6_DataBase.sol): exchange of different types of values 64 | 65 | One of contract functions call allows sending to the [DataBaseClient](https://github.com/everx-labs/samples/blob/master/solidity/6_DataBaseClient.sol) 66 | different values: 67 | - uint64 array; 68 | - five uint arrays; 69 | - five uint256; 70 | - struct array. 71 | 72 | #### 7. [Giver](https://github.com/everx-labs/samples/blob/master/solidity/7_Giver.sol): simple giver contract 73 | 74 | This sample shows usage of different types of currency transactions and usage of a fallback function. 75 | 76 | Call `Giver.transferToAddress(address payable destination, uint amount)` or 77 | `Giver.do_tvm_transfer(address payable remote_addr, uint128 ton_value, bool bounce, uint16 sendrawmsg_flag)` 78 | to perform a currency transaction. 79 | Call `Giver.transferToCrashContract(address payable destination, uint amount)` to implement a crash 80 | during transaction. That will cause an exception in [CrashContract](https://github.com/everx-labs/samples/blob/master/solidity/7_CrashContract.sol) 81 | and Giver's contract fallback function calling. 82 | Call `Giver.transferToAbstractContract(address payable destination, uint amount)` with a 83 | non-existent address AbstractContract will also call a fallback function of Giver. 84 | 85 | #### 8. [Kamikaze](https://github.com/everx-labs/samples/blob/master/solidity/8_Kamikaze.sol): selfdestruct function 86 | 87 | Call `Kamikaze.sendAllMoney(address anotherContract)`. This function destroys the contract and sends 88 | all its funds to the specified address of [Heir](https://github.com/everx-labs/samples/blob/master/solidity/8_Heir.sol) 89 | contract. 90 | 91 | #### 9. [PiggyBank](https://github.com/everx-labs/samples/blob/master/solidity/9_PiggyBank.sol): Piggy bank with two clients 92 | 93 | This sample consists of 3 contracts: 94 | - [PiggyBank](https://github.com/everx-labs/samples/blob/master/solidity/9_PiggyBank.sol) - piggy bank itself. 95 | - [PiggyBank_Owner](https://github.com/everx-labs/samples/blob/master/solidity/9_PiggyBank_Owner.sol) - piggy bank's owner - valid user, who can add to piggy bank's deposit and withdraw. 96 | - [PiggyBank_Stranger](https://github.com/everx-labs/samples/blob/master/solidity/9_PiggyBank_Stranger.sol) - stranger - invalid user, who can add to piggy bank but can not withdraw. 97 | 98 | Call `PiggyBank_Owner.addToDeposit(PiggyBank bankAddress, uint amount)` or 99 | `PiggyBank_Stranger.addToDeposit(PiggyBank bankAddress, uint amount)` to transfer tons from the 100 | contract to PiggyBank. 101 | 102 | Call `PiggyBank_Owner.withdrawDeposit(PiggyBank bankAddress)` of `PiggyBank_Stranger.withdrawDeposit(PiggyBank bankAddress)` 103 | to try to withdraw the deposit from PiggyBank. Transfer would occur only for the owner. 104 | 105 | #### 10. [Wallet](https://github.com/everx-labs/samples/blob/master/solidity/10_Wallet.sol): Simple wallet 106 | 107 | Call `Wallet.sendTransaction(address payable dest, uint128 value, bool bounce)`. This function 108 | allows transferring tons to the specified account. 109 | 110 | #### 11. [ContractDeployer](https://github.com/everx-labs/samples/blob/master/solidity/11_ContractDeployer.sol): Deploy Contract from contract via `new`. 111 | 112 | The way to get arguments for deploying is described [How to deploy contract from contract](https://github.com/everx-labs/samples/blob/master/solidity/17_ContractProducer.md). 113 | 114 | #### 12. [BadContract](https://github.com/everx-labs/samples/blob/master/solidity/12_BadContract.sol): Contract upgrade 115 | 116 | Contract code could be changed via using **tvm.setcode** function. It could be useful for fixing 117 | errors and functionality updating. In that example we have a [BadContract](https://github.com/everx-labs/samples/blob/master/solidity/12_BadContract.sol) (it is a [PiggyBank](https://github.com/everx-labs/samples/blob/master/solidity/9_PiggyBank.sol) contract with added upgrade functionality) and new version of that contract [NewVersion](https://github.com/everx-labs/samples/blob/master/solidity/12_NewVersion.sol). 118 | 119 | Call "PiggyBank.setCode(TvmCell memory newcode)" with argument that contains code of [NewVersion](https://github.com/everx-labs/samples/blob/master/solidity/12_NewVersion.sol) contract to change the code of the contract. 120 | 121 | #### 13. [BankCollector](https://github.com/everx-labs/samples/blob/master/solidity/13_BankCollector.sol): Mapping methods 122 | 123 | Developer can work with mappings using methods: **fetch**, **min**, **next**. This methods allow to 124 | check existence of the key, obtain lexicographically minimal key and lexicographically next key 125 | respectively. 126 | 127 | #### 14. [CustomReplayProtection](https://github.com/everx-labs/samples/blob/master/solidity/14_CustomReplayProtection.sol): Custom replay protection 128 | 129 | Developer can redefine function **afterSignatureCheck** to create his own replay protection function 130 | instead of default one. 131 | 132 | #### 15. [MessageSender](https://github.com/everx-labs/samples/blob/master/solidity/15_MessageSender.sol): Message construction and parsing 133 | 134 | Developer can use TVM specific types to build message manually and special api function 135 | **tvm.sendrawmsg()** to send it. Contract [MessageSender](https://github.com/everx-labs/samples/blob/master/solidity/15_MessageSender.sol) performs such actions to build a message which will call the function of another contract [MessageReceiver](https://github.com/everx-labs/samples/blob/master/solidity/15_MessageReceiver.sol). [MessageReceiver](https://github.com/everx-labs/samples/blob/master/solidity/15_MessageReceiver.sol) 136 | also shows how to parse a cell. 137 | 138 | #### 16. [onBounceHandler](https://github.com/everx-labs/samples/blob/master/solidity/16_onBounceHandler.sol): Working with bounced messages 139 | 140 | Developer can define **onBounce** function to work with bounced messages. If an error occurs while 141 | message transferring or handling it can be bounced back to the source contract. This sample 142 | demonstrates how you can handle such bounced message. 143 | --------------------------------------------------------------------------------