เกี่ยวกับวิธีการเขียนและเผยแพร่สัญญาอัจฉริยะใน TON
บทความนี้เกี่ยวกับอะไร?
ในบทความ ฉันจะพูดถึงว่าฉันมีส่วนร่วมในการแข่งขัน Telegram blockchain ครั้งแรก (จากสอง) ได้อย่างไร ไม่ได้รับรางวัล และตัดสินใจบันทึกประสบการณ์ของฉันในบทความเพื่อไม่ให้ถูกลืมเลือน และอาจช่วยได้ บางคน.
เนื่องจากฉันไม่ต้องการเขียนโค้ดเชิงนามธรรม แต่เพื่อทำบางสิ่งที่ได้ผล สำหรับบทความนี้ ฉันจึงเขียนสัญญาอัจฉริยะสำหรับลอตเตอรีทันทีและเว็บไซต์ที่แสดงข้อมูลสัญญาอัจฉริยะโดยตรงจาก TON โดยไม่ต้องใช้พื้นที่จัดเก็บข้อมูลระดับกลาง
บทความนี้จะเป็นประโยชน์สำหรับผู้ที่ต้องการสร้างสัญญาอัจฉริยะครั้งแรกใน TON แต่ไม่รู้ว่าจะเริ่มต้นอย่างไร
โดยใช้ลอตเตอรีเป็นตัวอย่าง ฉันจะเปลี่ยนจากการติดตั้งสภาพแวดล้อมไปจนถึงการเผยแพร่สัญญาอัจฉริยะ การโต้ตอบกับมัน และการเขียนเว็บไซต์สำหรับรับและเผยแพร่ข้อมูล
เกี่ยวกับการเข้าร่วมการแข่งขัน
เมื่อเดือนตุลาคมปีที่แล้ว Telegram ได้ประกาศการแข่งขันบล็อกเชนด้วยภาษาใหม่ Fift
и FunC
. จำเป็นต้องเลือกจากการเขียนสัญญาอัจฉริยะห้าข้อที่เสนอ ฉันคิดว่าคงจะดีถ้าได้ทำอะไรที่แตกต่างออกไป เรียนรู้ภาษาและทำอะไรบางอย่าง แม้ว่าฉันจะไม่ต้องเขียนอะไรอีกในอนาคตก็ตาม แถมยังมีหัวข้อติดปากอยู่ตลอดเวลา
มันคุ้มค่าที่จะบอกว่าฉันไม่มีประสบการณ์ในการพัฒนาสัญญาอัจฉริยะเลย
ฉันวางแผนที่จะเข้าร่วมจนถึงที่สุดจนกว่าจะทำได้จึงเขียนบทความวิจารณ์ แต่ฉันล้มเหลวทันทีในบทความแรก ฉัน FunC
และโดยทั่วไปแล้วมันได้ผล ฉันเอามันเป็นพื้นฐาน
ตอนนั้นผมคิดว่านี่เพียงพอที่จะคว้ารางวัลมาได้อย่างแน่นอน เป็นผลให้ผู้เข้าร่วมประมาณ 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));
}
นั่นแหล่ะ
ตอนนี้สิ่งที่เหลืออยู่นั้นเรียบง่าย มาสร้างวิธีการรับเพื่อให้เราสามารถรับข้อมูลเกี่ยวกับสถานะของสัญญาจากโลกภายนอก (อันที่จริงแล้ว อ่านข้อมูลจากที่เก็บข้อมูลสัญญาอัจฉริยะของพวกเขา)
ฉันยังลืมเพิ่มโค้ดที่จะประมวลผลคำขอแรกที่เกิดขึ้นเมื่อเผยแพร่สัญญาอัจฉริยะ
ขั้นตอนต่อไปคือการเผยแพร่สัญญาอัจฉริยะ มาสร้างโฟลเดอร์กันเถอะ 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
การพยายาม
ทีนี้ลองส่งกรัมไปที่นั่นเพื่อเติมเต็มจาก
เล่ม
บทความนี้ยาวกว่าที่ฉันคาดไว้มากบางทีอาจสั้นกว่านั้นหรืออาจเป็นเพียงสำหรับคนที่ไม่รู้อะไรเกี่ยวกับ TON และต้องการเขียนและเผยแพร่สัญญาอัจฉริยะที่ไม่ธรรมดาพร้อมความสามารถในการโต้ตอบกับ มัน. บางทีบางสิ่งอาจอธิบายได้ง่ายกว่านี้
บางทีการดำเนินการบางแง่มุมอาจทำได้มีประสิทธิภาพและสวยงามมากขึ้น แต่ก็อาจต้องใช้เวลามากขึ้นในการเตรียมบทความ อาจเป็นไปได้ว่าฉันทำผิดพลาดที่ไหนสักแห่งหรือไม่เข้าใจบางสิ่งบางอย่าง ดังนั้นหากคุณกำลังทำอะไรจริงจัง คุณต้องพึ่งพาเอกสารอย่างเป็นทางการหรือพื้นที่เก็บข้อมูลอย่างเป็นทางการด้วยรหัส TON
ควรสังเกตว่าเนื่องจาก TON ยังอยู่ในขั้นตอนการพัฒนาจึงอาจเกิดการเปลี่ยนแปลงที่จะทำให้ขั้นตอนใด ๆ ในบทความนี้เสียหาย (ซึ่งเกิดขึ้นในขณะที่ฉันกำลังเขียนก็ได้รับการแก้ไขแล้ว) แต่แนวทางทั่วไปคือ ไม่น่าจะเปลี่ยนแปลง
ฉันจะไม่พูดเกี่ยวกับอนาคตของ TON บางทีแพลตฟอร์มนี้อาจกลายเป็นสิ่งที่ยิ่งใหญ่ และเราควรใช้เวลาศึกษามันและเติมเต็มผลิตภัณฑ์ของเราในตอนนี้
นอกจากนี้ยังมี Libra จาก Facebook ซึ่งมีผู้ใช้งานมากกว่า TON ฉันแทบไม่รู้อะไรเลยเกี่ยวกับ Libra เมื่อพิจารณาจากฟอรัมแล้ว มีกิจกรรมที่นั่นมากกว่าในชุมชน TON แม้ว่านักพัฒนาและชุมชนของ TON จะเป็นเหมือนใต้ดินมากกว่า แต่ก็เจ๋งเช่นกัน
การอ้างอิง
- เอกสาร TON อย่างเป็นทางการ:
https://test.ton.org - พื้นที่เก็บข้อมูล TON อย่างเป็นทางการ:
https://github.com/ton-blockchain/ton - กระเป๋าเงินอย่างเป็นทางการสำหรับแพลตฟอร์มต่างๆ:
https://wallet.ton.org - พื้นที่เก็บข้อมูลสัญญาอัจฉริยะจากบทความนี้:
https://github.com/raiym/astonished - ลิงก์ไปยังเว็บไซต์สัญญาอัจฉริยะ:
https://ton-lottery.appspot.com - พื้นที่เก็บข้อมูลสำหรับส่วนขยายสำหรับ Visual Studio Code สำหรับ FunC:
https://github.com/raiym/func-visual-studio-plugin - พูดคุยเกี่ยวกับ TON ใน Telegram ซึ่งช่วยให้เข้าใจได้จริงๆ ในระยะเริ่มแรก ฉันคิดว่ามันคงไม่ผิดถ้าฉันบอกว่าทุกคนที่เขียนอะไรบางอย่างให้กับ TON ก็อยู่ที่นั่นด้วย คุณสามารถขอทดสอบกรัมได้ที่นั่น
https://t.me/tondev_ru - การสนทนาเกี่ยวกับ TON อีกครั้งซึ่งฉันพบข้อมูลที่เป็นประโยชน์:
https://t.me/TONgramDev - ขั้นตอนแรกของการแข่งขัน:
https://contest.com/blockchain - ขั้นตอนที่สองของการแข่งขัน:
https://contest.com/blockchain-2
ที่มา: will.com