mirror of
https://github.com/danog/ton.git
synced 2024-11-27 04:24:52 +01:00
486 lines
32 KiB
Plaintext
486 lines
32 KiB
Plaintext
|
The aim of this document is to provide step-by-step instructions for compiling and creating a simple smart contract (a simple wallet) in the TON Blockchain Test Network using the TON Blockchain Lite Client and associated software.
|
||
|
|
||
|
Download and installation instructions may be found in README. We assume here that the Lite Client is already properly downloaded, compiled and installed.
|
||
|
|
||
|
1. Smart-contract addresses
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
Smart-contract addresses in the TON Network consist of two parts: (a) the workchain ID (a signed 32-bit integer) and (b) the address inside the workchain (64-512 bits depending on the workchain). Currently, only the masterchain (workchain_id=-1) and occasionally the basic workchain (workchain_id=0) are running in the TON Blockchain Test Network. Both of them have 256-bit addresses, so we henceforth assume that workchain_id is either 0 or -1 and that the address inside the workchain is exactly 256-bit.
|
||
|
|
||
|
Under the conditions stated above, the smart-contract address can be represented in the following forms:
|
||
|
|
||
|
A) "Raw": <decimal workchain_id>:<64 hexadecimal digits with address>
|
||
|
B) "User-friendly", which is obtained by first generating:
|
||
|
- one tag byte (0x11 for "bounceable" addresses, 0x51 for "non-bounceable"; add +0x80 if the address should not be accepted by software running in the production network)
|
||
|
- one byte containing a signed 8-bit integer with the workchain_id (0x00 for the basic workchain, 0xff for the masterchain)
|
||
|
- 32 bytes containing 256 bits of the smart-contract address inside the workchain (big-endian)
|
||
|
- 2 bytes containing CRC16-CCITT of the previous 34 bytes
|
||
|
|
||
|
In case B), the 36 bytes thus obtained are then encoded using base64 (i.e., with digits, upper- and lowercase Latin letters, '/' and '+') or base64url (with '_' and '-' instead of '/' and '+'), yielding 48 printable non-space characters.
|
||
|
|
||
|
Example:
|
||
|
|
||
|
The "test giver" (a special smart contract residing in the masterchain of the Test Network that gives up to 20 test Grams to anybody who asks) has the address
|
||
|
|
||
|
-1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d
|
||
|
|
||
|
in the "raw" form (notice that uppercase Latin letters 'A'..'F' may be used instead of 'a'..'f')
|
||
|
|
||
|
and
|
||
|
|
||
|
Ef+BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb (base64) or
|
||
|
Ef-BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb (base64url)
|
||
|
|
||
|
|
||
|
in the "user-friendly" form (to be displayed by user-friendly clients). Notice that both forms (base64 and base64url) are valid and must be accepted.
|
||
|
|
||
|
2. Inspecting the state of a smart contract
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
Inspecting the state of smart contracts with the aid of the TON Lite Client is easy. For the sample smart contract described above, you would run the Lite Client and enter the following commands:
|
||
|
|
||
|
> last
|
||
|
...
|
||
|
> getaccount -1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d
|
||
|
or
|
||
|
> getaccount Ef-BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb
|
||
|
|
||
|
You will see something like this:
|
||
|
|
||
|
------------------------------------
|
||
|
got account state for -1:8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D with respect to blocks (-1,8000000000000000,1645):4D5E1B928490BE34A4F03C9BC996661B8AD4E988F1460DB69BE3634B1E843DAF:9D85BB116D1DA4BFD999E9092BBD43B3A9F96EC9C9ED7AFDC81B27B08D3861A0 and (-1,8000000000000000,1645):4D5E1B928490BE34A4F03C9BC996661B8AD4E988F1460DB69BE3634B1E843DAF:9D85BB116D1DA4BFD999E9092BBD43B3A9F96EC9C9ED7AFDC81B27B08D3861A0
|
||
|
account state is (account
|
||
|
addr:(addr_std
|
||
|
anycast:nothing workchain_id:-1 address:x8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D)
|
||
|
storage_stat:(storage_info
|
||
|
used:(storage_used
|
||
|
cells:(var_uint len:1 value:3)
|
||
|
bits:(var_uint len:2 value:539)
|
||
|
public_cells:(var_uint len:0 value:0)) last_paid:0
|
||
|
due_payment:nothing)
|
||
|
storage:(account_storage last_trans_lt:0
|
||
|
balance:(currencies
|
||
|
grams:(nanograms
|
||
|
amount:(var_uint len:7 value:1000000000000000))
|
||
|
other:(extra_currencies
|
||
|
dict:hme_empty))
|
||
|
state:(account_active
|
||
|
(
|
||
|
split_depth:nothing
|
||
|
special:nothing
|
||
|
code:(just
|
||
|
value:(raw@^Cell
|
||
|
x{}
|
||
|
x{FF0020DDA4F260D31F01ED44D0D31FD166BAF2A1F80001D307D4D1821804A817C80073FB0201FB00A4C8CB1FC9ED54}
|
||
|
))
|
||
|
data:(just
|
||
|
value:(raw@^Cell
|
||
|
x{}
|
||
|
x{00000000}
|
||
|
))
|
||
|
library:hme_empty))))
|
||
|
x{CFF8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D2068086C0000000000000000000000001C0E35FA931A000134_}
|
||
|
x{FF0020DDA4F260D31F01ED44D0D31FD166BAF2A1F80001D307D4D1821804A817C80073FB0201FB00A4C8CB1FC9ED54}
|
||
|
x{00000000}
|
||
|
------------------------------------
|
||
|
|
||
|
The first information line "got account state ... for ..." shows the account address and the masterchain block identifier with respect to which the account state has been dumped. Notice that even if the account state changes in a subsequent block, the `getaccount xxx` command will return the same result until the reference block is updated to a newer value by a `last` command. In this way one can study the state of all accounts and obtain consistent results.
|
||
|
|
||
|
The "account state is (account ... " line begins the pretty-printed deserialized view of the account state. It is a deserialization of TL-B data type Account, used to represent account states in the TON Blockchain as explained in the TON Blockchain documentation. (You can find the TL-B scheme used for deserialization in the source file crypto/block/block.tlb; notice that if the scheme is out of date, the deserialization may break down.)
|
||
|
|
||
|
Finally, the last several lines beginning with x{CFF538... (the "raw dump") contain the same information displayed as a tree of cells. In this case, we have one root cell containing the data bits CFF...134_ (the underscore means that the last binary one and all subsequent binary zeroes are to be removed, so hexadecimal "4_" corresponds to binary "0"), and two cells that are its children (displayed with one-space indentation).
|
||
|
|
||
|
We can see that x{FF0020DDA4F260...} is the code of this smart contract. If we consult the Appendix A of the TON Virtual Machine documentation, we can even disassemble this code: FF00 is SETCP 0, 20 is DUP, DD is IFNOTRET, A4 is INC, F260 is THROWIF 32, and so on. (Incidentally, you can find the source code of this smartcontract in the source file crypto/block/mc0.fif .)
|
||
|
|
||
|
We can also see that x{00000000} (the actual value you see may be different) is the persistent data of this smart contract. It is actually an unsigned 32-bit integer, used by the smart contract as the counter of operations performed so far. Notice that this value is big-endian (i.e., 3 is encoded as x{00000003}, not as x{03000000}), as are all integers inside the TON Blockchain.
|
||
|
|
||
|
The current balance of the smart contract is easily seen in the pretty-printed portion of the output. In this case, we see ... balance:(currencies:(grams:(nanograms:(... value:1000000000000000...)))), which is the balance of the account in (test) nanograms (a million test Grams in this example; the actual number you see may be smaller). If you study the TL-B scheme provided in crypto/block/scheme.tlb, you will be able to find this number (10^15) in binary big-endian form in the raw dump portion as well (it is located near the end of the data bits of the root cell).
|
||
|
|
||
|
3. Compiling a new smart contract
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
Before uploading a new smart contract into the TON Blockchain, you need to determine its code and data and save them in serialized form into a file (called a "bag-of-cells" or BOC file, usually with a .boc suffix). Let us consider the case of a simple wallet smart contract, which stores a 32-bit operations counter and a 256-bit Ed25519 public key of its owner in its persistent data.
|
||
|
|
||
|
Obviously, you'll need some tools for developing smart contracts - namely, a TON smart contract compiler. Basically, a TON smart contract compiler is a program that reads the source of a smart contract in a specialized high-level programming language and creates a .boc file from this source.
|
||
|
|
||
|
One such tool is the Fift interpreter, which is included in this distribution and can help create simple smart contracts. You may wish to develop more sophisticated tools. However, Fift is sufficient for demonstration purposes.
|
||
|
|
||
|
Create the file `new-wallet.fif` containing the source of our new smart contract:
|
||
|
|
||
|
------------------------------------
|
||
|
#!/usr/bin/fift -s
|
||
|
"Asm.fif" include
|
||
|
|
||
|
0 constant wc // create a wallet in workchain 0 (basechain)
|
||
|
|
||
|
// Create new simple wallet
|
||
|
<{ SETCP0 DUP IFNOTRET INC 32 THROWIF // return if recv_internal, fail unless recv_external
|
||
|
512 INT LDSLICEX DUP 32 PLDU // sign cs cnt
|
||
|
c4 PUSHCTR CTOS 32 LDU 256 LDU ENDS // sign cs cnt cnt' pubk
|
||
|
s1 s2 XCPU // sign cs cnt pubk cnt' cnt
|
||
|
EQUAL 33 THROWIFNOT // ( seqno mismatch? )
|
||
|
s2 PUSH HASHSU // sign cs cnt pubk hash
|
||
|
s0 s4 s4 XC2PU // pubk cs cnt hash sign pubk
|
||
|
CHKSIGNU // pubk cs cnt ?
|
||
|
34 THROWIFNOT // signature mismatch
|
||
|
ACCEPT
|
||
|
SWAP 32 LDU NIP
|
||
|
DUP SREFS IF:<{
|
||
|
8 LDU LDREF // pubk cnt mode msg cs
|
||
|
s0 s2 XCHG SENDRAWMSG // pubk cnt cs ; ( message sent )
|
||
|
}>
|
||
|
ENDS
|
||
|
INC NEWC 32 STU 256 STU ENDC c4 POPCTR
|
||
|
}>c
|
||
|
// code
|
||
|
<b 0 32 u,
|
||
|
newkeypair swap dup constant wallet_pk
|
||
|
"new-wallet.pk" B>file
|
||
|
B,
|
||
|
b> // data
|
||
|
// no libraries
|
||
|
<b b{00110} s, rot ref, swap ref, b> // create StateInit
|
||
|
dup ."StateInit: " <s csr. cr
|
||
|
dup hash dup constant wallet_addr
|
||
|
."new wallet address = " wc . .": " dup x. cr
|
||
|
wc over 7 smca>$ type cr
|
||
|
256 u>B "new-wallet.addr" B>file
|
||
|
<b 0 32 u, b>
|
||
|
dup ."signing message: " <s csr. cr
|
||
|
dup hash wallet_pk ed25519_sign_uint rot
|
||
|
<b b{1000100} s, wc 8 i, wallet_addr 256 u, b{000010} s, swap <s s, b{0} s, swap B, swap <s s, b>
|
||
|
dup ."External message for initialization is " <s csr. cr
|
||
|
2 boc+>B dup Bx. cr
|
||
|
"new-wallet-query.boc" tuck B>file
|
||
|
."(Saved to file " type .")" cr
|
||
|
--------------------------------------------
|
||
|
|
||
|
Incidentally, you can find a more sophisticated version of this sample file in crypto/smartcont/new-wallet.fif. It accepts command-line arguments, so you don't need to edit the source file each time you want to create a new wallet.
|
||
|
|
||
|
Now, provided that you have compiled Fift binary (usually located as "crypto/fift" with respect to the build directory), you can run
|
||
|
|
||
|
crypto/fift -I<source-directory>/crypto/fift/lib new-wallet.fif
|
||
|
|
||
|
assuming that you have copied new-wallet.fif into the current directory. Alternatively, you might skip the source editing phase and simply run
|
||
|
|
||
|
crypto/fift -I<source-directory>/crypto/fift/lib -s <source-directory>/smartcont/new-wallet.fif 0 my_wallet_name
|
||
|
|
||
|
where 0 is the workchain to contain the new wallet (0 = basechain, -1 = masterchain), `my_wallet_name` is any identifier you wish to be associated with this wallet. The address of the new wallet will be saved into file `my_wallet_name.addr`, its newly-generated private key will be saved to `my_wallet_name.pk` (unless this file already exists; then the key will be loaded from this file instead), and the external message will be saved into my_new_wallet-query.boc. If you do not indicate the name of your wallet (`my_wallet_name` in the example above), the default name `new-wallet` is used.
|
||
|
|
||
|
You may wish to set the FIFTPATH environment variable to <source-directory>/crypto/fift/lib, the directory containing Fift.fif and Asm.fif library files; then you can omit the -I argument to the Fift interpreter.
|
||
|
|
||
|
If everything worked, you'll see something like the following
|
||
|
|
||
|
--------------------------------------------
|
||
|
StateInit: x{34_}
|
||
|
x{FF0020DDA4F260810200D71820D70B1FED44D0D7091FD709FFD15112BAF2A122F901541044F910F2A2F80001D7091F3120D74A97D70907D402FB00DED1A4C8CB1FCBFFC9ED54}
|
||
|
x{00000000F61CF0BC8E891AD7636E0CD35229D579323AA2DA827EB85D8071407464DC2FA3}
|
||
|
|
||
|
new wallet address = -1 : 60c04141c6a7b96d68615e7a91d265ad0f3a9a922e9ae9c901d4fa83f5d3c0d0
|
||
|
0f9gwEFBxqe5bWhhXnqR0mWtDzqaki6a6ckB1PqD9dPA0EKD
|
||
|
signing message: x{00000000}
|
||
|
|
||
|
External message for initialization is x{89FEC18082838D4F72DAD0C2BCF523A4CB5A1E7535245D35D39203A9F507EBA781A0119401748E6F89C1BA026A363C9F58765508DFF6854475357210D0D69F07C3A5453CEEDF1A0383FC405B57FF10CE060C2377BDD954A336DE5161F0AC1C61084180E00000001_}
|
||
|
x{FF0020DDA4F260810200D71820D70B1FED44D0D7091FD709FFD15112BAF2A122F901541044F910F2A2F80001D7091F3120D74A97D70907D402FB00DED1A4C8CB1FCBFFC9ED54}
|
||
|
x{00000000F61CF0BC8E891AD7636E0CD35229D579323AA2DA827EB85D8071407464DC2FA3}
|
||
|
|
||
|
B5EE9C724104030100000000DA0002CF89FEC18082838D4F72DAD0C2BCF523A4CB5A1E7535245D35D39203A9F507EBA781A0119401748E6F89C1BA026A363C9F58765508DFF6854475357210D0D69F07C3A5453CEEDF1A0383FC405B57FF10CE060C2377BDD954A336DE5161F0AC1C61084180E0000000100102008CFF0020DDA4F260810200D71820D70B1FED44D0D7091FD709FFD15112BAF2A122F901541044F910F2A2F80001D7091F3120D74A97D70907D402FB00DED1A4C8CB1FCBFFC9ED54004800000000F61CF0BC8E891AD7636E0CD35229D579323AA2DA827EB85D8071407464DC2FA32DB9BE63
|
||
|
(Saved to file new-wallet-query.boc)
|
||
|
--------------------------------------------
|
||
|
|
||
|
In a nutshell, the Fift assembler (loaded by the "Asm.fif" include line) is used to compile the source code of the smart contract (contained in <{ SETCP0 ... c4 POPCTR }> lines) into its internal representation. The initial data of the smart contract is also created (by <b 0 32 u, ... b> lines), containing a 32-bit sequence number (equal to zero) and a 256-bit public key from a newly-generated Ed25519 keypair. The corresponding private key is saved into the file `new-wallet.pk` (be careful not to run this code twice in the same directory, otherwise this private key file will be overwritten).
|
||
|
|
||
|
The code and data for the new smart contract are combined into a StateInit structure (in the next lines), the address of the new smart contract (equal to the hash of this StateInit structure) is computed and output, and then an external message with a destination address equal to that of the new smart contract is created. This external message contains both the correct StateInit for the new smart contract and a non-trivial payload (signed by the correct private key).
|
||
|
|
||
|
Finally, the external message is serialized into a bag of cells (represented by B5EE...BE63) and saved into the file `new-wallet-query.boc`. Essentially, this file is your compiled smart contract with all additional information necessary to upload it into the TON Blockchain.
|
||
|
|
||
|
4. Transferring some funds to the new smart contract
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
You might try to upload the new smart contract immediately by running the Lite Client and typing
|
||
|
|
||
|
> sendfile new-wallet-query.boc
|
||
|
|
||
|
or
|
||
|
|
||
|
> sendfile my_new_wallet-query.boc
|
||
|
|
||
|
if you chose to name your wallet `my_new_wallet`.
|
||
|
|
||
|
Unfortunately, this won't work, because smart contracts must have a positive balance to be able to pay for storing and processing their data in the blockchain. So you have to transfer some funds to your new smart contract address first, displayed during its generation as -1:60c0...c0d0 (in raw form) and 0f9..EKD (in user-friendly form).
|
||
|
|
||
|
In a real scenario, you would either transfer some Grams from your already existing wallet, ask a friend to do so, or buy some Grams at a cryptocurrency exchange, indicating 0f9...EKD as the account to transfer the new Grams to.
|
||
|
|
||
|
In the Test Network, you have another option: you can ask the "test giver" to give you some test Grams (up to 20). Let us explain how to do it.
|
||
|
|
||
|
5. Using the test giver smart contract
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
You need to know the address of the test giver smart contract. We'll assume that it is -1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d, or, equivalently, Ef-BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb, as indicated in one of the previous examples. You inspect the state of this smart contract in the Lite Client by typing
|
||
|
|
||
|
> last
|
||
|
> getaccount Ef-BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb
|
||
|
|
||
|
as explained above in Section 2. The only number you need from the output is the 32-bit sequence number stored in the smart contract data (it is zero in the example above, but generally it will be non-zero).
|
||
|
|
||
|
Next, you create an external message to the test giver asking it to send another message to your (uninitialized) smart contract carrying a specified amount of test Grams. There is a special Fift source file for generating this external message, a more sophisticated version of which is located at crypto/smartcont/testgiver.fif:
|
||
|
|
||
|
--------------------------------------------
|
||
|
// "testgiver.addr" file>B 256 B>u@
|
||
|
0x8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d
|
||
|
dup constant wallet_addr ."Test giver address = " x. cr
|
||
|
|
||
|
0x60c04141c6a7b96d68615e7a91d265ad0f3a9a922e9ae9c901d4fa83f5d3c0d0
|
||
|
constant dest_addr
|
||
|
|
||
|
-1 constant wc
|
||
|
0 constant seqno
|
||
|
|
||
|
1000000000 constant Gram
|
||
|
{ Gram swap */ } : Gram*/
|
||
|
|
||
|
6.666 Gram*/ constant amount
|
||
|
|
||
|
// b x --> b' ( serializes a Gram amount )
|
||
|
{ -1 { 1+ 2dup 8 * ufits } until
|
||
|
rot over 4 u, -rot 8 * u, } : Gram,
|
||
|
|
||
|
// create a message (NB: 01b00.., b = bounce)
|
||
|
<b b{010000100} s, wc 8 i, dest_addr 256 u, amount Gram, 0 9 64 32 + + 1+ 1+ u, "GIFT" $, b>
|
||
|
<b seqno 32 u, 1 8 u, swap ref, b>
|
||
|
dup ."enveloping message: " <s csr. cr
|
||
|
<b b{1000100} s, wc 8 i, wallet_addr 256 u, 0 Gram, b{00} s,
|
||
|
swap <s s, b>
|
||
|
dup ."resulting external message: " <s csr. cr
|
||
|
2 boc+>B dup Bx. cr
|
||
|
"wallet-query.boc" B>file
|
||
|
---------------------------------------------
|
||
|
|
||
|
In general, you will need to edit the line containing the destination address (0x60c0...c0d0 in our example) and the sequence number of the test giver (the "0" in the "0 constant seqno" line). Alternatively, you can pass the required parameters as command-line arguments to
|
||
|
|
||
|
fift -I<include-path> -s <path-to-testgiver-fif> <dest-addr> <seqno> <amount> [<savefile>]
|
||
|
|
||
|
For instance,
|
||
|
|
||
|
fift -I<source-directory>/crypto/fift/lib:<source-directory>/crypto/smartcont -s testgiver.fif 0f9gwEFBxqe5bWhhXnqR0mWtDzqaki6a6ckB1PqD9dPA0EKD 6.666 wallet-query
|
||
|
|
||
|
(Again, setting FIFTPATH to <source-directory>/crypto/fift/lib:<source-directory>/crypto/smartcont and installing fift binary as /usr/bin/fift is a good idea.)
|
||
|
|
||
|
The newly-created message to the new smart contract must have its bounce bit clear, otherwise the transfer will be "bounced" to its sender.
|
||
|
|
||
|
This Fift code creates an internal message from the test giver smart contract to the address of our new smart contract carrying 6.666 test Grams (you can enter any other amount here up to approximately 20 Grams). Then this message is enveloped into an external message addressed to the test giver; this external message must also contain the correct sequence number of the test giver. When the test giver receives such an external message, it checks whether the sequence number matches the one stored in its persistent data, and if it does, sends the embedded internal message with the required amount of test Grams to its destination (our smart contract in this case).
|
||
|
|
||
|
The external message is serialized and saved into the file `wallet-query.boc`. Some output is generated in the process:
|
||
|
|
||
|
---------------------------------------------
|
||
|
Test giver address = 8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d
|
||
|
enveloping message: x{0000000001}
|
||
|
x{427FB06020A0E353DCB6B430AF3D48E932D6879D4D49174D74E480EA7D41FAE9E068280C6A98B4000000000000000000000000000047494654}
|
||
|
|
||
|
resulting external message: x{89FEA71F4F9849FF1D54203B094BE356FD065FC3B0966139BFDE9DD286E755901EFA00000000000C_}
|
||
|
x{427FB06020A0E353DCB6B430AF3D48E932D6879D4D49174D74E480EA7D41FAE9E068280C6A98B4000000000000000000000000000047494654}
|
||
|
|
||
|
B5EE9C7241040201000000006600014F89FEA71F4F9849FF1D54203B094BE356FD065FC3B0966139BFDE9DD286E755901EFA00000000000C010072427FB06020A0E353DCB6B430AF3D48E932D6879D4D49174D74E480EA7D41FAE9E068280C6A98B40000000000000000000000000000474946545D6254A9
|
||
|
---------------------------------------------
|
||
|
|
||
|
6. Uploading the external message to the test giver smart contract
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
Now we can invoke the Lite Client, check the state of the test giver (if the sequence number has changed, our external message will fail), and then type
|
||
|
|
||
|
> sendfile wallet-query.boc
|
||
|
|
||
|
We will see some output:
|
||
|
... external message status is 1
|
||
|
|
||
|
which means that the external message has been delivered to the collator pool. Afterwards one of the collators might choose to include this external message in a block, creating a transaction for the test giver smart contract to process this external message. We can check whether the state of the test giver has changed:
|
||
|
|
||
|
> last
|
||
|
> getaccount Ef-BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb
|
||
|
|
||
|
(If you forget to type `last`, you are likely to see the unchanged state of the test giver smart contract.) The resulting output would be:
|
||
|
|
||
|
---------------------------------------------
|
||
|
got account state for -1:8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D with respect to blocks (-1,8000000000000000,10441):2DCFB7F734913261B85B8866DFF7CBEF07205EAA0769F4EE15E242AB520A2CC5:4F96F417BCD74D5DAE0CC3CCC285E513B85652002D9AD8CC884781D8465E3591 and (-1,8000000000000000,10441):2DCFB7F734913261B85B8866DFF7CBEF07205EAA0769F4EE15E242AB520A2CC5:4F96F417BCD74D5DAE0CC3CCC285E513B85652002D9AD8CC884781D8465E3591
|
||
|
account state is (account
|
||
|
addr:(addr_std
|
||
|
anycast:nothing workchain_id:-1 address:x8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D)
|
||
|
storage_stat:(storage_info
|
||
|
used:(storage_used
|
||
|
cells:(var_uint len:1 value:3)
|
||
|
bits:(var_uint len:2 value:539)
|
||
|
public_cells:(var_uint len:0 value:0)) last_paid:0
|
||
|
due_payment:nothing)
|
||
|
storage:(account_storage last_trans_lt:10697000003
|
||
|
balance:(currencies
|
||
|
grams:(nanograms
|
||
|
amount:(var_uint len:7 value:999993280210000))
|
||
|
other:(extra_currencies
|
||
|
dict:hme_empty))
|
||
|
state:(account_active
|
||
|
(
|
||
|
split_depth:nothing
|
||
|
special:nothing
|
||
|
code:(just
|
||
|
value:(raw@^Cell
|
||
|
x{}
|
||
|
x{FF0020DDA4F260D31F01ED44D0D31FD166BAF2A1F80001D307D4D1821804A817C80073FB0201FB00A4C8CB1FC9ED54}
|
||
|
))
|
||
|
data:(just
|
||
|
value:(raw@^Cell
|
||
|
x{}
|
||
|
x{00000001}
|
||
|
))
|
||
|
library:hme_empty))))
|
||
|
x{CFF8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D2068086C00000000000000009F65D110DC0E35F450FA914134_}
|
||
|
x{FF0020DDA4F260D31F01ED44D0D31FD166BAF2A1F80001D307D4D1821804A817C80073FB0201FB00A4C8CB1FC9ED54}
|
||
|
x{00000001}
|
||
|
---------------------------------------------
|
||
|
|
||
|
You may notice that the sequence number stored in the persistent data has changed (in our example, to one), and the last_trans_lt field (the logical time of the last transaction of this account) has been increased.
|
||
|
|
||
|
Now we can inspect the state of our new smart contract:
|
||
|
|
||
|
> getaccount 0f9gwEFBxqe5bWhhXnqR0mWtDzqaki6a6ckB1PqD9dPA0EKD
|
||
|
or
|
||
|
> getaccount -1:60c04141c6a7b96d68615e7a91d265ad0f3a9a922e9ae9c901d4fa83f5d3c0d0
|
||
|
|
||
|
Now we see
|
||
|
|
||
|
---------------------------------------------
|
||
|
got account state for -1:60C04141C6A7B96D68615E7A91D265AD0F3A9A922E9AE9C901D4FA83F5D3C0D0 with respect to blocks (-1,8000000000000000,16481):890F4D549428B2929F5D5E0C5719FBCDA60B308BA4B907797C9E846E644ADF26:22387176928F7BCEF654411CA820D858D57A10BBF1A0E153E1F77DE2EFB2A3FB and (-1,8000000000000000,16481):890F4D549428B2929F5D5E0C5719FBCDA60B308BA4B907797C9E846E644ADF26:22387176928F7BCEF654411CA820D858D57A10BBF1A0E153E1F77DE2EFB2A3FB
|
||
|
account state is (account
|
||
|
addr:(addr_std
|
||
|
anycast:nothing workchain_id:-1 address:x60C04141C6A7B96D68615E7A91D265AD0F3A9A922E9AE9C901D4FA83F5D3C0D0)
|
||
|
storage_stat:(storage_info
|
||
|
used:(storage_used
|
||
|
cells:(var_uint len:1 value:1)
|
||
|
bits:(var_uint len:1 value:111)
|
||
|
public_cells:(var_uint len:0 value:0)) last_paid:1553210152
|
||
|
due_payment:nothing)
|
||
|
storage:(account_storage last_trans_lt:16413000004
|
||
|
balance:(currencies
|
||
|
grams:(nanograms
|
||
|
amount:(var_uint len:5 value:6666000000))
|
||
|
other:(extra_currencies
|
||
|
dict:hme_empty))
|
||
|
state:account_uninit))
|
||
|
x{CFF60C04141C6A7B96D68615E7A91D265AD0F3A9A922E9AE9C901D4FA83F5D3C0D02025BC2E4A0D9400000000F492A0511406354C5A004_}
|
||
|
---------------------------------------------
|
||
|
|
||
|
Our new smart contract has some positive balance (of 6.666 test Grams), but has no code or data (reflected by `state:account_uninit`).
|
||
|
|
||
|
7. Uploading the code and data of the new smart contract
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
Now you can finally upload the external message with the StateInit of the new smart contract, containing its code and data:
|
||
|
|
||
|
---------------------------------------------
|
||
|
> sendfile new-wallet-query.boc
|
||
|
... external message status is 1
|
||
|
> last
|
||
|
...
|
||
|
> getaccount -1:60c04141c6a7b96d68615e7a91d265ad0f3a9a922e9ae9c901d4fa83f5d3c0d0
|
||
|
...
|
||
|
got account state for -1:60C04141C6A7B96D68615E7A91D265AD0F3A9A922E9AE9C901D4FA83F5D3C0D0 with respect to blocks (-1,8000000000000000,16709):D223B25D8D68401B4AA19893C00221CF9AB6B4E5BFECC75FD6048C27E001E0E2:4C184191CE996CF6F91F59CAD9B99B2FD5F3AA6F55B0B6135069AB432264358E and (-1,8000000000000000,16709):D223B25D8D68401B4AA19893C00221CF9AB6B4E5BFECC75FD6048C27E001E0E2:4C184191CE996CF6F91F59CAD9B99B2FD5F3AA6F55B0B6135069AB432264358E
|
||
|
account state is (account
|
||
|
addr:(addr_std
|
||
|
anycast:nothing workchain_id:-1 address:x60C04141C6A7B96D68615E7A91D265AD0F3A9A922E9AE9C901D4FA83F5D3C0D0)
|
||
|
storage_stat:(storage_info
|
||
|
used:(storage_used
|
||
|
cells:(var_uint len:1 value:3)
|
||
|
bits:(var_uint len:2 value:963)
|
||
|
public_cells:(var_uint len:0 value:0)) last_paid:1553210725
|
||
|
due_payment:nothing)
|
||
|
storage:(account_storage last_trans_lt:16625000002
|
||
|
balance:(currencies
|
||
|
grams:(nanograms
|
||
|
amount:(var_uint len:5 value:5983177000))
|
||
|
other:(extra_currencies
|
||
|
dict:hme_empty))
|
||
|
state:(account_active
|
||
|
(
|
||
|
split_depth:nothing
|
||
|
special:nothing
|
||
|
code:(just
|
||
|
value:(raw@^Cell
|
||
|
x{}
|
||
|
x{FF0020DDA4F260810200D71820D70B1FED44D0D7091FD709FFD15112BAF2A122F901541044F910F2A2F80001D7091F3120D74A97D70907D402FB00DED1A4C8CB1FCBFFC9ED54}
|
||
|
))
|
||
|
data:(just
|
||
|
value:(raw@^Cell
|
||
|
x{}
|
||
|
x{00000001F61CF0BC8E891AD7636E0CD35229D579323AA2DA827EB85D8071407464DC2FA3}
|
||
|
))
|
||
|
library:hme_empty))))
|
||
|
x{CFF60C04141C6A7B96D68615E7A91D265AD0F3A9A922E9AE9C901D4FA83F5D3C0D020680F0C2E4A0EB280000000F7BB57909405928024A134_}
|
||
|
x{FF0020DDA4F260810200D71820D70B1FED44D0D7091FD709FFD15112BAF2A122F901541044F910F2A2F80001D7091F3120D74A97D70907D402FB00DED1A4C8CB1FCBFFC9ED54}
|
||
|
x{00000001F61CF0BC8E891AD7636E0CD35229D579323AA2DA827EB85D8071407464DC2FA3}
|
||
|
---------------------------------------------
|
||
|
|
||
|
You will see that the smart contract has been initialized using code and data from the StateInit of the external message, and its balance has been slightly decreased because of the processing fees. Now it is up and running, and you can activate it by generating new external messages and uploading them to the TON Blockchain using the "sendfile" command of the Lite Client.
|
||
|
|
||
|
8. Using the simple wallet smart contract
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
Actually, the simple wallet smart contract used in this example can be used to transfer test Grams to any other accounts. It is in this respect similar to the test giver smart contract discussed above, with the difference that it processes only external messages signed by the correct private key (of its owner). In our case, it is the private key saved into the file "new-wallet.pk" during the compilation of the smart contract (see Section 3).
|
||
|
|
||
|
An example of how you might use this smart contract is provided in sample file crypto/smartcont/wallet.fif :
|
||
|
|
||
|
--------------------------------------------------------
|
||
|
#!/usr/bin/fift -s
|
||
|
def? $1 { "new-wallet" =: $1 } ifnot
|
||
|
$1 $len { "new-wallet" =: $1 } ifnot
|
||
|
$1 +".addr" file>B 256 B>u@ dup constant wallet_addr
|
||
|
."Wallet address = " x. cr
|
||
|
$1 +".pk" file>B dup Blen 32 <> abort"Private key must be exactly 32 bytes long"
|
||
|
constant wallet_pk
|
||
|
|
||
|
// 0x1111111122222222333333334444444455555555666666667777777788888888 constant dest_addr
|
||
|
0x13CB612A00A7C092C7DFD2EA45D603A9B54591BA4C88F71E707E009B879F0FB2 constant dest_addr
|
||
|
-1 constant wc
|
||
|
0 constant seqno
|
||
|
|
||
|
1000000000 constant Gram
|
||
|
{ Gram swap */ } : Gram*/
|
||
|
|
||
|
.666 Gram*/ constant amount
|
||
|
|
||
|
// b x --> b' ( serializes a Gram amount )
|
||
|
{ -1 { 1+ 2dup 8 * ufits } until
|
||
|
rot over 4 u, -rot 8 * u, } : Gram,
|
||
|
|
||
|
// create a message
|
||
|
<b b{011000100} s, wc 8 i, dest_addr 256 u, amount Gram, 0 9 64 32 + + 1+ 1+ u, "TEST" $, b>
|
||
|
<b seqno 32 u, 1 8 u, swap ref, b>
|
||
|
dup ."signing message: " <s csr. cr
|
||
|
dup hash wallet_pk ed25519_sign_uint
|
||
|
<b b{1000100} s, wc 8 i, wallet_addr 256 u, 0 Gram, b{00} s,
|
||
|
swap B, swap <s s, b>
|
||
|
dup ."resulting external message: " <s csr. cr
|
||
|
2 boc+>B dup Bx. cr
|
||
|
$1 +"-query.boc" B>file
|
||
|
-------------------------------------
|
||
|
|
||
|
You can hard-code the address of your smart contract here by changing the fourth line to, say,
|
||
|
|
||
|
0x60c04141c6a7b96d68615e7a91d265ad0f3a9a922e9ae9c901d4fa83f5d3c0d0 dup constant wallet_addr
|
||
|
|
||
|
You will also need to change the destination address, the name of the file with the private key, the sequence number (it will be 1 immediately after the smart contract is initialized; the actual value can always be retrieved by inspecting the current account state) and the Gram amount to be transferred. The payload of the internal message contains 32 bits with the string "TEST"; it can be changed to something more useful if necessary.
|
||
|
|
||
|
Alternatively, you can use the more sophisticated version of this script provided in crypto/smartcont/wallet.fif in the source tree, and pass all the required parameters as command-line arguments:
|
||
|
|
||
|
fift -I<source-directory>/crypto/fift/lib:<source-directory>/crypto/smartcont -s wallet.fif <your-wallet-id> <destination-addr> <your-wallet-seqno> <gram-amount>
|
||
|
|
||
|
For example,
|
||
|
|
||
|
fift -I<source-directory>/crypto/fift/lib:<source-directory>/crypto/smartcont -s wallet.fif my_new_wallet kf8Ty2EqAKfAksff0upF1gOptUWRukyI9x5wfgCbh58Pss9j 1 .666
|
||
|
|
||
|
Here `my_new_wallet` is the identifier of your wallet used before with new-wallet.fif; the address and the private key of your test wallet will be loaded from files `my_new_wallet.addr` and `my_new_wallet.pk` in the current directory.
|
||
|
|
||
|
When you run this code (by invoking the Fift interpreter), you create an external message with a destination equal to the address of your wallet smart contract, containing a correct Ed25519 signature, a sequence number, and an enveloped internal message from your wallet smart contract to the smart contract indicated in dest_addr, with an arbitrary value attached and an arbitrary payload. When your smart contract receives and processes this external message, it first checks the signature and the sequence number. If they are correct, it accepts the external message, sends the embedded internal message from itself to the intended destination, and increases the sequence number in its persistent data (this is a simple measure to prevent replay attacks, in case this sample wallet smart contract code ends up used in a real wallet application).
|
||
|
|
||
|
Of course, a true TON Blockchain wallet application would hide all the intermediate steps explained above. It would first communicate the address of the new smart contract to the user, asking them to transfer some funds to the indicated address (displayed in its non-bounceable user-friendly form) from another wallet or a cryptocurrency exchange, and then would provide a simple interface to display the current balance and to transfer funds to whatever other addresses the user wants. (The aim of this document is to explain how to create new non-trivial smart contracts and experiment with the TON Blockchain Test Network, rather than to explain how one could use the Lite Client instead of a more user-friendly wallet application.)
|
||
|
|
||
|
One final remark: The above examples used smart contracts in the basic workchain (workchain 0). They would work in exactly the same way in the masterchain (workchain -1), if one changes the "0 constant wc" to "-1 constant wc" in relevant places. The only difference is that the processing and storage fees in the basic workchain are 10-100 times lower than in the masterchain. Some smart contracts (such as the validator election smart contract) accept transfers only from masterchain smart contracts, so you'll need a wallet in the masterchain if you wish to make stakes on behalf of your own validator and participate in the elections.
|