Solidity 學(xué)習(xí)筆記(三)

繼續(xù)前面的學(xué)習(xí)融虽。感興趣的同學(xué)蕉世,可以參考HiBlock社區(qū)有個(gè)Solidity 教程進(jìn)行學(xué)習(xí)歉摧。

第五章 ERC721 標(biāo)準(zhǔn)和加密收藏品

一個(gè) 代幣 在以太坊基本上就是一個(gè)遵循一些共同規(guī)則的智能合約——即它實(shí)現(xiàn)了所有其他代幣合約共享的一組標(biāo)準(zhǔn)函數(shù)阶女,例如 transfer(address _to, uint256 _value) 和 balanceOf(address _owner).

由于所有 ERC20 代幣共享具有相同名稱的同一組函數(shù)堕义,它們都可以以相同的方式進(jìn)行交互它浅。

這意味著如果你構(gòu)建的應(yīng)用程序能夠與一個(gè) ERC20 代幣進(jìn)行交互羊娃,那么它就也能夠與任何 ERC20 代幣進(jìn)行交互翼馆。 這樣一來(lái)东且,將來(lái)你就可以輕松地將更多的代幣添加到你的應(yīng)用中启具,而無(wú)需進(jìn)行自定義編碼。 你可以簡(jiǎn)單地插入新的代幣合約地址珊泳,然后嘩啦鲁冯,你的應(yīng)用程序有另一個(gè)它可以使用的代幣了。

ERC721 代幣是不能互換的色查,因?yàn)槊總€(gè)代幣都被認(rèn)為是唯一且不可分割的薯演。 你只能以整個(gè)單位交易它們,并且每個(gè)單位都有唯一的 ID秧了。

使用像 ERC721 這樣的標(biāo)準(zhǔn)的優(yōu)勢(shì)就是跨扮,我們不必在我們的合約中實(shí)現(xiàn)拍賣或托管邏輯,這決定了玩家能夠如何交易/出售我們的僵尸验毡。 如果我們符合規(guī)范衡创,其他人可以為加密可交易的 ERC721 資產(chǎn)搭建一個(gè)交易所平臺(tái),我們的 ERC721 僵尸將可以在該平臺(tái)上使用晶通。 所以使用代幣標(biāo)準(zhǔn)相較于使用你自己的交易邏輯有明顯的好處璃氢。

ERC721 標(biāo)準(zhǔn):

contract ERC721 {
  event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);
  event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId);

  function balanceOf(address _owner) public view returns (uint256 _balance);
  function ownerOf(uint256 _tokenId) public view returns (address _owner);
  function transfer(address _to, uint256 _tokenId) public;
  function approve(address _to, uint256 _tokenId) public;
  function takeOwnership(uint256 _tokenId) public;
}
  1. balanceOf: 這個(gè)函數(shù)只需要一個(gè)傳入 address 參數(shù),然后返回這個(gè) address 擁有多少代幣狮辽。
  2. ownerOf: 這個(gè)函數(shù)需要傳入一個(gè)代幣 ID 作為參數(shù) (我們的情況就是一個(gè)僵尸 ID)一也,然后返回該代幣擁有者的 address。

ERC721 規(guī)范有兩種不同的方法來(lái)轉(zhuǎn)移代幣:
(1) 第一種方法是代幣的擁有者調(diào)用transfer 方法喉脖,傳入他想轉(zhuǎn)移到的 address 和他想轉(zhuǎn)移的代幣的 _tokenId椰苟。
(2) 第二種方法是代幣擁有者首先調(diào)用 approve,然后傳入與以上相同的參數(shù)树叽。接著尊剔,該合約會(huì)存儲(chǔ)誰(shuí)被允許提取代幣,通常存儲(chǔ)到一個(gè) mapping (uint256 => address) 里。然后须误,當(dāng)有人調(diào)用 takeOwnership 時(shí)挨稿,合約會(huì)檢查 msg.sender 是否得到擁有者的批準(zhǔn)來(lái)提取代幣,如果是京痢,則將代幣轉(zhuǎn)移給他奶甘。

繼承自多個(gè)合約:

contract SatoshiNakamoto is NickSzabo, HalFinney {
  // 嘖嘖嘖,宇宙的奧秘泄露了
}

合約安全增強(qiáng): 溢出和下溢

為了防止這些情況祭椰,OpenZeppelin 建立了一個(gè)叫做 SafeMath 的 庫(kù)(library)臭家,默認(rèn)情況下可以防止這些問題。

一個(gè)庫(kù) 是 Solidity 中一種特殊的合約方淤。其中一個(gè)有用的功能是給原始數(shù)據(jù)類型增加一些方法钉赁。

比如,使用 SafeMath 庫(kù)的時(shí)候携茂,我們將使用 using SafeMath for uint256 這樣的語(yǔ)法你踩。 SafeMath 庫(kù)有四個(gè)方法 — add, sub讳苦, mul带膜, 以及 div。現(xiàn)在我們可以這樣來(lái)讓 uint256 調(diào)用這些方法:

using SafeMath for uint256;

uint256 a = 5;
uint256 b = a.add(3); // 5 + 3 = 8
uint256 c = a.mul(2); // 5 * 2 = 10

SafeMath 的部分代碼如下:

library SafeMath {

  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }

  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}
using SafeMath for uint;
// 這下我們可以為任何 uint 調(diào)用這些方法了
uint test = 2;
test = test.mul(3); // test 等于 6 了
test = test.add(5); // test 等于 11 了

注意 mul 和 add 其實(shí)都需要兩個(gè)參數(shù)鸳谜。 在我們聲明了 using SafeMath for uint 后膝藕,我們用來(lái)調(diào)用這些方法的 uint 就自動(dòng)被作為第一個(gè)參數(shù)傳遞進(jìn)去了(在此例中就是 test)

assert 和 require 相似,若結(jié)果為否它就會(huì)拋出錯(cuò)誤咐扭。 assert 和 require 區(qū)別在于芭挽,require 若失敗則會(huì)返還給用戶剩下的 gas, assert 則不會(huì)蝗肪。所以大部分情況下袜爪,你寫代碼的時(shí)候會(huì)比較喜歡 require,assert 只在代碼可能出現(xiàn)嚴(yán)重錯(cuò)誤的時(shí)候使用穗慕,比如 uint 溢出。

第六章 應(yīng)用前端和 Web3.js

當(dāng)你想要調(diào)用一份智能合約的一個(gè)方法妻导,你需要從其中一個(gè)節(jié)點(diǎn)中查找并告訴它:

  1. 智能合約的地址
  2. 你想調(diào)用的方法逛绵,以及
  3. 你想傳入那個(gè)方法的參數(shù)

以太坊節(jié)點(diǎn)只能識(shí)別一種叫做 JSON-RPC 的語(yǔ)言。這種語(yǔ)言直接讀起來(lái)并不好懂倔韭。幸運(yùn)的是 Web3.js 把這些令人討厭的查詢語(yǔ)句都隱藏起來(lái)了术浪, 所以你只需要與方便易懂的 JavaScript 界面進(jìn)行交互即可。

CryptoZombies.methods.createRandomZombie("Vitalik Nakamoto ??")
  .send({ from: "0xb60e8dd61c5d32be8058bb8eb970870f07233155", gas: "3000000" })

Web3 提供者

以太坊是由共享同一份數(shù)據(jù)的相同拷貝的 節(jié)點(diǎn) 構(gòu)成的寿酌。 在 Web3.js 里設(shè)置 Web3 的 Provider(提供者) 告訴我們的代碼應(yīng)該和 哪個(gè)節(jié)點(diǎn) 交互來(lái)處理我們的讀寫胰苏。這就好像在傳統(tǒng)的 Web 應(yīng)用程序中為你的 API 調(diào)用設(shè)置遠(yuǎn)程 Web 服務(wù)器的網(wǎng)址。

Infura 是一個(gè)服務(wù)醇疼,它維護(hù)了很多以太坊節(jié)點(diǎn)并提供了一個(gè)緩存層來(lái)實(shí)現(xiàn)高速讀取硕并。你可以用他們的 API 來(lái)免費(fèi)訪問這個(gè)服務(wù)法焰。 用 Infura 作為節(jié)點(diǎn)提供者,你可以不用自己運(yùn)營(yíng)節(jié)點(diǎn)就能很可靠地向以太坊發(fā)送倔毙、接收信息埃仪。

var web3 = new Web3(new Web3.providers.WebsocketProvider("wss://mainnet.infura.io/ws"));

這里是一些 Metamask 提供的示例代碼,用來(lái)檢查用戶是否安裝了MetaMask陕赃,如果沒有安裝就告訴用戶需要安裝MetaMask來(lái)使用我們的應(yīng)用卵蛉。

window.addEventListener('load', function() {

  // 檢查web3是否已經(jīng)注入到(Mist/MetaMask)
  if (typeof web3 !== 'undefined') {
    // 使用 Mist/MetaMask 的提供者
    web3js = new Web3(web3.currentProvider);
  } else {
    // 處理用戶沒安裝的情況, 比如顯示一個(gè)消息
    // 告訴他們要安裝 MetaMask 來(lái)使用我們的應(yīng)用
  }

  // 現(xiàn)在你可以啟動(dòng)你的應(yīng)用并自由訪問 Web3.js:
  startApp()

})

合約 ABI

ABI 意為應(yīng)用二進(jìn)制接口(Application Binary Interface)么库。 基本上傻丝,它是以 JSON 格式表示合約的方法,告訴 Web3.js 如何以合同理解的方式格式化函數(shù)調(diào)用诉儒。

實(shí)例化 Web3.js:

// 實(shí)例化 myContract
var myContract = new web3js.eth.Contract(myABI, myContractAddress);

調(diào)用和合約函數(shù)

Web3.js 有兩個(gè)方法來(lái)調(diào)用我們合約的函數(shù): call and send.

call 用來(lái)調(diào)用 view 和 pure 函數(shù)葡缰。它只運(yùn)行在本地節(jié)點(diǎn),不會(huì)在區(qū)塊鏈上創(chuàng)建事務(wù)允睹。

myContract.methods.myMethod(123).call()

send 將創(chuàng)建一個(gè)事務(wù)并改變區(qū)塊鏈上的數(shù)據(jù)运准。你需要用 send 來(lái)調(diào)用任何非 view 或者 pure 的函數(shù)。

myContract.methods.myMethod(123).send()

相對(duì) call 函數(shù)缭受,send 函數(shù)有如下主要區(qū)別:

  1. send 一個(gè)事務(wù)需要一個(gè) from 地址來(lái)表明誰(shuí)在調(diào)用這個(gè)函數(shù)(也就是你 Solidity 代碼里的 msg.sender )胁澳。 我們需要這是我們 DApp 的用戶,這樣一來(lái) MetaMask 才會(huì)彈出提示讓他們對(duì)事務(wù)簽名米者。
  2. send 一個(gè)事務(wù)將花費(fèi) gas
  3. 在用戶 send 一個(gè)事務(wù)到該事務(wù)對(duì)區(qū)塊鏈產(chǎn)生實(shí)際影響之間有一個(gè)不可忽略的延遲韭畸。這是因?yàn)槲覀儽仨毜却聞?wù)被包含進(jìn)一個(gè)區(qū)塊里,以太坊上一個(gè)區(qū)塊的時(shí)間平均下來(lái)是15秒左右蔓搞。如果當(dāng)前在以太坊上有大量掛起事務(wù)或者用戶發(fā)送了過低的 gas 價(jià)格胰丁,我們的事務(wù)可能需要等待數(shù)個(gè)區(qū)塊才能被包含進(jìn)去,往往可能花費(fèi)數(shù)分鐘喂分。

獲得 MetaMask中的用戶賬戶

MetaMask 允許用戶在擴(kuò)展中管理多個(gè)賬戶锦庸。

我們可以通過這樣來(lái)獲取 web3 變量中激活的當(dāng)前賬戶:

var userAccount = web3.eth.accounts[0]

檢查賬號(hào)是否切換:

var accountInterval = setInterval(function() {
  // 檢查賬戶是否切換
  if (web3.eth.accounts[0] !== userAccount) {
    userAccount = web3.eth.accounts[0];
    // 調(diào)用一些方法來(lái)更新界面
    updateInterface();
  }
}, 100);

在實(shí)際應(yīng)用中,你肯定想要在應(yīng)用中使用諸如 React 或 Vue.js 這樣的前端框架來(lái)讓你的前端開發(fā)變得輕松一些蒲祈。

function createRandomZombie(name) {
  // 這將需要一段時(shí)間甘萧,所以在界面中告訴用戶這一點(diǎn)
  // 事務(wù)被發(fā)送出去了
  $("#txStatus").text("正在區(qū)塊鏈上創(chuàng)建僵尸,這將需要一會(huì)兒...");
  // 把事務(wù)發(fā)送到我們的合約:
  return cryptoZombies.methods.createRandomZombie(name)
  .send({ from: userAccount })
  .on("receipt", function(receipt) {
    $("#txStatus").text("成功生成了 " + name + "!");
    // 事務(wù)被區(qū)塊鏈接受了梆掸,重新渲染界面
    getZombiesByOwner(userAccount).then(displayZombies);
  })
  .on("error", function(error) {
    // 告訴用戶合約失敗了
    $("#txStatus").text(error);
  });
}

receipt 將在合約被包含進(jìn)以太坊區(qū)塊上以后被觸發(fā)扬卷,這意味著僵尸被創(chuàng)建并保存進(jìn)我們的合約了。
error 將在事務(wù)未被成功包含進(jìn)區(qū)塊后觸發(fā)酸钦,比如用戶未支付足夠的 gas怪得。我們需要在界面中通知用戶事務(wù)失敗以便他們可以再次嘗試。

調(diào)用 Payable 函數(shù)

一個(gè) wei 是以太的最小單位 — 1 ether 等于 10^18 wei

// 把 1 ETH 轉(zhuǎn)換成 Wei
web3js.utils.toWei("1", "ether");
cryptoZombies.methods.levelUp(zombieId)
.send({ from: userAccount, value: web3js.utils.toWei("0.001","ether") })

訂閱事件

solidity中的事件:

event NewZombie(uint zombieId, string name, uint dna);

js中的調(diào)用

cryptoZombies.events.NewZombie()
.on("data", function(event) {
  let zombie = event.returnValues;
  console.log("一個(gè)新僵尸誕生了!", zombie.zombieId, zombie.name, zombie.dna);
}).on('error', console.error);

注意這段代碼將在 任何 僵尸生成的時(shí)候激發(fā)一個(gè)警告信息——而不僅僅是當(dāng)前用用戶的僵尸徒恋。如果我們只想對(duì)當(dāng)前用戶發(fā)出提醒呢蚕断?

為了篩選僅和當(dāng)前用戶相關(guān)的事件,我們的 Solidity 合約將必須使用 indexed 關(guān)鍵字因谎,就像我們?cè)?ERC721 實(shí)現(xiàn)中的Transfer 事件中那樣:

event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);
cryptoZombies.events.Transfer({ filter: { _to: userAccount } })
.on("data", function(event) {
  let data = event.returnValues;
  // 當(dāng)前用戶更新了一個(gè)僵尸基括!更新界面來(lái)顯示
}).on('error', console.error);

因?yàn)槟憧梢杂眠@個(gè)方法來(lái)查詢從最開始起的事件日志,這就有了一個(gè)非常有趣的用例: 用事件來(lái)作為一種更便宜的存儲(chǔ)财岔。若你還能記得风皿,在區(qū)塊鏈上保存數(shù)據(jù)是 Solidity 中最貴的操作之一。但是用事件就便宜太多太多了匠璧。

這里的短板是桐款,事件不能從智能合約本身讀取。但是夷恍,如果你有一些數(shù)據(jù)需要永久性地記錄在區(qū)塊鏈中以便可以在應(yīng)用的前端中讀取魔眨,這將是一個(gè)很好的用例。這些數(shù)據(jù)不會(huì)影響智能合約向前的狀態(tài)酿雪。

總結(jié)

這里介紹了代幣的代碼以及相關(guān)的實(shí)現(xiàn)遏暴。以及web3的使用。都是一些很實(shí)用的內(nèi)容指黎。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末朋凉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子醋安,更是在濱河造成了極大的恐慌杂彭,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吓揪,死亡現(xiàn)場(chǎng)離奇詭異亲怠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)柠辞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門团秽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人叭首,你說(shuō)我怎么就攤上這事习勤。” “怎么了放棒?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵姻报,是天一觀的道長(zhǎng)己英。 經(jīng)常有香客問我间螟,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任厢破,我火速辦了婚禮荣瑟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘摩泪。我一直安慰自己笆焰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布见坑。 她就那樣靜靜地躺著嚷掠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪荞驴。 梳的紋絲不亂的頭發(fā)上不皆,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音熊楼,去河邊找鬼霹娄。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鲫骗,可吹牛的內(nèi)容都是我干的犬耻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼执泰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼枕磁!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起坦胶,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤透典,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后顿苇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體峭咒,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年纪岁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凑队。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡幔翰,死狀恐怖漩氨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情遗增,我是刑警寧澤叫惊,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站做修,受9級(jí)特大地震影響霍狰,放射性物質(zhì)發(fā)生泄漏抡草。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一蔗坯、第九天 我趴在偏房一處隱蔽的房頂上張望康震。 院中可真熱鬧,春花似錦宾濒、人聲如沸腿短。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)橘忱。三九已至,卻和暖如春卸奉,著一層夾襖步出監(jiān)牢的瞬間鹦付,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工择卦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留敲长,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓秉继,卻偏偏與公主長(zhǎng)得像祈噪,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子尚辑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容