目錄
最重要的兩個全局變量(msg.sender 和 msg.value)
1 Solidity與智能合約
起源於以太坊(Ethereum),設計的目的是能在以太坊虛擬機(EVM)上運行。Solidity 是一門面向合約的、爲實現智能合約而創建的高級編程語言。所以先從智能合約開始。
參考文檔
Solidity文檔:https://www.tryblockchain.org/
solidity官方文檔: https://solidity-cn.readthedocs.io/zh/develop/
solidity英文文檔:https://docs.soliditylang.org/en/latest/control-structures.html#external-function-calls
以太坊發展的⽂章:https://www.jinse.com/blockchain/471570.html
官⽅⽹址: https://www.ethereum.org
交易瀏覽器: https://etherscan.io
以太坊⻩⽪書: https://github.com/ethereum/yellowpaper
2 智能合約概述
智能合約的定義:
“智能合約”(smart contract)這個術語至少可以追溯到1995年,是由多產的跨領域法律學者尼克·薩博(Nick Szabo)提出來的。他在發表在自己的網站的幾篇文章中提到了智能合約的理念。他的定義如下: “一個智能合約是一套以數字形式定義的承諾(promises),包括合約參與方可以在上面執行這些承諾的協議。” |
智能合約的本質:數字化合同。
智能合約的特點:代碼代替人仲裁和執行合同,同時能夠觸發支付。
智能合約於普通合約對比圖示:
普通合約圖示如下:
Bob和Alice簽署合同,由法院進行背書,公示和執行。
智能合約圖示如下:
Bob和Alice共同認可的合約,以代碼的形式上傳到區塊鏈上。雙方繳納保證金到合約中,當滿足一定條件,由外部輸入條件,合約根據邏輯觸發條件,將保證金轉賬給一方。
3 以太坊簡介
以太坊是運⾏在⼀個計算機⽹絡中的軟件,它確保數據以及稱爲智能合約的⼩程序可以在沒有中⼼協調者的情況下被所 有⽹絡中的計算機複製和處理。以太坊的願景是創建⼀個⽆法停⽌,抗屏蔽(審查)和⾃我維持的去中⼼化世界計算機。 它延伸了⽐特幣的區塊鏈概念:在全球範圍的多個計算機上驗證,存儲和複製交易數據(因此術語叫“分佈式賬本”)。以太坊(Ethereum)在這個概念上更進⼀步,使之(交易數據)在全球範圍的多個計算機上運⾏代碼成爲現實。 ⽐特幣⽤來分佈式儲存數據的,以太坊⽤來分佈式儲存數據並且計算。這些⼩型的電腦運⾏程序叫做智能合約,合約由參與者在他們⾃⼰的機器上通過⼀種稱爲 “以太坊虛擬機(EVM)”的操作系統運⾏。 |
智能合約與以太坊的關係——智能合約是⼀個部署在以太坊區塊鏈上的程序。
全世界的計算機通過網絡互連,每個節點都運行一個以太坊客戶端,即組成以太坊網絡。
小結:
|
4 以太坊交互工具
以太坊愛好者網站
https://ethfans.org/wikis/Home
- 開發者:web3.js 以太坊項目開發的js庫
- 一般用戶(消費者):
metamask (瀏覽器插件,firefox, chrome,適合開發測試,也適合小白用戶)
Ethereum Wallet
https://github.com/ethereum/mist/releases
mist瀏覽器 (很多bug,早期版本)
以太坊網絡
(國服,私服,美服,韓服),互不相通,但是功能⼀致,每個⼈都可以同時註冊
1.主網絡
花費真實的以太幣
2.測試網絡
使用geth,或者Ganache工具搭建本地測試網絡。
Morden(已停服)
Ropsten
以太坊官⽅提供的測試⽹絡,是爲了解決Morden難度炸彈問題⽽重新啓動的⼀條區塊鏈,⽬前仍在運⾏, 共識機制爲PoW。
獲取Ropsten測試幣
輸入公鑰地址即可。
Kovan
Kovan⽬前仍在運⾏,但僅有Parity錢包客戶端可以使⽤這個測試⽹絡。
爲了解決測試⽹絡中PoW共識機制的問題,以太坊錢包Parity的開發團隊發起了⼀個新的測試⽹絡Kovan。Kovan使⽤ 了權威證明(Proof-of-Authority)的共識機制,簡稱PoA。
PoW是⽤⼯作量來獲得⽣成區塊的權利,必須完成⼀定次數的計算後,發現⼀個滿⾜條件的謎題答案,才能夠⽣成有效 的區塊。 PoA是由若⼲個權威節點來⽣成區塊,其他節點⽆權⽣成,這樣也就不再需要挖礦。由於測試⽹絡上的以太幣⽆價值, 權威節點僅僅是⽤來防⽌區塊被隨意⽣成,造成測試⽹絡擁堵,完全是義務勞動,不存在作惡的動機,因此這種機制在 測試⽹絡上是可⾏的。 |
Kovan與主⽹絡使⽤不同的共識機制,影響的僅僅是誰有權來⽣成區塊,以及驗證區塊是否有效的⽅式,權威節點可以 根據開發⼈員的申請⽣成以太幣,並不影響開發者測試智能合約和其他功能。
Rinkeby
Rinkeby也是以太坊官⽅提供的測試⽹絡,使⽤PoA共識機制。(獲取測試幣⽐較困難)
5 開發環境搭建
remix在線編譯器
訪問鏈接,方便調試,但是依賴網絡,需翻牆。
舊版界面
左側是文件夾,右側 compile 編譯合約, run 使用deploy部署合約,At address(輸入合約地址,可以加載合約)
搭建本地網絡
方便調試,相對穩定。
npm install remix-ide -g
啓動
remix-ide
訪問 http://localhost:8080,即可打開本地編譯器。
編譯合約
remix編輯器中⾃動集成了solidity的編譯器,所以可以⾃動編譯我們的合約代碼
編譯原理
使⽤remix,由⾼級語⾔變成機器語⾔
- solidity ---> bytecode(機器語⾔,區塊鏈系統讀取)
格式片段
6080604052610410806100136000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063368b87721461005c578063ce6d41de146100c5578063e21f37ce14610155575b600080fd5b34801561006857600080fd5b506100c3600480360381019080803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091929192905050506101e5565b005b3480156100……
- solidity ---> ABI (application binary interface)(⽅便程序員調⽤)
json格式的描述⽂件
[
{
"constant": false,
"inputs": [
{
"name": "newMessage",
"type": "string"
}
……
]
圖示
6 常見概念
gas(汽油)(油耗) | 由於以太坊是公鏈,所有⼈都可以⾃由的參與,爲了防⽌垃圾數據充斥⽹絡,所以以太坊上規定每⼀個操作都是要 有成本的,這個成本由 gas 來體現,你要轉賬,部署智能合約,調⽤智能合約⽅法,都要消耗⼀定數量的gas。 |
gasprice(汽油價格)(油價) | 雖然操作消耗gas,但是最終真正花的還是eth,所以就有⼀個轉換率的問題,gasprice就是起到⼀個匯率的作⽤, 它代表的是⼀個gas值多少eth,gas*gasprice就是最終的⼿續費,也就是從你賬戶扣除的eth。 這種設計就可以保證⽤戶在以太坊上的操作的⼿續費不會隨着eth的價格⽽發⽣劇烈的變動(例如:如果eth漲,那 麼可以調低gasprice來降低⼿續費), |
gaslimit(汽油上限) (油箱) | 以太坊規定,每筆交易的gas最少21000,礦⼯可以調整這個值,所以最終的花費的gas是不確定的,所以以太坊就 設置了gaslimit,這個代表的是最多給曠⼯這麼多gas(==防⽌⾃⼰寫的合約⾥⾯有死循環==),如果最終使⽤的 gas少於這個gaslimit,剩餘的還會返給你的,但是如果你的gaslimit不⾜以⽀付這次交易,那就是不會退回的,並且交易也就失敗了,轉賬的額度也是回不來了,所以你轉賬設置的limit⼀定要⼤於21000。 |
每個操作中gas的成本
摘⾃以太坊⻩⽪書
https://github.com/wanshan1024/ethereum_yellowpaper/blob/master/ethereum_yellow_paper_cn.pdf
7 Solidity基礎語法
7.1 數據類型分類
數據類型思維導圖
https://naotu.baidu.com/file/8ea7d69f9df11dc89433d37d818214de
- 值類型(值傳遞)
- 引用類型(指針傳遞)
值類型
值類型是指變量在傳遞過程中將數值完整的copy一份,再賦值給新的變量。
這種方式需要重新開闢新的內存空間,兩個變量完全獨立,互不影響,修改一個不會影響另外一個。缺點是效率低。
值類型包含
- 布爾(bool)
- 整形(int)
- 地址(address)
- 定長數組(byte1……byte32)
- 有理數(小數)
- 枚舉(enums)
- 函數(function)
引用類型
solidity沒有指針類型,對於複雜的結構進行高效傳遞方式(相當於指針)是使用關鍵字storage進行修飾。
簡單來說就是,如果在變量之前添加storage就是引用傳遞,不加就是值傳遞。但是隻對複雜類型有效。
複雜類型,佔用空間較大。所以考慮通過引用傳遞。
引用類型包含
- 字符串
- 不定長數組
- 數組
- 結構體
- mapping
7.2 remix的使用--第一個智能合約
合約包含的基本元素
//指定solidy編譯器版本,版本標識符
pragma solidity ^0.4.25;
//關鍵字 contract 跟java的class一樣 智能合約名稱是helloworld
contract helloworld {
//狀態變量
//string 是數據類型,message是成員變量,在整個智能合約生命週期都可以訪問
//public 是訪問修飾符,是storage類型的變量,成員變量和是全局變量
string public message;
//address 是地址類型,
address public manager;
//構造函數,這裏在合約部署時將合約所有者傳入
constructor () public {
manager = msg.sender;
}
//函數以function開頭
function setMessage (string _message) public {
//局部變量
string memory tmp;
tmp = _message;
message = tmp;
}
//view是修飾符,表示該函數僅讀取成員變量,不做修改
function getMessage() public view returns (string) {
return message;
}
}
選擇auto compile 自動編譯
智能合約的部署與調用,默認使用VM即可。
JavaScript VM web內置的虛擬機,調試方便。我們使用這個。
Injected Web3 鏈接metamask
Web3 Provider 鏈接自定義網絡。
常見錯誤:
- contract 誤寫爲 constant
- 句末忘記添加分號
- 修改代碼後要重新create(舊版),新版本(deploy)重新部署合約。
- 在remix中,⼿動調⽤setMessage⽅法的時候,沒有加雙引號(英⽂的引號,否則報錯)
- 調⽤setMessage之後,沒有檢查是否設置成功,直接調⽤getMessage⽅法
- compile⼀直是紅⾊的,提示:Compiler not found,需要在Compile中選擇版本
7.3 值類型
7.3.1 布爾
bool b1;
bool b2 = false;
bool b3 = true;
7.3.2 整形
- int(有符號整型,有正有負)
- uint(無符號整型,無負數)
- 以8位爲區間,支持int8,int16,int24 至 int256,uint同理。 ==int默認爲int256,uint默認爲uint256
pragma solidity ^0.4.25;
//int
contract test2 {
int public i256 = 256;
int8 public i8 = 1;
function add() constant returns(int) {
return i8 + i256; //257
}
function isEqual(int a, int b) public pure returns(bool) {
return a == b;
}
//返回true
}
7.3.3 函數類型
函數類型也就是我們所說的函數,本身也是一個特殊的變量,它可以當做變量賦值
,當做函數參數傳遞
,當做返回值
。
函數名,函數簽名(返回值,參數類型,修飾符)
函數的幾個關鍵字
修飾符 | 說明 |
public | 共有,任何人(擁有以太坊賬戶的)都可以調用。 |
private | 私有,只有智能合約內部可以調用。 |
external | 僅合約外部可以調用,合約內部可以使用this調用 |
interanl | 僅合約內部和繼承的合約可以調用 |
view/constant | 函數會讀取但是不會修改任何合約的狀態變量 |
pure(純淨的) | 函數不使用任何智能合約的狀態變量 |
payable | 調用函數需要付錢,錢付給了智能合約的賬戶 |
returns | 指定函數返回值 |
pragma solidity ^0.4.25;
//function
contract Test3 {
//狀態變量
//類型不匹配時需要顯示轉換類型
//返回值需要使用returns描述
//public/private 可以修飾狀態變量
//狀態變量默認是私有的
uint256 public ui256 = 100;
int8 private i10 = -10;
//private 修飾的函數爲私有的,只有合約內部可以調用
function add() private view returns(uint256) {
return ui256 + uint256(i10);
}
function isEqueal() public view returns(bool) {
return ui256 == uint256(i10);
}
//Public修飾的函數爲共有的,合約內外都可以調用
function Add() public view returns(uint256){
return add();
}
}
常用關鍵字說明 view,constant,pure
1. 如果⼀個函數⾥⾯,訪問了狀態變量,但是沒有修改,我們使⽤view或者constant修飾。
2. 如果訪問了狀態變量,⽽且修改了,那麼就不能constant和view,否則會報錯,不修飾即可。
3. 如果沒有使⽤過狀態變量,我們要修飾爲pure。
4. 如果你修飾爲constant,但是你在函數中修改了,效果是:不會報錯,正常執⾏,但是值不會改變。
//常用關鍵字說明 view,constant,pure
contract test4 {
int8 public i8 = 100; //成員變量就是狀態變量
int i256 = 256;
//表示不會修改函數內的狀態變量
//爲了明確語義,一般要加上constant(view兩者完全相同)
function add() private constant returns(int) {
return i8 + i256;
}
//public 表示所有的人都可以看到的,而且可以調用
//private表示所有人都可以看到,但是無法調用
function mins() constant returns(uint256) {
return uint256(i256 - i8);
}
function isEqual(int a, int b) public pure returns(bool) {
return a == b;
}
//可以修改i8
function setValue(int8 num) {
i8 = num;
}
//修飾爲constant,在函數中修改了,效果是:不會報錯,正常執⾏,但是值不會改變
function setValue1(int8 num) constant {
i8 = num;
}
}
關鍵字payable
- 任何函數,只要修飾爲payable,那麼就可以在調用這個方法的時候,對value字段賦值,然後將價值value的錢轉給合約。
- 若這個函數沒有指定payable,但是對value賦值了,那麼本次調用會報錯。
//payable
contract test5 {
string public str;
//修飾爲payable的函數纔可以接收轉賬
function test1(string src) public payable {
str = src;
}
//不指定payable無法接收,調用,如果傳入value,會報錯
function test2(string src) public {
str = src;
}
function getbalance() public view returns(uint256) {
//this代表當前合約本身
//balance方法,獲取當前合約的餘額
return this.balance;
}
}
構造函數和匿名函數
構造函數:僅在部署合約時調用一次,完成對合約的初始化。可以在創建合約時轉錢到合約。相當於go裏面的init函數。
- 合約同名函數(已廢棄)
- constructor關鍵字修飾(推薦)
匿名函數
- 用於轉賬
一個合約可以有且只有一個匿名函數,此函數不能有參數,也不能有任何返回值,當我們企圖去執行一個合約上沒有的函數時,那麼合約就會執行這個匿名函數。
當合約在只收到以太幣的時候,也會調用這個匿名函數,而且一般情況下會消耗很少的gas,所以當你接收到以太幣後,想要執行一些操作的話,你儘可以把你想要的操作寫到這個匿名函數裏,因爲這樣做成本非常便宜。
contract test6 {
//構造函數,合約同名函數(已廢棄)
// test6() {
// }
//構造函數,constructor關鍵字修飾
constructor () {
//初始化內容
}
//如果想向合約轉賬,在合約中添加如下函數即可
function() payable {
//函數體什麼都不填
}
//balance方法,獲取當前合約的餘額
function getbalance() public view returns(uint256) {
//this代表當前合約本身餘額
return this.balance;
}
}
fallback 也被稱爲回滾函數,調用payable,花費最少的gas,省錢。
7.3.4 地址(Address)
以太坊地址的⻓度,⼤⼩ 20個字節 ,20 * 8 = 160位 ,所以可以⽤⼀個 uint160 編碼。地址是所有合約的基礎,所有的合約都會繼承地址對象,通過合約的地址串,調⽤合約內的函數。
運算符
描述 | 符號 |
比較運算 | <=,<,==, !=, >=,> |
地址操作
屬性/方法 | 含義 | 備註 |
balance | 獲取餘額 | 屬性,其餘的都是方法。 |
send | 轉賬 | 不建議使用 |
transfer | 轉賬 | 建議使用 |
call | 合約內部調用合約 | |
delegatecall | 調底層代碼,別用 | |
callcode | 調底層代碼,別用 |
注意:call(),delegatecall(),callcode() 都是底層的消息傳遞調用,最好不用,除非萬不得已再用。因爲他們破壞了Solidity的類型安全。
contract test7 {
address public addr1 = 0xca35b7d915458ef540ade6068dfe2f44e8fa733c;
//地址address類型本質上是一個160位的數字
//可以進行加減,需要強制轉換
function add() public view returns(uint160) {
return uint160(addr1) + 10;
}
//1. 匿名函數:沒有函數名,沒有參數,沒有返回值的函數,就是匿名函數
//2. 當調用一個不存在的方法時,合約會默認的去調用匿名函數
//3. 匿名函數一般用來給合約轉賬,因爲費用低
function () public payable {
}
//獲取addr1的餘額
function getBalance() public view returns(uint256) {
return addr1.balance;
}
function getContractBalance() public view returns(uint256) {
//this代表當前合約本身
//balance方法,獲取當前合約的餘額
return address(this).balance;
}
}
調試結果如下:
合約地址(this)
如果只是想返回當前合約賬戶的餘額,可以使⽤ this 指針, this 表示合約⾃身的地址
//this
contract test8 {
//1. 匿名函數:沒有函數名,沒有參數,沒有返回值的函數,就是匿名函數
//2. 當調用一個不存在的方法時,合約會默認的去調用匿名函數
//3. 匿名函數一般用來給合約轉賬,因爲費用低
function () public payable {
}
function getContractBalance() public view returns(uint256) {
//this代表當前合約本身
//balance方法,獲取當前合約的餘額
return this.balance;
// return address(this).balance;
}
}
轉賬(send,transfer)
send和transfer函數提供了由合約向其他地址轉賬的功能。
描述 | 參數 | 返回值 | |
send | 單位 wei | 轉賬金額 | true/false |
transfer | 比send更安全 | 轉賬金額 | 無(出錯拋異常) |
//send和transfer函數提供了由合約向其他地址轉賬的功能
contract test9 {
address public addr0 = 0x00ca35b7d915458ef540ade6068dfe2f44e8fa733c;
address public addr1 = 0x0014723a09acff6d2a60dcdf7aa4aff308fddc160c;
//1. 匿名函數:沒有函數名,沒有參數,沒有返回值的函數,就是匿名函數
//2. 當調用一個不存在的方法時,合約會默認的去調用匿名函數
//3. 匿名函數一般用來給合約轉賬,因爲費用低
function () public payable {
}
function getBalance() public view returns(uint256) {
return addr1.balance;
}
function getContractBalance() public view returns(uint256) {
return address(this).balance;
}
//由合約向addr1 轉賬10以太幣
function transfer() public {
//1. 轉賬的時候單位是wei
//2. 1 ether = 10 ^18 wei (10的18次方)
//3. 向誰轉錢,就用誰調用tranfer函數
//4. 花費的是合約的錢
//5. 如果金額不足,transfer函數會拋出異常
addr1.transfer(10 * 10 **18);
}
//send與tranfer使用方式一致,但是如果轉賬金額不足不會拋出異常,而是會返回false
function sendTest() public {
addr1.send(10 * 10 **18);
}
}
7.3.5 枚舉類型(enums)
- 枚舉類型是在Solidity中的一種用戶自定義類型。
- 枚舉可以顯示的轉換與整數進行轉換,但不能進行隱式轉換。顯示的轉換會在運行時檢查數值範圍,如果不匹配,將會引起異常。
- 枚舉類型應至少有一名成員,枚舉元素默認爲uint8,當元素數量足夠多時,會自動變爲uint16,第一個元素默認爲0,使用超出範圍的數值時會報錯。
//enum
contract test10 {
enum WeekDays {
//0 -- 6
Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
}
WeekDays currentDay;
WeekDays defaultday = WeekDays.Sunday;
//設置,如果超過6,比如7會拋異常
function setDay(WeekDays _day) public {
currentDay = _day;
}
function getDay() public view returns(uint256) {
return uint256(currentDay);
}
//調試結:6
function getDefaultDay() public view returns(uint256) {
return uint256(defaultday);
}
}
7.3.6 定長字節數組
solidity內置了一些數組的數據類型:(和go語言做一下對比, var b8 [8]byte),完全只讀
bytes1
, ... ,bytes32
,允許值以步長1遞增。- ==byte默認表示bytes1,byte是類型,bytes是類型,bytes1是內置數組==
- bytes1只能存儲1個字節,即8位的內容,bytes2最多隻能存儲2個字節,即16位的內容。以此類推...
- 長度可以讀取 length(返回bytes5類型的長度,而不是賦值的長度)
- 長度不可以修改
- 可以通過下標訪問
- 內容不可修改
- 內置成員:
length
,返回數組長度 - 存儲方式:16進制ascii碼
支持運算:
描述 | 運算符 |
比較運算 | <=,<,==,!=,>=,> |
位運算符 | &,\,^(異或),~非 |
下標訪問 | [0,n),n表示長度 |
//定長的字節數組
contract test11 {
bytes1 b1 ="h";
bytes20 b10 = "helloworld";
//bytes10 public b10 = 0x68656c6c6f776f726c64; //length == 20
function getLen() public view returns(uint256) {
return b10.length;
}
function setValue() public pure{
//1. 固定長度數組可以通過下標訪問
//2. 只能讀取,不能寫
// b1[0] = v;
}
//3. 存儲的時候是ascii值存儲
function getValue(uint256 i) public view returns(byte) {
return b10[i];
}
//length == 20,acsii的長度
function getLenth() public view returns(uint256) {
return b10.length;
}
}
在線轉換工具 http://www.ab126.com/goju/1711.html
7.4 引用類型
引用類型包含:字符串,不定長數組,數組,結構體,映射 (mapping)
7.4.1 不定長數組
bytes 相當於golang []byte
- 可以修改
- 支持
下標索引
- 引用類型(表明可以使用
storage
來修飾,進行引用傳遞,指針的效果) - 支持
length
、push
方法(push會幫助分配空間的) - 以十六進制格式賦值: 'h' -> 0x68 -> 104
- 格外注意:對於bytes,如果不使用下標訪問,那麼可以不用先申請空間, 直接賦值即可,或者直接push。
contract test12 {
bytes public name;
function getLen() public view returns(uint256) {
return name.length;
}
//1. 可以不分空間,直接進行字符串賦值,會自動分配空間
function setValue(bytes input) public {
name = input;
}
//2. 如果未分配過空間,使用下標訪問會訪問越界報錯
//0x68656c6c6f776f726c64 == "hello"
function getByIndex(uint256 i) public view returns(byte) {
return name[i];
}
//3. 可以設置長度,自動分配對應空間,並且初始化爲0
function setLen(uint256 len) public {
name.length = len;
}
//4.可以通過下標進行數據修改
function setValue2(uint256 i) public {
name[i] = "H";
}
//5. 支持push操作,在bytes最後面追加元素
function pushData() public {
name.push('h');
}
}
7.4.2 字符串(string)
- 動態尺⼨的UTF-8編碼字符串,是特殊的可變字節數組
- 引⽤類型
- 不⽀持下標索引
- 不⽀持length、push⽅法
- 可以修改(需通過bytes轉換)
contract test13 {
string public name = "lily";
function setName() public {
bytes(name)[0] = "L";
//name[0] = "H"; //ERROR,不⽀持下標索引
}
function getLength() public view returns(uint256) {
return bytes(name).length;
}
function setLength(uint256 i) public {
bytes(name).length = i;
bytes(name)[i - 1] = "H";
}
}
7.4.3 引用類型的內存分配(memory 和 storage)
引用類型(複雜類型),不同於之前 值類型 ,佔的空間更⼤,超過256字節,因爲拷⻉它們佔⽤更多的空間,如數組(arrays) 和 數據結構(struct) ,他們在Solidity中有⼀個額外的屬性,即數據的存儲位置: memory 和 storage 。
內存(memory)
- 數據不是永久存在的,存放在內存中,越過作⽤域後⽆法訪問,等待被回收。
- 被memory修飾的變量是直接拷⻉,即與上述的值類型傳遞⽅式相同。
存儲 (storage)
- 數據永久保存在。
- 被storage修飾的變量是引⽤傳遞,相當於只傳地址,新舊兩個變量指向同⼀⽚內存空間,效率較⾼,兩個變量有關聯,修改⼀個,另外⼀個同樣被修改。
- 只有引⽤類型的變量纔可以顯示的聲明爲 storage (注意:值類型使用storage無效)。
- 所有修飾爲storage都是上鍊的。
狀態變量
- 聲明在合約開頭,相當於golang和其他語言中的全局變量。
- 狀態變量總是stroage類型的,⽆法更改。
局部變量
- 局部變量,默認是storage類型(僅限數據結構或數組,string),但是可以聲明爲memory類型。
- 狀態變量,默認是storage變量,所有修飾爲storage都是上鍊的。
storage Vs Memory
- 調用call1,調用 setName, name不會被修改,num會被修改。
- 調用call2,調用setName2, name會被修改,num會被修改。
- 調用localTest,name會被修改,num會被修改。
- 調用localTest1,name不會被修改,num會被修改。
//memory vs storage
contract test14 {
string public name = "lily";
uint256 public num = 10;
function call1() public {
setName(name);
}
//對於引用類型數據,作爲函數參數時,默認是memory類型(值傳遞)
//function setName(string input) private {
function setName(string memory input) private {
num = 20;
bytes(input)[0] = "L";
}
function call2() public {
setName2(name);
}
//2. 如果想引用傳遞,那麼需要明確指定爲stroage類型
function setName2(string storage input) private {
num = 30;
bytes(input)[0] = "L";
}
//如果局部變量是string,數組,結構體類型數據,默認情況下是storage類型
function localTest() public {
//string tmp = name;
string storage tmp = name; //默認情況下是storage類型
num = 40;
bytes(tmp)[0] = "A";
}
function localTest1() public {
//也可以明確設置爲memory類型
string memory tmp = name;
num = 50;
bytes(tmp)[0] = "B";
}
}
7.4.4 轉換(byte1/bytes/string)
- 轉換過程:先將固定數組逐個複製,轉成bytes,然後轉成string
- string可以直接轉成bytes
- 調用bytesToString,調用fixedByteToBytes,將bytes轉換成string
- 調用stringToBytes,調用bytesToString,將string轉成bytes
//bytes1, bytes,string 轉換
contract test15 {
//定長數組
bytes10 public b10 = 0x68656c6c6f776f726c64; //helloworld
//不定長數組
bytes public bs10 = new bytes(b10.length);
//將固定長度數組的值賦值給不定長度數組
function fixedByteToBytes() public {
//bs10 = b10;
for (uint256 i = 0; i < b10.length; i++) {
bs10[i] = b10[i];
}
}
//將bytes轉成string
string public str1; //string
function bytesToString() public {
fixedByteToBytes();
str1 = string(bs10);
}
//將string轉成bytes
bytes public bs20;
function stringToBytes() public {
bytesToString();
bs20 = bytes(str1);
}
}
7.4.5 數組
內置數組
- string(不定⻓)
- bytes(不定⻓)
- bytes1...bytes32(定⻓)
自定義數組
相當於golang numbers [10] uint
- 類型T,長度K的數組定義爲T[K],例如:uint [5] numbers, byte [10] names;
- 內容可變
- 長度不可變,不支持push
- 支持length方法
//自定義定長數組
contract test16 {
//Type[Len] name
uint256[10] public numbers = [1,2,3,4,5,6,7,8,9, 10];
uint256 public sum;
// - 類型T,長度K的數組定義爲T[K],例如:uint [5] numbers, byte [10] names;
// - 內容可變
// - 長度不可變,不支持push
// - 支持length方法
function total() public returns(uint256) {
for (uint256 i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
return sum; //55
}
function setLen() public {
// numbers.length = 10;
}
function changeValue(uint256 i , uint256 value) public {
numbers[i] = value;
}
//++++++++++++++++++++++++++++++++++
bytes10 public helloworldFixed = 0x68656c6c6f776f726c64;
byte[10] public helloworldDynamic = [byte(0x68), 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64];
bytes public b10;
//bytes10 to bytes
function setToBytes() public returns (string){
for (uint256 i=0; i< helloworldDynamic.length; i++) {
byte b1 = helloworldDynamic[i];
b10.push(b1);
}
return string(b10);
}
}
不定長數組
- 定義格式爲T [ ],例如:string[ ] names, byte[ ] citys。
- 內容可以修改
- 可以改變長度(僅限storage類型) 支持
length
、push
方法 - memory類型的不定長數組不支持修改長度
- 即使沒有手動分配空間,直接改變長度,那麼也會自動分配空間
contract test17 {
//第一種創建方式,直接賦值
uint8[] numbers = [1,2,3,4,5,6,7,8,9,10];
function pushData(uint8 num) public {
numbers.push(num);
}
function getNumbers() public view returns(uint8[]) {
return numbers;
}
//第二種:使用new關鍵字進行創建,賦值給storage變量數組
uint8[] numbers2;
function setNumbers2() public {
numbers2 = new uint8[](0);
numbers2.length = 20;
numbers2.push(10);
}
function getNumbers2() public view returns(uint8[]) {
return numbers2;
}
function setNumbers3() public {
//使用new創建的memory類型數組,無法改變長度
uint8[] memory numbers3 = new uint8[](7);
// uint8[] memory numbers3;
// numbers3.length = 100; //無法修改
// numbers3.push(x0);
}
}
二維數組
TODO
7.4.6 結構體
contract test18 {
//定義結構之後無分號,與枚舉一致
struct Student {
string name;
uint age;
uint score;
string sex;
}
Student[] public students;
//兩種賦值方式
Student public stu1 = Student("lily", 18, 90, "girl");
Student public stu2 = Student({name:"Jim", age:20, score:80, sex:"boy"});
function assign() public {
students.push(stu1);
students.push(stu2);
stu1.name = "Lily";
}
}
7.4.7 字典/映射/hash表(mapping)
相當於golang map,Python中的字典
- 鍵key的類型允許除映射外的所有類型,如數組,合約,枚舉,結構體,值的類型無限制。
- 無法判斷一個mapping中是否包含某個key,因爲它認爲每一個都存在,不存在的返回0或false。
- 映射可以被視作爲一個哈希表,在映射表中,==不存儲鍵的數據==,僅僅存儲它的
keccak256
哈希值,用來查找值時使用。 - 映射類型,僅能用來定義狀態變量,或者是在內部函數中作爲storage類型的引用。
- 不支持length。
contract test19 {
//id -> name
mapping(uint => string) public id_names;
//構造函數:
//1. 對象在創建的時候,自動執行的函數,完成對象的初始化工作
//2. 構造函數僅執行一次
// function Test() public {
// }
constructor() public{
id_names[1] = "lily";
id_names[2] = "Jim";
id_names[3] = "Lily";
id_names[3] = "Tom"; //覆蓋
}
function getNameById(uint id) public view returns (string){
//加上storage如何賦值?
string memory name = id_names[id];
return name;
}
//將輸入的key值修改爲Hello
function setNameById(uint id) public returns (string){
// mapping(uint => string) memory id_name = id_names;
// var ids = id_names;
id_names[id] = "Hello";
}
// function getMapLength() public returns (uint){
// return id_names.length;
// }
}
8 Solidity高級語法
8.1 ⾃動推導var(忘了她吧)
爲了⽅便,並不總是需要明確指定⼀個變量的類型,編譯器會通過第⼀個向這個對象賦予的值的類型來進⾏推斷。
uint24 x = 0x123;
var y = x;
需要特別注意的是,由於類型推斷是根據第一個變量進行的賦值。所以下面的代碼將是一個無限循環,因爲一個uint8的i的將小於2000。
for (var i = 0; i < 2000; i++)
{
//uint8 -> 255 永遠 <2000
//無限循環
}
//var
contract test20{
function a() view returns (uint, uint){
uint count = 0;
var i = 0;
for (; i < 257; i++) {
count++;
//防止死循環
if(count >= 260){
break;
}
}
return (count, i);
}
}
結果:
0: uint256: 260
1: uint256: 3
分析:
i, count
255, 256
//溢出
0, 257
1, 258
2, 259
3, 260
00
01
10
11111111111111111111111
1000000000000000
1000000000000001
8.2 全局函數/變量
函數 | 含義 |
block.blockhash(uint blockNumber) | 哈希值(byte32) |
block.coinbase | (address) 當前塊礦⼯的地址 |
block.diffiffifficulty | (uint)當前塊的難度 |
block.gaslimit | (uint)當前塊的gaslimit |
block.number | (uint)當前區塊的塊號 |
block.timestamp | (uint)當前塊的時間戳 |
msg.data | (bytes)完整的調⽤數據(calldata) |
msg.gas | (uint)當前還剩的gas |
msg.sender | (address)當前調⽤發起⼈的地址 |
msg.sig | (bytes4)調⽤數據的前四個字節(函數標識符) |
msg.value | (uint)這個消息所附帶的貨幣量,單位爲wei |
now (uint)當前塊的時間戳 | 等同於block.timestamp |
tx.gasprice | (uint) 交易的gas價格 |
tx.origin | (address)交易的發送者(完整的調⽤鏈) |
最重要的兩個全局變量(msg.sender 和 msg.value)
注意調試此處代碼,需要開啓ganache,查看區塊信息。
contract test21 {
bytes32 public blockhash1;
address public coinbase;
uint public difficulty;
uint public gaslimit;
uint public blockNum;
uint public timestamp;
bytes public calldata;
uint public gas;
address public sender;
bytes4 public sig;
uint public msgValue;
uint public now1;
uint public gasPrice;
address public txOrigin;
function test() public payable {
blockNum = block.number;// (uint)當前區塊的塊號。
//給定區塊號的哈希值,只支持最近256個區塊,且不包含當前區塊
blockhash1 = blockhash(block.number - 1);
coinbase = block.coinbase ;//當前塊礦工的地址。
difficulty = block.difficulty;//當前塊的難度。
gaslimit = block.gaslimit;// (uint)當前塊的gaslimit。
timestamp = block.timestamp;// (uint)當前塊的時間戳。
calldata = msg.data;// (bytes)完整的調用數據(calldata)。
gas = gasleft();// (uint)當前還剩的gas。
sender = msg.sender; // (address)當前調用發起人的地址。
sig = msg.sig;// (bytes4)調用數據的前四個字節(函數標識符)。
msgValue = msg.value;// (uint)這個消息所附帶的貨幣量,單位爲wei。
now1 = now;// (uint)當前塊的時間戳,等同於block.timestamp
gasPrice = tx.gasprice;// (uint) 交易的gas價格。
txOrigin = tx.origin;// (address)交易的發送者(完整的調用鏈)
}
}
8.2.3 msg.sender (重要)
每一次和以太坊交互時都會產生一筆交易,這筆交易的執行人就是msg.sender。簡而言之:誰調用的,msg.sender就是誰,每筆交易的msg.sender都可以不同。舉例:
- 部署合約的時候,msg.sender就是部署的賬戶。
- 調用setMessage時,msg.sender就是調用賬戶。
- 調用getMessage時,msg.sender就是調用賬戶。
contract test22 {
address public owner;
uint256 a;
address public caller;
constructor() public {
//在部署合約的時候,設置一個全局唯一的合約所有者,後面可以使用權限控制
owner = msg.sender;
}
//切換賬號調試,查看caller
//1. msg.sender是一個可以改變的值,並不一定是合約的創造者
//2. 任何人調用了合約的方法,那麼這筆交易中的from就是當前msg.sender
function setValue(uint256 input) public {
a = input;
caller = msg.sender;
}
}
8.2.4 msg.value (重要)
我們在介紹payable關鍵字的時候說,如果函數修飾爲payable,那麼這個函數可以接收轉賬,這筆錢通過remix的value輸入框傳遞進來。
在轉賬操作中,這筆錢是通過我們調用一個函數從而產生一筆交易而轉入合約的,換句話說,是這筆交易附帶了一筆錢。在合約中,每次轉入的value是可以通過msg.value來獲取到的。
注意:
- 單位是wei。
- 有msg.value,就必須函數有payable關鍵字。
//msg.value
contract test23 {
//uint256 public money;
mapping(address=> uint256) public whoToMoney;
//函數裏面使用了msg.value,那麼函數要修飾爲payable
function paly() public payable {
// 如果轉賬不是100wei,那麼參與失敗
// 否則成功,並且添加到維護的mapping中
if (msg.value != 100) {
throw;
}
//map調用者的address,對應值是msg.value,記錄到map中。
whoToMoney[msg.sender] = msg.value;
}
function getBalance() public view returns(uint256) {
return address(this).balance;
}
}
8.3 錯誤處理
在創建合約時設置owner(合約的所有人)
傳統方法:採用 throw 和 if ... throw 模式==(已過時)==,例如合約中有一些功能,只能被授權爲擁有者的地址才能調用
contract test24 {
//定義變量:類型 + 變量名
string public message; // var name string
address public manager; //合約的部署者(擁有者)
address public caller; //合約函數的調用者
constructor() payable public{
manager = msg.sender;
}
function setMessage(string newMessage) public {
caller = msg.sender;
// if (manager != msg.sender) {
// throw; //如果函數調用者不是管理員,直接拋異常
// }
// 斷言:
// 1. 一條語句,既包含了條件,又可以拋異常(推薦)
// 2. 條件是期望的結果,與普通的條件判斷相反
// (條件爲true,繼續執行,條件爲false,拋出異常)
// require(manager == msg.sender);
assert(manager == msg.sender);
message = newMessage;
}
//如果有返回值,一定要加上returns關鍵字,使用()包裹起來
function getMessage() public constant returns(string){
return message;
}
}
錯誤的幾種等價
if(msg.sender != owner) {
throw;
}
//等價於如下任意一種形式:
if(msg.sender != owner) {
revert(); //和throw沒啥區別
}
//assert和require是推薦的方式,裏面的參數要求值爲true,即期望的結果
assert(msg.sender == owner);
require(msg.sender == owner);
8.4 修飾器(modifier)
有點像中間件
- 修改器(Modifiers)可以用來輕易的改變一個函數的行爲。
- 比如用於在函數執行前檢查某種前置條件。
- 修改器是一種合約屬性,可被繼承,同時還可被派生的合約重寫(override)。
contract test25 {
//定義變量:類型 + 變量名
string public message; // var name string
address public manager; //合約的部署者(擁有者)
address public caller; //合約函數的調用者
constructor() payable public{
manager = msg.sender;
}
//一個函數可以使用多個修飾器
function setMessage(string newMessage) public onlyManager onlyManager2(msg.sender){
caller = msg.sender;
message = newMessage;
}
//如果有返回值,一定要加上returns關鍵字,使用()包裹起來
function getMessage() public constant returns(string){
return message;
}
modifier onlyManager {
require(manager == msg.sender);
_; //下劃線代表修飾器所修飾的代碼
}
//修飾器可以帶有參數
modifier onlyManager2(address _caller) {
require(manager == _caller);
_; //下劃線代表修飾器所修飾的代碼
}
}
8.5 兩個常用單位
8.5.1 貨幣單位
- 一個字面量的數字,可以使用後綴
wei
,finney
,szabo
或ether
來在不同面額中轉換。 - 不含任何後綴的默認單位是
wei
。如1ether
== 1000finney
的結果是true
。 - 不同的場景下習慣使用不同的單位,通常交易行都是以Ether爲單位,購買一杯咖啡之類的小額交易使用Finney,計算Gas價格時一般使用GWei,在以太坊代碼開發中使用最基本的單位Wei。
參考 https://ethgasstation.info/blog/gwei/
https://www.investopedia.com/terms/g/gwei-ethereum.asp
https://my.oschina.net/u/3734107/blog/1839814
Kwei (babbage) 1e3 wei 1,000 (K 是 kilo)
Mwei (lovelace) 1e6 wei 1,000,000 (M million)
Gwei (shannon) 1e9 wei 1,000,000,000 (G giga 千兆/十億)
microether (szabo) 1e12 wei 1,000,000,000,000
milliether (finney) 1e15 wei 1,000,000,000,000,000
ether 1e18 wei 1,000,000,000,000,000,000
babbage 亨利·巴貝奇(Henry Babbage,1791-1871年)是一位英國數學家和機械工程師,被認爲是計算機之父。
lovelace 傑出的數學家,詩人拜倫勳爵的女兒艾達·洛夫萊斯(Ada Lovelace,1815-1852年)
shannon 克勞德·香農(Claude Shannon,1916-2001年)是美國數學家和電氣工程師,被稱爲“信息論之父”。
szabo 1998年,尼克·薩博(Nick Szabo)設計了比特黃金(bit gold),一種去中心化的數字貨幣,據說也影響了中本聰(Satoshi Nakamoto)的比特幣設計。薩博提出並創造了“智能合約”這個術語。儘管他一再否認自己是中本聰,但他仍然是另一個被懷疑是中本聰的人。
finney 哈爾·芬尼(Hal Finney,1956-2014年)是加密活動家,PGP Corporation的開發人員,可重複使用的工作證明的創建者以及早期的比特幣貢獻者。Finney甚至是中本聰本人發送的比特幣交易的第一個接收者。
Ether(buterin) -對於以太坊的創建者Vitalik Buterin。
contract test26{
uint a = 1 ether;
uint b = 10 ** 18 wei;
uint c = 1000 finney;
uint d = 1000000 szabo;
function f1() constant public returns (bool){
return a == b;
}
function f2() constant public returns (bool){
return a == c;
}
function f3() constant public returns (bool){
return a == d;
}
function f4() pure public returns (bool){
return 1 ether == 100 wei;
}
}
時間單位
- seconds,minutes,hours,days,weeks,years均可做爲後綴,默認是seconds爲單位。
- 1 = 1 seconds
- 1 minutes = 60 seconds
- 1 hours = 60 minutes
- 1 days = 24 hours
- 1 weeks = 7 days
- 1 years = 365 days
//time
contract test27{
function f1() pure public returns (bool) {
return 1 == 1 seconds;
}
function f2() pure public returns (bool) {
return 1 minutes == 60 seconds;
}
function f3() pure public returns (bool) {
return 1 hours == 60 minutes;
}
function f4() pure public returns (bool) {
return 1 days == 24 hours;
}
function f5() pure public returns (bool) {
return 1 weeks == 7 days;
}
function f6() pure public returns (bool) {
return 1 years == 365 days;
}
}
8.6 事件(Event)
相當於打印log,在remix看log字段,在web3.js 代碼調用時可以看到。
contract test28 {
//uint256 public money;
mapping(address=> uint256) public personToMoney;
// 1. 定義一個事件,使用圓括號,後面加上分號
// 2. 需要使用emit關鍵字
// 3. 在web3調用時可以監聽到事件
event playEvent(address, uint256, uint256);
function paly() public payable {
require(msg.value == 100);
personToMoney[msg.sender] = msg.value;
emit playEvent(msg.sender, msg.value, block.timestamp);
}
function getBalance() public view returns(uint256) {
return address(this).balance;
}
}
8.7 訪問函數(Getter Functions)
編譯器爲自動爲所有的public的狀態變量
創建訪問函數。下面的合約例子中,編譯器會生成一個名叫data的無參,返回值是uint的類型的值data。狀態變量的初始化可以在定義時完成。
contract test29 {
// 加了public 的轉態變量,solidity會自動的生成一個同名個訪問函數。
// 在合約內部使用這個狀態變量的時候,直接當初變量使用即可
// 如果在合約外面向訪問這個public變量(data),就需要使用xx.data()形式
uint256 public data = 200;
function getData() public view returns(uint256) {
return data;
}
//This代表合約本身,如果在合約內部使用this自己的方法的話,相當於外部調用
function getData1() public view returns(uint256) {
//return this.data; //不能使用.data形式
return this.data();
}
}
//is 是繼承
contract test30 is test29{
//外部調用
function getValue() public view returns(uint256) {
test29 t1 = new test29();
return t1.data();
}
}
8.8 合約
8.8.1 合約的創建
創建合約和外部調用
- new關鍵字,返回值是一個address,
需要顯示轉化類型
後才能使用。 - C c1形式,此時c1是空的,需要賦值地址才能使用,否則報錯。
pragma solidity ^0.4.25;
contract C1 {
uint256 public value ;
constructor(uint256 input) public {
value = input;
}
function getValue() public view returns(uint256) {
return value;
}
}
contract C2 {
C1 public c1; //0x0000000000000
C1 public c11; //0x0000000000000
C1 public c13;
function getValue1() public returns(uint256) {
//創建一個合約,返回地址
address addr1 = new C1(10); //balance , transfer方法
//return addr1.getValue();
//需要顯示的轉換爲特定類型,纔可以正常使用
c1 = C1(addr1);
return c1.getValue();
}
function getValue2() public returns(uint256) {
//定義合約的時候,同時完成類型轉換
c11 = new C1(20);
return c11.getValue();
}
//
function getValue3(address addr) public view returns(uint256) {
//傳進來的地址必須是同類型的,如果是不是C1類型的,轉換時報錯
c13 = C1(addr);
return c13.getValue();
}
}
8.8.2 合約的繼承
- is關鍵字, 可以同時繼承多個父合約。
- 當父合約存在同名函數時,默認爲最遠繼承原則(離is最遠)。
- 可以指定某個父合約,調用它的方法。
contract Base1{
function data() public pure returns(uint){
return 1;
}
}
contract Base2{
function data() public pure returns(uint){
return 2;
}
}
// 1. 使用is關鍵字進行繼承,
// 2. 多個繼承間使用逗號分隔,
// 3. 如果兩個父合約含有相同方法,那麼默認是最遠繼承原則
contract son1 is Base1, Base2{
//return 2
}
//return 1
contract son2 is Base2, Base1{
}
//4. 可以指定父合約,調用特定的方法
contract son3 is Base1, Base2{
function mydata() public pure returns(uint){
return Base1.data();
}
}
contract son4 is Base2, Base1{
function mydata() public pure returns(uint){
return Base2.data();
}
}
8.8.3 合約間如何轉錢
創建兩個合約,先創建InfoFeed,然後創建Consumer,然後調用callFeed,向InfoFeed轉賬。
//合約間如何轉錢
contract InfoFeed {
function info() public payable returns (uint ret) {
return 42;
}
function getBlance() public view returns(uint256) {
return address(this).balance;
}
}
contract Consumer {
InfoFeed public feed; //0x0000000000000
function setFeed(address addr) public {
feed = InfoFeed(addr); //0xfeabcdf......
}
function callFeed() public {
//合約間轉賬語法,
feed.info.value(10).gas(800)();
}
function () payable public {
}
function getBlance() public view returns(uint256) {
return address(this).balance;
}
}
1.部署合約InfoFeed,調用info,將合約中轉10eth
2.部署Consumer,調用fallback,向合約轉入10eth
3.調用callFeed
8.8.4 internal和external
訪問函數有外部(external)可見性。如果通過內部(internal)的方式訪問,比如直接訪問,你可以直接把它當一個變量進行使用,但如果使用外部(external)的方式來訪問,如通過this.,那麼它必須通過函數的方式來調用。
pragma solidity ^0.4.25;
//private , intenal , external, public
//合約本身可以調用, 合約及子類可以調用, 只能在合約外部調用, 可以被任意的合約調用
contract C1{
uint public c = 10;
function accessPrivate() private returns(uint) {
return c;
}
function accessInternal() internal returns (uint){
return c;
}
function accessExternal() external returns(uint){
return c;
}
function call1() public returns(uint) {
// accessExternal(); //無法在內部調用external修飾的函數
accessInternal();
}
function call2() public {
this.accessExternal(); //this調用函數,相當於外部調用
// this.c; // ok
// uint a = this.c; // error
uint b = this.c(); // ok
// c();
}
function call3() public returns(uint) {
}
}
contract C2{
function callExternal() public returns(uint){
C1 c1 = new C1();
// external修飾的只能在外部調用
return c1.accessExternal();
//internal修飾的只能在內部調用
// return c1.accessInternal();
}
}
contract C3 is C1 {
function test() public returns(uint) {
// C1 c1 = new C1();
// c1.accessPrivate();
// this.accessInternal(); //error
// c1.accessInternal(); // error
return accessInternal();
}
}
8.9 元組(tuple)
return(a, b, c)
solidity無法返回自定義的數據結構,所以若想返回一個自定義結構的數據,需要在函數中一次返回多個值,即元組。元組是一個數據集合,類似於字典但是無法修改數據,使用圓括號包括多種數據類型。
- 可以包含多個數據
- 類型可以不同
- 不可以修改
- 使用圓括號包裹
contract tuple {
struct Student {
string name;
uint age;
uint score;
string sex;
}
//兩種賦值方式
Student public stu1 = Student("lily", 18, 90, "girl");
Student public stu2 = Student({name:"Jim", age:20, score:80, sex:"boy"});
Student[] public Students;
function assign() public {
Students.push(stu1);
Students.push(stu2);
stu1.name = "Lily";
}
//1. 返回一個Student結構
function getLily() public view returns(string, uint, uint, string) {
require(Students.length != 0);
Student memory lily = Students[0];
//使用圓括號包裹的多個類型不一致的數據集合:元組
return (lily.name, lily.age, lily.score, lily.sex);
}
}
8.10 內置數學函數
ripemd160
keccak256
addmod
ecrecover
哈希函數,代替sha3(廢棄)
//keccak256
contract keccak256test {
function test() public pure returns(bytes32){
bytes memory v1 = abi.encodePacked("hello", "b", uint256(1), "hello");
return keccak256(v1);
}
function test1() public pure returns(bytes32) {
//bytes32 hash = sha3("hello", 1, "world", 2);
//bytes32 hash = keccak256("hello", "b", uint256(1), "hello");
//return hash;
return keccak256("hello", "world");
}
}
8.11 其他
for、break、continue
8.11.1 new
創建對象,合約等
8.11.2 delete
- delete操作符可以用於任何變量(map除外),將其設置成默認值
- 如果對動態數組使用delete,則刪除所有元素,其長度變爲0: uint[ ] array0 ; arry0 = new uint
- 如果對靜態數組使用delete,則重置所有索引的值: uint[10] array1 = [1,2,3,4,5,6];
- 如果對map類型使用delete,什麼都不會發生
- 但如果對map類型中的一個鍵使用delete,則會刪除與該鍵相關的值
//other
contract other {
//01. string
string public str1 = "hello";
function deleteStr() public {
delete str1;
}
function setStr(string input) public {
str1 = input;
}
//02. array 對於固定長度的數組,會刪除每個元素的值,但是數組長度不變
uint256[10] public arry1 = [1,2,3,4,5];
function deleteFiexedArry() public {
delete arry1;
}
//03. array new
uint256[] arry2 ;
function setArray2() public {
arry2 = new uint256[](10);
for (uint256 i = 0; i< arry2.length; i++) {
arry2[i] = i;
}
}
function getArray2() public view returns(uint256[]) {
return arry2;
}
function deleteArray2() public {
delete arry2;
}
//04. mapping
mapping(uint256 => string) public m1;
function setMap() public {
m1[0] =
"hello";
m1[1] = "world";
}
//Mapping不允許直接使用delete,但是可以對mapping的元素進行指定刪除
// function deleteM1() public {
// delete m1;
// }
function deleteMapping(uint256 i) public {
delete m1[i];
}
}
8.12 合約銷燬
- selfdestruct(msg.sender); 可以銷燬合約,並將合約內的金額轉給指定的地址。
- 合約銷燬後,不再工作,無法繼續調用其方法。
- 合約銷燬,並不是將合約刪除,只是不工作。
- 合約銷燬權限一定要控制好。
//kill
contract killmyself {
string public name;
address manager;
constructor(string _input) public payable {
name = _input;
manager = msg.sender;
}
//合約銷燬後,調用無效
function setName() public {
bytes(name)[0] = "L";
}
function getBalance() public view returns(uint256) {
return address(this).balance;
}
function kill() public {
require(manager == msg.sender) ;
selfdestruct(msg.sender);
}
}
9.0 發幣
登錄以太坊代幣瀏覽器
看下BNB,它的合約比較早,簡單易懂。
https://etherscan.io/token/0xB8c77482e45F1F44dE1745F52C74426C631bDD52
https://etherscan.io/address/0xB8c77482e45F1F44dE1745F52C74426C631bDD52#code
9.1 代幣源碼
ERC20標準
以太坊:什麼是ERC20標準?
https://www.jianshu.com/p/a5158fbfaeb9
以太坊ERC20 Token標準完整說明
https://blog.csdn.net/diandianxiyu_geek/article/details/78082551?utm_source=gold_browser_extension
合約中實現這些標準接口函數
contract ERC20 {
//token 發行總量
function totalSupply() constant returns (uint totalSupply);
//查看某個賬號的餘額
function balanceOf(address _owner) constant returns (uint balance);
//某個人花費自己的幣
function transfer(address _to, uint _value) returns (bool success);
//授權轉賬函數,與approve搭配使用,approve批准之後,調用transferFrom函數來轉移token。
function transferFrom(address _from, address _to, uint _value) returns (bool success);
//授權,如找一個人A幫你花費token,這部分錢並不打A的賬戶,只是對A進行花費的授權人的錢。
function approve(address _spender, uint _value) returns (bool success);
//查看授權額度
function allowance(address _owner, address _spender) constant returns (uint remaining);
//事件,當成功轉移token時,一定要觸發Transfer事件
event Transfer(address indexed _from, address indexed _to, uint _value);
//事件,當調用approval函數成功時,一定要觸發Approval事件
event Approveal(address indexed _owner, address indexed _spender, uint _value);
string public constant name = "Token Name"; //token 名稱
string public constant symbol = "BNB"; //token 符號
uint8 public constant decimals = 18; // 大部分都是18
}
9.2 發幣
1000000, "ShieldCoin", "SC"
發行幣名,ShieldCoin,符號,SC,發行量 100萬
單位:wei, 最小分割,小數點後面的尾數 1ether = 10** 18wei
pragma solidity ^0.4.25;
/**
* Math operations with safety checks
*/
contract SafeMath {
//internal > private
//internal < public
//修飾的函數只能在合約的內部或者子合約中使用
//乘法
function safeMul(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a * b;
//assert斷言函數,需要保證函數參數返回值是true,否則拋異常
assert(a == 0 || c / a == b);
return c;
}
//除法
function safeDiv(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b > 0);
uint256 c = a / b;
// a = 11
// b = 10
// c = 1
//b*c = 10
//a %b = 1
//11
assert(a == b * c + a % b);
return c;
}
//減法
function safeSub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
assert(b >=0);
return a - b;
}
function safeAdd(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c>=a && c>=b);
return c;
}
}
contract ShieldCoin is SafeMath{
string public name;
string public symbol;
uint8 public decimals;
uint256 public totalSupply;
//發行者
address public owner;
/* This creates an array with all balances */
mapping (address => uint256) public balanceOf;
//key:授權人 key:被授權人 value: 配額
mapping (address => mapping (address => uint256)) public allowance;
mapping (address => uint256) public freezeOf;
/* This generates a public event on the blockchain that will notify clients */
event Transfer(address indexed from, address indexed to, uint256 value);
/* This notifies clients about the amount burnt */
event Burn(address indexed from, uint256 value);
/* This notifies clients about the amount frozen */
event Freeze(address indexed from, uint256 value);
/* This notifies clients about the amount unfrozen */
event Unfreeze(address indexed from, uint256 value);
/* Initializes contract with initial supply tokens to the creator of the contract */
//1000000, "ShieldCoin", "SC"
constructor(
uint256 _initialSupply, //發行數量
string _tokenName, //token的名字 SCoin
//uint8 _decimalUnits, //最小分割,小數點後面的尾數 1ether = 10** 18wei
string _tokenSymbol //SC
) public {
decimals = 18;//_decimalUnits; // Amount of decimals for display purposes
balanceOf[msg.sender] = _initialSupply * 10 ** 18; // Give the creator all initial tokens
totalSupply = _initialSupply * 10 ** 18; // Update total supply
name = _tokenName; // Set the name for display purposes
symbol = _tokenSymbol; // Set the symbol for display purposes
owner = msg.sender;
}
/* Send coins */
//某個人花費自己的幣
function transfer(address _to, uint256 _value) public {
require (_to == 0x0); // Prevent transfer to 0x0 address. Use burn() instead
require (_value <= 0);
require (balanceOf[msg.sender] < _value); // Check if the sender has enough
require (balanceOf[_to] + _value < balanceOf[_to]); // Check for overflows
balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value); // Subtract from the sender
balanceOf[_to] = SafeMath.safeAdd(balanceOf[_to], _value); // Add the same to the recipient
emit Transfer(msg.sender, _to, _value); // Notify anyone listening that this transfer took place
}
/* Allow another contract to spend some tokens in your behalf */
//找一個人A幫你花費token,這部分錢並不打A的賬戶,只是對A進行花費的授權
//A: 1萬
function approve(address _spender, uint256 _value) public
returns (bool success) {
require (_value <= 0);
//allowance[管理員][A] = 1萬
allowance[msg.sender][_spender] = _value;
return true;
}
/* A contract attempts to get the coins */
function transferFrom(address _from /*管理員*/, address _to, uint256 _value) public returns (bool success) {
require (_to == 0x0); // Prevent transfer to 0x0 address. Use burn() instead
require (_value <= 0);
require (balanceOf[_from] < _value); // Check if the sender has enough
require (balanceOf[_to] + _value < balanceOf[_to]); // Check for overflows
require (_value > allowance[_from][msg.sender]); // Check allowance
// mapping (address => mapping (address => uint256)) public allowance;
balanceOf[_from] = SafeMath.safeSub(balanceOf[_from], _value); // Subtract from the sender
balanceOf[_to] = SafeMath.safeAdd(balanceOf[_to], _value); // Add the same to the recipient
//allowance[管理員][A] = 1萬-五千 = 五千
allowance[_from][msg.sender] = SafeMath.safeSub(allowance[_from][msg.sender], _value);
emit Transfer(_from, _to, _value);
return true;
}
function burn(uint256 _value) public returns (bool success) {
require (balanceOf[msg.sender] < _value); // Check if the sender has enough
require (_value <= 0);
balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value); // Subtract from the sender
totalSupply = SafeMath.safeSub(totalSupply,_value); // Updates totalSupply
emit Burn(msg.sender, _value);
return true;
}
function freeze(uint256 _value) public returns (bool success) {
require (balanceOf[msg.sender] < _value); // Check if the sender has enough
require (_value <= 0);
balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value); // Subtract from the sender
freezeOf[msg.sender] = SafeMath.safeAdd(freezeOf[msg.sender], _value); // Updates totalSupply
emit Freeze(msg.sender, _value);
return true;
}
function unfreeze(uint256 _value) public returns (bool success) {
require (freezeOf[msg.sender] < _value); // Check if the sender has enough
require (_value <= 0);
freezeOf[msg.sender] = SafeMath.safeSub(freezeOf[msg.sender], _value); // Subtract from the sender
balanceOf[msg.sender] = SafeMath.safeAdd(balanceOf[msg.sender], _value);
emit Unfreeze(msg.sender, _value);
return true;
}
// transfer balance to owner
function withdrawEther(uint256 amount) public {
require (msg.sender != owner);
owner.transfer(amount);
}
// can accept ether
function() public payable {
}
}
本地部署合約測試
使用ganche,啓動本地環境
使用賬戶部署合約
0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
1.測試(幣名,貨幣標識符,總髮行量)
查看 name,symbol,totalSupply
發幣--部署eth ropsten測試網絡
1.將metemask 連接到ropsten
2.remix鏈接到Injected Web3 ,然後,輸入參數,點擊Deploy
大概需要等10分鐘,會部署成功。點擊鏈接查看
https://ropsten.etherscan.io/tx/0x6a29f9bd6ff7ab3d8d18cdc108026cb3e6fab80437ef860864f5d7191f5fc027
驗證合約
1. 爲什麼要驗證合約
部署了一個合約之後,如果想讓大家參與進來,那麼必須接受大家的審計(審計一定要部署完就做,這樣可以保證完全匹配),以確保你的合約的功能確實如你所說,全世界的人都看到了合約的所有功能,那麼就可以放心使用了。
2. 驗證方式
輸入合約名稱ShieldCoin ,
選擇編譯器版本對應代碼版本 v0.4.24+commit.e67f0147,
優化 no
將代碼原封不動粘貼過來,上傳之後,點擊合約地址
查看合約,發幣完成。
https://ropsten.etherscan.io/address/0xe9550e939139211ef2e0ce4f8784d6b0af654767#code
10 編碼規範
- public放到最前面
- 函數參數加下劃線