初學Solidity(一):語法大致總結

BSN研習社發表於2022-12-02
Solidity是物件導向的高階程式語言,是用於開發智慧合約的語言之一,語法類似於JavasSript,但又有所不同。本期,我們為初學Solidity的開發者們推薦了CSDN作者DayDayUp丶關於學習Solidity語法的一篇總結文章。

一、資料型別

1.1、值型別

1.1.1、布林

pragma solidity ^0.4.25;
contract TestBool {
    bool flag;
    int num1 = 100;
    int num2 = 200;
    // default false
    function getFlag() public view returns(bool) {
        return flag;  // false
    }
    // 非
    function getFlag2() public view returns(bool) {
        return !flag;  // true
    }
    // 與
    function getFlagAnd() public view returns(bool) {
        return (num1 != num2) && !flag;  // true
    }
    // 或
    function getFlagOr() public view returns(bool) {
        return (num1 == num2) || !flag;  // true
    }
}
1.1.2、整數
加減乘除、取餘、冪運算,
pragma solidity ^0.4.25;
// 整型特性與運算
contract TestInteger {
  int num1; // 有符號整型 int256
  uint num2; // 無符號整型 uint256
  function add(uint _a, uint _b) public pure returns(uint) {
      return _a + _b;
  }
  function sub(uint _a, uint _b) public pure returns(uint) {
      return _a - _b;
  }
  function mul(uint _a, uint _b) public pure returns(uint) {
      return _a * _b;
  }
  function div(uint _a, uint _b) public pure returns(uint) {
      return _a / _b;  // 在solidity中,除法是做整除,沒有小數點
  }
  function rem(uint _a, uint _b) public pure returns(uint) {
      return _a % _b;
  }
  function square(uint _a, uint _b) public pure returns(uint) {
      return _a ** _b;  // 冪運算在0.8.0之後,變為右優先,即a ** b ** c => a ** (b ** c)
  }
  function max() public view returns(uint) {
      return uint(-1);
      // return type(uint).max;  // 0.8不再允許uint(-1)
  }
}
位運算,
pragma solidity ^0.4.25;
// 位運算
contract TestBitwise {
  uint8 num1 = 3;
  uint8 num2 = 4;
  function bitAdd() public view returns(uint) {
      return num1 & num2;
  }
  function bitOr() public view returns(uint) {
      return num1 | num2;
  }
  function unBit() public view returns(uint) {
      return ~num1;
  }
  function bitXor() public view returns(uint) {
      return num1 ^ num2;
  }
  function bitRight() public view returns(uint) {
      return num1 >> 1;
  }
  function bitLeft() public view returns(uint) {
      return num1 << 1;
  }
}
1.1.3、定長浮點型
目前只支援定義,不支援賦值使用,
fixed num; // 有符號
ufixed num; // 無符號
1.1.4、地址型別
address addr = msg.sender;
address addr = 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF;
1.1.5、合約型別
在合約TestType中使用TestBitwise合約,
TestBitwise t = TestBitwise(addr);
1.1.6、列舉型別
enum ActionChoices { Up, Down, Left, Right }
1.1.7、定長位元組陣列
pragma solidity ^0.4.25;
// 固定長度的位元組陣列(靜態),以及轉換為string型別
contract TestBytesFixed {
    // public 自動生成同名的get方法
    bytes1 public num1 = 0x7a;  // 1 byte = 8 bit
    bytes1 public num2 = 0x68;
    bytes2 public num3 = 0x128b;  // 2 byte = 16 bit
    // 獲取位元組陣列長度
    function getLength() public view returns(uint) {
        return num3.length; // 2
    }
    // 位元組陣列比較
    function compare() public view returns(bool) {
        return num1 < num2;
    }
    // 位元組陣列位運算
    function bitwise() public view returns(bytes1,bytes1,bytes1) {  // 多返回值
        return ((num1 & num2), (num1 | num2), (~num1));
    }
    // 先轉為bytes動態陣列,再透過string構造  0x7a7a -> zz
    function toString(bytes2 _val) public pure returns(string) {
        bytes memory buf = new bytes(_val.length);
        for (uint i = 0; i < _val.length; i++) {
            buf[i] = _val[i];
        }
        return string(buf);
    }
}
固定長度位元組陣列的擴充和壓縮,
pragma solidity ^0.4.25;
// 固定長度的位元組陣列(靜態)擴容和壓縮
contract TestBytesExpander {
    // public 自動生成同名的get方法
    bytes6 name = 0x796f7269636b;
    function changeTo1() public view returns(bytes1) {
        return bytes1(name); // 0x79
    }
    function changeTo2() public view returns(bytes2) {
        return bytes2(name); // 0x796f
    }
    function changeTo16() public view returns(bytes16) {
        return bytes16(name); // 0x796f7269636b00000000000000000000
    }
}
1.1.8、函式型別
function (<parameter types>) {internal|external|public|private} [pure|constant|view|payable] [returns (<return types>)]
1.2、引用型別

1.2.1、字串

pragma solidity ^0.4.25;
// 修改string型別的資料
contract TestString {
    string name = 'yorick';  // 字串可以使用單引號或者雙引號
    string name2 = "!@#$%^&";  // 特殊字元佔1個byte
    string name3 = "張三";  // 中文在string中使用utf8的編碼方式儲存,佔用3個byte
    function getLength() view public returns(uint) {
        // 不可以直接獲取string的length
        return bytes(name).length; // 6
    }
    function getLength2() view public returns(uint) {
        return bytes(name2).length; // 7
    }
    function getLength3() view public returns(uint) {
        return bytes(name3).length; // 6
    }
    function getElmName() view public returns(bytes1) {
        // 不可以直接透過陣列下標name[0]獲取
        return bytes(name)[0]; // 0x79
    }
    function changeElmName() public {
        bytes(name)[0] = "h";
    }
    function getName() view public returns(bytes) {
        return bytes(name);
    }
}

1.2.2、變長位元組陣列

pragma solidity ^0.4.25;
// 動態的位元組陣列,以及轉換為string型別
contract TestBytesDynamic {
    bytes public dynamicBytes;
    function setDynamicBytes(string memory val) public {
        dynamicBytes = bytes(val);
    }
    function getVal() public view returns(string){
        return string(dynamicBytes);
    }
}

1.2.3、陣列

定長陣列,
pragma solidity ^0.4.25;
// 定長陣列
contract TestArrFixed {
    uint[5] arr = [1,2,3,4,5];
    // 修改陣列元素內容
    function modifyElements() public {
        arr[0] = 12;
        arr[1] = 14;
    }
    // 檢視陣列
    function watchArr() public view returns(uint[5]) {
        return arr;
    }
    // 陣列元素求和計算
    function sumArr() public view returns(uint) {
        uint sum = 0;
        for (uint i = 0; i < arr.length; i++) {
            sum += arr[i];
        }
        return sum;
    }
    // 陣列長度
    function getLength() public view returns(uint) {
        return arr.length;
    }
    // delete重置陣列某下標的元素值,不會真正刪除該元素
    function deleteElm(uint idx) public {
        delete arr[idx];
    }
    // delete重置整個陣列
    function deleteArr() public {
        delete arr;
    }
    /**  定長陣列不允許改變長度和push
    // 壓縮陣列後,右側多餘元素被丟棄
    function changeLengthTo1() public {
        arr.length = 1;
    }
    // 擴容陣列後,右側元素補0
    function changeLengthTo10() public {
        arr.length = 10;
    }
    // 追加新元素
    function pushElm(uint _elm) public {
        arr.push(_elm);
    }
    */
}
變長陣列,
pragma solidity ^0.4.25;
// 變長陣列
contract TestArrDynamic {
    uint[] arr = [1,2,3,4,5];
    // 檢視陣列
    function watchArr() public view returns(uint[]) {
        return arr;
    }
    // 陣列長度
    function getLength() public view returns(uint) {
        return arr.length;
    }
    // 壓縮陣列後,右側多餘元素被丟棄
    function changeLengthTo1() public {
        arr.length = 1;
    }
    // 擴容陣列後,右側元素補0
    function changeLengthTo10() public {
        arr.length = 10;
    }
    // 追加新元素
    function pushElm(uint _elm) public {
        arr.push(_elm);
    }
    // delete重置陣列某下標的元素值,不會真正刪除該元素
    function deleteElm(uint idx) public {
        delete arr[idx];
    }
    // delete重置整個陣列
    function deleteArr() public {
        delete arr;
    }
}
二維陣列,
pragma solidity ^0.4.25;
/**
二維陣列
solidity的二維陣列與其他語言不同,[2] [3]表示3行2列,而其他語言為2行3列;
二維動態陣列與一維陣列類似,可以改變其陣列長度;
*/
contract TestArr2Dimensional {
    uint[2][3] arr = [[1,2],[3,4],[5,6]];
    function getRowSize() public view returns(uint) {
        return arr.length; // 3
    }
    function getColSize() public view returns(uint) {
        return arr[0].length; // 2
    }
    function watchArr() public view returns(uint[2][3]) {
        return arr;  // 1,2,3,4,5,6
    }
    function sumArr() public view returns(uint) {
        uint sum = 0;
        for (uint i = 0; i < getRowSize(); i++) {
            for (uint j = 0; j < getColSize(); j++) {
                sum += arr[i][j];
            }
        }
        return sum;
    }
    function modifyArr() public {
        arr[0][0] = 99;
    }
}
陣列字面值,
pragma solidity ^0.4.25;
// 陣列字面值
contract TestArrLiteral {
    // 最小儲存匹配,未超過255,所以使用uint8儲存
    function getLiteral8() pure public returns(uint8[3]) {
        return [1,2,3];
    }
    // 超過255,所以使用uint16儲存
    function getLiteral16() pure public returns(uint16[3]) {
        return [256,2,3];  // [255,2,3] 不被uint16允許
    }
    // 強制轉換為uint256
    function getLiteral256() pure public returns(uint[3]) {
        return [uint(1),2,3];  // 給任意元素強轉即可,否則不被允許
    }
    // 計算外界傳入的內容
    function addLiterals(uint[3] arr) pure external returns(uint) {
        uint sum = 0;
        for (uint i = 0; i < arr.length; i++) {
            sum += arr[i];
        }
        return sum;
    }
}
1.2.4、結構體
pragma solidity ^0.4.25;
// 結構體初始化的兩種方法
contract TestStruct {
    struct Student {
        uint id;
        string name;
        mapping(uint=>string) map;
    }
    // 預設為storage型別,只能透過storage型別操作結構體中的mapping型別資料
    Student storageStu;
    // mapping型別可以不用在定義的時候賦值,而其他型別不賦值則會報錯
    function init() public pure returns(uint, string) {
        Student memory stu = Student(100, "Jay");
        return (stu.id, stu.name);
    }
    function init2() public pure returns(uint, string) {
        Student memory stu = Student({name: "Jay", id: 100});
        return (stu.id, stu.name);
    }
    function init3() public returns(uint, string, string) {
        Student memory stu = Student({name: "Jay", id: 100});
        // 直接操作結構體中的mapping不被允許: Student memory out of storage
        // stu.map[1] = "artist";
        // 透過storage型別的變數操作結構體中的mapping
        storageStu = stu;
        storageStu.map[1] = "artist";
        return (storageStu.id, storageStu.name, storageStu.map[1]);
    }
    // 結構體作為入參時,為memory型別,並且不能使用public或external修飾函式:Internal or recursive type is not allowed for public or external functions.
    // 賦值時也要指定為memory,否則報錯
    function testIn(Student stu) internal returns(uint) {
        return stu.id;
    }
    // 結構體作為出參,同樣只能private或internal宣告內部使用
    function testOut(Student stu) private returns(Student) {
        return stu;
    }
}

1.3、對映

contract TestMapping {
    mapping(address => uint) private scores;  // <學生,分數>的單層對映
    mapping(address => mapping(bytes32 => uint8)) private _scores;  // <學生,<科目,分數>>的兩層對映
    function getScore() public view returns(address, uint) {
        address addr = msg.sender;
        return (addr, scores[addr]);
    }
    function setScore() public {
        scores[msg.sender] = 100;
    }
}

二、作用域(訪問修飾符)

contract TestAccessCtrl {
    constructor () public {}
    uint public num1 = 1;  // 自動為public生成同名的get函式,但在編碼時不可直接呼叫num1()
    uint private num2 = 2;
    uint num3 = 3;  // 不寫則預設private
    function funcPublic() public returns(string) {
        return "public func";
    }
    function funcPrivate() private returns(string) {
        return "private func";
    }
    function funcInternal() internal returns(string) {
        return "internal func";
    }
    function funcExternal() external returns(string) {
        return "external func";
    }
    function test1(uint choice) public returns(string) {
        if (choice == 1) return funcPublic();
        if (choice == 2) return funcPrivate();
        if (choice == 3) return funcInternal();
        //if (choice == 4) return funcExternal();  // external不允許直接在內部用
        if (choice == 4) return this.funcExternal();  // 間接透過this才可以呼叫external
    }
}
contract TestAccessCtrlSon is TestAccessCtrl {
    function test2(uint choice) public returns(string) {
        if (choice == 1) return funcPublic();  // public允許派生合約使用
        //if (choice == 2) return funcPrivate();  // private不允許派生合約使用
        if (choice == 3) return funcInternal();  // internal允許派生合約使用
        //if (choice == 4) return funcExternal();  // external也不允許派生合約直接使用
    }
}
contract TestAccessCtrl2 {
    function test2(uint choice) public returns(string) {
        TestAccessCtrl obj = new TestAccessCtrl();
        return obj.funcExternal();  // external只允許在外部合約中這樣間接呼叫
    }
}

2.1、private

僅在當前合約使用,且不可被繼承,私有狀態變數只能從當前合約內部訪問,派生合約內不能訪問。

2.2、public

同時支援內部和外部呼叫。修飾狀態變數時,自動生成同名get函式,

初學Solidity(一):語法大致總結
編輯 切換為居中
新增圖片註釋,不超過 140 字(可選)
但在編碼時不可直接呼叫num1()。

2.3、internal

只支援內部呼叫,也包括其派生合約內訪問。

2.4、external

外部才可呼叫,內部想要呼叫可以用this。

三、函式修飾符

contract TestFuncDecorator {
    uint public num = 1;
    /// pure
    function testPure(uint _num) public pure {
        //uint num1 = num;  // pure不允許讀狀態變數
        //num = _num;  // pure不允許修改狀態變數
    }
    /// view
    function testView(uint _num) public view {
        uint num1 = num;  // 允許讀狀態變數
        num = _num;  // 0.4語法上允許修改狀態變數,但實際不會修改,所以num還是1
        // 0.5及之後不允許在view中這樣修改,否則編譯不透過
    }
    /// payable
    function () public payable {}
    function getBalance() public view returns(uint) {  // balance獲取合約地址下的以太幣餘額
        return address(this).balance;
    }
    // 充值函式payable,只有新增這個關鍵字,才能在執行這個函式時,給這個合約充以太幣,否則該函式自動拒絕所有傳送給它的以太幣
    function testPayable() payable public {  // transfer轉賬
        address(this).transfer(msg.value);
    }
}
3.1、pure
表明該函式是純函式,連狀態變數都不用讀,函式的執行僅僅依賴於引數。不消耗gas。承諾不讀取或修改狀態,否則編譯出錯。
3.2、view
設定了view修飾符,就是一次呼叫,不需要執行共識、進入EVM,而是直接查詢本地節點資料,因此效能會得到很大提升。不消耗gas。不會發起交易,所以不能實際改變狀態變數。
3.3、payable
允許函式被呼叫的時候,讓合約接收以太幣。如果未指定,該函式將自動拒絕所有傳送給它的以太幣。
四、建構函式
唯一,不可過載。也可以接收入參。
pragma solidity ^0.4.25;
contract Test1 {
    address private _owner;
    constructor() public {
        _owner = msg.sender;
    }
    /**constructor(int num) public {  過載構造->編譯錯誤
        _owner = msg.sender;
    }*/
}
contract Test2 {
    uint public num;
    constructor(uint x) public {  // 帶參構造,在deploy時傳入
        num = x;
    }
}
五、修飾器modifier
方法修飾器modifier,類似AOP處理。
pragma solidity ^0.4.25;
contract TestModifier {
    address private _owner;
    bool public endFlag;  // 執行完test後的endFlag仍是true
    constructor() public {
        _owner = msg.sender;
    }
    modifier onlyOwner {  // 許可權攔截器,非合約部署賬號執行test()則被攔截
        require(_owner == msg.sender, "Auth: only owner is authorized.");
        _;  // 類似被代理的test()方法呼叫
        endFlag = true;
    }
    function test() public onlyOwner {
        endFlag = false;
    }
}
六、資料位置
6.1、memory
其生命週期只存在於函式呼叫期間,區域性變數預設儲存在記憶體,不能用於外部呼叫。記憶體位置是臨時資料,比儲存位置便宜。它只能在函式中訪問。
通常,記憶體資料用於儲存臨時變數,以便在函式執行期間進行計算。一旦函式執行完畢,它的內容就會被丟棄。你可以把它想象成每個單獨函式的記憶體(RAM)。
6.2、storage
狀態變數儲存的位置,只要合約存在就一直儲存在區塊鏈中。該儲存位置儲存永久資料,這意味著該資料可以被合約中的所有函式訪問。可以把它視為計算機的硬碟資料,所有資料都永久儲存。
儲存在儲存區(Storage)中的變數,以智慧合約的狀態儲存,並且在函式呼叫之間保持永續性。與其他資料位置相比,儲存區資料位置的成本較高。
6.3、calldata
calldata是不可修改的只讀的非永續性資料位置,所有傳遞給函式的值,都儲存在這裡。此外,calldata是外部函式的引數(而不是返回引數)的預設位置。
0.4的external入參宣告calldata則報錯,0.5及之後的external入參必須宣告calldata否則報錯。
從 Solidity 0.6.9 版本開始,之前僅用於外部函式的calldata位置,現在可以在內部函式使用了。
6.4、stack
堆疊是由EVM (Ethereum虛擬機器)維護的非永續性資料。EVM使用堆疊資料位置在執行期間載入變數。堆疊位置最多有1024個級別的限制。
【總結】
花費gas:storage > memory(calldata) > stack
狀態變數總是儲存在儲存區storage中。(隱式地標記狀態變數的位置)
函式引數(值型別)包括返回引數(值型別)都儲存在記憶體memory中
值型別的區域性變數:棧(stack)
值型別的區域性變數儲存在記憶體中。但是,對於引用型別,需要顯式地指定資料位置
不能顯式宣告具有值型別的區域性變數為memory還是storage

七、事件event

定義使用event,類似一個函式宣告,呼叫試圖emit,
contract Test {
    event testEvent(uint a, uint b, uint c, uint result);
    function calc(uint a, uint b, uint c) public returns(uint) {
        uint result = a ** b ** c;
        emit testEvent(a, b, c, result);
        return result;
    }
}
事件會輸出在logs中,
[
	{
		"from": "0x19a0870a66B305BE9917c0F14811C970De18E6fC",
		"topic": "0x271cb5fa8dca917938dbd3f2522ef54cf70092ead9e1871225a2b3b407f9a81a",
		"event": "testEvent",
		"args": {
			"0": "2",
			"1": "1",
			"2": "3",
			"3": "8",
			"a": "2",
			"b": "1",
			"c": "3",
			"result": "8"
		}
	}
]
透過給event形參新增indexed,便於事件條件的篩選,indexed不能超過三個,否則編譯出錯,
event testEvent(uint indexed a, uint b, uint c, uint result);
八、單位和全域性變數
時間單位不加預設s,以太幣預設wei,
function testUnit() pure public {
    require(1 == 1 seconds);
    require(1 minutes == 60 seconds);
    require(1 hours == 60 minutes);
    require(1 days == 24 hours);
    require(1 weeks == 7 days);
    require(1 years == 365 days);  // years 從 0.5.0 版本開始不再支援
    require(1 ether == 1000 finney);
    require(1 finney == 1000 szabo);    // 從0.7.0開始 finney 和 szabo 被移除了
    require(1 szabo == 1e12 wei);
    //require(1 gwei == 1e9);  // 0.7.0開始加入gwei
}
全域性變數,
pragma solidity ^0.8.0;
contract TestGlobalVariable {
    function test1() public view returns(/**bytes32,*/ uint, uint, address, uint, uint, uint, uint) {
        return (
            // blockhash(block.number - 1),  // 指定區塊的區塊雜湊,僅可用於最新的 256 個區塊且不包括當前區塊,否則返回0。0.5移除了block.blockhash
            block.basefee,  // 當前區塊的基礎費用
            block.chainid,  // 當前鏈 id
            block.coinbase,  // 挖出當前區塊的礦工地址
            block.difficulty,  // 當前區塊難度
            block.gaslimit,  // 當前區塊 gas 限額
            block.number,  // 當前區塊號
            block.timestamp  // 自 unix epoch 起始當前區塊以秒計的時間戳,0.7.0移除了now
        );
    }
    function test2() public payable returns(bytes memory, address, bytes4, uint, uint256, uint, address) {
        return (
            msg.data,  // 完整的 calldata
            msg.sender,  // 訊息傳送者(當前呼叫)
            msg.sig,  // calldata 的前 4 位元組(也就是函式識別符號)
            msg.value,  // 隨訊息傳送的 wei 的數量
            gasleft(),  // 剩餘的 gas,0.5移除了msg.gas
            tx.gasprice,  // 交易的 gas 價格
            tx.origin  // 交易發起者(完全的呼叫鏈)
        );
    }
}
九、異常處理

9.1、assert

assert(1 == 1 seconds);

9.2、require

require(1 == 1 seconds);

9.3、revert

if (x != y) revert("x should equal to b");

9.4、try/catch

Solidity0.6版本之後加入。try/catch僅適用於外部呼叫,另外,try大括號內的程式碼是不能被catch捕獲的。
pragma solidity ^0.6.10;
contract TestTryCatch {
    function execute (uint256 amount) external returns(bool){
        try this.onlyEvent(amount){
            return true;
        } catch {
            return false;
        }
    }
    function onlyEvent (uint256 a) public {
        //code that can revert
        require(a % 2 == 0, "Ups! Reverting");
    }
}
【assert和require的選擇】
在EVM裡,處理assert和require兩種異常的方式是不一樣的,雖然他們都會回退狀態,不同點表現在:
  1. gas消耗不同。assert型別的異常會消耗掉所有剩餘的gas,而require不會消耗掉剩餘的gas(剩餘的gas會返還給呼叫者)
  2. 運算子不同。當assert發生異常時,Solidity會執行一個無效操作(無效指令0xfe)。當發生require型別的異常時,Solidity會執行一個回退操作(REVERT指令0xfd)
  • 優先使用require()
  • 用於檢查使用者輸入。
  • 用於檢查合約呼叫返回值,如require(external.send(amount))。
  • 用於檢查狀態,如msg.send == owner。
  • 通常用於函式開頭。
  • 不知道使用哪一個的時候,就使用require。
  • 優先使用assert()
  • 用於檢查溢位錯誤,如z = x + y; assert(z >= x);
  • 用於檢查不應該發生的異常情況。
  • 用於在狀態改變之後,檢查合約狀態。
  • 儘量少使用assert。
  • 通常用於函式中間或者尾部。

十、過載

// 過載
function addNums(uint x, uint y) public pure returns(uint) {
    return x + y;
}
function addNums(uint x, uint y, uint z) public pure returns(uint) {
    return x + y + z;
}

十一、繼承

子合約is父合約的格式,
pragma solidity ^0.4.25;
contract TestExtendA { // 父類要寫在子類之前
    uint public a;
    constructor() public {
        a = 1;
    }
}
contract TestExtend is TestExtendA {
    uint public b;
    constructor() public {
        b = 2;
    }
}
直接在繼承列表中指定基類的構造引數,
contract A { // 父類要寫在子類之前
    uint public x;
    constructor(uint _a) public {  // 帶參構造
        x = _a;
    }
}
contract B is A(1) {  // 指定父類構造引數
    uint public y;
    constructor() public {
        y = 2;
    }
}
透過派生合約(子類)的建構函式中使用修飾符方式呼叫基類合約,
contract A { // 父類要寫在子類之前
    uint public x;
    constructor(uint _a) public {  // 帶參構造
        x = _a;
    }
}
// 方式一:
contract B1 is A {
    uint public b;
    constructor() A(1) public {  // 子類構造使用父類帶參修飾符A(1)
        b = 2;
    }
}
// 方式二:
contract B2 is A {
    uint public b;
    constructor(uint _b) A(_b / 2) public {  // 子類帶參構造使用父類帶參修飾符A(_b / 2)
        b = _b;
    }
}
連續繼承、多重繼承,
/// 連續繼承,Z繼承Y,Y又繼承X
contract X {
    uint public x;
    constructor() public{
        x = 1;
    }
}
contract Y is X {
    uint public y;
    constructor() public{
        y = 1;
    }
}
// 如果是多個基類合約之間也有繼承關係,那麼is後面的合約書寫順序就很重要。順序應該是,基類合約在前面,派生合約在後面,否則無法編譯。
// 實際上Z只需要繼承Y就行
contract Z is X,Y {  // 所以必須是X,Y而不是Y,X
}
/// 多重繼承,子類可以擁有多個基類的屬性
contract Father {
    uint public x = 180;
}
contract Mother {
    uint public y = 170;
}
contract Son is Father, Mother {
}
十二、抽象合約
0.6開始支援。
如果一個合約有建構函式,且是內部(internal)函式,或者合約包含沒有實現的函式,這個合約將被標記為抽象合約,使用關鍵字abstract,抽象合約無法成功部署,他們通常用作基類合約。
抽象合約可以宣告一個virtual純虛擬函式,純虛擬函式沒有具體實現程式碼的函式。其函式宣告用;結尾,而不是用{}結尾。
如果合約繼承自抽象合約,並且沒有透過重寫(override)來實現所有未實現的函式,那麼他本身就是抽象合約的,隱含了一個抽象合約的設計思路,即要求任何繼承都必須實現其方法。
//pragma solidity ^0.4.25;  // 0.4/0.5不相容
pragma solidity ^0.6.10;
abstract contract TestAbstractContract {
    uint public a;
	constructor(uint _a) internal {
		a = _a;
	}
    function get () virtual public;
}

十三、重寫

0.8以下不支援。
合約中的虛擬函式(函式使用了virtual修飾的函式)可以在子合約重寫該函式,以更改他們在父合約中的行為。重寫的函式需要使用關鍵字override修飾。
pragma solidity ^0.8.0;
contract TestOverride {
    function get() virtual public{}
}
contract Middle is TestOverride {
}
contract Inherited is Middle{
    function get() public override{
    }
}
對於多重繼承,如果有多個父合約有相同定義的函式,override關鍵字後必須指定所有的父合約名稱,
contract Base1 {
    function get () virtual public{}
}
contract Base2 {
    function get () virtual public{}
}
contract Middle2 is Base1, Base2{  // 指定所有父合約名稱
    function get() public override( Base1, Base2){
    }
}
注意:如果函式沒有標記為virtual(除介面外,因為介面裡面所有的函式會自動標記為virtual),那麼派生合約是不能重寫來更改函式行為的。另外,private的函式是不可標記為virtual的。
十四、介面
0.8以下不支援。
介面和抽象合約類似,與之不同的是,介面不實現任何函式,同時還有以下限制:
  1. 無法繼承其他合約或者介面
  2. 無法定義建構函式
  3. 無法定義變數
  4. 無法定義結構體
  5. 無法定義列舉
pragma solidity ^0.8.0;
interface TestInterface {
    function transfer (address recipient, uint amount) external;
}
contract TestInterfaceSon {
    function transfer(address recipient, uint amount) public {
    }
}
就像繼承其他合約一樣,合約可以繼承介面,介面中的函式會隱式地標記為virtual,意味著他們會被重寫。
十五、庫
開發合約的時候,總是會有一些函式經常被多個合約呼叫,這個時候可以把這些函式封裝為一個庫,庫的關鍵字用library來定義。
如果合約引用的庫函式都是內部(internal)函式,那麼編譯器在編譯合約時,會把庫函式的程式碼嵌入到合約裡,就像合約自己實現了這些函式,這時並不會單獨部署。
pragma solidity >=0.4.0 <0.7.0;
//pragma solidity ^0.4.25;
library TestLibrary{
	function add (uint a,uint b) internal pure returns (uint){
		uint c = a + b;
		require(c > a, "SafeMath: addition overflow");
		return c;
	}
}
庫的呼叫,
contract Test {
    function add (uint x, uint y) public pure returns(uint){
        return TestLibrary.add(x, y);  // 呼叫庫
    }
}
除了使用上面的TestLibrary.add(x, y)這種方式來呼叫庫函式,還有一個是使用using LibA for B這種附著庫的方式。
它表示把所有LibA的庫函式關聯到資料型別B,這樣就可以在B型別直接呼叫庫函式。
contract Test {
    using TestLibrary for uint;
    //using TestLibrary for *;
    function add2 (uint x,uint y) public pure returns (uint){
        return x.add(y);  // uint的資料x就可以直接呼叫add(y)
    }
}
0x1、示例程式碼
在下,基於不同版本語法的差異,分別用三個合約檔案基本覆蓋到了以上語法,
  • Test04.sol => ^0.4.25
  • Test06.sol => ^0.6.10
  • Test08.sol => ^0.8.0
1.Test04.sol,
pragma solidity ^0.4.25;
//pragma solidity ^0.8.0;
/** 1.1.1 */
contract TestBool {
    bool flag;
    int num1 = 100;
    int num2 = 200;
    // default false
    function getFlag() public view returns(bool) {
        return flag;  // false
    }
    // 非
    function getFlag2() public view returns(bool) {
        return !flag;  // true
    }
    // 與
    function getFlagAnd() public view returns(bool) {
        return (num1 != num2) && !flag;  // true
    }
    // 或
    function getFlagOr() public view returns(bool) {
        return (num1 == num2) || !flag;  // true
    }
}
/** 1.1.2 */
// 整型特性與運算
contract TestInteger {
    int num1; // 有符號整型 int256
    uint num2; // 無符號整型 uint256
    function add(uint _a, uint _b) public pure returns(uint) {
        return _a + _b;
    }
    function sub(uint _a, uint _b) public pure returns(uint) {
        return _a - _b;
    }
    function mul(uint _a, uint _b) public pure returns(uint) {
        return _a * _b;
    }
    function div(uint _a, uint _b) public pure returns(uint) {
        return _a / _b;  // 在solidity中,除法是做整除,沒有小數點
    }
    function rem(uint _a, uint _b) public pure returns(uint) {
        return _a % _b;
    }
    function square(uint _a, uint _b) public pure returns(uint) {
        return _a ** _b;  // 冪運算在0.8.0之後,變為右優先,即a ** b ** c => a ** (b ** c)
    }
    function max() public view returns(uint) {
        return uint(-1);
        // return type(uint).max;  // 0.8不再允許uint(-1)
    }
}
// 位運算
contract TestBitwise {
    uint8 num1 = 3;
    uint8 num2 = 4;
    function bitAdd() public view returns(uint) {
        return num1 & num2;
    }
    function bitOr() public view returns(uint) {
        return num1 | num2;
    }
    function unBit() public view returns(uint) {
        return ~num1;
    }
    function bitXor() public view returns(uint) {
        return num1 ^ num2;
    }
    function bitRight() public view returns(uint) {
        return num1 >> 1;
    }
    function bitLeft() public view returns(uint) {
        return num1 << 1;
    }
}
/** 1.1.3 - 1.1.6 */
contract TestType {
    fixed num;
    ufixed num2;
    fixed8x8 decimal;  // fixedMxN, M表示位寬,必須位8的整數倍,N表示十進位制小數部分的位數
    address addr = msg.sender;
    address addr2 = 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF;
    TestBitwise t = TestBitwise(addr);
    enum ActionChoices { Up, Down, Left, Right }
}
/** 1.1.7 */
// 固定長度的位元組陣列(靜態),以及轉換為string型別
contract TestBytesFixed {
    // public 自動生成同名的get方法
    bytes1 public num1 = 0x7a;  // 1 byte = 8 bit
    bytes1 public num2 = 0x68;
    bytes2 public num3 = 0x128b;  // 2 byte = 16 bit
    // 獲取位元組陣列長度
    function getLength() public view returns(uint) {
        return num3.length; // 2
    }
    // 位元組陣列比較
    function compare() public view returns(bool) {
        return num1 < num2;
    }
    // 位元組陣列位運算
    function bitwise() public view returns(bytes1,bytes1,bytes1) {  // 多返回值
        return ((num1 & num2), (num1 | num2), (~num1));
    }
    // 先轉為bytes動態陣列,再透過string構造  0x7a7a -> zz
    function toString(bytes2 _val) public pure returns(string) {
        bytes memory buf = new bytes(_val.length);
        for (uint i = 0; i < _val.length; i++) {
            buf[i] = _val[i];
        }
        return string(buf);
    }
}
// 固定長度的位元組陣列(靜態)擴容和壓縮
contract TestBytesExpander {
    // public 自動生成同名的get方法
    bytes6 name = 0x796f7269636b;
    function changeTo1() public view returns(bytes1) {
        return bytes1(name); // 0x79
    }
    function changeTo2() public view returns(bytes2) {
        return bytes2(name); // 0x796f
    }
    function changeTo16() public view returns(bytes16) {
        return bytes16(name); // 0x796f7269636b00000000000000000000
    }
}
/** 1.2.1 */
// 修改string型別的資料
contract TestString {
    string name = 'yorick';  // 字串可以使用單引號或者雙引號
    string name2 = "!@#$%^&";  // 特殊字元佔1個byte
    string name3 = "張三";  // 中文在string中使用utf8的編碼方式儲存,佔用3個byte  ->  切換到0.8則中文字元報錯
    // string memory str = unicode"Hello ?";  // 0.7.0支援Unicode字串
    // string memory str2 = unicode"\u20ac";  // 0.7.0支援Unicode字串
    function getLength() view public returns(uint) {
        // 不可以直接獲取string的length
        return bytes(name).length; // 6
    }
    function getLength2() view public returns(uint) {
        return bytes(name2).length; // 7
    }
    function getLength3() view public returns(uint) {
        return bytes(name3).length; // 6
    }
    function getElmName() view public returns(bytes1) {
        // 不可以直接透過陣列下標name[0]獲取
        return bytes(name)[0]; // 0x79
    }
    function changeElmName() public {
        bytes(name)[0] = "h";
    }
    function getName() view public returns(bytes) {
        return bytes(name);
    }
}
/** 1.2.2 */
// 動態的位元組陣列,以及轉換為string型別
contract TestBytesDynamic {
    bytes public dynamicBytes;
    function setDynamicBytes(string memory val) public {
        dynamicBytes = bytes(val);
    }
    function getVal() public view returns(string){
        return string(dynamicBytes);
    }
}
/** 1.2.3 */
// 定長陣列
contract TestArrFixed {
    uint[5] arr = [1,2,3,4,5];
    // 修改陣列元素內容
    function modifyElements() public {
        arr[0] = 12;
        arr[1] = 14;
    }
    // 檢視陣列
    function watchArr() public view returns(uint[5]) {
        return arr;
    }
    // 陣列元素求和計算
    function sumArr() public view returns(uint) {
        uint sum = 0;
        for (uint i = 0; i < arr.length; i++) {
            sum += arr[i];
        }
        return sum;
    }
    // 陣列長度
    function getLength() public view returns(uint) {
        return arr.length;
    }
    // delete重置陣列某下標的元素值,不會真正刪除該元素
    function deleteElm(uint idx) public {
        delete arr[idx];
    }
    // delete重置整個陣列
    function deleteArr() public {
        delete arr;
    }
    /**  定長陣列不允許改變長度和push
    // 壓縮陣列後,右側多餘元素被丟棄
    function changeLengthTo1() public {
        arr.length = 1;
    }
    // 擴容陣列後,右側元素補0
    function changeLengthTo10() public {
        arr.length = 10;
    }
    // 追加新元素
    function pushElm(uint _elm) public {
        arr.push(_elm);
    }
    */
}
// 變長陣列
contract TestArrDynamic {
    uint[] arr = [1,2,3,4,5];
    // 檢視陣列
    function watchArr() public view returns(uint[] memory) {
        return arr;
    }
    // 陣列長度
    function getLength() public view returns(uint) {
        return arr.length;
    }
    // 壓縮陣列後,右側多餘元素被丟棄
    function changeLengthTo1() public {
        arr.length = 1;
    }
    // 擴容陣列後,右側元素補0
    function changeLengthTo10() public {
        arr.length = 10;
    }
    // 追加新元素
    function pushElm(uint _elm) public {
        arr.push(_elm);
    }
    /**
    // 彈出元素
    function popElm(uint _elm) public {
        arr.pop();  // 0.4不支援pop
    }
    */
    // delete重置陣列某下標的元素值,不會真正刪除該元素
    function deleteElm(uint idx) public {
        delete arr[idx];
    }
    // delete重置整個陣列
    function deleteArr() public {
        delete arr;
    }
}
/**
二維陣列
solidity的二維陣列與其他語言不同,[2] [3]表示3行2列,而其他語言為2行3列;
二維動態陣列與一維陣列類似,可以改變其陣列長度;
*/
contract TestArr2Dimensional {
    uint[2][3] arr = [[1,2],[3,4],[5,6]];
    function getRowSize() public view returns(uint) {
        return arr.length; // 3
    }
    function getColSize() public view returns(uint) {
        return arr[0].length; // 2
    }
    function watchArr() public view returns(uint[2][3]) {
        return arr;  // 1,2,3,4,5,6
    }
    function sumArr() public view returns(uint) {
        uint sum = 0;
        for (uint i = 0; i < getRowSize(); i++) {
            for (uint j = 0; j < getColSize(); j++) {
                sum += arr[i][j];
            }
        }
        return sum;
    }
    function modifyArr() public {
        arr[0][0] = 99;
    }
}
// 陣列字面值
contract TestArrLiteral {
    // 最小儲存匹配,未超過255,所以使用uint8儲存
    function getLiteral8() pure public returns(uint8[3]) {
        return [1,2,3];
    }
    // 超過255,所以使用uint16儲存
    function getLiteral16() pure public returns(uint16[3]) {
        return [256,2,3];  // [255,2,3] 不被uint16允許
    }
    // 強制轉換為uint256
    function getLiteral256() pure public returns(uint[3]) {
        return [uint(1),2,3];  // 給任意元素強轉即可,否則不被允許
    }
    // 計算外界傳入的內容
    function addLiterals(uint[3] arr) pure external returns(uint) {
        uint sum = 0;
        for (uint i = 0; i < arr.length; i++) {
            sum += arr[i];
        }
        return sum;
    }
}
/** 1.2.4 */
// 結構體初始化的兩種方法
contract TestStruct {
    struct Student {
        uint id;
        string name;
        mapping(uint=>string) map;
    }
    // 預設為storage型別,只能透過storage型別操作結構體中的mapping型別資料
    Student storageStu;
    // mapping型別可以不用在定義的時候賦值,而其他型別不賦值則會報錯
    function init() public pure returns(uint, string) {
        Student memory stu = Student(100, "Jay");
        return (stu.id, stu.name);
    }
    function init2() public pure returns(uint, string) {
        Student memory stu = Student({name: "Jay", id: 100});
        return (stu.id, stu.name);
    }
    function init3() public returns(uint, string, string) {
        Student memory stu = Student({name: "Jay", id: 100});
        // 直接操作結構體中的mapping不被允許: Student memory out of storage
        // stu.map[1] = "artist";
        // 透過storage型別的變數操作結構體中的mapping
        storageStu = stu;
        storageStu.map[1] = "artist";
        return (storageStu.id, storageStu.name, storageStu.map[1]);
    }
    // 結構體作為入參時,為memory型別,並且不能使用public或external修飾函式:Internal or recursive type is not allowed for public or external functions.
    // 賦值時也要指定為memory,否則報錯
    function testIn(Student stu) internal returns(uint) {
        return stu.id;
    }
    // 結構體作為出參,同樣只能private或internal宣告內部使用
    function testOut(Student stu) private returns(Student) {
        return stu;
    }
}
/** 1.3 */
contract TestMapping {
    mapping(address => uint) private scores;  // <學生,分數>的單層對映
    mapping(address => mapping(bytes32 => uint8)) private _scores;  // <學生,<科目,分數>>的兩層對映
    function getScore() public view returns(address, uint) {
        address addr = msg.sender;
        return (addr, scores[addr]);
    }
    function setScore() public {
        scores[msg.sender] = 100;
    }
}
/** 二 */
contract TestAccessCtrl {
    constructor () public {}
    uint public num1 = 1;  // 自動為public生成同名的get函式,但在編碼時不可直接呼叫num1()
    uint private num2 = 2;
    uint num3 = 3;  // 不寫則預設private
    function funcPublic() public returns(string) {
        return "public func";
    }
    function funcPrivate() private returns(string) {
        return "private func";
    }
    function funcInternal() internal returns(string) {
        return "internal func";
    }
    function funcExternal() external returns(string) {
        return "external func";
    }
    function test1(uint choice) public returns(string) {
        if (choice == 1) return funcPublic();
        if (choice == 2) return funcPrivate();
        if (choice == 3) return funcInternal();
        //if (choice == 4) return funcExternal();  // external不允許直接在內部用
        if (choice == 4) return this.funcExternal();  // 間接透過this才可以呼叫external
    }
}
contract TestAccessCtrlSon is TestAccessCtrl {
    function test2(uint choice) public returns(string) {
        if (choice == 1) return funcPublic();  // public允許派生合約使用
        //if (choice == 2) return funcPrivate();  // private不允許派生合約使用
        if (choice == 3) return funcInternal();  // internal允許派生合約使用
        //if (choice == 4) return funcExternal();  // external也不允許派生合約直接使用
    }
}
contract TestAccessCtrl2 {
    function test2(uint choice) public returns(string) {
        TestAccessCtrl obj = new TestAccessCtrl();
        if (choice == 4) {
            return obj.funcExternal();  // external只允許在外部合約中這樣間接呼叫
        } else return "0x0";
    }
}
/** 三 */
contract TestFuncDecorator {
    uint public num = 1;
    /// pure
    function testPure(uint _num) public pure {
        //uint num1 = num;  // pure不允許讀狀態變數
        //num = _num;  // pure不允許修改狀態變數
    }
    /// view
    function testView(uint _num) public view {
        uint num1 = num;  // 允許讀狀態變數
        num = _num;  // 0.4語法上允許修改狀態變數,但實際不會修改,所以num還是1
        // 0.5及之後不允許在view中這樣修改,否則編譯不透過
    }
    /// payable
    function () public payable {}
    function getBalance() public view returns(uint) {  // balance獲取合約地址下的以太幣餘額
        return address(this).balance;
    }
    // 充值函式payable,只有新增這個關鍵字,才能在執行這個函式時,給這個合約充以太幣,否則該函式自動拒絕所有傳送給它的以太幣
    function testPayable() payable public {  // transfer轉賬
        address(this).transfer(msg.value);
    }
}
/** 四 */
contract TestConstruct1 {
    address private _owner;
    constructor() public {
        _owner = msg.sender;
    }
    /**constructor(int num) public {  // 過載構造->編譯錯誤
        _owner = msg.sender;
    }*/
    /**
    function TestFuncDecorator(uint x) {}  // 0.5之前還可以用同名函式定義
    */
}
contract TestConstruct2 {
    uint public num;
    constructor(uint x) public {  // 帶參構造,在deploy時傳入
        num = x;
    }
}
/** 五 */
contract TestModifier {
    address private _owner;
    bool public endFlag;  // 執行完test後的endFlag仍是true
    constructor() public {
        _owner = msg.sender;
    }
    modifier onlyOwner {  // 許可權攔截器,非合約部署賬號執行test()則被攔截
        require(_owner == msg.sender, "Auth: only owner is authorized.");
        _;  // 類似被代理的test()方法呼叫
        endFlag = true;
    }
    function test() public onlyOwner {
        endFlag = false;
    }
}
/** 七 */
contract TestEvent {
    event testEvent(uint indexed a, uint indexed b, uint indexed c, uint result); // indexed不能超過三個
    function calc(uint a, uint b, uint c) public returns(uint) {
        uint result = a ** b ** c;
        emit testEvent(a, b, c, result);  // 事件會輸出在logs中
        return result;
    }
}
/** 八 */
contract TestUnit {
    function testUnit() pure public {
        require(1 == 1 seconds);
        require(1 minutes == 60 seconds);
        require(1 hours == 60 minutes);
        require(1 days == 24 hours);
        require(1 weeks == 7 days);
        require(1 years == 365 days); // years 從 0.5.0 版本開始不再支援
        require(1 ether == 1000 finney);
        require(1 finney == 1000 szabo);
        require(1 szabo == 1e12 wei);
        //require(1 gwei == 1e9);  // 0.7.0開始加入gwei
    }
}
/** 九 */
contract TestException {
    function testAssert(int x) public pure {
        assert(x >= 0);
    }
    function testRequire(int x) public pure {
        require(x >= 0);
        //require(x >= 0, "x < 0");
    }
    function testRevert(int x, int y) public pure {
        if (x != y) revert("x should equal to b");
    }
}
/** 十 */
contract TestOverload {  // 過載
    function addNums(uint x, uint y) public pure returns(uint) {
        return x + y;
    }
    function addNums(uint x, uint y, uint z) public pure returns(uint) {
        return x + y + z;
    }
}
/** 十一 */
contract TestExtendA { // 父類TestExtendA要寫在子類TestExtend之前
    uint public a;
    constructor() public {
        a = 1;
    }
}
contract TestExtend is TestExtendA {
    uint public b;
    constructor() public {
        b = 2;
    }
}
/// 直接在繼承列表中指定基類的構造引數
contract A { // 父類要寫在子類之前
    uint public x;
    constructor(uint _a) public {  // 帶參構造
        x = _a;
    }
}
contract B is A(1) {  // 指定父類構造引數
    uint public y;
    constructor() public {
        y = 2;
    }
}
/// 透過派生合約(子類)的建構函式中使用修飾符方式呼叫基類合約
// 方式一:
contract B1 is A {
    uint public b;
    constructor() A(1) public {  // 子類構造使用父類帶參修飾符A(1)
        b = 2;
    }
}
// 方式二:
contract B2 is A {
    uint public b;
    constructor(uint _b) A(_b / 2) public {  // 子類帶參構造使用父類帶參修飾符A(_b / 2)
        b = _b;
    }
}
/// 連續繼承,Z繼承Y,Y又繼承X
contract X {
    uint public x;
    constructor() public{
        x = 1;
    }
}
contract Y is X {
    uint public y;
    constructor() public{
        y = 1;
    }
}
// 如果是多個基類合約之間也有繼承關係,那麼is後面的合約書寫順序就很重要。順序應該是,基類合約在前面,派生合約在後面,否則無法編譯。
// 實際上Z只需要繼承Y就行
contract Z is X,Y {  // 所以必須是X,Y而不是Y,X
}
/// 多重繼承,子類可以擁有多個基類的屬性
contract Father {
    uint public x = 180;
}
contract Mother {
    uint public y = 170;
}
contract Son is Father, Mother {
}
/** 十五 */
library TestLibrary{
	function add (uint a,uint b) internal pure returns (uint){
		uint c = a + b;
		require(c > a, "Math: addition overflow");
		return c;
	}
}
// 庫的呼叫
contract TestLibraryCall {
    function add(uint x, uint y) public pure returns(uint){
        return TestLibrary.add(x, y);  // 呼叫庫
    }
}
// 除了使用上面的TestLibrary.add(x, y)這種方式來呼叫庫函式,還有一個是使用using LibA for B這種附著庫的方式。
// 它表示把所有LibA的庫函式關聯到資料型別B,這樣就可以在B型別直接呼叫庫函式。
contract TestLibraryUsing {
    using TestLibrary for uint;
    //using TestLibrary for *;
    function add2(uint x,uint y) public pure returns (uint){
        return x.add(y);  // uint的資料x就可以直接呼叫add(y)
    }
}
2.Test06.sol,
pragma solidity ^0.6.10;
/** 1.2.3 */
contract TestArrDynamic {
    uint[] arr = [1,2,3,4,5];
    // 彈出元素
    function popElm() public {
        arr.pop();
    }
    function watchArr() public view returns(uint[] memory) {
        return arr;
    }
    /**
    0.6開始不再可以透過修改length改變陣列長度,需要透過push(),push(value),pop的方式,或者賦值一個完整的陣列
    */
    /**function changeLengthTo1() public {
        arr.length = 1;
    }*/
}
/** 九 */
// Solidity0.6版本之後加入。try/catch僅適用於外部呼叫,另外,try大括號內的程式碼是不能被catch捕獲的。
contract TestTryCatch {
    function execute (uint256 amount) external returns(bool) {
        try this.run(amount) {  // 這裡的函式異常會被捕獲
            return true;  // 這裡的異常不再會被捕獲
        } catch {
            return false;
        }
    }
    function run(uint256 a) public {
        //code that can revert
        require(a % 2 == 0, "Ups! Reverting");
    }
}
/** 十二 */
// 0.6後支援。抽象合約不能使用new建立
abstract contract TestAbstractContract {
    uint public a;
	constructor(uint _a) internal {
		a = _a;
	}
    function get () virtual public;
}
3.Test08.sol,
pragma solidity ^0.8.0;
/** 1.2.1 */
contract TestString {
    function test() public view returns(string memory, string memory, string memory) {
        string memory str = unicode"Hello ?";  // Hello ?
        string memory str2 = unicode"\u20ac";  // €
        string memory str3 = hex"414243444546474849";  // ABCDEFGHI
        // string memory name3 = "張三";  // 0.8不允許中文字元,必須改為unicode
        return (str, str2, str3);
    }
}
/** 八 */
contract TestGlobalVariable {
    function test1() public view returns(/**bytes32,*/ uint, uint, address, uint, uint, uint, uint) {
        return (
            // blockhash(block.number - 1),  // 指定區塊的區塊雜湊,僅可用於最新的 256 個區塊且不包括當前區塊,否則返回0。0.5移除了block.blockhash
            block.basefee,  // 當前區塊的基礎費用
            block.chainid,  // 當前鏈 id
            block.coinbase,  // 挖出當前區塊的礦工地址
            block.difficulty,  // 當前區塊難度
            block.gaslimit,  // 當前區塊 gas 限額
            block.number,  // 當前區塊號
            block.timestamp  // 自 unix epoch 起始當前區塊以秒計的時間戳,0.7.0移除了now
        );
    }
    function test2() public payable returns(bytes memory, address, bytes4, uint, uint256, uint, address) {
        return (
            msg.data,  // 完整的 calldata
            msg.sender,  // 訊息傳送者(當前呼叫)
            msg.sig,  // calldata 的前 4 位元組(也就是函式識別符號)
            msg.value,  // 隨訊息傳送的 wei 的數量
            gasleft(),  // 剩餘的 gas,0.5移除了msg.gas
            tx.gasprice,  // 交易的 gas 價格
            tx.origin  // 交易發起者(完全的呼叫鏈)
        );
    }
}
/** 十三 */
// 合約中的虛擬函式(函式使用了virtual修飾的函式)可以在子合約重寫該函式,以更改他們在父合約中的行為。重寫的函式需要使用關鍵字override修飾。
// 0.8以下不支援。
contract TestOverride {
    function get() virtual public{}
}
contract Middle is TestOverride {
}
contract Inherited is Middle {
    function get() public override {
    }
}
// 對於多重繼承,如果有多個父合約有相同定義的函式,override關鍵字後必須指定所有的父合約名稱
contract Base1 {
    function get() virtual public {}
}
contract Base2 {
    function get() virtual public {}
}
contract Middle2 is Base1, Base2 {  // 指定所有父合約名稱
    function get() public override (Base1, Base2){
    }
}
/** 十四 */
// 0.8以下不支援。
interface TestInterface {
    function transfer(address recipient, uint amount) external;
}
contract TestInterfaceSon {
    function transfer(address recipient, uint amount) public {
    }
}
0x2、各版本主要變化
0.5.0
  • sha3改用keccak256, keccak256只允許接收一個引數,使用abi.encodePacked等組合params
  • 建構函式由同名空參方法變成constructor
0.6.0
  • 僅標記virtual的介面才可以被覆蓋,覆蓋時需要使用新關鍵字override,如果多個基類同方法名時,需要像這樣列出 override(Base1, Base2)
  • 不能透過修改length來修改陣列長度,需要透過push(),push(value),pop的方式,或者賦值一個完整的陣列
  • 使用abstract標識抽象合約,抽象合約不能使用new建立
  • 回撥函式由function()拆分為fallback()和receive()
  • 新增try/catch,可對呼叫失敗做一定處理
  • 陣列切片,例如: abi.decode(msg.data[4:], (uint, uint)) 是一個對函式呼叫payload進行解碼底層方法
  • payable(x) 把 address 轉換為 address payable
0.7.0
  • call方式呼叫方法由x.f.gas(1000).value(1 ether)(arg1,arg2)改成 x.f{gas:1000,value:1 ether}(arg1,arg2)
  • now 不推薦使用,改用block.timestamp
  • gwei增加為關鍵字
  • 字串支援ASCII字元,Unicode字串
  • 建構函式不在需要 public修飾符,如需防止建立,可定義成abstract
  • 不允許在同一繼承層次結構中具有同名同引數型別的多個事件
  • using A for B,只在當前合約有效, 以前是會繼承的,現在需要使用的地方,都得宣告一次
0.8.0
  • 棄用safeMath,預設加了溢位檢查,如需不要檢查使用 unchecked { ... } , 可以節省丟丟手續費
  • 預設支援ABIEncoderV2,不再需要宣告
  • 求冪是右結合的,即表示式a**b**c被解析為a**(b**c)。在 0.8.0 之前,它被解析為(a**b)**c
  • assert 不在消耗完 完整的gas,功能和require基本一致,但是try/catch錯誤裡面體現不一樣,還有一定作用…
  • 不再允許使用uint(-1),改用type(uint).max
版權宣告:本文為CSDN博主「DayDayUp丶」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。
原文連結:
https://blog.csdn.net/songzehao/article/details/126294980


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70012206/viewspace-2926415/,如需轉載,請註明出處,否則將追究法律責任。

相關文章