售前客服二维码
文章均源于网络收集编辑侵删
提示:仅接受技术开发咨询!
初始设置完成并接入喂价后,我们接下来就可以调用函数了,先来写一个期权合约。卖家调用writeOption函数,并填入期权具体的参数,小数点后保留18位。这里必须要明确小数点位数,以确保合约中使用的所有参数都格式统一。比如,整数777没有小数点,但是如果我们规定的逻辑是保留两位小数,则表示成7.77。我们这里的规则是小数点后保留18位,因为以太币和LINK都是18位小数。如果小数点后不到18位,则可以添加0变成18位。接下来,我们就可以第一次使用之前计算出的以太币和LINK字符串哈希值。为了明确卖家的期权合约针对的是什么通证,我们需要比较字符串。然而Solidity不支持在字符串之间进行==操作,因为其长度是动态的。我们不需要写一个函数一个个字节地比较字符串,而只需用keccak256哈希函数计算每个字符串的32位哈希值,并直接对比。只要哈希值一样,字符串就一样。现在我们知道卖家用的是哪种通证,就可以有的放矢了。如果是以太币,我们就可以用msg.value确认转账到期权合约的以太币数量是否正确。我们可以用require()函数严格执行。如果require的第一个字段为false,则交易会被拒绝,无法进行下去。这样一来,我们可以确保所有期权合约的转账都完全符合之前约定的金额(tknAmnt)。检查通过后,我们就可以创建期权合约,提供所有必须的字段生成结构。基于当前以太币价格,使用SafeMath函数而非内置操作符计算当前行使期权的费用(LatestCost)。使用Chainlink的updatePrices() helper函数获取当前价格,这个函数会更新全局以太币和LINK价格。注意ethPrice要乘以10的10次方。这样做是因为Chainlink喂价返回的是8位小数的美元价格,但正如上文所述,我们现在的标准是18位小数。所以添加10个零可以将其调整成18位小数,符合以太币和LINK通证的格式。最后,我们将期权的结构压入ethOpts的数组中,这样期权合约就写完了,而且里面有足够的资金。
针对一枚LINK通证写一个LINK期权合约,设定Unix到期时间,行权价格为10美元,期权费用为0.1个LINK。
如果是LINK期权合约,那么就需要做一些修改了。Msg.value只提供交易中以太币的金额。因此如果要确保LINK的金额充足,我们需要直接接入LINK通证合约。我们之前已经导入并初始化了LINK通证接口,因此可以访问所有LINK通证函数,其中一个是transferFrom(),这个函数可以将LINK从一个地址转移到另一个地址。当然,我们不能让任何合约都可以随便转移你的LINK资产,所以必须首先调用LINK的approve()函数,并具体说明允许转移的LINK数量以及转移到的合约地址。
合约ABI接口
当你在Etherscan上查看合约时,会出现两个tab,即:Read Contract和Write Contract。你可以用这两个tab与合约进行交互。比如:LINK通证主网合约。Etherscan知道这些函数是什么以及如何通过合约的ABI调用函数。使用JSON格式调用ABI,规定函数调用参数。在主网上可以直接调用,但是在Kovan上的LINK合约需要导入这个模块。各位可以在LinkToken的Github上查看ABI。所幸,在生产系统中,这些都可以用web3js的界面来处理,用户可以用一个简单的MetaMask请求来进行批准。但在我们这个开发实例中,暂时需要手动操作。
购买看涨期权
//购买看涨期权,需要通证,期权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;
}
}
现在期权合约创建完成且资金充足。接下来就等人来买了!买家只需表明购买以太币或LINK期权的意愿以及期权ID即可。由于期权数组被定义成公开的,因此可以直接查看,无需支付gas费,买家可以查看所有期权合约及其ID字段。选择完期权合约后,我们再次调用require()函数验证期权费用的支付金额是否正确。这次,我们不仅需要确认msg.value(仅针对以太币),还需要将期权费用转给卖家。Solidity中的所有以太币地址都有一个address.transfer()函数,我们调用这个函数将期权费用从合约转账给卖家。然后设置期权合约的买家地址字段,就完成购买了!如果是LINK的话,操作就稍微简单一些。可以用transferFrom函数直接将买家的期权费转账给卖家(注:需要先批准)。如果是以太币的话,期权费则需要先经过合约再到卖家地址。
行使期权
//行使看涨期权,需要通证,期权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.p(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.p(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;
}
}
对于期权所有者来说,以太币或LINK的价格如果超过行权价格,就能获利。这样一来,他们便愿意行使期权,以行权价购买通证。这次我们必须先确认几个条件,即:合约由消息发送者所有;合约还未行权;以及现在期权还没到期。如果以上任何一个条件不满足,则撤回交易。
如果条件都满足,则向卖家支付行权费,并向买家支付合约数量的通证。行权时,买家需以行权价购买每一个通证。然而,行权价是以美元计价,而合约数量是以以太币或LINK计价。因此我们需要接入Chainlink喂价计算与行权费等值的以太币或LINK数量。换算成等值的以太币或LINK后,我们就可以开始转账了。转账时需使用之前提过的方法,即以太币会调用msg.value/address.transfer函数,LINK则调用transferFrom()函数。
以上就是成功行使期权的完整交易过程。LINK价格是11.56美元,合约行权价格是10美元,数量1个LINK。也就是说,买家只需要花10美元而不是11.56美元购便可购买一个LINK。10/11.56 = 0.86,即买家只需要花0.86个LINK就可以获得1个LINK。算上0.1LINK的期权费用,总共获利0.04LINK。
取消合约/删除资金
//允许卖家取消合约或从没有成功达成交易的期权中退回资金。
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如何解决矿工抢跑问题。
文章均源于网络收集编辑侵删
提示:仅接受技术开发咨询!