เชื่อมต่อกับ Chainlink ของเครื่อง Oracle แบบกระจายศูนย์เพื่อป้อนราคาและพัฒนาตัวอย่างแพลตฟอร์มการซ
หมวดหมู่กว้างๆ ของ DeFi นั้นรวมถึงสถานการณ์แอปพลิเคชันสัญญาอัจฉริยะมากมาย เช่นการลงคะแนนแบบบล็อกเชน、การขุดสภาพคล่อง、การขุดสภาพคล่องฟีดราคา Chainlinkฟีดราคา ChainlinkOracle ใช้ Solidity เพื่อพัฒนาแพลตฟอร์มการซื้อขาย DeFi ตัวเลือกการโทรอย่างง่ายบน Ethereum mainnet แน่นอน คุณสามารถปรับเปลี่ยนตัวอย่างนี้เล็กน้อยเพื่อพัฒนาแพลตฟอร์มการซื้อขายตัวเลือกใส่ แพลตฟอร์มนี้มีฟังก์ชันที่ทรงพลัง กล่าวคือ การโอนมูลค่าทั้งหมดจะดำเนินการผ่านสัญญาอัจฉริยะ และทั้งสองฝ่ายในธุรกรรมสามารถทำธุรกรรมได้โดยตรงโดยไม่ต้องผ่านคนกลาง ดังนั้น กระบวนการนี้จึงไม่เกี่ยวข้องกับบุคคลที่สาม มีเพียงสัญญาอัจฉริยะและฟีดราคา Chainlink แบบกระจายอำนาจ ซึ่งเป็นแอปพลิเคชัน DeFi ทั่วไป การพัฒนาแพลตฟอร์มการซื้อขายออปชันแบบกระจายอำนาจจะครอบคลุมสิ่งต่อไปนี้:
เปรียบเทียบสตริงใน Solidity
แปลงจำนวนเต็มเป็นจำนวนทศนิยมคงที่
สร้างและเริ่มต้นอินเทอร์เฟซโทเค็น เช่น LINK
โอนโทเค็นระหว่างผู้ใช้/สัญญาอัจฉริยะ
อนุมัติการโอนโทเค็น
SafeMath
อินเทอร์เฟซ ABI สัญญาอัจฉริยะ
ดำเนินการสถานะการทำธุรกรรมด้วยrequire()
Ethereum msg.Value และความแตกต่างจากธุรกรรมมูลค่าโทเค็น
แปลงระหว่าง int และ uint
ที่อยู่เจ้าหนี้
สุดท้าย ใช้อินเทอร์เฟซตัวรวบรวมข้อมูล Chainlink เพื่อรับข้อมูลราคา DeFi
และGitHubและRemixตรวจสอบรหัสที่เกี่ยวข้อง ก่อนที่เราจะเริ่มต้นอย่างเป็นทางการ เราจะแนะนำสั้น ๆ ว่าสัญญาออปชั่นคืออะไร สัญญาออปชั่นให้คุณมีตัวเลือกในการดำเนินการซื้อขายในราคาที่ตกลงกันภายในวันที่กำหนด โดยเฉพาะอย่างยิ่ง หากเนื้อหาของสัญญาออปชันคือการซื้อสินทรัพย์ เช่น หุ้นหรือโทเค็น จะเรียกว่าคอลออปชัน นอกจากนี้ โค้ดตัวอย่างในบทความนี้สามารถแก้ไขได้เล็กน้อยเพื่อใส่ตัวเลือก ตัวเลือกการขายนั้นตรงกันข้ามกับตัวเลือกการโทร และเนื้อหาของมันไม่ใช่การซื้อสินทรัพย์ แต่เพื่อขาย ต่อไปนี้เป็นคำนามเฉพาะที่เกี่ยวข้องกับตัวเลือก:
ราคาใช้สิทธิ์: ราคาซื้อ/ขายสินทรัพย์ที่ตกลงกัน
ค่าธรรมเนียมออปชัน: ค่าธรรมเนียมที่จ่ายให้กับผู้ขายเมื่อซื้อสัญญา
วันหมดอายุ: เวลาที่สัญญาสิ้นสุดลง
การประท้วง: การกระทำของผู้ซื้อที่ใช้สิทธิ์ในการซื้อหรือขายสินทรัพย์ในราคาใช้สิทธิ
ไม่ว่าคุณกำลังพัฒนาการโทรหรือตัวเลือกการส่ง คุณต้องมีองค์ประกอบพื้นฐานของการนำเข้า ตัวสร้าง และตัวแปรส่วนกลาง
pragma solidity ^0.6.7;
import "https://github.com/smartcontractkit/chainlink/blob/develop/evm-contracts/src/v0.6/interfaces/LinkTokenInterface.sol";
import "https://github.com/smartcontractkit/chainlink/blob/master/evm-contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/math/SafeMath.sol";
contract chainlinkOptions {
// ตัวดำเนินการที่ปลอดภัยล้น
using SafeMath for uint;
// อินเทอร์เฟซฟีดราคา
AggregatorV3Interface internal ethFeed;
AggregatorV3Interface internal linkFeed;
// อินเตอร์เฟสโทเค็น LINK
LinkTokenInterface internal LINK;
uint ethPrice;
uint linkPrice;
// คำนวณค่าแฮชของสตริงล่วงหน้า
bytes32 ethHash = keccak256(abi.encodePacked("ETH"));
bytes32 linkHash = keccak256(abi.encodePacked("LINK"));
address payable contractAddr;
// ตัวเลือกถูกจัดเก็บในรูปแบบของอาร์เรย์โครงสร้าง
struct option {
uint strike; //Price in USD (18 decimal places) option allows buyer to purchase tokens at
uint premium; //Fee in contract token that option writer charges
uint expiry; //Unix timestamp of expiration time
uint amount; //Amount of tokens the option contract is for
bool exercised; //Has option been exercised
bool canceled; //Has option been canceled
uint id; //Unique ID of option, also array index
uint latestCost; //Helper to show last updated cost to exercise
address payable writer; //Issuer of option
address payable buyer; //Buyer of option
}
option[] public ethOpts;
option[] public linkOpts;
// ราคาฟีด Kovan: https://docs.chain.link/docs/reference-contracts
constructor() public {
// ฟีดราคา Kovan สำหรับ ETH/USD
ethFeed = AggregatorV3Interface(0x9326BFA02ADD2366b30bacB125260Af641031331);
//LINK/USD ราคาฟีด Kovan
linkFeed = AggregatorV3Interface(0x396c5E36DD0a0F5a5D33dae44368D4193f69a1F0);
// ที่อยู่โทเค็นลิงก์บน Kovan
LINK = LinkTokenInterface(0xa36085F69e2889c224210F603D836748e7dC0088);
contractAddr = payable(address(this));
}
เมื่อนำเข้า เราจำเป็นต้องเข้าถึงอินเทอร์เฟซตัวรวบรวมข้อมูลของ Chainlink เพื่อใช้งานฟังก์ชันฟีดราคา และเข้าถึงอินเทอร์เฟซโทเค็น LINK (หมายเหตุ: ที่นี่ เราจำเป็นต้องใช้ LINK เพื่อโอนเงิน ดังนั้น เราจำเป็นต้องใช้ฟังก์ชัน ERC20 ของสัญญาโทเค็น) . ในที่สุดเราก็นำเข้าSafeMath โดย OpenZeppelinสัญญาซึ่งเป็นการดำเนินการไลบรารีมาตรฐานที่ดำเนินการตรวจสอบโอเวอร์โฟลว์ในตัว ในขณะที่ Solidity ไม่รวมการตรวจสอบโอเวอร์โฟลว์ในตัวดำเนินการในตัว
ฟีดราคา Chainlink
ฟีดราคา Chainlink
// คืนค่า LINK ล่าสุด
function getLinkPrice() public view returns (uint) {
(
uint80 roundID,
int price,
uint startedAt,
uint timeStamp,
uint80 answeredInRound
) = linkFeed.latestRoundData();
//หากรอบนี้ยังไม่สิ้นสุด การประทับเวลาจะเป็น 0
require(timeStamp > 0, "Round not complete");
//ราคาจะไม่ติดลบ คุณจึงแปลง int เป็น uint ได้
//ราคามีจุดทศนิยม 8 หลัก แล้วต้องบวก 10 หลักถึงจะเป็น 18 หลัก
return uint(price);
}
ชื่อระดับแรก
เขียนสัญญาตัวเลือกการโทร
// อนุญาตให้ผู้ใช้เขียนตัวเลือกการโทรที่ระงับ
// ประเภทโทเค็นที่ได้รับ, ราคาใช้สิทธิ์ (โทเค็นเป็นสกุลเงิน USD, มี 18 หลักหลังจุดทศนิยม), ค่าธรรมเนียมออปชั่น (เหมือนกับจุดทศนิยมของโทเค็น), วันหมดอายุ (ยูนิกซ์), โทเค็นในปริมาณสัญญา
function writeOption(string memory token, uint strike, uint premium, uint expiry, uint tknAmt) public payable {
bytes32 tokenHash = keccak256(abi.encodePacked(token));
require(tokenHash == ethHash || tokenHash == linkHash, "Only ETH and LINK tokens are supported");
updatePrices();
if (tokenHash == ethHash) {
require(msg.value == tknAmt, "Incorrect amount of ETH supplied");
uint latestCost = strike.mul(tknAmt).div(ethPrice.mul(10**10)); // ต้นทุนการใช้สิทธิเป็นอีเทอร์ ปรับจุดทศนิยม
ethOpts.push(option(strike, premium, expiry, tknAmt, false, false, ethOpts.length, latestCost, msg.sender, address(0)));
} else {
require(LINK.transferFrom(msg.sender, contractAddr, tknAmt), "Incorrect amount of LINK supplied");
uint latestCost = strike.mul(tknAmt).div(linkPrice.mul(10**10));
linkOpts.push(option(strike, premium, expiry, tknAmt, false, false, linkOpts.length, latestCost, msg.sender, address(0)));
}
}
คำอธิบายภาพ
เขียนสัญญาออปชั่น LINK สำหรับโทเค็น LINK ตั้งเวลาหมดอายุของ Unix ราคาใช้สิทธิ์คือ $10 และค่าธรรมเนียมออปชันคือ 0.1 LINK
ชื่อระดับแรก
อินเตอร์เฟสสัญญา ABI
เมื่อคุณดูสัญญาบน Etherscan จะมีแท็บสองแท็บ ได้แก่: อ่านสัญญาและเขียนสัญญา คุณสามารถใช้สองแท็บนี้เพื่อโต้ตอบกับสัญญาได้ ตัวอย่างเช่น: สัญญาเครือข่ายหลักของโทเค็น LINK Etherscan รู้ว่าฟังก์ชันเหล่านี้คืออะไรและจะเรียกใช้งานผ่าน ABI ของสัญญาได้อย่างไร ใช้รูปแบบ JSON เพื่อเรียก ABI และระบุพารามิเตอร์การเรียกใช้ฟังก์ชัน สามารถเรียกได้โดยตรงบนเครือข่ายหลัก แต่สัญญา LINK บน Kovan จำเป็นต้องนำเข้าโมดูลนี้ คุณสามารถเข้าไปLinkTokenคำอธิบายภาพ
คำอธิบายภาพ
ชื่อระดับแรก
ซื้อตัวเลือกการโทร
//หากต้องการซื้อตัวเลือกการโทร คุณต้องใช้โทเค็น รหัสตัวเลือก และการชำระเงิน
function buyOption(string memory token, uint ID) public payable {
bytes32 tokenHash = keccak256(abi.encodePacked(token));
require(tokenHash == ethHash || tokenHash == linkHash, "Only ETH and LINK tokens are supported");
updatePrices();
if (tokenHash == ethHash) {
require(!ethOpts[ID].canceled && ethOpts[ID].expiry > now, "Option is canceled/expired and cannot be bought");
//ผู้ซื้อจ่ายออปชั่นพรีเมี่ยม
require(msg.value == ethOpts[ID].premium, "Incorrect amount of ETH sent for premium");
// ผู้ขายได้รับค่าธรรมเนียมออปชัน
ethOpts[ID].writer.transfer(ethOpts[ID].premium);
ethOpts[ID].buyer = msg.sender;
} else {
require(!linkOpts[ID].canceled && linkOpts[ID].expiry > now, "Option is canceled/expired and cannot be bought");
// โอนพรีเมี่ยมออปชั่นจากผู้ซื้อไปยังผู้ขาย
require(LINK.transferFrom(msg.sender, linkOpts[ID].writer, linkOpts[ID].premium), "Incorrect amount of LINK sent for premium");
linkOpts[ID].buyer = msg.sender;
}
}
ชื่อระดับแรก
ตัวเลือกการออกกำลังกาย
// ตัวเลือกการโทรออก, ต้องการโทเค็น, ID ตัวเลือกและการชำระเงิน
function exercise(string memory token, uint ID) public payable {
//หากออปชันยังไม่หมดอายุและยังไม่ได้ใช้สิทธิ์ เจ้าของออปชันจะได้รับอนุญาตให้ใช้สิทธิ์ได้
//หากต้องการใช้ออปชัน ผู้ซื้อต้องจ่ายเงินให้ผู้ขายตามจำนวนราคาใช้สิทธิ์ * ปริมาณ และรับจำนวนโทเค็นที่ตกลงในสัญญา
bytes32 tokenHash = keccak256(abi.encodePacked(token));
require(tokenHash == ethHash || tokenHash == linkHash, "Only ETH and LINK tokens are supported");
if (tokenHash == ethHash) {
require(ethOpts[ID].buyer == msg.sender, "You do not own this option");
require(!ethOpts[ID].exercised, "Option has already been exercised");
require(ethOpts[ID].expiry > now, "Option is expired");
//ตรงตามเงื่อนไขชำระเงิน
updatePrices();
// ค่าใช้สิทธิ
uint exerciseVal = ethOpts[ID].strike*ethOpts[ID].amount;
//เชื่อมต่อกับ Chainlink เพื่อป้อนราคาและแปลงเป็น Ethereum
uint equivEth = exerciseVal.div(ethPrice.mul(10**10)); //แปลงทศนิยม 8 ตำแหน่งของราคาฟีดเป็น 18
//ผู้ซื้อจ่ายอีเธอร์เทียบเท่ากับราคาใช้สิทธิ์*ปริมาณเพื่อใช้ออปชั่น
require(msg.value == equivEth, "Incorrect LINK amount sent to exercise");
//ชำระค่าธรรมเนียมการใช้สิทธิ์ให้กับผู้ขาย
ethOpts[ID].writer.transfer(equivEth);
// ชำระเงินให้ผู้ซื้อตามจำนวนสัญญาของอีเธอร์
msg.sender.transfer(ethOpts[ID].amount);
ethOpts[ID].exercised = true;
} else {
require(linkOpts[ID].buyer == msg.sender, "You do not own this option");
require(!linkOpts[ID].exercised, "Option has already been exercised");
require(linkOpts[ID].expiry > now, "Option is expired");
updatePrices();
uint exerciseVal = linkOpts[ID].strike*linkOpts[ID].amount;
uint equivLink = exerciseVal.div(linkPrice.mul(10**10));
//ผู้ซื้อใช้สิทธิและชำระค่าธรรมเนียมการใช้สิทธิให้ผู้ขาย
require(LINK.transferFrom(msg.sender, linkOpts[ID].writer, equivLink), "Incorrect LINK amount sent to exercise");
// ชำระเงินตามสัญญาของโทเค็น LINK ให้กับผู้ขาย
require(LINK.transfer(msg.sender, linkOpts[ID].amount), "Error: buyer was not paid");
linkOpts[ID].exercised = true;
}
}
คำอธิบายภาพ
ตัวอย่าง: ผลลัพธ์ของเอาต์พุต Remix เมื่อธุรกรรมไม่ตรงตามเงื่อนไขตั้งแต่หนึ่งข้อขึ้นไป
หากตรงตามเงื่อนไข ค่าธรรมเนียมการใช้สิทธิ์จะจ่ายให้กับผู้ขาย และจำนวนสัญญาของโทเค็นจะจ่ายให้กับผู้ซื้อ เมื่อใช้ตัวเลือก ผู้ซื้อจำเป็นต้องซื้อแต่ละโทเค็นในราคาใช้สิทธิ อย่างไรก็ตาม ราคาใช้สิทธิ์เป็นสกุลเงิน USD ในขณะที่ขนาดสัญญาเป็น Ether หรือ LINK ดังนั้นเราจึงจำเป็นต้องเข้าถึงฟีดราคา Chainlink เพื่อคำนวณจำนวน ETH หรือ LINK ที่เทียบเท่ากับค่าธรรมเนียมการใช้สิทธิ์ หลังจากแปลงเป็น ETH หรือ LINK ที่เทียบเท่าแล้ว เราสามารถเริ่มโอนได้ เมื่อทำการโอนเงิน คุณต้องใช้วิธีการที่กล่าวถึงข้างต้น กล่าวคือ สกุลเงินอีเทอร์จะเรียกใช้ฟังก์ชัน msg.value/address.transfer และ LINK จะเรียกใช้ฟังก์ชัน TransferFrom()
ชื่อระดับแรก
ยกเลิกสัญญา/ลบกองทุน
// อนุญาตให้ผู้ขายยกเลิกสัญญาหรือรับเงินคืนจากตัวเลือกที่ปิดการซื้อขายไม่สำเร็จ
function cancelOption(string memory token, uint ID) public payable {
bytes32 tokenHash = keccak256(abi.encodePacked(token));
require(tokenHash == ethHash || tokenHash == linkHash, "Only ETH and LINK tokens are supported");
if (tokenHash == ethHash) {
require(msg.sender == ethOpts[ID].writer, "You did not write this option");
// ต้องไม่ถูกยกเลิกหรือซื้อแล้ว
require(!ethOpts[ID].canceled && ethOpts[ID].buyer == address(0), "This option cannot be canceled");
ethOpts[ID].writer.transfer(ethOpts[ID].amount);
ethOpts[ID].canceled = true;
} else {
require(msg.sender == linkOpts[ID].writer, "You did not write this option");
require(!linkOpts[ID].canceled && linkOpts[ID].buyer == address(0), "This option cannot be canceled");
require(LINK.transferFrom(address(this), linkOpts[ID].writer, linkOpts[ID].amount), "Incorrect amount of LINK sent");
linkOpts[ID].canceled = true;
}
}
//อนุญาตให้ผู้ขายแลกเงินจากออปชันที่หมดอายุ ค้างชำระ และไม่ถูกยกเลิก
function retrieveExpiredFunds(string memory token, uint ID) public payable {
bytes32 tokenHash = keccak256(abi.encodePacked(token));
require(tokenHash == ethHash || tokenHash == linkHash, "Only ETH and LINK tokens are supported");
if (tokenHash == ethHash) {
require(msg.sender == ethOpts[ID].writer, "You did not write this option");
//ต้องหมดอายุ ไม่ใช้สิทธิ์ และไม่ถูกยกเลิก
require(ethOpts[ID].expiry <= now && !ethOpts[ID].exercised && !ethOpts[ID].canceled, "This option is not eligible for withdraw");
ethOpts[ID].writer.transfer(ethOpts[ID].amount);
// แก้ไขค่าสถานะการยกเลิกเป็นจริงเพื่อหลีกเลี่ยงการแลกหลายรายการ
ethOpts[ID].canceled = true;
} else {
require(msg.sender == linkOpts[ID].writer, "You did not write this option");
require(linkOpts[ID].expiry <= now && !linkOpts[ID].exercised && !linkOpts[ID].canceled, "This option is not eligible for withdraw");
require(LINK.transferFrom(address(this), linkOpts[ID].writer, linkOpts[ID].amount), "Incorrect amount of LINK sent");
linkOpts[ID].canceled = true;
}
}
เนื่องจากตลาดผันผวน ผู้ขายอาจยกเลิกสัญญาออปชันและไถ่ถอนเงินของตนหากยังไม่ได้ขายออปชัน ในทำนองเดียวกัน หากออปชันหมดอายุโดยไม่ได้ใช้งาน ผู้ขายก็ต้องการไถ่ถอนเงินในสัญญาอย่างแน่นอน ดังนั้นเราจึงเพิ่มฟังก์ชัน cancelOption() และ retrieveExpiredFunds()
จุดที่สำคัญที่สุดของฟังก์ชันทั้งสองนี้คือต้องตรงตามเงื่อนไขการแลกรับจึงจะเรียกได้สำเร็จ ผู้ขายต้องปฏิบัติตามเงื่อนไขบางประการเพื่อแลกเงิน และสามารถแลกได้เพียงครั้งเดียวเท่านั้น ผู้ขายไม่สามารถยกเลิกสัญญาที่ขายไปแล้วได้ ดังนั้น เราจำเป็นต้องยืนยันว่าที่อยู่ของผู้ซื้อยังคงเป็นค่าเริ่มต้น 0 นอกจากนี้ เรายังจำเป็นต้องยืนยันว่าตัวเลือกนั้นไม่ได้ถูกยกเลิก แล้วจึงขอคืนเงิน หากแลกเงินหลังจากออปชันหมดอายุ สถานการณ์จะแตกต่างออกไปเล็กน้อย ในกรณีนี้ ออปชันอาจถูกขายแต่ไม่ได้ใช้สิทธิ์ และควรคืนเงินให้กับผู้ขาย เราต้องการยืนยันว่าสัญญาหมดอายุและยังไม่ได้ใช้สิทธิ จากนั้นตั้งค่าสถานะการยกเลิกของตัวเลือกเป็น "จริง" เพื่อคืนเงินหากตรงตามเงื่อนไข
ฉันหวังว่าบทความนี้จะช่วยคุณพัฒนากรณีการใช้งาน Chainlink บน mainnet ได้ทันที และช่วยให้คุณเข้าใจถึงความสามารถเฉพาะของ Solidity หากคุณต้องการเรียนรู้เพิ่มเติมเกี่ยวกับคุณสมบัติของ Chainlink ลองดูChainlink VRF(ฟังก์ชันสุ่มตรวจสอบได้) หรือดูบริการสั่งซื้อ Chainlink Fairเรียนรู้ว่า Chainlink แก้ปัญหาการทำงานของนักขุดได้อย่างไร
และเข้าร่วมกับเราได้ที่เอกสารประกอบสำหรับนักพัฒนาและเข้าร่วมกับเราได้ที่Discordติดต่อเรา.คลิกที่นี่ติดต่อเรา.


