BTC
ETH
HTX
SOL
BNB
查看行情
简中
繁中
English
日本語
한국어
ภาษาไทย
Tiếng Việt

接入去中心化預言機Chainlink餵價開發DeFi看漲期權交易平台實例

Chainlink
特邀专栏作者
2020-11-17 05:17
本文約11317字,閱讀全文需要約17分鐘
本文將教大家如何使用Chainlink餵價預言機在以太坊主網上用Solidity開發簡單的看漲期權DeFi交易平台。
AI總結
展開
本文將教大家如何使用Chainlink餵價預言機在以太坊主網上用Solidity開發簡單的看漲期權DeFi交易平台。

DeFi這個大類下包含許多智能合約應用場景,如Chainlink餵價流動性挖礦流動性挖礦Chainlink餵價Chainlink餵價預言機在以太坊主網上用Solidity開發簡單的看漲期權DeFi交易平台。當然,你也可以將這個實例稍作修改,開發一個看跌期權交易平台。這個平台擁有一個強大的功能,那就是所有價值轉移都通過智能合約進行,交易雙方可以繞過中間方直接展開交易。因此,這個過程不包含任何第三方,只包含智能合約和去中心化的Chainlink餵價,這就是最典型的DeFi應用。開發一個去中心化期權交易平台將涵蓋以下內容:

  • 在Solidity中對比字符串

  • 將整數轉換成固定位數的小數

  • 創建並初始化一個通證接口,比如LINK

  • 在用戶/智能合約之間轉移通證

  • 批准通證轉移

  • SafeMath

  • 智能合約ABI接口

  • 用require()執行交易狀態

  • 以太坊msg.Value及其與通證價值交易的區別

  • 在int和uint之間進行轉換

  • 應付(payable)的地址

GitHubRemix上查看相關代碼。在我們正式開始前,先來簡單介紹一下什麼是期權合約。期權合約讓你有權選擇在某個期限前以約定的價格執行交易。具體而言,如果期權合約內容是買入股票或通證等資產,則被稱為看漲期權。另外,本文的示例代碼可以稍作修成看跌期權。看跌期權與看漲期權正好相反,其內容不是買入資產而是賣出資產。以下是期權相關的一些專有名詞:

  • 行權價格:約定的資產買進/賣出價格

  • 期權費用:購買合約時支付給賣家的費用

  • 到期日:合約終止的時間

  • 行權:買家行使其權利以行權價格買賣資產的行為

//溢出安全操作符

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餵價

ethFeed = AggregatorV3Interface(0x9326BFA02ADD2366b30bacB125260Af641031331);

//LINK/美元的Kovan餵價

linkFeed = AggregatorV3Interface(0x396c5E36DD0a0F5a5D33dae44368D4193f69a1F0);

//Kovan上的LINK通證地址

LINK = LinkTokenInterface(0xa36085F69e2889c224210F603D836748e7dC0088);

contractAddr = payable(address(this));

}

OpenZeppelin的SafeMathOpenZeppelin的SafeMathChainlink餵價

Chainlink餵價

Chainlink餵價

//返回最新的LINK價格

function getLinkPrice() public view returns (uint) {

(

uint80 roundID,

int price,

uint startedAt,

uint timeStamp,

uint80 answeredInRound

) = linkFeed.latestRoundData();

//如果這輪還沒有結束,則timestamp是0

require(timeStamp > 0, "Round not complete");

//價格永遠不會是負數,因此可以將int轉換成uint

一級標題

return uint(price);

}

一級標題

寫一個看漲期權合約

//允許用戶寫保持看漲期權

//接收的通證類型,行權價格(通證以美元計價,小數點後保留18位),期權費用(與通證小數點位數一樣),到期日(unix),合約中的通證數量

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

圖片描述

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

}

}

圖片描述


一級標題

一級標題

合約ABI接口

一級標題LinkToken圖片描述

圖片描述

一級標題

購買看漲期權

//購買看漲期權,需要通證,期權ID和付款

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餵價換算成以太幣

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

圖片描述

require(LINK.transfer(msg.sender, linkOpts[ID].amount), "Error: buyer was not paid");

linkOpts[ID].exercised = true;

}

}

圖片描述

示例:交易未滿足一個或以上條件時Remix輸出的結果。

一級標題

一級標題

取消合約/刪除資金

//允許賣家取消合約或從沒有成功達成交易的期權中退回資金。

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

//將取消標誌修改為true,避免多次贖回

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。另外,我們還要確認期權還未被取消,然後再退款。如果是期權到期後再贖回資金,那情況就會稍有不同。這種情況下,期權可能已經賣出去但沒有行使,資金仍應被退還給賣家。我們要確認合約已經到期並且還未被行使。然後也要將期權的取消標誌設置為true,如果條件滿足則進行退款。

希望本文能幫助各位立刻在主網上開發Chainlink用例,並讓各位了解了Solidity獨特的功能。如果你想了解更多的Chainlink功能,請查看Chainlink VRF(可驗證隨機函數),或查看Chainlink公允排序服務,了解Chainlink如何解決礦工搶跑問題。

開發者文檔開發者文檔聯繫我們。Discord聯繫我們。點擊此處聯繫我們。

Chainlink
DeFi
开发者
期权
歡迎加入Odaily官方社群