Solidity初學-0.8新特性

蕪光發表於2023-12-05

https://www.youtube.com/watch?v=xv9OmztShIw&list=PLO5VPQH6OWdVQwpQfw9rZ67O6Pjfo6q-p

helloworld和溢位問題

contract HelloWorld {
  /**
   * @dev Prints Hello World string
   */
  function print() public pure returns (string memory) {
    return "Hello World!";
  }
}
contract SafeMath {
  function testUnderflow() public pure returns(uint) {
    uint x = 0;
    x--;
    return x; // 0.8版本之前跟C一樣會返回最大值,之後會報錯
  }
  function testUncheckedUnderflow() public pure returns(uint) {
    uint x = 0;
    unchecked {x--;}
    return x; // 正常版本
  }
}

部署SafeMath
測試結果
testUncheckedUnderflow方法它會返回一個非常大的數字,如果沒有unchecked程式碼塊的話也就是testUnderflow方法,可以看到右邊控制檯輸出了一個Error occured

0.8新特性:自定義錯誤

0.8版本除了處理了溢位問題之外,還讓開發者可以自定義錯誤custom error, 相比於直接返回錯誤資訊字串要好得多. 返回字串的話, 是要消耗gas的

contract VendingMachine {
  address payable owner = payable(msg.sender);
  function withdraw() public {
    if (msg.sender != owner)
       revert(""); // 2560 gas 並且輸入字串越長它越多
    // 可能是版本問題, 加上這段程式碼會變成infinite gas
    // owner.transfer(address(this).balance); 
  }
}
contract VendingMachine {
  address payable owner = payable(msg.sender);

  error Unauthorized();

  function withdraw() public {
    if (msg.sender != owner)
      revert Unauthorized(); // 2324 gas, 比上面那種形式少了一些
      // owner.transfer(address(this).balance);
  }
}
contract VendingMachine {
  address payable owner = payable(msg.sender);

  error Unauthorized(address caller);

  function withdraw() public  {
    if (msg.sender != owner)
      revert Unauthorized(msg.sender);
    owner.transfer(address(this).balance);
  }
}

理想效果如下
油管影片截圖
0.8版本之後, error也可以放到合約之外去定義, 這樣一來各個合約就都能用到自定義error了. 函式也同理. 甚至可以給其它.sol檔案引入

// HelloWorld.sol
function helper(uint x) view returns (uint){
  return x * 2;
}
// Import.sol
pragma solidity ^0.8;
import {Unauthorized, helper as h1} from './HelloWorld.sol';
// 注意不能重名
function helper() view returns(uint){
    return 1;
}
contract Import {
    //...
}

Create2

Create2文件解釋

// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
contract D {
    uint public x;
    constructor(uint a) {
        x = a;
    }
}

contract Create2 {
    function getBytes32(uint salt) external pure returns (bytes32) {
        return bytes32(salt);
    }
    function getAddress(bytes32 salt, uint arg) external view returns (address) {
        address addr = address(uint160(uint(keccak256(abi.encodePacked(
            bytes1(0xff),
            address(this),
            salt,
            keccak256(abi.encodePacked(
                type(D).creationCode,
                arg
            ))
        )))));
        return addr;
    }
    address public deployedAddr;
    function createDsalted(bytes32 salt, uint arg) public {
        D d = new D{salt : salt}(arg);
        deployedAddr = address(d);
    }
}

這段Solidity程式碼包含兩個合約:DCreate2。下面是對程式碼的解釋:

  1. 合約 D

    • D 是一個簡單的合約,包含一個公共的 uint 型別變數 x 和一個建構函式。
    • 建構函式在合約部署時被呼叫,接受一個引數 a 並將其賦值給變數 x
  2. 合約 Create2

    • Create2 包含兩個外部函式和一個公共的地址變數 deployedAddr

    • 函式 getBytes32

      • 接受一個 uint 型別的引數 salt,並返回它的 bytes32 表示形式。
    • 函式 getAddress

      • 接受一個 bytes32 型別的引數 salt 和一個 uint 型別的引數 arg
      • 使用 keccak256 雜湊函式構造一個地址,並返回該地址。
      • 構造地址的方式是利用 CREATE2 EVM 指令,該指令允許在特定地址上建立合約,而不是在交易之後立即建立。這是為了在合約部署時能夠預測合約的地址。
      • 構造地址的關鍵部分是使用 keccak256 對合約建立程式碼和引數進行雜湊,以及指定的 salt。這確保了地址的唯一性。
    • 函式 createDsalted

      • 接受一個 bytes32 型別的引數 salt 和一個 uint 型別的引數 arg
      • 使用 CREATE2 指令在特定地址上建立一個新的 D 合約例項,傳遞 arg 作為建構函式的引數。
      • 將新建立的合約地址儲存在 deployedAddr 變數中。

這個合約的主要目的是演示使用 CREATE2 指令在特定地址上建立合約的過程。透過使用 bytes32 型別的 salt,可以在不同的情況下建立具有相同構造引數的合約,並且每個合約都會有唯一的地址。這在某些場景下可能是有用的,例如在鏈上建立合約的預測地址,以便在將來進行互動。
Create2使用
可以看到,用123byte32資料和salt=777,獲取了地址0x023...
然後呼叫createDsalted方法, 引數為上面的byte32salt, 最終可以獲取到一樣的地址.

未解決的問題

在測試自定義錯誤輸出的時候, 反覆對比教程發現輸出不一樣, 我的測試並沒有返回error. 換方法名或者加payable都不起作用.
img

相關文章