เกี่ยวกับวิธีการเขียนและเผยแพร่สัญญาอัจฉริยะใน Telegram Open Network (TON)

เกี่ยวกับวิธีการเขียนและเผยแพร่สัญญาอัจฉริยะใน TON

บทความนี้เกี่ยวกับอะไร?

ในบทความ ฉันจะพูดถึงว่าฉันมีส่วนร่วมในการแข่งขัน Telegram blockchain ครั้งแรก (จากสอง) ได้อย่างไร ไม่ได้รับรางวัล และตัดสินใจบันทึกประสบการณ์ของฉันในบทความเพื่อไม่ให้ถูกลืมเลือน และอาจช่วยได้ บางคน.

เนื่องจากฉันไม่ต้องการเขียนโค้ดเชิงนามธรรม แต่เพื่อทำบางสิ่งที่ได้ผล สำหรับบทความนี้ ฉันจึงเขียนสัญญาอัจฉริยะสำหรับลอตเตอรีทันทีและเว็บไซต์ที่แสดงข้อมูลสัญญาอัจฉริยะโดยตรงจาก TON โดยไม่ต้องใช้พื้นที่จัดเก็บข้อมูลระดับกลาง

บทความนี้จะเป็นประโยชน์สำหรับผู้ที่ต้องการสร้างสัญญาอัจฉริยะครั้งแรกใน TON แต่ไม่รู้ว่าจะเริ่มต้นอย่างไร

โดยใช้ลอตเตอรีเป็นตัวอย่าง ฉันจะเปลี่ยนจากการติดตั้งสภาพแวดล้อมไปจนถึงการเผยแพร่สัญญาอัจฉริยะ การโต้ตอบกับมัน และการเขียนเว็บไซต์สำหรับรับและเผยแพร่ข้อมูล

เกี่ยวกับการเข้าร่วมการแข่งขัน

เมื่อเดือนตุลาคมปีที่แล้ว Telegram ได้ประกาศการแข่งขันบล็อกเชนด้วยภาษาใหม่ Fift и FunC. จำเป็นต้องเลือกจากการเขียนสัญญาอัจฉริยะห้าข้อที่เสนอ ฉันคิดว่าคงจะดีถ้าได้ทำอะไรที่แตกต่างออกไป เรียนรู้ภาษาและทำอะไรบางอย่าง แม้ว่าฉันจะไม่ต้องเขียนอะไรอีกในอนาคตก็ตาม แถมยังมีหัวข้อติดปากอยู่ตลอดเวลา

มันคุ้มค่าที่จะบอกว่าฉันไม่มีประสบการณ์ในการพัฒนาสัญญาอัจฉริยะเลย

ฉันวางแผนที่จะเข้าร่วมจนถึงที่สุดจนกว่าจะทำได้จึงเขียนบทความวิจารณ์ แต่ฉันล้มเหลวทันทีในบทความแรก ฉัน เขียนกระเป๋าเงิน พร้อมเปิดลายเซ็นหลายลายเซ็น FunC และโดยทั่วไปแล้วมันได้ผล ฉันเอามันเป็นพื้นฐาน สัญญาอัจฉริยะเกี่ยวกับ Solidity.

ตอนนั้นผมคิดว่านี่เพียงพอที่จะคว้ารางวัลมาได้อย่างแน่นอน เป็นผลให้ผู้เข้าร่วมประมาณ 40 คนจาก 60 คนกลายเป็นผู้ชนะรางวัล และฉันไม่ได้เป็นหนึ่งในนั้น โดยทั่วไปไม่มีอะไรผิดปกติในเรื่องนี้ แต่มีสิ่งหนึ่งที่รบกวนจิตใจฉัน ตอนที่ประกาศผลการทบทวนการทดสอบสัญญาของฉันยังไม่เสร็จสิ้น ฉันถามผู้เข้าร่วมในแชทว่ามีใครอีกไหมที่ไม่มี ไม่มีเลย

เห็นได้ชัดว่าให้ความสนใจกับข้อความของฉัน สองวันต่อมาผู้พิพากษาได้เผยแพร่ความคิดเห็น และฉันยังไม่เข้าใจว่าพวกเขาพลาดสัญญาอันชาญฉลาดของฉันโดยไม่ได้ตั้งใจในระหว่างการตัดสิน หรือเพียงคิดว่ามันแย่มากจนไม่จำเป็นต้องแสดงความคิดเห็น ฉันถามคำถามในเพจแต่ไม่ได้รับคำตอบ แม้ว่าผู้ตัดสินจะไม่เป็นความลับ แต่ฉันถือว่าไม่จำเป็นต้องเขียนข้อความส่วนตัว

ใช้เวลาทำความเข้าใจไปมากจึงตัดสินใจเขียนบทความ เนื่องจากยังไม่มีข้อมูลมากนัก บทความนี้จะช่วยประหยัดเวลาสำหรับทุกคนที่สนใจ

แนวคิดของสัญญาอัจฉริยะใน TON

ก่อนที่คุณจะเขียนอะไร คุณต้องรู้ว่าจะเข้าหาสิ่งนี้จากด้านใด ดังนั้นตอนนี้ผมจะบอกคุณว่าระบบประกอบด้วยส่วนใดบ้าง แม่นยำยิ่งขึ้นส่วนใดที่คุณจำเป็นต้องรู้เพื่อเขียนสัญญาการทำงานบางประเภทเป็นอย่างน้อย

เราจะมุ่งเน้นไปที่การเขียนสัญญาที่ชาญฉลาดและทำงานร่วมกับ TON Virtual Machine (TVM), Fift и FunCดังนั้นบทความนี้จึงเป็นเหมือนคำอธิบายการพัฒนาโปรแกรมทั่วไปมากกว่า เราจะไม่ยึดติดกับวิธีการทำงานของแพลตฟอร์มที่นี่

โดยทั่วไปเกี่ยวกับวิธีการทำงาน TVM และภาษา Fift มีเอกสารราชการที่ดี ตอนที่เข้าร่วมการแข่งขันและตอนนี้ในขณะที่เขียนสัญญาฉบับปัจจุบัน ฉันมักจะหันไปหาเธอ

ภาษาหลักที่ใช้เขียนสัญญาอัจฉริยะคือ FunC. ยังไม่มีเอกสารประกอบในขณะนี้ ดังนั้นเพื่อที่จะเขียนบางสิ่ง คุณต้องศึกษาตัวอย่างของสัญญาอัจฉริยะจากพื้นที่เก็บข้อมูลอย่างเป็นทางการและการใช้งานภาษานั้นที่นั่น นอกจากนี้ คุณสามารถดูตัวอย่างสัญญาอัจฉริยะจากสองช่วงที่ผ่านมาได้ การแข่งขัน ลิงค์ในตอนท้ายของบทความ

สมมติว่าเราได้เขียนสัญญาอันชาญฉลาดไว้แล้ว FunCหลังจากนั้นเราจะคอมไพล์โค้ดลงใน Fift assembler

สัญญาอัจฉริยะที่รวบรวมไว้ยังคงรอการเผยแพร่ ในการดำเนินการนี้ คุณจะต้องเขียนฟังก์ชันเข้าไป Fiftซึ่งจะใช้โค้ดสัญญาอัจฉริยะและพารามิเตอร์อื่นๆ เป็นอินพุต และเอาต์พุตจะเป็นไฟล์ที่มีนามสกุล .boc (ซึ่งหมายถึง "ถุงของเซลล์") และคีย์ส่วนตัวและที่อยู่ซึ่งสร้างขึ้นตามรหัสสัญญาอัจฉริยะ ขึ้นอยู่กับว่าเราเขียนอย่างไร คุณสามารถส่งกรัมไปยังที่อยู่ของสัญญาอัจฉริยะที่ยังไม่ได้เผยแพร่ได้แล้ว

เพื่อเผยแพร่สัญญาอัจฉริยะใน TON ที่ได้รับ .boc ไฟล์จะต้องถูกส่งไปยังบล็อคเชนโดยใช้ไคลเอนต์แบบเบา (เพิ่มเติมด้านล่าง) แต่ก่อนที่จะเผยแพร่ คุณต้องถ่ายโอนกรัมไปยังที่อยู่ที่สร้างขึ้น มิฉะนั้นสัญญาอัจฉริยะจะไม่ถูกเผยแพร่ หลังจากการเผยแพร่ คุณสามารถโต้ตอบกับสัญญาอัจฉริยะได้โดยการส่งข้อความจากภายนอก (เช่น การใช้ไคลเอ็นต์แบบเบา) หรือจากภายใน (เช่น สัญญาอัจฉริยะอันหนึ่งส่งข้อความอีกอันภายใน 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

หากบิลด์สำเร็จ หลังจากเปิดใช้งาน คุณจะเห็นบันทึกการเชื่อมต่อของไลท์ไคลเอ็นต์กับโหนด

[ 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 หากคุณต้องการเพิ่มบางสิ่งในทันที ฉันจึงทำให้ปลั๊กอินดังกล่าวพร้อมใช้งานแบบสาธารณะ นอกจากนี้ ก่อนหน้านี้มีคนสร้างปลั๊กอินสำหรับทำงานกับ Fift คุณยังสามารถติดตั้งและค้นหาได้ใน VSC

มาสร้างพื้นที่เก็บข้อมูลทันทีที่เราจะส่งมอบผลลัพธ์ระดับกลาง

เพื่อให้ชีวิตของเราง่ายขึ้น เราจะเขียนสัญญาอัจฉริยะและทดสอบในพื้นที่จนกว่าจะพร้อม หลังจากนั้นเราจะเผยแพร่ใน TON

สัญญาอัจฉริยะมีวิธีภายนอกสองวิธีที่สามารถเข้าถึงได้ อันดับแรก, 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 บิต และลิงก์ไปยังเซลล์อื่นได้สูงสุด 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

เราได้รวบรวมโค้ดแอสเซมเบลอร์ 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 บิต unsigned int จากข้อความที่ส่งมา

ต่อไปเราจะโหลด 32 บิตจากที่เก็บข้อมูลสัญญาอัจฉริยะ เราตรวจสอบว่าหมายเลขที่โหลดตรงกับหมายเลขที่ส่ง ถ้าไม่ใช่ เราจะโยนข้อยกเว้น ในกรณีของเรา เนื่องจากเรากำลังผ่านการแข่งขันที่ไม่ตรงกัน จึงควรมีข้อยกเว้นเกิดขึ้น

ตอนนี้เรามารวบรวม

~/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

ที่นี่ คุณสามารถดูการคอมมิตที่เกี่ยวข้องกับผลลัพธ์ปัจจุบันได้

โปรดทราบว่าการคัดลอกโค้ดที่คอมไพล์แล้วของสัญญาอัจฉริยะลงในไฟล์ที่มีการทดสอบอยู่ตลอดเวลานั้นไม่สะดวก ดังนั้นมาเขียนสคริปต์ที่จะเขียนโค้ดให้เป็นค่าคงที่ให้เรา และเราจะเชื่อมต่อโค้ดที่คอมไพล์กับการทดสอบของเราโดยใช้ "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 คุณอ่านได้ ในเอกสารฉบับที่ห้า.

และเพื่อทำการทดสอบเราจะเรียกอีกครั้ง

message_to_send @ 
recv_external 
code 
storage @
c7
runvmctx

เช่นนี้ ไฟล์ที่มีการทดสอบควรมีลักษณะเช่นนี้ในขั้นตอนนี้

มาทำการทดสอบกันดีกว่า มันจะล้มเหลว ดังนั้นเราจะเปลี่ยนสัญญาอัจฉริยะเพื่อให้สามารถรับข้อความในรูปแบบนี้และตรวจสอบลายเซ็นได้

ขั้นแรก เรานับลายเซ็นจากข้อความ 512 บิตและเขียนลงในตัวแปร จากนั้นเรานับตัวแปรตัวนับ 32 บิต

เนื่องจากเรามีฟังก์ชันสำหรับอ่านข้อมูลจากการจัดเก็บสัญญาอัจฉริยะ เราจะใช้มัน

ต่อไปคือการตรวจสอบเคาน์เตอร์ที่โอนพร้อมกับการจัดเก็บและตรวจสอบลายเซ็น หากมีบางอย่างไม่ตรงกัน เราจะทำการยกเว้นด้วยโค้ดที่เหมาะสม

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

การกระทำที่เกี่ยวข้อง ตรงนี้.

ลองทำการทดสอบและดูว่าการทดสอบครั้งที่สองล้มเหลว ด้วยเหตุผลสองประการ ทำให้ข้อความมีบิตไม่เพียงพอและมีบิตในที่เก็บข้อมูลไม่เพียงพอ ดังนั้นโค้ดจึงหยุดทำงานเมื่อแยกวิเคราะห์ เราจำเป็นต้องเพิ่มลายเซ็นให้กับข้อความที่เรากำลังส่งและคัดลอกที่เก็บข้อมูลจากการทดสอบครั้งล่าสุด

ในการทดสอบครั้งที่สอง เราจะเพิ่มลายเซ็นข้อความและเปลี่ยนที่เก็บข้อมูลสัญญาอัจฉริยะ เช่นนี้ ไฟล์ที่มีการทดสอบดูเหมือนว่าในขณะนี้

มาเขียนการทดสอบครั้งที่สี่กัน โดยเราจะส่งข้อความที่เซ็นชื่อด้วยคีย์ส่วนตัวของผู้อื่น มาสร้างคีย์ส่วนตัวอีกอันแล้วบันทึกลงในไฟล์ not-owner.pk. เราจะลงนามข้อความด้วยรหัสส่วนตัวนี้ มาทำการทดสอบและตรวจสอบให้แน่ใจว่าการทดสอบทั้งหมดผ่าน ให้สัญญา ณ ตอนนี้.

ในที่สุดเราก็สามารถก้าวไปสู่การนำตรรกะของสัญญาอัจฉริยะไปใช้ได้แล้ว
В recv_external() เราจะรับข้อความสองประเภท

เนื่องจากสัญญาของเราจะสะสมการสูญเสียของผู้เล่น เงินจำนวนนี้จึงต้องโอนไปยังผู้สร้างลอตเตอรี ที่อยู่กระเป๋าเงินของผู้สร้างลอตเตอรีจะถูกบันทึกไว้ในที่เก็บข้อมูลเมื่อมีการสร้างสัญญา

ในกรณีที่เราต้องการความสามารถในการเปลี่ยนที่อยู่ที่เราส่งผู้แพ้จำนวนหนึ่งไป เราก็ควรจะสามารถส่งกรัมจากลอตเตอรี่ไปยังที่อยู่ของเจ้าของได้เช่นกัน

เริ่มจากอันแรกกันก่อน ก่อนอื่นเรามาเขียนการทดสอบที่จะตรวจสอบว่าหลังจากส่งข้อความแล้ว สัญญาอัจฉริยะจะบันทึกที่อยู่ใหม่ในที่เก็บข้อมูล โปรดทราบว่านอกเหนือจากเคาน์เตอร์และที่อยู่ใหม่แล้ว เรายังส่งข้อมูลในข้อความด้วย action ตัวเลขที่ไม่ใช่ลบจำนวน 7 บิต ขึ้นอยู่กับจำนวนนั้น เราจะเลือกวิธีประมวลผลข้อความในสัญญาอัจฉริยะ

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

ในการทดสอบ คุณจะเห็นว่าที่เก็บข้อมูล smartcontract ถูกดีซีเรียลไลซ์อย่างไร storage ในห้า การดีซีเรียลไลเซชันของตัวแปรอธิบายไว้ในเอกสารประกอบที่ห้า

ลิงก์ยืนยัน พร้อมแป้งเพิ่ม

มาทำการทดสอบและให้แน่ใจว่ามันล้มเหลว ตอนนี้เรามาเพิ่มตรรกะในการเปลี่ยนที่อยู่ของเจ้าของสลาก

ในสัญญาอัจฉริยะ เรายังคงแยกวิเคราะห์ต่อไป message, อ่านเข้าไป action. เราขอเตือนคุณว่าเราจะมีสองคน action: เปลี่ยนที่อยู่แล้วส่งกรัม

จากนั้นเราจะอ่านที่อยู่ใหม่ของเจ้าของสัญญาและบันทึกไว้ในที่เก็บข้อมูล
เราทำการทดสอบและพบว่าการทดสอบครั้งที่สามล้มเหลว มันขัดข้องเนื่องจากสัญญาตอนนี้แยกวิเคราะห์เพิ่มเติม 7 บิตจากข้อความซึ่งขาดหายไปในการทดสอบ เพิ่มข้อความที่ไม่มีอยู่ในข้อความ 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 บิตที่รับผิดชอบเวิร์กเชน และหมายเลขบัญชีเฉพาะจำนวนเต็มไม่เป็นลบ 256 บิตในเวิร์กเชนนี้ ตัวอย่างเช่น -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 บิตเป็นไบต์

"sender.addr" -1 BYTES:0000000000000000000000000000000000000000000000000000000000003039

swap สลับสององค์ประกอบบนสุดของสแต็ก

"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039 -1

32 i>B แปลงจำนวนเต็ม 32 บิตเป็นไบต์

"sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039 BYTES:FFFFFFFF

B+ เชื่อมต่อสองลำดับของไบต์

 "sender.addr" BYTES:0000000000000000000000000000000000000000000000000000000000003039FFFFFFFF

อีกครั้ง swap.

BYTES:0000000000000000000000000000000000000000000000000000000000003039FFFFFFFF "sender.addr" 

และในที่สุดไบต์ก็ถูกเขียนลงในไฟล์ 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 ด้วยซ้ำ ถัดไป คุณต้องส่งหลายกรัมไปยังที่อยู่นี้ และหลังจากนั้นคุณจะต้องส่งไฟล์ด้วยสัญญาอัจฉริยะเอง เนื่องจากเครือข่ายได้รับค่าคอมมิชชั่นในการจัดเก็บสัญญาอัจฉริยะและการดำเนินการในนั้น (ผู้ตรวจสอบที่จัดเก็บและดำเนินการอย่างชาญฉลาด สัญญา) สามารถดูรหัสได้ที่นี่.

ต่อไปเราจะรันโค้ดการเผยแพร่และรับ 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 กรัม และหลังจากนั้นไม่กี่วินาที เราก็ดำเนินการคำสั่งเดียวกัน เพื่อส่งกรัมฉันใช้ กระเป๋าเงินอย่างเป็นทางการและคุณสามารถขอให้ใครบางคนจากการแชททดสอบกรัมซึ่งฉันจะพูดถึงในตอนท้ายของบทความ

> 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ซึ่งถูกสร้างขึ้นก่อนหน้านี้เมื่อสร้างสัญญาอัจฉริยะ นี่คือการกระทำที่เกี่ยวข้อง.

การรับข้อมูลจากสัญญาอัจฉริยะโดยใช้วิธีการรับ

ตอนนี้เรามาดูวิธีการเรียกใช้วิธีการรับสัญญาอัจฉริยะ

ปล่อย lite-client และเรียกใช้เมธอด get ที่เราเขียน

$ ./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 เพื่อแสดงข้อมูลจากสัญญาอัจฉริยะด้วยวิธีที่สะดวก ที่นี่ฉันจะไม่พูดถึงรายละเอียดและจะเผยแพร่เว็บไซต์ ในการกระทำครั้งเดียว.

การร้องขอไปยัง TON นั้นทำมาจาก Python ด้วย lite-client. เพื่อความสะดวก ไซต์ดังกล่าวได้รับการบรรจุใน Docker และเผยแพร่บน Google Cloud ลิงค์.

การพยายาม

ทีนี้ลองส่งกรัมไปที่นั่นเพื่อเติมเต็มจาก เงิน. เราจะส่ง40กรัม. และเรามาเดิมพันกันสองสามอย่างเพื่อความชัดเจน เราเห็นว่าเว็บไซต์แสดงประวัติการเดิมพัน เปอร์เซ็นต์การชนะในปัจจุบัน และข้อมูลที่เป็นประโยชน์อื่นๆ

ที่เราเห็นว่าเราชนะอันแรก แพ้อันที่สอง

เล่ม

บทความนี้ยาวกว่าที่ฉันคาดไว้มากบางทีอาจสั้นกว่านั้นหรืออาจเป็นเพียงสำหรับคนที่ไม่รู้อะไรเกี่ยวกับ TON และต้องการเขียนและเผยแพร่สัญญาอัจฉริยะที่ไม่ธรรมดาพร้อมความสามารถในการโต้ตอบกับ มัน. บางทีบางสิ่งอาจอธิบายได้ง่ายกว่านี้

บางทีการดำเนินการบางแง่มุมอาจทำได้มีประสิทธิภาพและสวยงามมากขึ้น แต่ก็อาจต้องใช้เวลามากขึ้นในการเตรียมบทความ อาจเป็นไปได้ว่าฉันทำผิดพลาดที่ไหนสักแห่งหรือไม่เข้าใจบางสิ่งบางอย่าง ดังนั้นหากคุณกำลังทำอะไรจริงจัง คุณต้องพึ่งพาเอกสารอย่างเป็นทางการหรือพื้นที่เก็บข้อมูลอย่างเป็นทางการด้วยรหัส TON

ควรสังเกตว่าเนื่องจาก TON ยังอยู่ในขั้นตอนการพัฒนาจึงอาจเกิดการเปลี่ยนแปลงที่จะทำให้ขั้นตอนใด ๆ ในบทความนี้เสียหาย (ซึ่งเกิดขึ้นในขณะที่ฉันกำลังเขียนก็ได้รับการแก้ไขแล้ว) แต่แนวทางทั่วไปคือ ไม่น่าจะเปลี่ยนแปลง

ฉันจะไม่พูดเกี่ยวกับอนาคตของ TON บางทีแพลตฟอร์มนี้อาจกลายเป็นสิ่งที่ยิ่งใหญ่ และเราควรใช้เวลาศึกษามันและเติมเต็มผลิตภัณฑ์ของเราในตอนนี้

นอกจากนี้ยังมี Libra จาก Facebook ซึ่งมีผู้ใช้งานมากกว่า TON ฉันแทบไม่รู้อะไรเลยเกี่ยวกับ Libra เมื่อพิจารณาจากฟอรัมแล้ว มีกิจกรรมที่นั่นมากกว่าในชุมชน TON แม้ว่านักพัฒนาและชุมชนของ TON จะเป็นเหมือนใต้ดินมากกว่า แต่ก็เจ๋งเช่นกัน

การอ้างอิง

  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

ที่มา: will.com

เพิ่มความคิดเห็น