Σχετικά με τον τρόπο σύνταξης και δημοσίευσης ενός έξυπνου συμβολαίου στο Telegram Open Network (TON)

Σχετικά με το πώς να γράψετε και να δημοσιεύσετε ένα έξυπνο συμβόλαιο στο TON

Τι αφορά αυτό το άρθρο;

Στο άρθρο θα μιλήσω για το πώς έλαβα μέρος στον πρώτο (από τους δύο) διαγωνισμό blockchain Telegram, δεν πήρα βραβείο και αποφάσισα να καταγράψω την εμπειρία μου σε ένα άρθρο για να μην βυθιστεί στη λήθη και, ίσως, να βοηθήσει κάποιος.

Επειδή δεν ήθελα να γράψω αφηρημένο κώδικα, αλλά να κάνω κάτι που να λειτουργεί, για το άρθρο έγραψα ένα έξυπνο συμβόλαιο για μια στιγμιαία λαχειοφόρο αγορά και έναν ιστότοπο που εμφανίζει δεδομένα έξυπνων συμβολαίων απευθείας από το TON χωρίς τη χρήση ενδιάμεσου αποθηκευτικού χώρου.

Το άρθρο θα είναι χρήσιμο σε όσους θέλουν να κάνουν το πρώτο τους έξυπνο συμβόλαιο στην TON, αλλά δεν ξέρουν από πού να ξεκινήσουν.

Χρησιμοποιώντας την λοταρία ως παράδειγμα, θα πάω από την εγκατάσταση του περιβάλλοντος στη δημοσίευση ενός έξυπνου συμβολαίου, την αλληλεπίδραση με αυτό και τη σύνταξη ενός ιστότοπου για λήψη και δημοσίευση δεδομένων.

Σχετικά με τη συμμετοχή στο διαγωνισμό

Τον περασμένο Οκτώβριο, η Telegram ανακοίνωσε έναν διαγωνισμό blockchain με νέες γλώσσες Fift и FunC. Ήταν απαραίτητο να επιλέξετε από τη σύνταξη οποιουδήποτε από τα πέντε προτεινόμενα έξυπνα συμβόλαια. Σκέφτηκα ότι θα ήταν ωραίο να κάνω κάτι διαφορετικό, να μάθω μια γλώσσα και να φτιάξω κάτι, ακόμα κι αν δεν χρειαστεί να γράψω κάτι άλλο στο μέλλον. Επιπλέον, το θέμα είναι συνεχώς στα χείλη.

Αξίζει να πω ότι δεν είχα εμπειρία στην ανάπτυξη έξυπνων συμβολαίων.

Σχεδίαζα να συμμετάσχω μέχρι το τέλος μέχρι να μπορέσω και μετά να γράψω ένα άρθρο κριτικής, αλλά απέτυχα αμέσως στο πρώτο. Εγώ έγραψε ένα πορτοφόλι με πολλαπλή υπογραφή FunC και γενικά λειτούργησε. Το πήρα ως βάση έξυπνο συμβόλαιο στο Solidity.

Εκείνη την εποχή, σκέφτηκα ότι αυτό ήταν σίγουρα αρκετό για να πάρει τουλάχιστον κάποια θέση βραβείου. Ως αποτέλεσμα, περίπου 40 στους 60 συμμετέχοντες έγιναν νικητές και εγώ δεν ήμουν ανάμεσά τους. Σε γενικές γραμμές, δεν υπάρχει τίποτα κακό σε αυτό, αλλά ένα πράγμα με ενόχλησε. Την ώρα της ανακοίνωσης των αποτελεσμάτων δεν είχε γίνει η αναθεώρηση του τεστ για το συμβόλαιό μου, ρώτησα τους συμμετέχοντες στο chat αν υπήρχε κάποιος άλλος που δεν το είχε, δεν υπήρχε.

Προφανώς προσέχοντας τα μηνύματά μου, δύο μέρες αργότερα οι κριτές δημοσίευσαν ένα σχόλιο και ακόμα δεν καταλαβαίνω αν έχασαν κατά λάθος το έξυπνο συμβόλαιό μου κατά τη διάρκεια της κρίσης ή απλώς θεώρησαν ότι ήταν τόσο κακό που δεν χρειαζόταν σχόλιο. Έκανα μια ερώτηση στη σελίδα, αλλά δεν έλαβα απάντηση. Αν και δεν είναι μυστικό ποιος έκρινε, θεώρησα περιττό να γράφω προσωπικά μηνύματα.

Αφιερώθηκε πολύς χρόνος για την κατανόηση, οπότε αποφασίστηκε να γραφτεί ένα άρθρο. Δεδομένου ότι δεν υπάρχουν ακόμη πολλές πληροφορίες, αυτό το άρθρο θα βοηθήσει στην εξοικονόμηση χρόνου για όλους τους ενδιαφερόμενους.

Η έννοια των έξυπνων συμβολαίων στο TON

Πριν γράψετε οτιδήποτε, πρέπει να καταλάβετε από ποια πλευρά να προσεγγίσετε αυτό το πράγμα. Επομένως, τώρα θα σας πω από ποια μέρη αποτελείται το σύστημα. Πιο συγκεκριμένα, ποια μέρη πρέπει να γνωρίζετε για να συντάξετε τουλάχιστον κάποιο είδος σύμβασης εργασίας.

Θα επικεντρωθούμε στη σύνταξη ενός έξυπνου συμβολαίου και στη συνεργασία TON Virtual Machine (TVM), Fift и FunC, επομένως το άρθρο μοιάζει περισσότερο με περιγραφή της ανάπτυξης ενός κανονικού προγράμματος. Δεν θα σταθούμε στο πώς λειτουργεί η ίδια η πλατφόρμα εδώ.

Γενικά για το πώς λειτουργεί TVM και γλώσσα Fift υπάρχει καλή επίσημη τεκμηρίωση. Ενώ συμμετείχα στον διαγωνισμό και τώρα ενώ έγραφα το τρέχον συμβόλαιο, στρεφόμουν συχνά σε αυτήν.

Η κύρια γλώσσα στην οποία συντάσσονται τα έξυπνα συμβόλαια είναι FunC. Δεν υπάρχει τεκμηρίωση επί του παρόντος, επομένως για να γράψετε κάτι πρέπει να μελετήσετε παραδείγματα έξυπνων συμβολαίων από το επίσημο αποθετήριο και την εφαρμογή της ίδιας της γλώσσας εκεί, καθώς και παραδείγματα έξυπνων συμβολαίων από τα δύο προηγούμενα διαγωνισμούς. Σύνδεσμοι στο τέλος του άρθρου.

Ας πούμε ότι έχουμε ήδη γράψει ένα έξυπνο συμβόλαιο για FunC, μετά από αυτό μεταγλωττίζουμε τον κώδικα σε Fift assembler.

Απομένει να δημοσιευτεί το καταρτισμένο έξυπνο συμβόλαιο. Για να γίνει αυτό πρέπει να γράψετε μια συνάρτηση Fift, το οποίο θα λάβει τον κωδικό έξυπνης σύμβασης και ορισμένες άλλες παραμέτρους ως είσοδο και η έξοδος θα είναι ένα αρχείο με την επέκταση .boc (που σημαίνει «σάκος με κελιά») και, ανάλογα με τον τρόπο που το γράφουμε, ένα ιδιωτικό κλειδί και μια διεύθυνση, που δημιουργείται με βάση τον κωδικό έξυπνης σύμβασης. Μπορείτε ήδη να στείλετε γραμμάρια στη διεύθυνση ενός έξυπνου συμβολαίου που δεν έχει ακόμη δημοσιευτεί.

Για να δημοσιεύσετε ένα έξυπνο συμβόλαιο σε TON που ελήφθη .boc το αρχείο θα πρέπει να σταλεί στο blockchain χρησιμοποιώντας έναν ελαφρύ πελάτη (περισσότερα για αυτό παρακάτω). Αλλά πριν από τη δημοσίευση, πρέπει να μεταφέρετε γραμμάρια στη διεύθυνση που δημιουργήθηκε, διαφορετικά το έξυπνο συμβόλαιο δεν θα δημοσιευτεί. Μετά τη δημοσίευση, μπορείτε να αλληλεπιδράσετε με το έξυπνο συμβόλαιο στέλνοντάς του μηνύματα από το εξωτερικό (για παράδειγμα, χρησιμοποιώντας ένα ελαφρύ πρόγραμμα-πελάτη) ή από το εσωτερικό (για παράδειγμα, ένα έξυπνο συμβόλαιο στέλνει ένα μήνυμα στο άλλο μέσα στο TON).

Μόλις καταλάβουμε πώς δημοσιεύεται ο κώδικας, γίνεται ευκολότερο. Ξέρουμε χονδρικά τι θέλουμε να γράψουμε και πώς θα λειτουργήσει το πρόγραμμά μας. Και ενώ γράφουμε, αναζητούμε πώς αυτό εφαρμόζεται ήδη σε υπάρχοντα έξυπνα συμβόλαια ή εξετάζουμε τον κώδικα υλοποίησης Fift и FunC στο επίσημο αποθετήριο ή κοιτάξτε στην επίσημη τεκμηρίωση.

Πολύ συχνά έψαχνα για λέξεις-κλειδιά στη συνομιλία του Telegram όπου συγκεντρώθηκαν όλοι οι συμμετέχοντες στο διαγωνισμό και οι υπάλληλοι της Telegram, και συνέβη ότι κατά τη διάρκεια του διαγωνισμού μαζεύτηκαν όλοι εκεί και άρχισαν να συζητούν το Fift και το FunC. Σύνδεσμος στο τέλος του άρθρου.

Ήρθε η ώρα να περάσουμε από τη θεωρία στην πράξη.

Προετοιμασία του περιβάλλοντος για εργασία με την TON

Έκανα όλα όσα θα περιγραφούν στο άρθρο για το MacOS και τα έλεγξα ξανά σε καθαρό Ubuntu 18.04 LTS στο Docker.

Το πρώτο πράγμα που πρέπει να κάνετε είναι να κάνετε λήψη και εγκατάσταση lite-client με το οποίο μπορείτε να στείλετε αιτήματα στην TON.

Οι οδηγίες στην επίσημη ιστοσελίδα περιγράφουν τη διαδικασία εγκατάστασης αρκετά λεπτομερώς και με σαφήνεια, παραλείποντας ορισμένες λεπτομέρειες. Εδώ ακολουθούμε τις οδηγίες, εγκαθιστώντας τυχόν εξαρτήσεις που λείπουν στην πορεία. Δεν μεταγλώττισα κάθε έργο μόνος μου, αλλά το εγκατέστησα από το επίσημο αποθετήριο. Ubuntu (σε MacOS χρησιμοποίησα 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 

Μόλις εγκατασταθούν όλες οι εξαρτήσεις, μπορείτε να εγκαταστήσετε lite-client, Fift, FunC.

Αρχικά, κλωνοποιούμε το αποθετήριο TON μαζί με τις εξαρτήσεις του. Για ευκολία, θα κάνουμε τα πάντα σε έναν φάκελο ~/TON.

cd ~/TON
git clone https://github.com/ton-blockchain/ton.git
cd ./ton
git submodule update --init --recursive

Το αποθετήριο αποθηκεύει επίσης υλοποιήσεις Fift и FunC.

Τώρα είμαστε έτοιμοι να συναρμολογήσουμε το έργο. Ο κώδικας του αποθετηρίου κλωνοποιείται σε ένα φάκελο ~/TON/ton. Σε ~/TON δημιουργήστε ένα φάκελο build και να συγκεντρώσει το έργο σε αυτό.

mkdir ~/TON/build 
cd ~/TON/build
cmake ../ton

Εφόσον πρόκειται να γράψουμε ένα έξυπνο συμβόλαιο, δεν χρειαζόμαστε μόνο lite-clientΑλλά Fift с FunC, οπότε ας τα συγκεντρώσουμε όλα. Δεν είναι γρήγορη διαδικασία, οπότε περιμένουμε.

cmake --build . --target lite-client
cmake --build . --target fift
cmake --build . --target func

Στη συνέχεια, πραγματοποιήστε λήψη του αρχείου διαμόρφωσης που περιέχει δεδομένα σχετικά με τον κόμβο στον οποίο lite-client θα συνδεθεί.

wget https://test.ton.org/ton-lite-client-test1.config.json

Κάνοντας τα πρώτα αιτήματα στην TON

Τώρα ας ξεκινήσουμε lite-client.

cd ~/TON/build
./lite-client/lite-client -C ton-lite-client-test1.config.json

Εάν η κατασκευή ήταν επιτυχής, τότε μετά την εκκίνηση θα δείτε ένα αρχείο καταγραφής της σύνδεσης του προγράμματος-πελάτη light με τον κόμβο.

[ 1][t 2][1582054822.963129282][lite-client.h:201][!testnode]   conn ready
[ 2][t 2][1582054823.085654020][lite-client.cpp:277][!testnode] server version is 1.1, capabilities 7
[ 3][t 2][1582054823.085725069][lite-client.cpp:286][!testnode] server time is 1582054823 (delta 0)
...

Μπορείτε να εκτελέσετε την εντολή help και δείτε ποιες εντολές είναι διαθέσιμες.

help

Ας παραθέσουμε τις εντολές που θα χρησιμοποιήσουμε σε αυτό το άρθρο.

list of available commands:
last    Get last block and state info from server
sendfile <filename> Load a serialized message from <filename> and send it to server
getaccount <addr> [<block-id-ext>]  Loads the most recent state of specified account; <addr> is in [<workchain>:]<hex-or-base64-addr> format
runmethod <addr> [<block-id-ext>] <method-id> <params>...   Runs GET method <method-id> of account <addr> with specified parameters

last получает последний созданный блок с сервера. 

sendfile <filename> отправляет в TON файл с сообщением, именно с помощью этой команды публикуется смарт-контракт и запрсосы к нему. 

getaccount <addr> загружает текущее состояние смарт-контракта с указанным адресом. 

runmethod <addr> [<block-id-ext>] <method-id> <params>  запускает get-методы смартконтракта. 

Τώρα είμαστε έτοιμοι να γράψουμε το ίδιο το συμβόλαιο.

Реализация

Ιδέα

Όπως έγραψα και παραπάνω, το έξυπνο συμβόλαιο που γράφουμε είναι λαχείο.

Επιπλέον, δεν πρόκειται για μια κλήρωση στην οποία πρέπει να αγοράσετε ένα εισιτήριο και να περιμένετε μια ώρα, μέρα ή μήνα, αλλά μια στιγμιαία κλήρωση στην οποία ο χρήστης μεταβαίνει στη διεύθυνση του συμβολαίου N γραμμάρια και το παίρνει αμέσως πίσω 2 * N γραμμάρια ή χάνει. Θα κάνουμε την πιθανότητα να κερδίσουμε περίπου 40%. Εάν δεν υπάρχουν αρκετά γραμμάρια για πληρωμή, τότε θα θεωρήσουμε τη συναλλαγή ως συμπλήρωμα.

Επιπλέον, είναι σημαντικό τα στοιχήματα να φαίνονται σε πραγματικό χρόνο και σε βολική μορφή, ώστε ο χρήστης να μπορεί να καταλάβει αμέσως αν κέρδισε ή έχασε. Επομένως, πρέπει να δημιουργήσετε έναν ιστότοπο που θα εμφανίζει στοιχήματα και αποτελέσματα απευθείας από την TON.

Σύνταξη έξυπνου συμβολαίου

Για διευκόλυνση, έχω επισημάνει τον κώδικα για το FunC, η προσθήκη μπορεί να βρεθεί και να εγκατασταθεί στην αναζήτηση του Visual Studio Code. Αν θέλετε ξαφνικά να προσθέσετε κάτι, έχω κάνει την προσθήκη διαθέσιμη δημόσια. Επίσης, κάποιος έφτιαξε προηγουμένως ένα πρόσθετο για εργασία με το Fift, μπορείτε επίσης να το εγκαταστήσετε και να το βρείτε στο VSC.

Ας δημιουργήσουμε αμέσως ένα αποθετήριο όπου θα δεσμεύσουμε τα ενδιάμεσα αποτελέσματα.

Για να κάνουμε τη ζωή μας πιο εύκολη, θα γράψουμε ένα έξυπνο συμβόλαιο και θα το δοκιμάσουμε τοπικά μέχρι να είναι έτοιμο. Μόνο μετά από αυτό θα το δημοσιεύσουμε σε ΤΟΝ.

Το έξυπνο συμβόλαιο έχει δύο εξωτερικές μεθόδους στις οποίες μπορείτε να έχετε πρόσβαση. Πρώτα, recv_external() αυτή η λειτουργία εκτελείται όταν ένα αίτημα για τη σύμβαση προέρχεται από τον έξω κόσμο, δηλαδή όχι από την TON, για παράδειγμα, όταν εμείς οι ίδιοι δημιουργούμε ένα μήνυμα και το στέλνουμε μέσω του lite-client. Δεύτερος, recv_internal() αυτό συμβαίνει όταν, εντός της ίδιας της TON, οποιαδήποτε σύμβαση αναφέρεται στη δική μας. Και στις δύο περιπτώσεις, μπορείτε να μεταβιβάσετε παραμέτρους στη συνάρτηση.

Ας ξεκινήσουμε με ένα απλό παράδειγμα που θα λειτουργήσει εάν δημοσιευτεί, αλλά δεν υπάρχει λειτουργικό φορτίο σε αυτό.

() recv_internal(slice in_msg) impure {
    ;; TODO: implementation 
}

() recv_external(slice in_msg) impure {
    ;; TODO: implementation  
}

Εδώ πρέπει να εξηγήσουμε τι είναι slice. Όλα τα δεδομένα που αποθηκεύονται στο TON Blockchain είναι μια συλλογή TVM cell ή απλά cell, σε ένα τέτοιο κελί μπορείτε να αποθηκεύσετε έως και 1023 bit δεδομένων και έως και 4 συνδέσμους προς άλλα κελιά.

TVM cell slice ή slice αυτό είναι μέρος του υπάρχοντος cell χρησιμοποιείται για την ανάλυση του, θα γίνει σαφές αργότερα. Το βασικό για εμάς είναι ότι μπορούμε να μεταγραφούμε slice και ανάλογα με τον τύπο του μηνύματος, επεξεργαστείτε τα δεδομένα recv_external() ή recv_internal().

impure — μια λέξη-κλειδί που υποδεικνύει ότι η συνάρτηση τροποποιεί τα δεδομένα έξυπνων συμβολαίων.

Ας αποθηκεύσουμε τον κωδικό της σύμβασης lottery-code.fc και μεταγλωττίζουν.

~/TON/build/crypto/func -APSR -o lottery-compiled.fif ~/TON/ton/crypto/smartcont/stdlib.fc ./lottery-code.fc 

Η σημασία των σημαιών μπορεί να προβληθεί χρησιμοποιώντας την εντολή

~/TON/build/crypto/func -help

Έχουμε μεταγλωττίσει τον κώδικα assembler Fift στο 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>c

Μπορεί να ξεκινήσει τοπικά, γι' αυτό θα προετοιμάσουμε το περιβάλλον.

Σημειώστε ότι η πρώτη γραμμή συνδέεται Asm.fif, αυτός είναι ο κώδικας γραμμένος στο Fift για τη συναρμολογητή Fift.

Εφόσον θέλουμε να εκτελέσουμε και να δοκιμάσουμε το έξυπνο συμβόλαιο τοπικά, θα δημιουργήσουμε ένα αρχείο lottery-test-suite.fif και αντιγράψτε τον μεταγλωττισμένο κώδικα εκεί, αντικαθιστώντας την τελευταία γραμμή σε αυτήν, η οποία γράφει τον κωδικό έξυπνου συμβολαίου σε μια σταθερά codeγια να το μεταφέρετε στη συνέχεια στην εικονική μηχανή:

"TonUtil.fif" include
"Asm.fif" include

PROGRAM{
  DECLPROC recv_internal
  DECLPROC recv_external
  recv_internal PROC:<{
    //  in_msg
    DROP    // 
  }>
  recv_external PROC:<{
    //  in_msg
    DROP    // 
  }>
}END>s constant code

Μέχρι στιγμής φαίνεται ξεκάθαρο, τώρα ας προσθέσουμε στο ίδιο αρχείο τον κώδικα που θα χρησιμοποιήσουμε για την εκκίνηση του TVM.

0 tuple 0x076ef1ea , // magic
0 , 0 , // actions msg_sents
1570998536 , // unix_time
1 , 1 , 3 , // block_lt, trans_lt, rand_seed
0 tuple 100000000000000 , dictnew , , // remaining balance
0 , dictnew , // contract_address, global_config
1 tuple // wrap to another tuple
constant c7

0 constant recv_internal // to run recv_internal() 
-1 constant recv_external // to invoke recv_external()

В c7 καταγράφουμε το πλαίσιο, δηλαδή τα δεδομένα με τα οποία θα εκκινηθεί το TVM (ή η κατάσταση δικτύου). Ακόμη και κατά τη διάρκεια του διαγωνισμού, ένας από τους προγραμματιστές έδειξε πώς να δημιουργεί c7 και αντέγραψα. Σε αυτό το άρθρο ίσως χρειαστεί να αλλάξουμε rand_seed δεδομένου ότι η δημιουργία ενός τυχαίου αριθμού εξαρτάται από αυτό και εάν δεν αλλάξει, ο ίδιος αριθμός θα επιστρέφεται κάθε φορά.

recv_internal и recv_external σταθερές με τιμές 0 και -1 θα είναι υπεύθυνες για την κλήση των αντίστοιχων συναρτήσεων στο έξυπνο συμβόλαιο.

Τώρα είμαστε έτοιμοι να δημιουργήσουμε την πρώτη δοκιμή για το άδειο έξυπνο συμβόλαιό μας. Για λόγους σαφήνειας, προς το παρόν θα προσθέσουμε όλες τις δοκιμές στο ίδιο αρχείο lottery-test-suite.fif.

Ας δημιουργήσουμε μια μεταβλητή storage και γράψτε ένα κενό σε αυτό cell, αυτή θα είναι η έξυπνη αποθήκευση συμβολαίου.

message Αυτό είναι το μήνυμα που θα μεταδώσουμε στην έξυπνη επαφή από έξω. Θα το αδειάσουμε επίσης προς το παρόν.

variable storage 
<b b> storage ! 

variable message 
<b b> message ! 

Αφού έχουμε προετοιμάσει τις σταθερές και τις μεταβλητές, ξεκινάμε το TVM χρησιμοποιώντας την εντολή runvmctx και περάστε τις παραμέτρους που δημιουργήσατε στην είσοδο.

message @ 
recv_external 
code 
storage @ 
c7 
runvmctx 

Στο τέλος θα τα καταφέρουμε σαν αυτό ενδιάμεσος κωδικός για Fift.

Τώρα μπορούμε να εκτελέσουμε τον κώδικα που προκύπτει.

export FIFTPATH=~/TON/ton/crypto/fift/lib // выполняем один раз для удобства 
~/TON/build/crypto/fift -s lottery-test-suite.fif 

Το πρόγραμμα πρέπει να τρέχει χωρίς σφάλματα και στην έξοδο θα δούμε το αρχείο καταγραφής εκτέλεσης:

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=0

Τέλεια, έχουμε γράψει την πρώτη λειτουργική έκδοση του έξυπνου συμβολαίου.

Τώρα πρέπει να προσθέσουμε λειτουργικότητα. Πρώτα ας ασχοληθούμε με μηνύματα που έρχονται από τον έξω κόσμο recv_external()

Ο ίδιος ο προγραμματιστής επιλέγει τη μορφή μηνύματος που μπορεί να δεχθεί η σύμβαση.

Αλλά συνήθως

  • πρώτον, θέλουμε να προστατεύσουμε το συμβόλαιό μας από τον έξω κόσμο και να το κάνουμε έτσι ώστε μόνο ο ιδιοκτήτης του συμβολαίου να μπορεί να του στέλνει εξωτερικά μηνύματα.
  • δεύτερον, όταν στέλνουμε ένα έγκυρο μήνυμα στην TON, θέλουμε αυτό να συμβεί ακριβώς μία φορά και όταν στείλουμε ξανά το ίδιο μήνυμα, το έξυπνο συμβόλαιο το απορρίπτει.

Έτσι, σχεδόν κάθε συμβόλαιο λύνει αυτά τα δύο προβλήματα, αφού το συμβόλαιό μας δέχεται εξωτερικά μηνύματα, πρέπει να το φροντίσουμε και αυτό.

Θα το κάνουμε με την αντίστροφη σειρά. Αρχικά, ας λύσουμε το πρόβλημα με την επανάληψη· εάν το συμβόλαιο έχει ήδη λάβει ένα τέτοιο μήνυμα και το έχει επεξεργαστεί, δεν θα το εκτελέσει δεύτερη φορά. Και τότε θα λύσουμε το πρόβλημα, ώστε μόνο ένας συγκεκριμένος κύκλος ανθρώπων να μπορεί να στείλει μηνύματα στο έξυπνο συμβόλαιο.

Υπάρχουν διάφοροι τρόποι επίλυσης του προβλήματος με διπλότυπα μηνύματα. Να πώς θα το κάνουμε. Στο έξυπνο συμβόλαιο, αρχικοποιούμε τον μετρητή των ληφθέντων μηνυμάτων με την αρχική τιμή 0. Σε κάθε μήνυμα στο έξυπνο συμβόλαιο, θα προσθέτουμε την τρέχουσα τιμή μετρητή. Εάν η τιμή του μετρητή στο μήνυμα δεν ταιριάζει με την τιμή στο έξυπνο συμβόλαιο, τότε δεν το επεξεργαζόμαστε· εάν συμβαίνει, τότε το επεξεργαζόμαστε και αυξάνουμε τον μετρητή στο έξυπνο συμβόλαιο κατά 1.

Ας επιστρέψουμε στο lottery-test-suite.fif και προσθέστε ένα δεύτερο τεστ σε αυτό. Εάν στείλουμε έναν λανθασμένο αριθμό, ο κωδικός θα πρέπει να δημιουργήσει μια εξαίρεση. Για παράδειγμα, αφήστε τα δεδομένα της σύμβασης να αποθηκεύσουν το 166 και θα στείλουμε το 165.

<b 166 32 u, b> storage !
<b 165 32 u, b> message !

message @ 
recv_external 
code 
storage @ 
c7 
runvmctx

drop 
exit_code ! 
."Exit code " exit_code @ . cr 
exit_code @ 33 - abort"Test #2 Not passed"

Ας ξεκινήσουμε.

 ~/TON/build/crypto/fift -s lottery-test-suite.fif 

Και θα δούμε ότι το τεστ εκτελείται με σφάλμα.

[ 1][t 0][1582283084.210902214][words.cpp:3046] lottery-test-suite.fif:67: abort": Test #2 Not passed
[ 1][t 0][1582283084.210941076][fift-main.cpp:196]      Error interpreting file `lottery-test-suite.fif`: error interpreting included file `lottery-test-suite.fif` : lottery-test-suite.fif:67: abort": Test #2 Not passed

Σε αυτό το στάδιο lottery-test-suite.fif θα πρέπει να μοιάζει по ссылке.

Τώρα ας προσθέσουμε τη λογική του μετρητή στο έξυπνο συμβόλαιο lottery-code.fc.

() recv_internal(slice in_msg) impure {
    ;; TODO: implementation 
}

() 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);
}

В slice in_msg είναι το μήνυμα που στέλνουμε.

Το πρώτο πράγμα που κάνουμε είναι να ελέγξουμε αν το μήνυμα περιέχει δεδομένα, αν όχι, τότε απλά βγαίνουμε.

Στη συνέχεια αναλύουμε το μήνυμα. in_msg~load_uint(32) φορτώνει τον αριθμό 165, 32-bit unsigned int από το μεταδιδόμενο μήνυμα.

Στη συνέχεια, φορτώνουμε 32 bit από την αποθήκευση έξυπνων συμβολαίων. Ελέγχουμε ότι ο φορτωμένος αριθμός ταιριάζει με τον περασμένο, αν όχι, ρίχνουμε μια εξαίρεση. Στην περίπτωσή μας, αφού περνάμε μη ματς, θα πρέπει να γίνει εξαίρεση.

Τώρα ας μεταγλωττίσουμε.

~/TON/build/crypto/func -APSR -o lottery-compiled.fif ~/TON/ton/crypto/smartcont/stdlib.fc ./lottery-code.fc 

Αντιγράψτε τον κωδικό που προκύπτει στο lottery-test-suite.fif, χωρίς να ξεχνάμε να αντικαταστήσουμε την τελευταία γραμμή.

Ελέγχουμε ότι το τεστ περνάει:

~/TON/build/crypto/fift -s lottery-test-suite.fif

Ακριβώς εδώ Μπορείτε να δείτε το αντίστοιχο commit με τα τρέχοντα αποτελέσματα.

Σημειώστε ότι δεν είναι βολικό να αντιγράφουμε συνεχώς τον μεταγλωττισμένο κώδικα ενός έξυπνου συμβολαίου σε ένα αρχείο με δοκιμές, επομένως θα γράψουμε ένα σενάριο που θα γράψει τον κώδικα σε μια σταθερά για εμάς και απλώς θα συνδέσουμε τον μεταγλωττισμένο κώδικα στις δοκιμές μας χρησιμοποιώντας "include".

Δημιουργήστε ένα αρχείο στο φάκελο του έργου build.sh με το ακόλουθο περιεχόμενο.

#!/bin/bash

~/TON/build/crypto/func -SPA -R -o lottery-compiled.fif ~/TON/ton/crypto/smartcont/stdlib.fc ./lottery-code.fc

Ας το κάνουμε εκτελέσιμο.

chmod +x ./build.sh

Τώρα, απλώς εκτελέστε το σενάριο μας για να μεταγλωττίσετε το συμβόλαιο. Αλλά εκτός από αυτό, πρέπει να το γράψουμε σε μια σταθερά code. Έτσι θα δημιουργήσουμε ένα νέο αρχείο lotter-compiled-for-test.fif, που θα συμπεριλάβουμε στο αρχείο lottery-test-suite.fif.

Ας προσθέσουμε τον κώδικα skirpt στο sh, ο οποίος απλώς θα αντιγράψει το μεταγλωττισμένο αρχείο lotter-compiled-for-test.fif και αλλάξτε την τελευταία γραμμή σε αυτό.

# 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.fif

Τώρα, για έλεγχο, ας εκτελέσουμε το σενάριο που προκύπτει και θα δημιουργηθεί ένα αρχείο lottery-compiled-for-test.fif, που θα συμπεριλάβουμε στο δικό μας lottery-test-suite.fif

В lottery-test-suite.fif διαγράψτε τον κωδικό της σύμβασης και προσθέστε τη γραμμή "lottery-compiled-for-test.fif" include.

Κάνουμε δοκιμές για να ελέγξουμε αν περνούν.

~/TON/build/crypto/fift -s lottery-test-suite.fif

Τέλεια, τώρα για να αυτοματοποιήσουμε την έναρξη των δοκιμών, ας δημιουργήσουμε ένα αρχείο test.sh, το οποίο θα εκτελεστεί πρώτα build.shκαι, στη συνέχεια, εκτελέστε τις δοκιμές.

touch test.sh
chmod +x test.sh

Γράφουμε μέσα

./build.sh 

echo "nCompilation completedn"

export FIFTPATH=~/TON/ton/crypto/fift/lib
~/TON/build/crypto/fift -s lottery-test-suite.fif

Ας το κάνουμε test.sh και εκτελέστε το για να βεβαιωθείτε ότι οι δοκιμές λειτουργούν.

chmod +x ./test.sh
./test.sh

Ελέγχουμε ότι το συμβόλαιο συντάσσεται και οι δοκιμές εκτελούνται.

Τέλεια, τώρα στην εκκίνηση test.sh Τα τεστ θα συγκεντρωθούν και θα εκτελεστούν αμέσως. Εδώ είναι ο σύνδεσμος προς διαπράττω.

Εντάξει, πριν συνεχίσουμε, ας κάνουμε κάτι ακόμα για ευκολία.

Ας δημιουργήσουμε ένα φάκελο build όπου θα αποθηκεύσουμε το αντιγραμμένο συμβόλαιο και τον κλώνό του γραμμένο σε σταθερά lottery-compiled.fif, lottery-compiled-for-test.fif. Ας δημιουργήσουμε επίσης έναν φάκελο test πού θα αποθηκευτεί το αρχείο δοκιμής; lottery-test-suite.fif και ενδεχομένως άλλα υποστηρικτικά αρχεία. Σύνδεσμος προς σχετικές αλλαγές.

Ας συνεχίσουμε να αναπτύσσουμε το έξυπνο συμβόλαιο.

Στη συνέχεια θα πρέπει να γίνει ένα τεστ που ελέγχει ότι το μήνυμα έχει ληφθεί και ο μετρητής ενημερώνεται στο κατάστημα όταν στέλνουμε τον σωστό αριθμό. Αλλά θα το κάνουμε αργότερα.

Τώρα ας σκεφτούμε ποια δομή δεδομένων και ποια δεδομένα πρέπει να αποθηκευτούν στο έξυπνο συμβόλαιο.

Θα περιγράψω όλα όσα αποθηκεύουμε.

`seqno` 32-х битное целое положительное число счетчик. 

`pubkey` 256-ти битное целое положительное число публичный ключ, с помощью которого, мы будем проверять подпись отправленного извне сообщения, о чем ниже. 

`order_seqno` 32-х битное целое положительное число хранит счетчик количества ставок. 

`number_of_wins` 32-х битное целое положительное число хранит  количество побед. 

`incoming_amount` тип данных Gram (первые 4 бита отвечает за длину), хранит общее количество грамов, которые были отправлены на контртакт. 

`outgoing_amount` общее количество грамов, которое было отправлено победителям. 

`owner_wc` номер воркчейна, 32-х битное (в некоторых местах написано, что 8-ми битное) целое число. В данный момент всего два -1 и 0. 

`owner_account_id` 256-ти битное целое положительное число, адрес контракта в текущем воркчейне. 

`orders` переменная типа словарь, хранит последние двадцать ставок. 

Στη συνέχεια πρέπει να γράψετε δύο συναρτήσεις. Ας καλέσουμε το πρώτο pack_state(), το οποίο θα συσκευάσει τα δεδομένα για μετέπειτα αποθήκευση στο χώρο αποθήκευσης έξυπνων συμβολαίων. Ας καλέσουμε το δεύτερο unpack_state() θα διαβάσει και θα επιστρέψει δεδομένα από την αποθήκευση.

_ 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;
}

Προσθέτουμε αυτές τις δύο λειτουργίες στην αρχή του έξυπνου συμβολαίου. Θα βγει σαν αυτό ενδιάμεσο αποτέλεσμα.

Για να αποθηκεύσετε δεδομένα θα χρειαστεί να καλέσετε την ενσωματωμένη λειτουργία set_data() και θα γράψει δεδομένα από pack_state() στην αποθήκευση έξυπνων συμβολαίων.

cell packed_state = pack_state(arg_1, .., arg_n); 
set_data(packed_state);

Τώρα που έχουμε βολικές λειτουργίες για εγγραφή και ανάγνωση δεδομένων, μπορούμε να προχωρήσουμε.

Πρέπει να ελέγξουμε ότι το μήνυμα που εισέρχεται από έξω είναι υπογεγραμμένο από τον κάτοχο της σύμβασης (ή άλλο χρήστη που έχει πρόσβαση στο ιδιωτικό κλειδί).

Όταν δημοσιεύουμε ένα έξυπνο συμβόλαιο, μπορούμε να το αρχικοποιήσουμε με τα δεδομένα που χρειαζόμαστε στην αποθήκευση, τα οποία θα αποθηκευτούν για μελλοντική χρήση. Θα καταγράψουμε το δημόσιο κλειδί εκεί για να επαληθεύσουμε ότι το εισερχόμενο μήνυμα υπογράφηκε με το αντίστοιχο ιδιωτικό κλειδί.

Πριν συνεχίσουμε, ας δημιουργήσουμε ένα ιδιωτικό κλειδί και ας το γράψουμε test/keys/owner.pk. Για να γίνει αυτό, ας ξεκινήσουμε το Fift σε διαδραστική λειτουργία και ας εκτελέσουμε τέσσερις εντολές.

`newkeypair` генерация публичного и приватного ключа и запись их в стек. 

`drop` удаления из стека верхнего элемента (в данном случае публичный ключ)  

`.s` просто посмотреть что лежит в стеке в данный момент 

`"owner.pk" B>file` запись приватного ключа в файл с именем `owner.pk`. 

`bye` завершает работу с Fift. 

Ας δημιουργήσουμε ένα φάκελο keys μέσα στο φάκελο test και γράψε το ιδιωτικό κλειδί εκεί.

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
bye

Βλέπουμε ένα αρχείο στον τρέχοντα φάκελο owner.pk.

Αφαιρούμε το δημόσιο κλειδί από τη στοίβα και όταν χρειάζεται μπορούμε να το πάρουμε από το ιδιωτικό.

Τώρα πρέπει να γράψουμε μια επαλήθευση υπογραφής. Ας ξεκινήσουμε με το τεστ. Πρώτα διαβάζουμε το ιδιωτικό κλειδί από το αρχείο χρησιμοποιώντας τη συνάρτηση file>B και γράψτε το σε μια μεταβλητή owner_private_key, στη συνέχεια χρησιμοποιώντας τη συνάρτηση priv>pub μετατρέψτε το ιδιωτικό κλειδί σε δημόσιο κλειδί και γράψτε το αποτέλεσμα owner_public_key.

variable owner_private_key
variable owner_public_key 

"./keys/owner.pk" file>B owner_private_key !
owner_private_key @ priv>pub owner_public_key !

Θα χρειαστούμε και τα δύο κλειδιά.

Αρχικοποιούμε την αποθήκευση έξυπνων συμβολαίων με αυθαίρετα δεδομένα με την ίδια σειρά όπως στη συνάρτηση pack_state()και γράψτε το σε μια μεταβλητή storage.

variable owner_private_key
variable owner_public_key 
variable orders
variable owner_wc
variable 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, 0 32 u, 0 32 u, 0 Gram, 0 Gram, owner_wc @ 32 i, owner_account_id @ 256 u,  orders @ dict, b> storage !

Στη συνέχεια, θα συνθέσουμε ένα υπογεγραμμένο μήνυμα, θα περιέχει μόνο την υπογραφή και την τιμή του μετρητή.

Αρχικά, δημιουργούμε τα δεδομένα που θέλουμε να μεταδώσουμε, μετά τα υπογράφουμε με ένα ιδιωτικό κλειδί και τέλος δημιουργούμε ένα υπογεγραμμένο μήνυμα.

variable message_to_sign
variable message_to_send
variable signature
<b 0 32 u, b> message_to_sign !
message_to_sign @ hashu owner_private_key @ ed25519_sign_uint signature !
<b signature @ B, 0 32 u, b> <s  message_to_send !  

Ως αποτέλεσμα, το μήνυμα που θα στείλουμε στο έξυπνο συμβόλαιο καταγράφεται σε μια μεταβλητή message_to_send, σχετικά με τις λειτουργίες hashu, ed25519_sign_uint μπορείς να διαβάσεις στην τεκμηρίωση Fift.

Και για να εκτελέσουμε το τεστ καλούμε ξανά.

message_to_send @ 
recv_external 
code 
storage @
c7
runvmctx

Εδώ Το αρχείο με τις δοκιμές θα πρέπει να μοιάζει με αυτό σε αυτό το στάδιο.

Ας εκτελέσουμε τη δοκιμή και θα αποτύχει, οπότε θα αλλάξουμε το έξυπνο συμβόλαιο ώστε να μπορεί να λαμβάνει μηνύματα αυτής της μορφής και να επαληθεύει την υπογραφή.

Αρχικά, μετράμε 512 bit της υπογραφής από το μήνυμα και το γράφουμε σε μια μεταβλητή, μετά μετράμε 32 bit της μεταβλητής μετρητή.

Εφόσον έχουμε μια λειτουργία για την ανάγνωση δεδομένων από την αποθήκευση έξυπνων συμβολαίων, θα τη χρησιμοποιήσουμε.

Ακολουθεί ο έλεγχος του μετρητή που μεταφέρθηκε με την αποθήκευση και ο έλεγχος της υπογραφής. Αν κάτι δεν ταιριάζει, τότε ρίχνουμε μια εξαίρεση με τον κατάλληλο κωδικό.

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));

Σχετική δέσμευση εδώ.

Ας εκτελέσουμε τις δοκιμές και ας δούμε ότι το δεύτερο τεστ αποτυγχάνει. Για δύο λόγους, δεν υπάρχουν αρκετά bit στο μήνυμα και δεν υπάρχουν αρκετά bit στον χώρο αποθήκευσης, επομένως ο κώδικας διακόπτεται κατά την ανάλυση. Πρέπει να προσθέσουμε μια υπογραφή στο μήνυμα που στέλνουμε και να αντιγράψουμε το χώρο αποθήκευσης από την τελευταία δοκιμή.

Στη δεύτερη δοκιμή, θα προσθέσουμε μια υπογραφή μηνύματος και θα αλλάξουμε την αποθήκευση έξυπνου συμβολαίου. Εδώ το αρχείο με τις δοκιμές μοιάζει αυτή τη στιγμή.

Ας γράψουμε ένα τέταρτο τεστ, στο οποίο θα στείλουμε ένα μήνυμα υπογεγραμμένο με το ιδιωτικό κλειδί κάποιου άλλου. Ας δημιουργήσουμε ένα άλλο ιδιωτικό κλειδί και ας το αποθηκεύσουμε σε ένα αρχείο not-owner.pk. Θα υπογράψουμε το μήνυμα με αυτό το ιδιωτικό κλειδί. Ας εκτελέσουμε τα τεστ και ας βεβαιωθούμε ότι όλα τα τεστ περνούν. Διαπράττω αυτή τη στιγμή.

Τώρα μπορούμε επιτέλους να προχωρήσουμε στην εφαρμογή της λογικής των έξυπνων συμβολαίων.
В recv_external() θα δεχτούμε δύο τύπους μηνυμάτων.

Δεδομένου ότι το συμβόλαιό μας θα συσσωρεύσει τις απώλειες των παικτών, αυτά τα χρήματα πρέπει να μεταφερθούν στον δημιουργό της λοταρίας. Η διεύθυνση πορτοφολιού του δημιουργού της λοταρίας καταγράφεται στο χώρο αποθήκευσης κατά τη δημιουργία του συμβολαίου.

Για κάθε ενδεχόμενο, χρειαζόμαστε τη δυνατότητα να αλλάξουμε τη διεύθυνση στην οποία στέλνουμε γραμμάρια των χαμένων. Θα πρέπει επίσης να μπορούμε να στέλνουμε γραμμάρια από τη λαχειοφόρο αγορά στη διεύθυνση του ιδιοκτήτη.

Ας ξεκινήσουμε με το πρώτο. Ας γράψουμε πρώτα μια δοκιμή που θα ελέγχει ότι μετά την αποστολή του μηνύματος, το έξυπνο συμβόλαιο αποθήκευσε τη νέα διεύθυνση στο χώρο αποθήκευσης. Σημειώστε ότι στο μήνυμα, εκτός από τον μετρητή και τη νέα διεύθυνση, μεταδίδουμε και action Ένας ακέραιος μη αρνητικός αριθμός 7-bit, ανάλογα με αυτόν, θα επιλέξουμε πώς θα επεξεργαστούμε το μήνυμα στο έξυπνο συμβόλαιο.

<b 0 32 u, 1 @ 7 u, new_owner_wc @  32 i, new_owner_account_id @ 256 u, b> message_to_sign !

Στη δοκιμή μπορείτε να δείτε πώς η αποθήκευση έξυπνου συμβολαίου είναι αφηρημένη storage στο Fift. Η αποσειροποίηση μεταβλητών περιγράφεται στην τεκμηρίωση Fift.

Σύνδεσμος δέσμευσης με την προσθήκη ζύμης.

Ας εκτελέσουμε το τεστ και ας βεβαιωθούμε ότι θα αποτύχει. Τώρα ας προσθέσουμε λογική για να αλλάξουμε τη διεύθυνση του ιδιοκτήτη της λοταρίας.

Στο έξυπνο συμβόλαιο συνεχίζουμε να αναλύουμε message, διαβάστε μέσα action. Να σας υπενθυμίσουμε ότι θα έχουμε δύο action: αλλαγή διεύθυνσης και αποστολή γραμμαρίων.

Στη συνέχεια διαβάζουμε τη νέα διεύθυνση του συμβασιούχου και την αποθηκεύουμε στην αποθήκευση.
Εκτελούμε τα τεστ και βλέπουμε ότι το τρίτο τεστ αποτυγχάνει. Καταρρέει λόγω του γεγονότος ότι το συμβόλαιο αναλύει επιπλέον 7 bit από το μήνυμα, τα οποία λείπουν στη δοκιμή. Προσθέστε ένα ανύπαρκτο στο μήνυμα action. Ας κάνουμε τις δοκιμές και ας δούμε ότι όλα θα περάσουν. Εδώ δεσμευτείτε για αλλαγές. Εξαιρετική.

Τώρα ας γράψουμε τη λογική για την αποστολή του καθορισμένου αριθμού γραμμαρίων στην προηγουμένως αποθηκευμένη διεύθυνση.

Αρχικά, ας γράψουμε ένα τεστ. Θα γράψουμε δύο τεστ, το ένα όταν δεν υπάρχει αρκετή ισορροπία, το δεύτερο όταν όλα πρέπει να περάσουν με επιτυχία. Οι δοκιμές μπορούν να προβληθούν σε αυτή τη δέσμευση.

Τώρα ας προσθέσουμε τον κώδικα. Αρχικά, ας γράψουμε δύο βοηθητικές μεθόδους. Η πρώτη μέθοδος λήψης είναι να μάθετε το τρέχον υπόλοιπο ενός έξυπνου συμβολαίου.

int balance() inline_ref method_id {
    return get_balance().pair_first();
}

Και το δεύτερο είναι για αποστολή γραμμαρίων σε άλλο έξυπνο συμβόλαιο. Αντέγραψα πλήρως αυτή τη μέθοδο από ένα άλλο έξυπνο συμβόλαιο.

() 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
}

Ας προσθέσουμε αυτές τις δύο μεθόδους στο έξυπνο συμβόλαιο και ας γράψουμε τη λογική. Αρχικά, αναλύουμε τον αριθμό των γραμμαρίων από το μήνυμα. Στη συνέχεια ελέγχουμε την ισορροπία, αν δεν είναι αρκετή ρίχνουμε μια εξαίρεση. Εάν όλα είναι καλά, τότε στέλνουμε τα γραμμάρια στην αποθηκευμένη διεύθυνση και ενημερώνουμε τον μετρητή.

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));

Εδώ μοιάζει με το έξυπνο συμβόλαιο αυτή τη στιγμή. Ας κάνουμε τα τεστ και ας φροντίσουμε να περάσουν.

Παρεμπιπτόντως, κάθε φορά αφαιρείται προμήθεια από το έξυπνο συμβόλαιο για ένα επεξεργασμένο μήνυμα. Προκειμένου τα μηνύματα έξυπνου συμβολαίου να εκτελέσουν το αίτημα, μετά από βασικούς ελέγχους πρέπει να καλέσετε accept_message().

Τώρα ας περάσουμε στα εσωτερικά μηνύματα. Στην πραγματικότητα, θα δεχόμαστε μόνο γραμμάρια και θα στείλουμε πίσω το διπλάσιο ποσό στον παίκτη εάν κερδίσει και ένα τρίτο στον ιδιοκτήτη εάν χάσει.

Αρχικά, ας γράψουμε ένα απλό τεστ. Για να γίνει αυτό, χρειαζόμαστε μια δοκιμαστική διεύθυνση του έξυπνου συμβολαίου από την οποία υποτίθεται ότι στέλνουμε γραμμάρια στο έξυπνο συμβόλαιο.

Η διεύθυνση έξυπνου συμβολαίου αποτελείται από δύο αριθμούς, έναν ακέραιο 32-bit υπεύθυνο για την αλυσίδα εργασίας και έναν μη αρνητικό ακέραιο μοναδικό αριθμό λογαριασμού 256-bit σε αυτήν την αλυσίδα εργασίας. Για παράδειγμα, -1 και 12345, αυτή είναι η διεύθυνση που θα αποθηκεύσουμε σε ένα αρχείο.

Αντέγραψα τη λειτουργία για την αποθήκευση της διεύθυνσης από TonUtil.fif.

// ( wc addr fname -- )  Save address to file in 36-byte format
{ -rot 256 u>B swap 32 i>B B+ swap B>file } : save-address

Ας δούμε πώς λειτουργεί η συνάρτηση, αυτό θα δώσει μια κατανόηση του πώς λειτουργεί το Fift. Εκκινήστε το Fift σε διαδραστική λειτουργία.

~/TON/build/crypto/fift -i 

Πρώτα πιέζουμε -1, 12345 και το όνομα του μελλοντικού αρχείου "sender.addr" στη στοίβα:

-1 12345 "sender.addr" 

Το επόμενο βήμα είναι να εκτελέσετε τη συνάρτηση -rot, το οποίο μετατοπίζει τη στοίβα με τέτοιο τρόπο ώστε στην κορυφή της στοίβας να υπάρχει ένας μοναδικός αριθμός έξυπνου συμβολαίου:

"sender.addr" -1 12345

256 u>B μετατρέπει έναν μη αρνητικό ακέραιο 256-bit σε byte.

"sender.addr" -1 BYTES:0000000000000000000000000000000000000000000000000000000000003039

swap ανταλλάσσει τα δύο κορυφαία στοιχεία της στοίβας.

"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039 -1

32 i>B μετατρέπει έναν ακέραιο 32-bit σε byte.

"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039 BYTES:FFFFFFFF

B+ συνδέει δύο ακολουθίες byte.

 "sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039FFFFFFFF

Και πάλι swap.

BYTES:0000000000000000000000000000000000000000000000000000000000003039FFFFFFFF "sender.addr" 

Και τέλος τα byte γράφονται στο αρχείο B>file. Μετά από αυτό η στοίβα μας είναι άδεια. Σταματάμε Fift. Ένα αρχείο έχει δημιουργηθεί στον τρέχοντα φάκελο sender.addr. Ας μετακινήσουμε το αρχείο στον φάκελο που δημιουργήθηκε test/addresses/.

Ας γράψουμε ένα απλό τεστ που θα στείλει γραμμάρια σε ένα έξυπνο συμβόλαιο. Εδώ είναι η δέσμευση.

Ας δούμε τώρα τη λογική του λαχείου.

Το πρώτο πράγμα που κάνουμε είναι να ελέγξουμε το μήνυμα bounced ή όχι αν bounced, τότε το αγνοούμε. bounced σημαίνει ότι το συμβόλαιο θα επιστρέψει γραμμάρια εάν παρουσιαστεί κάποιο σφάλμα. Δεν θα επιστρέψουμε γραμμάρια εάν παρουσιαστεί ξαφνικά σφάλμα.

Ελέγχουμε, αν το υπόλοιπο είναι μικρότερο από μισό γραμμάριο, τότε απλώς δεχόμαστε το μήνυμα και το αγνοούμε.

Στη συνέχεια, αναλύουμε τη διεύθυνση του έξυπνου συμβολαίου από το οποίο προήλθε το μήνυμα.

Διαβάζουμε τα δεδομένα από το χώρο αποθήκευσης και, στη συνέχεια, διαγράφουμε παλιά στοιχήματα από το ιστορικό εάν υπάρχουν περισσότερα από είκοσι από αυτά. Για ευκολία, έγραψα τρεις επιπλέον λειτουργίες pack_order(), unpack_order(), remove_old_orders().

Στη συνέχεια, εξετάζουμε εάν το υπόλοιπο δεν επαρκεί για την πληρωμή, τότε θεωρούμε ότι δεν πρόκειται για στοίχημα, αλλά για αναπλήρωση και αποθηκεύουμε την αναπλήρωση στο orders.

Στη συνέχεια, τέλος, η ουσία του έξυπνου συμβολαίου.

Αρχικά, αν ο παίκτης χάσει, το αποθηκεύουμε στο ιστορικό στοιχημάτων και αν το ποσό είναι μεγαλύτερο από 3 γραμμάρια, στέλνουμε το 1/3 στον κάτοχο του έξυπνου συμβολαίου.

Εάν ο παίκτης κερδίσει, τότε στέλνουμε το διπλάσιο του ποσού στη διεύθυνση του παίκτη και στη συνέχεια αποθηκεύουμε τις πληροφορίες για το στοίχημα στο ιστορικό.

() 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() < 2 * order_amount + 500000000) { ;; not enough grams to pay the bet back, so this is re-fill
        builder order = pack_order(order_seqno, 1, 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));
        return ();
    }
    if (rand(10) >= 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));
}

Αυτό είναι όλο. Αντίστοιχη δέσμευση.

Τώρα το μόνο που μένει είναι απλό, ας δημιουργήσουμε μεθόδους λήψης, ώστε να μπορούμε να λάβουμε πληροφορίες σχετικά με την κατάσταση της σύμβασης από τον έξω κόσμο (στην πραγματικότητα, διαβάστε τα δεδομένα από την αποθήκευση έξυπνων συμβολαίων τους).

Ας προσθέσουμε μεθόδους λήψης. Θα γράψουμε παρακάτω για το πώς να λαμβάνετε πληροφορίες σχετικά με ένα έξυπνο συμβόλαιο.

Ξέχασα επίσης να προσθέσω τον κωδικό που θα επεξεργαστεί το πρώτο αίτημα που προκύπτει κατά τη δημοσίευση ενός έξυπνου συμβολαίου. Αντίστοιχη δέσμευση. Και επιπλέον διορθώθηκε σφάλμα με την αποστολή του 1/3 του ποσού στον λογαριασμό του κατόχου.

Το επόμενο βήμα είναι η δημοσίευση του έξυπνου συμβολαίου. Ας δημιουργήσουμε ένα φάκελο requests.

Πήρα ως βάση τον κωδικό δημοσίευσης simple-wallet-code.fc ο οποίος μπορεί να βρει στο επίσημο αποθετήριο.

Κάτι που αξίζει να προσέξεις. Δημιουργούμε μια έξυπνη αποθήκευση συμβολαίου και ένα μήνυμα εισαγωγής. Μετά από αυτό, δημιουργείται η διεύθυνση του έξυπνου συμβολαίου, δηλαδή η διεύθυνση είναι γνωστή ακόμη και πριν από τη δημοσίευση στο TON. Στη συνέχεια, πρέπει να στείλετε πολλά γραμμάρια σε αυτήν τη διεύθυνση και μόνο μετά από αυτό θα πρέπει να στείλετε ένα αρχείο με το ίδιο το έξυπνο συμβόλαιο, καθώς το δίκτυο παίρνει προμήθεια για την αποθήκευση του έξυπνου συμβολαίου και των λειτουργιών σε αυτό (επικυρωτές που αποθηκεύουν και εκτελούν smart συμβόλαια). Μπορείτε να δείτε τον κωδικό εδώ.

Στη συνέχεια εκτελούμε τον κώδικα δημοσίευσης και παίρνουμε lottery-query.boc αρχείο και διεύθυνση έξυπνου συμβολαίου.

~/TON/build/crypto/fift -s requests/new-lottery.fif 0

Μην ξεχάσετε να αποθηκεύσετε τα αρχεία που δημιουργούνται: lottery-query.boc, lottery.addr, lottery.pk.

Μεταξύ άλλων, θα δούμε τη διεύθυνση του έξυπνου συμβολαίου στα αρχεία καταγραφής εκτέλεσης.

new wallet address = 0:044910149dbeaf8eadbb2b28722e7d6a2dc6e264ec2f1d9bebd6fb209079bc2a 
(Saving address to file lottery.addr)
Non-bounceable address (for init): 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd
Bounceable address (for later access): kQAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8KpFY

Για πλάκα, ας κάνουμε ένα αίτημα στον TON

$ ./lite-client/lite-client -C ton-lite-client-test1.config.json 
getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd

Και θα δούμε ότι ο λογαριασμός με αυτή τη διεύθυνση είναι κενός.

account state is empty

Στέλνουμε στη διεύθυνση 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd 2 γραμμάρια και μετά από λίγα δευτερόλεπτα εκτελούμε την ίδια εντολή. Για να στείλω γραμμάρια χρησιμοποιώ επίσημο πορτοφόλι, και μπορείτε να ζητήσετε από κάποιον από το chat δοκιμαστικά γραμμάρια, για τα οποία θα μιλήσω στο τέλος του άρθρου.

> getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd

Μοιάζει με μη αρχικοποιημένο (state:account_uninit) ένα έξυπνο συμβόλαιο με την ίδια διεύθυνση και υπόλοιπο 1 νανογραμμάρια.

account state is (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_}
last transaction lt = 3825478000001 hash = B043616AE016682699477FFF01E6E903878CDFD6846042BA1BFC64775E7AC6C4
account balance is 2000000000ng

Τώρα ας δημοσιεύσουμε το έξυπνο συμβόλαιο. Ας ξεκινήσουμε το lite-client και ας εκτελέσουμε.

> sendfile lottery-query.boc
[ 1][t 2][1583008371.631410122][lite-client.cpp:966][!testnode] sending query from file lottery-query.boc
[ 3][t 1][1583008371.828550100][lite-client.cpp:976][!query]    external message status is 1 

Ας ελέγξουμε ότι η σύμβαση έχει δημοσιευτεί.

> last
> getaccount 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd

Μεταξύ άλλων παίρνουμε.

  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_active

Το βλέπουμε αυτό account_active.

Αντίστοιχη δέσμευση με αλλαγές εδώ.

Τώρα ας δημιουργήσουμε αιτήματα για αλληλεπίδραση με το έξυπνο συμβόλαιο.

Πιο συγκεκριμένα, θα αφήσουμε το πρώτο για την αλλαγή της διεύθυνσης ως ανεξάρτητο έργο και το δεύτερο θα το κάνουμε για την αποστολή γραμμαρίων στη διεύθυνση του ιδιοκτήτη. Στην πραγματικότητα, θα χρειαστεί να κάνουμε το ίδιο πράγμα όπως στο τεστ για την αποστολή γραμμαρίων.

Αυτό είναι το μήνυμα που θα στείλουμε στο έξυπνο συμβόλαιο, όπου msg_seqno 165, action 2 και 9.5 γραμμάρια για αποστολή.

<b 165 32 u, 2 7 u, 9500000000 Gram, b>

Μην ξεχάσετε να υπογράψετε το μήνυμα με το ιδιωτικό σας κλειδί lottery.pk, το οποίο δημιουργήθηκε νωρίτερα κατά τη δημιουργία του έξυπνου συμβολαίου. Εδώ είναι το αντίστοιχο commit.

Λήψη πληροφοριών από ένα έξυπνο συμβόλαιο χρησιμοποιώντας μεθόδους λήψης

Τώρα ας δούμε πώς να εκτελούμε μεθόδους λήψης έξυπνων συμβολαίων.

Εκκίνηση lite-client και εκτελέστε τις μεθόδους λήψης που γράψαμε.

$ ./lite-client/lite-client -C ton-lite-client-test1.config.json
> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd balance
arguments:  [ 104128 ] 
result:  [ 64633878952 ] 
...

В result περιέχει την τιμή που επιστρέφει η συνάρτηση balance() από το έξυπνο συμβόλαιό μας.
Θα κάνουμε το ίδιο για αρκετές ακόμη μεθόδους.

> runmethod 0QAESRAUnb6vjq27KyhyLn1qLcbiZOwvHZvr1vsgkHm8Ksyd get_seqno
...
arguments:  [ 77871 ] 
result:  [ 1 ] 

Ας ζητήσουμε το ιστορικό στοιχημάτων σας.

> 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]) ] 

Θα χρησιμοποιήσουμε το lite-client και θα λάβουμε μεθόδους για την εμφάνιση πληροφοριών σχετικά με το έξυπνο συμβόλαιο στον ιστότοπο.

Εμφάνιση δεδομένων έξυπνων συμβολαίων στον ιστότοπο

Έγραψα έναν απλό ιστότοπο στην Python για να εμφανίσω τα δεδομένα από το έξυπνο συμβόλαιο με βολικό τρόπο. Εδώ δεν θα σταθώ σε αυτό αναλυτικά και θα δημοσιεύσω το site σε μια δέσμευση.

Τα αιτήματα προς TON γίνονται από Python μέσω lite-client. Για ευκολία, ο ιστότοπος συσκευάζεται σε Docker και δημοσιεύεται στο Google Cloud. Σύνδεσμος.

Ας δοκιμάσουμε

Τώρα ας προσπαθήσουμε να στείλουμε γραμμάρια εκεί για αναπλήρωση από πορτοφόλι. Θα στείλουμε 40 γραμμάρια. Και ας κάνουμε μερικά στοιχήματα για σαφήνεια. Βλέπουμε ότι ο ιστότοπος δείχνει το ιστορικό των στοιχημάτων, το τρέχον ποσοστό κερδών και άλλες χρήσιμες πληροφορίες.

Βλέπουμεότι κερδίσαμε το πρώτο, χάσαμε το δεύτερο.

Επίλογος

Το άρθρο αποδείχθηκε πολύ μεγαλύτερο από ό,τι περίμενα, ίσως θα μπορούσε να ήταν πιο σύντομο ή ίσως απλώς για ένα άτομο που δεν γνωρίζει τίποτα για το TON και θέλει να γράψει και να δημοσιεύσει ένα όχι και τόσο απλό έξυπνο συμβόλαιο με δυνατότητα αλληλεπίδρασης με το. Ίσως κάποια πράγματα θα μπορούσαν να εξηγηθούν πιο απλά.

Ίσως ορισμένες πτυχές της εφαρμογής θα μπορούσαν να είχαν γίνει πιο αποτελεσματικά και κομψά, αλλά τότε θα χρειαζόταν ακόμη περισσότερος χρόνος για την προετοιμασία του άρθρου. Είναι επίσης πιθανό να έκανα λάθος κάπου ή να μην κατάλαβα κάτι, οπότε αν κάνετε κάτι σοβαρό, πρέπει να βασιστείτε στην επίσημη τεκμηρίωση ή στο επίσημο αποθετήριο με τον κωδικό TON.

Θα πρέπει να σημειωθεί ότι δεδομένου ότι η ίδια η TON βρίσκεται ακόμα στο ενεργό στάδιο ανάπτυξης, ενδέχεται να προκύψουν αλλαγές που θα σπάσουν οποιοδήποτε από τα βήματα αυτού του άρθρου (που συνέβη ενώ έγραφα, έχει ήδη διορθωθεί), αλλά η γενική προσέγγιση είναι απίθανο να αλλάξει.

Δεν θα μιλήσω για το μέλλον του TON. Ίσως η πλατφόρμα γίνει κάτι μεγάλο και θα πρέπει να αφιερώσουμε χρόνο μελετώντας την και να γεμίσουμε μια θέση με τα προϊόντα μας τώρα.

Υπάρχει επίσης το Libra από το Facebook, το οποίο έχει δυνητικό κοινό χρηστών μεγαλύτερο από τον TON. Δεν ξέρω σχεδόν τίποτα για το Libra, αν κρίνω από το φόρουμ υπάρχει πολύ περισσότερη δραστηριότητα εκεί από ό,τι στην κοινότητα TON. Αν και οι προγραμματιστές και η κοινότητα του TON μοιάζουν περισσότερο με underground, κάτι που είναι επίσης ωραίο.

παραπομπές

  1. Επίσημη τεκμηρίωση για TON: https://test.ton.org
  2. Επίσημο αποθετήριο TON: https://github.com/ton-blockchain/ton
  3. Επίσημο πορτοφόλι για διαφορετικές πλατφόρμες: https://wallet.ton.org
  4. Αποθετήριο έξυπνων συμβολαίων από αυτό το άρθρο: https://github.com/raiym/astonished
  5. Σύνδεσμος στον ιστότοπο του έξυπνου συμβολαίου: https://ton-lottery.appspot.com
  6. Αποθετήριο για την επέκταση για Visual Studio Code για FunC: https://github.com/raiym/func-visual-studio-plugin
  7. Συζητήστε για το TON στο Telegram, το οποίο βοήθησε πραγματικά να το καταλάβουμε στο αρχικό στάδιο. Νομίζω ότι δεν θα είναι λάθος αν πω ότι όλοι όσοι έγραψαν κάτι για τον TON είναι εκεί. Μπορείτε επίσης να ζητήσετε δοκιμαστικά γραμμάρια εκεί. https://t.me/tondev_ru
  8. Μια άλλη συνομιλία για το TON στην οποία βρήκα χρήσιμες πληροφορίες: https://t.me/TONgramDev
  9. Πρώτο στάδιο του διαγωνισμού: https://contest.com/blockchain
  10. Δεύτερο στάδιο του διαγωνισμού: https://contest.com/blockchain-2

Πηγή: www.habr.com

Αγοράστε αξιόπιστη φιλοξενία για ιστότοπους με προστασία DDoS, διακομιστές VPS VDS 🔥 Αγοράστε αξιόπιστη φιλοξενία ιστοσελίδων με προστασία DDoS, διακομιστές VPS VDS | ProHoster