Come scrivere e pubblicare un contratto smart su TON
Di cosa parla questo articolo?
In questo articolo racconterò come ho partecipato al primo (su due) concorso di Telegram sulla blockchain, non ho vinto e ho deciso di documentare l'esperienza in un articolo, affinché non vada persa e possa aiutare qualcun altro.
Poiché non volevo scrivere codice astratto ma creare qualcosa di funzionante, per l'articolo ho scritto un contratto smart per una lotteria istantanea e un sito che mostra i dati del contratto smart direttamente da TON senza usare archivi intermedi.
L'articolo sarà utile a chi desidera realizzare il proprio primo contratto smart su TON ma non sa da dove iniziare.
Attraverso l'esempio della lotteria passerò dall'installazione dell'ambiente alla pubblicazione del contratto smart, alla sua interazione e scriverò un sito per ottenere e pubblicare i dati.
Sulla partecipazione al concorso
Lo scorso ottobre Telegram ha annunciato un concorso sulla blockchain con nuovi linguaggi Fift e FunC. È stato necessario scrivere a scelta uno qualsiasi dei cinque contratti smart proposti. Ho pensato che sarebbe stato interessante occuparmi di qualcosa di insolito, imparare un linguaggio e fare qualcosa, anche se in futuro non avessi più avuto la necessità di scrivere altro. Inoltre, il tema è sempre molto attuale.
Va detto che non avevo esperienza nello sviluppo di contratti smart.
Avevo pianificato di partecipare fino alla fine e poi scrivere un articolo riepilogativo, ma ho fallito subito al primo tentativo. Io con multi-firma su FunC e ha funzionato. Ho preso come base .
A quel tempo pensai che fosse sicuramente sufficiente per guadagnare almeno un posto in classifica. Alla fine, circa 40 dei 60 partecipanti sono diventati premiati e io non ero tra loro. In generale, non c'è niente di male, ma una cosa mi ha turbato. Al momento dell'annuncio dei risultati, la revisione con il test per il mio contratto non era stata effettuata, ho chiesto ai partecipanti in chat se ci fosse qualcuno a cui mancava, e non c'erano.
Sembra che, prestando attenzione ai miei messaggi, dopo due giorni i giudici abbiano pubblicato un commento. Non ho mai capito se hanno semplicemente trascurato il mio smart contract durante la valutazione o se lo hanno giudicato così scarso da non meritare un commento. Ho posto una domanda nella pagina, ma non ho ricevuto risposta. Anche se chi ha giudicato non è un segreto, ho ritenuto superfluo inviare un messaggio personale.
Ho dedicato molto tempo a capire, quindi ho deciso di scrivere un articolo. Poiché le informazioni al momento sono limitate, l'articolo aiuterà a risparmiare tempo a tutti gli interessati.
Concetto di funzionamento degli smart contract in TON
Prima di scrivere qualcosa, è necessario capire da quale angolazione affrontare questo argomento. Quindi ora spiegherò di quali parti è composta la sistema, più precisamente quali componenti è necessario conoscere per scrivere almeno un contratto funzionante.
Ci concentreremo sulla scrittura di uno smart contract e sul lavoro con TON Virtual Machine (TVM), Fift e FunC, quindi l'articolo assomiglia più a una descrizione dello sviluppo di un programma normale. Non ci soffermeremo sul funzionamento della piattaforma stessa.
In generale, su come funziona TVM e il linguaggio Fift c'è una buona documentazione ufficiale. Durante la partecipazione al concorso e ora mentre scrivo l'attuale contratto, mi sono spesso riferito ad essa.
Il linguaggio principale in cui sono scritti i contratti intelligenti è FunC. Al momento non esiste documentazione su di esso, quindi per scrivere qualcosa è necessario studiare gli esempi di contratti intelligenti dal repository ufficiale e la stessa implementazione del linguaggio, oltre a esaminare gli esempi di contratti intelligenti dei due concorsi passati. I link si trovano alla fine dell'articolo.
Supponiamo che abbiamo già scritto un contratto intelligente in FunC, dopo di che compiliamo il codice in assembler Fift.
Il contratto intelligente compilato resta da pubblicare. Per questo, è necessario scrivere una funzione in Fift, che accetterà come input il codice del contratto intelligente e alcuni altri parametri, mentre in output si otterrà un file con estensione .boc (che significa "bag of cells"), e, a seconda di come lo scriviamo, la chiave privata e l'indirizzo, che vengono generati sulla base del codice del contratto intelligente. Su un indirizzo di un contratto intelligente, che non è ancora stato pubblicato, è possibile già inviare grammi.
Per pubblicare un contratto intelligente in TON, bisogna ottenere .boc il file dovrà essere inviato nella blockchain tramite un light client (di seguito maggiori dettagli). Ma prima di pubblicare, è necessario trasferire i grammi all'indirizzo generato, altrimenti il contratto intelligente non verrà pubblicato. Dopo la pubblicazione, sarà possibile interagire con il contratto intelligente, inviando messaggi esternamente (ad esempio, tramite il light client) o internamente (ad esempio, un contratto intelligente invia un messaggio a un altro all'interno di TON).
Dopo aver compreso come viene pubblicato il codice, le cose diventano più facili. Abbiamo un'idea di cosa vogliamo scrivere e di come funzionerà il nostro programma. E durante la scrittura cerchiamo come è già stato implementato in contratti intelligenti esistenti, oppure diamo un'occhiata al codice di implementazione Fift e FunC nel repository ufficiale, oppure consultiamo la documentazione ufficiale.
Molto spesso cercavo con parole chiave nella chat di Telegram dove si erano riuniti tutti i partecipanti al concorso, compresi i dipendenti di Telegram; durante il concorso, infatti, si sono riuniti lì per discutere di Fift e FunC. Il link si trova alla fine dell'articolo.
È ora di passare dalla teoria alla pratica.
Preparazione dell'ambiente di lavoro per TON.
Tutto ciò che sarà descritto nell'articolo l'ho fatto su MacOS e verificato su una pulita Ubuntu 18.04 LTS su Docker.
La prima cosa da fare è scaricare e installare lite-client con il quale è possibile inviare richieste a TON.
Le istruzioni sul sito ufficiale descrivono piuttosto dettagliatamente e chiaramente il processo di installazione, omettendo alcuni dettagli. Qui seguiamo le istruzioni installando nel contempo le dipendenze mancanti. Non ho compilato personalmente ogni progetto e ho utilizzato i repository ufficiali di Ubuntu (su MacOS ho utilizzato brew).
apt -y install git
apt -y install wget
apt -y install cmake
apt -y install g++
apt -y install zlib1g-dev
apt -y install libssl-dev Dopo aver installato tutte le dipendenze, possiamo installare lite-client, Fift, FunC.
Prima cloniamo il repository TON insieme alle dipendenze. Per comodità faremo tutto nella cartella ~/TON.
cd ~/TON
git clone https://github.com/ton-blockchain/ton.git
cd ./ton
git submodule update --init --recursiveNel repository sono anche conservate le implementazioni Fift e FunC.
Ora siamo pronti a costruire il progetto. Il codice del repository è stato clonato nella cartella ~/TON/ton. In ~/TON creiamo una cartella build e in essa compiliamo il progetto.
mkdir ~/TON/build
cd ~/TON/build
cmake ../tonPoiché stiamo per scrivere un contratto intelligente, abbiamo bisogno non solo di lite-client, ma anche Fift con FunC, quindi compiliamo tutto. Non è un processo veloce, quindi aspettiamo.
cmake --build . --target lite-client
cmake --build . --target fift
cmake --build . --target funcSuccessivamente, scarichiamo il file di configurazione che contiene i dati sul nodo a cui lite-client ci si connetterà.
wget https://test.ton.org/ton-lite-client-test1.config.jsonFacciamo le prime richieste a TON
Ora avviamo lite-client.
cd ~/TON/build
./lite-client/lite-client -C ton-lite-client-test1.config.jsonSe la compilazione è andata a buon fine, dopo l'avvio vedrete il log di connessione del client leggero al nodo.
[ 1][t 2][1582054822.963129282][lite-client.h:201][!testnode] conn ready
[ 2][t 2][1582054823.085654020][lite-client.cpp:277][!testnode] la versione del server è 1.1, capacità 7
[ 3][t 2][1582054823.085725069][lite-client.cpp:286][!testnode] il tempo del server è 1582054823 (delta 0)
...È possibile eseguire il comando help e vedere quali comandi sono disponibili.
helpElencando i comandi che useremo in questo articolo.
elenco dei comandi disponibili:
last Ottieni le ultime informazioni sul blocco e sullo stato dal server
sendfile Carica un messaggio serializzato da e invialo al server
getaccount [] Carica lo stato più recente dell'account specificato; è nel formato []:
runmethod [] ... Esegue il metodo GET dell'account con i parametri specificatilast riceve l'ultimo blocco creato dal server.
sendfile invia in TON un file con un messaggio, ed è proprio attraverso questo comando che viene pubblicato il contratto smart e le richieste ad esso.
getaccount carica lo stato attuale del contratto smart all'indirizzo specificato.
runmethod [] avvia i metodi get del contratto smart. Ora siamo pronti per scrivere il contratto stesso.
Implementazione
Idea
Come scritto in precedenza, il contratto smart che stiamo scrivendo è una lotteria.
E non è una lotteria in cui devi comprare un biglietto e aspettare un'ora, un giorno o un mese, ma una istantanea in cui l'utente trasferisce grammi all'indirizzo del contratto N e riceve immediatamente indietro 2 * N grammi o perde. Fissiamo la probabilità di vincita intorno al 40%. Se i grammi per il pagamento non sono sufficienti, considereremo la transazione come un rifornimento.
È importante che le scommesse possano essere visualizzate in tempo reale e in modo conveniente, affinché l'utente possa capire immediatamente se ha vinto o perso. Pertanto, abbiamo bisogno di creare un sito web che mostri le scommesse e il risultato direttamente da TON.
Scrittura del contratto smart
Per comodità, ho creato un evidenziatore di sintassi per FunC; puoi trovare e installare il plugin cercandolo nel Visual Studio Code. Se desideri aggiungere qualcosa, ho reso il plugin accessibile al pubblico. Inoltre, qualcuno aveva già creato un plugin per lavorare con Fift, che può essere trovato e installato in VSC.
Iniziamo subito a creare un repository dove costantemente committeremo i risultati intermedi.
Per semplificarci la vita, scriveremo un contratto intelligente e lo testeremo localmente finché non sarà pronto. Solo dopo lo pubblicheremo su TON.
Il contratto intelligente ha due metodi esterni a cui si può accedere. Il primo, recv_external() questa funzione viene eseguita quando una richiesta al contratto proviene dall'esterno, cioè non da TON; ad esempio, quando formiamo un messaggio e lo inviamo tramite lite-client. Il secondo, recv_internal() è quando un contratto all'interno dello stesso TON si riferisce al nostro. In entrambi i casi, è possibile passare parametri alla funzione.
Iniziamo con un semplice esempio, che funzionerà se verrà pubblicato, ma non ha alcun carico funzionale.
() recv_internal(slice in_msg) impure {
;; TODO: implementation
}
() recv_external(slice in_msg) impure {
;; TODO: implementation
}Qui dobbiamo spiegare cosa sono slice. Tutti i dati memorizzati nella blockchain TON sono una collezione TVM cell o semplicemente cell, in cui è possibile memorizzare fino a 1023 bit di dati e fino a 4 link ad altre celle.
TVM cell slice o slice è una parte esistente cell che viene utilizzata per il suo parsing, sarà chiaro più avanti. La cosa principale per noi è che nel contratto intelligente possiamo passare slice e a seconda del tipo di messaggio elaborare i dati in recv_external() o recv_internal().
impure è una parola chiave che indica che la funzione modifica i dati del contratto intelligente.
Salveremo il codice del contratto in lottery-code.fc e lo compileremo.
~/TON/build/crypto/func -APSR -o lottery-compiled.fif ~/TON/ton/crypto/smartcont/stdlib.fc ./lottery-code.fc Il valore dei flag può essere visualizzato usando il comando
~/TON/build/crypto/func -helpAbbiamo ottenuto il codice assemblato Fift compilato in lottery-compiled.fif:
// lottery-compiled.fif
"Asm.fif" include
// automatically generated from `/Users/rajymbekkapisev/TON/ton/crypto/smartcont/stdlib.fc` `./lottery-code.fc`
PROGRAM{
DECLPROC recv_internal
DECLPROC recv_external
recv_internal PROC:<{
// in_msg
DROP //
}>
recv_external PROC:<{
// in_msg
DROP //
}>
}END>cPuò essere eseguito localmente, per questo prepariamo l'ambiente.
Notiamo che la prima riga importa Asm.fif, questo è un codice scritto in Fift per l'assemblatore Fift.
Dal momento che vogliamo eseguire e testare il contratto intelligente localmente, creiamo un file lottery-test-suite.fif e copiamo lì il codice compilato, sostituendo l'ultima riga, che scrive il codice del contratto intelligente in una costante. code, per poi trasferirlo nella macchina virtuale:
"TonUtil.fif" include
"Asm.fif" include
PROGRAM{
DECLPROC recv_internal
DECLPROC recv_external
recv_internal PROC:
recv_external PROC:
}END>s costante codice
Fino a qui è chiaro, ora aggiungiamo nello stesso file il codice che utilizzeremo per avviare TVM.
0 tuple 0x076ef1ea , \/\/ magico
0 , 0 , \/\/ azioni msg_sents
1570998536 , \/\/ unix_time
1 , 1 , 3 , \/\/ block_lt, trans_lt, rand_seed
0 tuple 100000000000000 , dictnew , , \/\/ saldo rimanente
0 , dictnew , \/\/ contract_address, global_config
1 tuple \/\/ impacchetta in un altro tuple
constant c7
0 constant recv_internal \/\/ per eseguire recv_internal()
-1 constant recv_external \/\/ per invocare recv_external()In c7 Registriamo il contesto, ossia i dati con cui verrà avviato TVM (o lo stato della rete). Durante il concorso uno degli sviluppatori ha mostrato come viene creato c7 e io l'ho copiato. In questo articolo potremmo dover cambiare rand_seed poiché da esso dipende la generazione di numeri casuali e se non viene cambiato, ogni volta verrà restituito lo stesso numero.
recv_internal e recv_external le costanti con valore 0 e -1 risponderanno alla chiamata delle rispettive funzioni nel contratto smart.
Ora siamo pronti a creare il primo test per il nostro contratto smart vuoto. Per chiarezza, per ora aggiungeremo tutti i test in questo stesso file. lottery-test-suite.fif.
Creeremo una variabile storage e ci scriveremo dentro un vuoto cell, questo sarà lo storage del contratto smart.
message questo è il messaggio che passeremo al contratto smart dall'esterno. Anche questo lo faremo vuoto per ora.
memoria variabile
<b b> memoria!
messaggio variabile
<b b> messaggio! Dopo aver preparato le costanti e le variabili, avviamo TVM con il comando runvmctx e passiamo i parametri creati come input.
message @
recv_external
code
storage @
c7
runvmctx Alla fine avremo Ora possiamo eseguire il codice risultante. Fift.
Ora possiamo avviare il codice migliorato.
export FIFTPATH=~/TON/ton/crypto/fift/lib // eseguiamo una volta per comodità
~/TON/build/crypto/fift -s lottery-test-suite.fif Il programma dovrebbe funzionare senza errori e nell'output vediamo il log di esecuzione:
execute SETCP 0
execute DICTPUSHCONST 19 (xC_,1)
execute DICTIGETJMPZ
execute DROP
execute implicit RET
[ 3][t 0][1582281699.325381279][vm.cpp:479] steps: 5 gas: used=304, max=9223372036854775807, limit=9223372036854775807, credit=0Ottimo, abbiamo scritto la prima versione funzionante del contratto smart.
Ora dobbiamo aggiungere funzionalità. Iniziamo con i messaggi, che arrivano dal mondo esterno in recv_external()
Lo sviluppatore sceglie il formato del messaggio che il contratto può accettare.
Ma di solito,
- Innanzitutto, vogliamo proteggere il nostro contratto dal mondo esterno e garantire che solo il proprietario del contratto possa inviare messaggi esterni ad esso.
- In secondo luogo, quando inviamo un messaggio valido in TON, vogliamo che questo accada esattamente una sola volta e che, al tentativo di reinvio dello stesso messaggio, il contratto intelligente lo rifiuti.
Pertanto, in quasi ogni contratto vengono affrontati questi due problemi, dato che il nostro contratto accetta messaggi esterni, dobbiamo anche occuparci di questo.
Procederemo in ordine inverso. Prima risolveremo il problema della ripetizione; se il contratto ha già ricevuto e gestito un messaggio simile, non verrà eseguito una seconda volta. E poi affronteremo la questione di permettere solo a un certo gruppo di persone di inviare messaggi al contratto intelligente.
Ci sono diversi modi per risolvere il problema dei messaggi duplicati. Ecco come faremo. Inizializziamo un contatore dei messaggi ricevuti nel contratto smart con un valore iniziale di 0. Aggiungeremo il valore attuale del contatore a ciascun messaggio inviato al contratto smart. Se il valore del contatore nel messaggio non corrisponde al valore nel contratto smart, non lo elaboriamo; se corrisponde, lo elaboriamo e incrementiamo il contatore nel contratto smart di 1.
Torniamo a lottery-test-suite.fif e aggiungiamo un secondo test. Invieremo un numero errato, il codice dovrebbe generare un'eccezione. Ad esempio, supponiamo che nei dati del contratto ci sia 166, mentre noi inviamo 165.
<b 166 32 u, b> memoria!
<b 165 32 u, b> messaggio!
messaggio @
recv_external
codice
memo @
c7
runvmctx
drop
exit_code !
."Codice di uscita " exit_code @ . cr
exit_code @ 33 - abort"Test n.°2 non superato"Avviamo.
~/TON/build/crypto/fift -s lottery-test-suite.fif E vedremo che il test viene eseguito con un errore.
[ 1][t 0][1582283084.210902214][words.cpp:3046] lottery-test-suite.fif:67: abort": Test #2 Non superato
[ 1][t 0][1582283084.210941076][fift-main.cpp:196] Errore nell'interpretazione del file `lottery-test-suite.fif`: errore nell'interpretazione del file incluso `lottery-test-suite.fif`: lottery-test-suite.fif:67: abort": Test #2 Non superatoA questo punto lottery-test-suite.fif dovrebbe apparire come .
Ora scriviamo la logica del contatore nel contratto smart in lottery-code.fc.
() recv_internal(slice in_msg) impure {
;; TODO: implementazione
}
() recv_external(slice in_msg) impure {
if (slice_empty?(in_msg)) {
return ();
}
int msg_seqno = in_msg~load_uint(32);
var ds = begin_parse(get_data());
int stored_seqno = ds~load_uint(32);
throw_unless(33, msg_seqno == stored_seqno);
}In slice in_msg ci sono messaggi che stiamo inviando.
Prima di tutto, controlliamo se ci sono dati nel messaggio; se non ci sono, usciamo semplicemente.
Poi analizziamo il messaggio. in_msg~load_uint(32) carica il numero 165, 32 bit unsigned int dal messaggio inviato.
Successivamente, carichiamo 32 bit dallo storage del contratto intelligente. Verifichiamo che il numero caricato corrisponda a quello inviato, altrimenti scartiamo un'eccezione. Nel nostro caso, dato che stiamo inviando valori non corrispondenti, dovrebbe essere scartata un'eccezione.
Ora compiliamo.
~/TON/build/crypto/func -APSR -o lottery-compiled.fif ~/TON/ton/crypto/smartcont/stdlib.fc ./lottery-code.fc Copia il codice risultante in lottery-test-suite.fif, ricordando di sostituire l'ultima riga.
Verifichiamo che il test funzioni:
~/TON/build/crypto/fift -s lottery-test-suite.fifpuoi vedere il commit corrispondente con i risultati attuali.
Notiamo che copiare continuamente il codice compilato del contratto intelligente in un file di test è scomodo, quindi scriveremo uno script che registrerà il codice in una costante per noi, così possiamo semplicemente includere il codice compilato nei nostri test tramite "include".
Nella cartella del progetto creiamo un file build.sh con il seguente contenuto.
#!/bin/bash
~/TON/build/crypto/func -SPA -R -o lottery-compiled.fif ~/TON/ton/crypto/smartcont/stdlib.fc ./lottery-code.fcRendiamolo eseguibile.
chmod +x ./build.shOra, basta eseguire il nostro script per compilare il contratto. Inoltre, dobbiamo memorizzarlo in una costante code. Pertanto, creeremo un nuovo file lotter-compiled-for-test.fif, che includeremo nel file lottery-test-suite.fif.
Aggiungiamo allo script sh il codice che semplicemente copierà il file compilato in lotter-compiled-for-test.fif e cambierà l'ultima riga.
# copy and change for test
cp lottery-compiled.fif lottery-compiled-for-test.fif
sed '$d' lottery-compiled-for-test.fif > test.fif
rm lottery-compiled-for-test.fif
mv test.fif lottery-compiled-for-test.fif
echo -n "}END>s constant code" >> lottery-compiled-for-test.fifOra, per testare, eseguiamo lo script risultante e genereremo il file lottery-compiled-for-test.fif, che includeremo nel nostro lottery-test-suite.fif
In lottery-test-suite.fif rimuoviamo il codice del contratto e aggiungiamo la riga "lottery-compiled-for-test.fif" include.
Eseguiamo i test per verificare che superino.
~/TON/build/crypto/fift -s lottery-test-suite.fifOttimo, ora per automatizzare l'esecuzione dei test creiamo un file test.sh, che inizialmente eseguirà build.sh, e poi lancerà i test.
touch test.sh
chmod +x test.shAll'interno scriviamo
./build.sh
echo "nCompilazione completata n"
export FIFTPATH=~/TON/ton/crypto/fift/lib
~/TON/build/crypto/fift -s lottery-test-suite.fifFaremo test.sh e avviamo per assicurarci che i test funzionino.
chmod +x ./test.sh
./test.shVerifichiamo che il contratto si compili e che i test vengano eseguiti.
Ottimo, ora all'avvio test.sh La compilazione e l'esecuzione dei test avverranno immediatamente. Ecco il link a .
Ok, prima di continuare, facciamo un'altra cosa per comodità.
Creiamo una cartella build dove conserveremo il contratto compilato e la sua copia registrata in una costante lottery-compiled.fif, lottery-compiled-for-test.fif. Creeremo anche una cartella test dove saranno archiviati i file dei test lottery-test-suite.fif e potenzialmente altri file di supporto. .
Continuiamo lo sviluppo del contratto smart.
Dopo dovrebbe esserci un test che verifica che il messaggio venga ricevuto e il contatore venga aggiornato nello storage quando inviamo un numero corretto. Ma faremo questo più tardi.
Ora riflettiamo su quale struttura dati e quali dati devono essere conservati nel contratto smart.
Descriverò tutto ciò che stiamo archiviando.
`seqno` è un numero intero positivo a 32 bit utilizzato come contatore.
`pubkey` è un numero intero positivo a 256 bit che funge da chiave pubblica, con cui verificheremo la firma del messaggio inviato esternamente, come spiegato di seguito.
`order_seqno` è un numero intero positivo a 32 bit che memorizza il contatore delle scommesse.
`number_of_wins` è un numero intero positivo a 32 bit che memorizza il numero di vittorie.
`incoming_amount` è un tipo di dato Gram (i primi 4 bit rappresentano la lunghezza), memorizza il numero totale di grammi inviati al contratto.
`outgoing_amount` rappresenta il numero totale di grammi inviati ai vincitori.
`owner_wc` è il numero del workchain, un intero a 32 bit (in alcune fonti viene indicato come un intero a 8 bit). Attualmente ci sono solo due valori -1 e 0.
`owner_account_id` è un numero intero positivo a 256 bit, che rappresenta l'indirizzo del contratto nell'attuale workchain.
`orders` è una variabile di tipo dizionario che memorizza le ultime venti scommesse. Successivamente, dobbiamo scrivere due funzioni. La prima la chiameremo pack_state(), che si occuperà di imballare i dati per la successiva memorizzazione nel deposito del contratto intelligente. La seconda, la chiameremo unpack_state() , leggerà e restituirà i dati dal deposito.
_ pack_state(int seqno, int pubkey, int order_seqno, int number_of_wins, int incoming_amount, int outgoing_amount, int owner_wc, int owner_account_id, cell orders) inline_ref {
return begin_cell()
.store_uint(seqno, 32)
.store_uint(pubkey, 256)
.store_uint(order_seqno, 32)
.store_uint(number_of_wins, 32)
.store_grams(incoming_amount)
.store_grams(outgoing_amount)
.store_int(owner_wc, 32)
.store_uint(owner_account_id, 256)
.store_dict(orders)
.end_cell();
}
_ unpack_state() inline_ref {
var ds = begin_parse(get_data());
var unpacked = (ds~load_uint(32), ds~load_uint(256), ds~load_uint(32), ds~load_uint(32), ds~load_grams(), ds~load_grams(), ds~load_int(32), ds~load_uint(256), ds~load_dict());
ds.end_parse();
return unpacked;
}Aggiungiamo queste due funzioni all'inizio del contratto intelligente. Risultato: risultato intermedio.
Per salvare i dati, sarà necessario chiamare la funzione integrata set_data() e scriverà i dati in pack_state() nello storage del contratto intelligente.
cell packed_state = pack_state(arg_1, .., arg_n);
set_data(packed_state);Ora che abbiamo funzioni comode per scrivere e leggere i dati, possiamo procedere.
Dobbiamo verificare che il messaggio in arrivo sia firmato dal proprietario del contratto (o da un altro utente che ha accesso alla chiave privata).
Quando pubblichiamo un contratto smart, possiamo inizializzarlo con i dati necessari nel nostro archivio, che verranno salvati per un uso futuro. Qui registreremo la chiave pubblica, in modo da poter verificare che la firma del messaggio in arrivo sia stata fatta dalla corrispondente chiave privata.
Prima di procedere, creiamo una chiave privata e registriamola in test/keys/owner.pk. Per fare questo, avviamo Fift in modalità interattiva e eseguiamo quattro comandi.
`newkeypair` genera una chiave pubblica e una chiave privata e le scrive nello stack.
`drop` rimuove l'elemento superiore dallo stack (in questo caso, la chiave pubblica)
`.s` mostra semplicemente cosa c'è attualmente nello stack
`"owner.pk" B>file` scrive la chiave privata in un file chiamato `owner.pk`.
`bye` termina il lavoro con Fift. Creiamo una cartella keys all'interno della cartella test e qui scriveremo la chiave privata.
mkdir test/keys
cd test/keys
~/TON/build/crypto/fift -i
newkeypair
ok
.s
BYTES:128DB222CEB6CF5722021C3F21D4DF391CE6D5F70C874097E28D06FCE9FD6917 BYTES:DD0A81AAF5C07AAAA0C7772BB274E494E93BB0123AA1B29ECE7D42AE45184128
drop
ok
"owner.pk" B>file
ok
byeVediamo nella cartella attuale il file owner.pk.
Rimuoviamo la chiave pubblica dallo stack; quando necessario, possiamo recuperarla dalla chiave privata.
Ora dobbiamo scrivere il controllo della firma. Iniziamo con un test. Prima leggiamo la chiave privata da un file utilizzando la funzione file>B e la scriviamo in una variabile owner_private_key, poi, utilizzando la funzione priv>pub convertiamo la chiave privata in pubblica e scriviamo il risultato in owner_public_key.
variabile owner_private_key
variabile owner_public_key
"./keys/owner.pk" file>B owner_private_key !
owner_private_key @ priv>pub owner_public_key !Entrambe le chiavi ci serviranno.
Inizializziamo il deposito del contratto intelligente con dati arbitrari nella stessa sequenza della funzione pack_state()e scriviamo in una variabile storage.
variabile owner_private_key
variabile owner_public_key
variabile orders
variabile owner_wc
variabile owner_account_id
"./keys/owner.pk" file>B owner_private_key !
owner_private_key @ priv>pub owner_public_key !
dictnew orders !
0 owner_wc !
0 owner_account_id !
<b 0 32 u, owner_public_key @ b, gram, owner_wc i, owner_account_id 256 orders dict, b> memoria!Successivamente costruiamo un messaggio firmato, che conterrà solo la firma e il valore del contatore.
Prima creiamo i dati che vogliamo trasmettere, poi li firmiamo con la chiave privata e infine formiamo il messaggio firmato.
variabile messaggio_da_firmare
variabile messaggio_da_inviare
variabile firma
<b 0 32 u, b> messaggio_da_firmare !
messaggio_da_firmare @ chiave_privata_del_proprietario @ ed25519_firma_uint firma !
<b signature @ b, 0 32 u, b> <s ! Alla fine, il messaggio che invieremo al contratto intelligente è registrato nella variabile message_to_send, riguardo alle funzioni hashu, ed25519_sign_uint puoi leggere .
E per avviare il test, chiamiamo di nuovo.
message_to_send @
recv_external
code
storage @
c7
runvmctxil file con i test dovrebbe apparire a questo punto.
Avvieremo il test e fallirà, quindi modificheremo lo smart contract per ricevere messaggi di questo formato e verificare la firma.
Prima leggiamo dai messaggi 512 bit di firma e li memorizziamo in una variabile, poi leggiamo 32 bit della variabile contatore.
Poiché abbiamo una funzione per leggere i dati dal deposito dello smart contract, la utilizzeremo.
Successivamente, verifichiamo il contatore fornito con quello memorizzato e controlliamo la firma. Se qualcosa non corrisponde, solleviamo un'eccezione con il codice appropriato.
var signature = in_msg~load_bits(512);
var message = in_msg;
int msg_seqno = message~load_uint(32);
(int stored_seqno, int pubkey, int order_seqno, int number_of_wins, int incoming_amount, int outgoing_amount, int owner_wc, int owner_account_id, cell orders) = unpack_state();
throw_unless(33, msg_seqno == stored_seqno);
throw_unless(34, check_signature(slice_hash(in_msg), signature, pubkey));Commit corrispondente .
Avvieremo i test e vedremo che il secondo test fallisce. Per due motivi: mancanza di bit nel messaggio e mancanza di bit nel deposito, quindi il codice fallisce durante il parsing. Dobbiamo aggiungere la firma del messaggio che stiamo inviando e copiare il deposito dall'ultimo test.
Nel secondo test aggiungeremo la firma del messaggio e modificheremo il deposito dello smart contract. il file con i test appare attualmente.
Scriviamo il quarto test, in cui invieremo un messaggio firmato con la chiave privata di qualcun altro. Creeremo un'altra chiave privata e la salveremo nel file not-owner.pk. Utilizzeremo questa chiave privata per firmare il messaggio. Eseguiamo i test e verifichiamo che tutti i test siano superati. per il momento.
Ora finalmente possiamo passare all'implementazione della logica del contratto intelligente.
In recv_external() accetteremo due tipi di messaggi.
Poiché il nostro contratto accumulerà le perdite dei giocatori, questi denaro devono essere trasferiti al creatore della lotteria. L'indirizzo del portafoglio del creatore della lotteria viene registrato nello storage al momento della creazione del contratto.
Per sicurezza abbiamo bisogno di poter cambiare l'indirizzo a cui inviare i grammi dei perdenti. Inoltre, dobbiamo avere la possibilità di inviare grammi dalla lotteria all'indirizzo del proprietario.
Iniziamo dal primo. Scriviamo innanzitutto un test che verifichi che dopo l'invio del messaggio il contratto intelligente abbia salvato il nuovo indirizzo nello storage. Notiamo che nel messaggio, oltre al contatore e al nuovo indirizzo, stiamo anche inviando action Un numero intero non negativo a 7 bit, in base a questo, decideremo come elaborare il messaggio nel contratto intelligente.
<b 0 32 u, 1 @ 7 new_owner_wc i, new_owner_account_id 256 b> messaggio_da_firmare !Nel test puoi vedere come avviene la deserializzazione dello storage del contratto intelligente. storage in Fift. La deserializzazione delle variabili è descritta nella documentazione di Fift.
con l'aggiunta del test.
Eseguiamo il test e verifichiamo che fallisca. Ora aggiungiamo la logica per cambiare l'indirizzo del proprietario della lotteria.
Nel contratto intelligente continuiamo a analizzare message, leggiamo in action. Ricordiamo che avremo due action: cambio dell'indirizzo e invio di grammi.
Poi leggiamo il nuovo indirizzo del proprietario del contratto e lo salviamo nello storage.
Eseguiamo i test e vediamo che il terzo test fallisce. Fallisce perché il contratto ora analizza ulteriormente 7 bit dal messaggio, che non sono presenti nel test. Aggiungiamo nel messaggio un non esistente action. Eseguiamo i test e vediamo che tutti passano. commit delle modifiche. Ottimo.
Ora scriviamo la logica per inviare la quantità specificata di grammi all'indirizzo salvato in precedenza.
Iniziamo a scrivere il test. Scriveremo due test: uno quando il saldo non è sufficiente e l'altro quando tutto dovrebbe andare a buon fine. I test possono essere visualizzati .
Ora completiamo il codice. Iniziamo a scrivere due metodi ausiliari. Il primo è un metodo getter per conoscere l'attuale saldo del contratto intelligente.
int balance() inline_ref method_id {
return get_balance().pair_first();
}E il secondo per inviare grammi a un altro contratto intelligente. Questo metodo l'ho copiato integralmente da un altro contratto intelligente.
() send_grams(int wc, int addr, int grams) impure {
;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000
cell msg = begin_cell()
;; .store_uint(0, 1) ;; 0 <= format indicator int_msg_info$0
;; .store_uint(1, 1) ;; 1 <= ihr disabled
;; .store_uint(1, 1) ;; 1 <= bounce = true
;; .store_uint(0, 1) ;; 0 <= bounced = false
;; .store_uint(4, 5) ;; 00100 <= address flags, anycast = false, 8-bit workchain
.store_uint (196, 9)
.store_int(wc, 8)
.store_uint(addr, 256)
.store_grams(grams)
.store_uint(0, 107) ;; 106 zeroes + 0 as an indicator that there is no cell with the data.
.end_cell();
send_raw_message(msg, 3); ;; mode, 2 for ignoring errors, 1 for sender pays fees, 64 for returning inbound message value
}Aggiungiamo questi due metodi nel contratto intelligente e scriviamo la logica. Iniziamo estraendo la quantità di grammi dal messaggio. Poi verifichiamo il saldo, se non è sufficiente solleviamo un'eccezione. Se tutto va bene, inviamo i grammi all'indirizzo salvato e aggiorniamo il contatore.
int amount_to_send = message~load_grams();
throw_if(36, amount_to_send + 500000000 > balance());
accept_message();
send_grams(owner_wc, owner_account_id, amount_to_send);
set_data(pack_state(stored_seqno + 1, pubkey, order_seqno, number_of_wins, incoming_amount, outgoing_amount, owner_wc, owner_account_id, orders));Questo è l'aspetto del contratto intelligente al momento. Eseguiamo i test e assicuriamoci che passino.
A proposito, per ogni messaggio elaborato, il contratto intelligente addebita una commissione. Affinché il contratto intelligente esegua la richiesta del messaggio, dopo le verifiche di base è necessario chiamare accept_message().
Ora occupiamoci dei messaggi interni. Fondamentalmente, accetteremo solo grammi e rimanderemo indietro al giocatore il doppio della somma in caso di vittoria e un terzo al proprietario in caso di perdita.
Iniziamo scrivendo un test semplice. Per questo avremo bisogno di un indirizzo di test del contratto intelligente da cui invieremo grammi al contratto intelligente.
L'indirizzo del contratto intelligente è composto da due numeri, un intero a 32 bit è responsabile della workchain e un numero unico non negativo a 256 bit identifica l'account in questa workchain. Ad esempio, -1 e 12345, questo indirizzo lo salveremo in un file.
Ho copiato la funzione per salvare l'indirizzo da .
// ( wc addr fname -- ) Save address to file in 36-byte format
{ -rot 256 u>B swap 32 i>B B+ swap B>file } : save-addressAnalizziamo come funziona la funzione, questo aiuterà a comprendere come funziona Fift. Avviamo Fift in modalità interattiva.
~/TON/build/crypto/fift -i Per prima cosa mettiamo nello stack -1, 12345 e il nome del file futuro "sender.addr":
-1 12345 "sender.addr" Il passo successivo esegue la funzione -rot, che sposta lo stack in modo che il numero unico del contratto intelligente si trovi in cima allo stack:
"sender.addr" -1 12345256 u>B converte l'intero non negativo a 256 bit in byte.
"sender.addr" -1 BYTES:0000000000000000000000000000000000000000000000000000000000003039swap scambia i due elementi superiori dello stack.
"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039 -132 i>B converte l'intero a 32 bit in byte.
"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039 BYTES:FFFFFFFFB+ collega due sequenze di byte.
"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039FFFFFFFFAncora swap.
BYTES:0000000000000000000000000000000000000000000000000000000000003039FFFFFFFF "sender.addr" E infine registriamo i byte nel file B>file. Dopo di che, il nostro stack è vuoto. Ferma Fift. È stato creato un file nella cartella corrente sender.addr. Sposteremo il file nella cartella creata test/addresses/.
Scriveremo un semplice test che invierà grammi al contratto intelligente. .
Ora ci occupiamo della logica della lotteria.
La prima cosa che facciamo è controllare se il messaggio è rimbalzato o no, se è rimbalzato, lo ignoriamo. è rimbalzato significa che il contratto restituirà grammi se si verifica un errore. Non restituiremo grammi se si presenta un errore.
Controlliamo, se il saldo è inferiore a mezzo grammo, accettiamo semplicemente il messaggio e lo ignoriamo.
Successivamente, facciamo il parsing dell'indirizzo del contratto intelligente da cui è arrivato il messaggio.
Leggiamo i dati dallo storage e poi rimuoviamo le vecchie scommesse dalla cronologia se sono più di venti. Per comodità, ho scritto tre funzioni aggiuntive pack_order(), unpack_order(), remove_old_orders().
Poi, controlliamo se il saldo non è sufficiente per il pagamento, quindi consideriamo che non si tratta di una scommessa, ma di un reintegro e salviamo il reintegro in orders.
Infine, entriamo nel merito del contratto intelligente.
All'inizio, se il giocatore perde, salviamo la sua scommessa nella cronologia delle scommesse e se l'importo supera i 3 grammi, inviamo 1/3 al proprietario del contratto intelligente.
Se il giocatore vince, allora inviamo il doppio dell'importo all'indirizzo del giocatore e successivamente salviamo l'informazione sulla scommessa nella cronologia.
() recv_internal(int order_amount, cell in_msg_cell, slice in_msg) impure {
var cs = in_msg_cell.begin_parse();
int flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
if (flags & 1) { ;; ignore bounced
return ();
}
if (order_amount < 500000000) { ;; just receive grams without changing state
return ();
}
slice src_addr_slice = cs~load_msg_addr();
(int src_wc, int src_addr) = parse_std_addr(src_addr_slice);
(int stored_seqno, int pubkey, int order_seqno, int number_of_wins, int incoming_amount, int outgoing_amount, int owner_wc, int owner_account_id, cell orders) = unpack_state();
orders = remove_old_orders(orders, order_seqno);
if (balance() = 4) {
builder order = pack_order(order_seqno, 3, now(), order_amount, src_wc, src_addr);
orders~udict_set_builder(32, order_seqno, order);
set_data(pack_state(stored_seqno, pubkey, order_seqno + 1, number_of_wins, incoming_amount + order_amount, outgoing_amount, owner_wc, owner_account_id, orders));
if (order_amount > 3000000000) {
send_grams(owner_wc, owner_account_id, order_amount / 3);
}
return ();
}
send_grams(src_wc, src_addr, 2 * order_amount);
builder order = pack_order(order_seqno, 2, now(), order_amount, src_wc, src_addr);
orders~udict_set_builder(32, order_seqno, order);
set_data(pack_state(stored_seqno, pubkey, order_seqno + 1, number_of_wins + 1, incoming_amount, outgoing_amount + 2 * order_amount, owner_wc, owner_account_id, orders));
}Ecco fatto. .
Resta solo da implementare i metodi GET, così sarà possibile ottenere informazioni sullo stato del contratto dal mondo esterno (essenzialmente leggendo i dati dal loro repository nel contratto intelligente).
. Di come ottenere informazioni sul contratto intelligente ne parleremo più avanti.
Inoltre, ho dimenticato di aggiungere il codice che gestisce la prima richiesta, che si verifica quando il contratto intelligente viene pubblicato. . E in più c'è un bug nell'invio di 1/3 dell'importo all'account del proprietario.
Dopo, resta da pubblicare il contratto intelligente. Creeremo una cartella requests.
Ho preso come base il codice di pubblicazione che è presente nel repository ufficiale.
Un aspetto importante da notare è che stiamo formando il repository del contratto intelligente e il messaggio in ingresso. Dopo di ciò, viene generato l'indirizzo del contratto intelligente, quindi l'indirizzo è noto anche prima della pubblicazione su TON. Successivamente, su questo indirizzo bisogna inviare qualche gram e solo dopo è necessario inviare il file con il contratto intelligente stesso, poiché per lo stoccaggio del contratto intelligente e le operazioni in esso la rete addebita una commissione (i validatori che immagazzinano ed eseguono i contratti intelligenti). .
Successivamente eseguiamo il codice di pubblicazione e otteniamo lottery-query.boc il file e l'indirizzo del contratto intelligente.
~/TON/build/crypto/fift -s requests/new-lottery.fif 0Non dimentichiamo di salvare i file generati: lottery-query.boc, lottery.addr, lottery.pk.
Tra le altre cose, nei log di esecuzione vedremo l'indirizzo del contratto intelligente.
new wallet address = 0:044910149dbeaf8eadbb2b28722e7d6a2dc6e264ec2f1d9bebd6fb209079bc2a
(Salvando l'indirizzo nel file lottery.addr)
Indirizzo non rimborsabile (per init): 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd
Indirizzo rimborsabile (per accesso futuro): kQAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8KpFYPer curiosità, facciamo una richiesta a TON
$ ./lite-client/lite-client -C ton-lite-client-test1.config.json
getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8KsydE vediamo che l'account con questo indirizzo è vuoto.
lo stato dell'account è vuotoInvio all'indirizzo 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd 2 Gram e dopo alcuni secondi eseguiamo di nuovo il comando. Per inviare grammi uso , e i grammi di test possono essere chiesti a qualcuno del chat, di cui parlerò alla fine dell'articolo.
> getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8KsydSi osserva che è emerso un contratto intelligente non inizializzato (state:account_uninit) con questo indirizzo e un saldo di 1 000 000 000 nanogram.
Lo stato dell'account è (account
addr:(addr_std
anycast:nothing workchain_id:0 address:x044910149DBEAF8EADBB2B28722E7D6A2DC6E264EC2F1D9BEBD6FB209079BC2A)
storage_stat:(storage_info
used:(storage_used
cells:(var_uint len:1 value:1)
bits:(var_uint len:1 value:103)
public_cells:(var_uint len:0 value:0)) last_paid:1583257959
due_payment:nothing)
storage:(account_storage last_trans_lt:3825478000002
balance:(currencies
grams:(nanograms
amount:(var_uint len:4 value:2000000000))
other:(extra_currencies
dict:hme_empty))
state:account_uninit))
x{C00044910149DBEAF8EADBB2B28722E7D6A2DC6E264EC2F1D9BEBD6FB209079BC2A20259C2F2F4CB3800000DEAC10776091DCD650004_}
L'ultima transazione lt = 3825478000001 hash = B043616AE016682699477FFF01E6E903878CDFD6846042BA1BFC64775E7AC6C4
Il saldo dell'account è 2000000000ngOra pubblicheremo il contratto intelligente. Avvieremo il lite-client ed eseguiremo.
> sendfile lottery-query.boc
[ 1][t 2][1583008371.631410122][lite-client.cpp:966][!testnode] invio della query dal file lottery-query.boc
[ 3][t 1][1583008371.828550100][lite-client.cpp:976][!query] lo stato del messaggio esterno è 1 Verifichiamo che il contratto sia stato pubblicato.
> last
> getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8KsydTra le altre cose otterremo.
storage:(account_storage last_trans_lt:3825499000002
balance:(currencies
grams:(nanograms
amount:(var_uint len:4 value:1987150999))
other:(extra_currencies
dict:hme_empty))
state:(account_activeVediamo che account_active.
Il commit corrispondente con le modifiche .
Ora creeremo richieste per interagire con il contratto intelligente.
Inizialmente, il primo per modificare l'indirizzo lo lasceremo come lavoro autonomo, mentre il secondo per inviare i grammi all'indirizzo del proprietario lo faremo. In effetti, dobbiamo fare la stessa cosa che abbiamo fatto nel test di invio dei grammi.
Questo è il messaggio che invieremo al contratto intelligente, dove msg_seqno 165, action 2 e 9,5 grammi per l'invio.
<b 165 32 u, 2 7 9500000000 gram, b>Non dimentichiamo di firmare il messaggio con la chiave privata lottery.pk, che è stata generata in precedenza durante la creazione del contratto intelligente. .
Otteniamo informazioni dal contratto intelligente tramite i metodi get
Ora vediamo come avviare i metodi get del contratto intelligente.
Avviamo lite-client e avviamo i metodi get che abbiamo scritto.
$ ./lite-client/lite-client -C ton-lite-client-test1.config.json
> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd balance
arguments: [ 104128 ]
result: [ 64633878952 ]
...In result contiene il valore che restituisce la funzione balance() dal nostro contratto intelligente.
Faremo lo stesso anche per altri metodi.
> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd get_seqno
...
arguments: [ 77871 ]
result: [ 1 ] Richiesta della cronologia delle offerte.
> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd get_orders
...
arguments: [ 67442 ]
result: [ ([0 1 1583258284 10000000000 0 74649920601963823558742197308127565167945016780694342660493511643532213172308] [1 3 1583258347 4000000000 0 74649920601963823558742197308127565167945016780694342660493511643532213172308] [2 1 1583259901 50000000000 0 74649920601963823558742197308127565167945016780694342660493511643532213172308]) ] Utilizzeremo lite-client e metodi get per mostrare informazioni sul contratto intelligente sul sito.
Mostriamo i dati del contratto intelligente sul sito
Ho scritto un semplice sito web in Python per mostrare i dati del contratto intelligente in modo conveniente. Qui non entrerò nei dettagli e pubblicherò il sito. .
Le richieste a TON vengono effettuate da Python utilizzando lite-client. Per comodità, il sito viene impacchettato in Docker e pubblicato su Google Cloud. .
Proviamo
Ora proviamo a inviare grammi per ricaricare da . Invieremo 40 grammi e faremo alcune scommesse per chiarezza. Possiamo vedere che il sito mostra la cronologia delle scommesse, l'attuale percentuale di vincita e altre informazioni utili.
, che la prima l'abbiamo vinta, la seconda l'abbiamo persa.
Epifania
L'articolo è risultato molto più lungo di quanto avessi previsto; forse si poteva essere più brevi, o forse è proprio per una persona che non sa nulla di TON e vuole scrivere e pubblicare un contratto intelligente non così semplice da poter interagire. Potrebbero esserci delle cose che si potevano spiegare in modo più semplice.
È possibile che alcuni aspetti dell'implementazione potessero essere realizzati in modo più efficiente ed elegante, ma ciò avrebbe richiesto ancora più tempo per la preparazione dell'articolo. Inoltre, è probabile che abbia commesso degli errori o che non abbia compreso alcune cose, quindi se state facendo qualcosa di serio è bene fare riferimento alla documentazione ufficiale o al repository ufficiale con il codice di TON.
Va notato che, poiché TON è ancora in fase attiva di sviluppo, potrebbero verificarsi cambiamenti che influenzeranno alcuni dei passaggi in questo articolo (cosa che è successa mentre scrivevo, ho già corretto), ma l'approccio generale difficilmente cambierà.
Non mi pronuncerò sul futuro di TON. È possibile che la piattaforma diventi qualcosa di più grande e che valga la pena spendere del tempo per studiarla e occupare una nicchia con i propri prodotti già adesso.
C'è anche Libra di Facebook, che ha un potenziale pubblico di utenti più grande di TON. Di Libra non so quasi nulla, ma a quanto pare nei forum l'attività è molto più alta rispetto alla comunità di TON. Anche se gli sviluppatori e la comunità di TON sembrano più underground, il che è comunque interessante.
Link
- Documentazione ufficiale su TON:
- Repository ufficiale di TON:
- Portafoglio ufficiale per diverse piattaforme:
- Repository del contratto intelligente di questo articolo:
- Link al sito del contratto intelligente:
- Repository dell'estensione per Visual Studio Code per FunC:
- Chat su TON in Telegram, che è stata molto utile per orientarsi all'inizio. Penso non sia sbagliato dire che ci sono tutti quelli che hanno scritto qualcosa per TON. Lì puoi anche chiedere grammi di prova.
- Un altro chat su TON dove ho trovato informazioni utili:
- Primo stadio del concorso:
- Secondo stadio del concorso:
Fonte: habr.com
