目錄
1 Solidity與智能合約
2 智能合約概述
3 以太坊簡介
4 以太坊互動工具
5 開發環境搭建
6 常見概念
7 Solidity基礎語法
7.1 資料型別分類
7.2 remix的使用--第一個智能合約
7.3 值型別
7.4 參考型別
8 Solidity高級語法
8.1 ?動推導var(忘了她吧)
8.2 全域函式/變數
最重要的兩個全域變數(msg.sender 和 msg.value)
8.3 錯誤處理
8.4 修飾器(modifier)
8.5 兩個常用單位
8.5.1 貨幣單位
8.6 事件(Event)
8.7 訪問函式(Getter Functions)
8.8 合約
8.8.1 合約的創建
8.8.2 合約的繼承
8.8.3 合約間如何轉錢
8.8.4 internal和external
8.9 元組(tuple)
8.10 內置數學函式
8.11 其他
8.12 合約銷毀
9.0 發幣
9.1 代幣原始碼
9.2 發幣
本地部署合約測驗
驗證合約
10 編碼規范
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測驗幣
https://faucet.ropsten.be
輸入公鑰地址即可,

Kovan
Kovan?前仍在運?,但僅有Parity錢包客戶端可以使?這個測驗?絡,
為了解決測驗?絡中PoW共識機制的問題,以太坊錢包Parity的開發團隊發起了?個新的測驗?絡Kovan,Kovan使? 了權威證明(Proof-of-Authority)的共識機制,簡稱PoA,
| PoW是??作量來獲得?成區塊的權利,必須完成?定次數的計算后,發現?個滿?條件的謎題答案,才能夠?成有效 的區塊, PoA是由若?個權威節點來?成區塊,其他節點?權?成,這樣也就不再需要挖礦,由于測驗?絡上的以太幣?價值, 權威節點僅僅是?來防?區塊被隨意?成,造成測驗?絡擁堵,完全是義務勞動,不存在作惡的動機,因此這種機制在 測驗?絡上是可?的, |
Kovan與主?絡使?不同的共識機制,影響的僅僅是誰有權來?成區塊,以及驗證區塊是否有效的?式,權威節點可以 根據開發?員的申請?成以太幣,并不影響開發者測驗智能合約和其他功能,
Rinkeby
Rinkeby也是以太坊官?提供的測驗?絡,使?PoA共識機制,(獲取測驗幣?較困難)

5 開發環境搭建
remix在線編譯器
訪問鏈接,方便除錯,但是依賴網路,需翻墻,
https://remix.ethereum.org/

舊版界面
左側是檔案夾,右側 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 發幣
登錄以太坊代幣瀏覽器
https://etherscan.io/tokens
看下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放到最前面
- 函式引數加下劃線
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/249123.html
標籤:區塊鏈
