Solidity基本语法学习3

发布时间 2023-12-09 16:03:20作者: 芜光

文档: https://solidity-by-example.org/
视频教程: https://www.youtube.com/watch?v=xv9OmztShIw&list=PLO5VPQH6OWdVQwpQfw9rZ67O6Pjfo6q-p

说明:

本文内容: Enum, struct, data location, function, View and Pure Function, Error

Enum(枚举)

Solidity支持枚举,这对model choicekeep track of stack很有用。
枚举可以在contract之外声明。
跟其它编程语言类似, 枚举类默认都可以用数字替代, Solidity默认是用的uint8.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
enum Status { Single, Married }
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
import "./EnumDeclaration.sol";
contract Enum {
    // 对应了0, 1, 2, 3
    enum Season { Spring, Summer, Autumn, Winter }
    
    Season public season;
    Status public status;
    function get() public view returns (Season){
        return season;
    } 
    function set(Season s) public {
        season = s;
    }
    function updateToWinter() public {
        season = Season.Winter;
    }
    function deleteToDefault() public {
        delete season; // 恢复成默认值: Spring, 也可以说是0
    }
    function getStatus() public view returns (Status){
        return status;
    }
    function setStatus(Status _status) public {
        status = _status;
    }
}

img

struct

这位更是重量级, 终于能自定义类型了.
可以通过创建struct来定义自己的类型。它们对于将相关数据分组很有用。
struct可以在contract外部声明,并在另一个contract中导入。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

struct MyTodoStruct{
    string text;
    bool completed;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
import "./MyTodo.sol";
// 跟enum一样, 都可以通过外部引入. 
contract Todos {
    struct Todo {
        string text;
        bool completed;
    }

    Todo[] public todos;

    function create(string calldata _text) public {
        // 3 ways to initialize a struct
        // - calling it like  a function
        todos.push(Todo(_text,false));

        //  key value mapping
        todos.push(Todo({text: _text, completed: false}));

        // initialize an empty struct and then update it
        Todo memory todo;
        todo.text = _text;
        // todo.completed initialied tofalse

        todos.push(todo);
    }

    // Solidity automatically created a getter for 'todos' so
    // you don't actually need this function.
    function get(uint _index) public view returns(string memory text, bool completed){
        Todo storage todo = todos[_index];
        return (todo.text, todo.completed);
    }

    function updateText(uint _index,  string calldata _text) public {
        Todo storage todo = todos[_index];
        todo.text = _text;
    }

    function toggleCompleted(uint _index) public {
        Todo storage todo = todos[_index];
        todo.completed = !todo.completed;
    }
}

Data Locations - Storage, Memory and Calldata

变量声明为storagememorycalldata,以显式指定数据的位置。

  • storage变量是一个状态变量(存储在区块链上)
  • memory变量在内存中,当函数被调用时它才存在
  • calldata 包含函数参数的特殊数据位置. Calldata arrays are read-only.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract DataLocations {
    uint[] public arr;
    mapping(uint => address) map;
    struct MyStruct {
        uint foo;
    }
    mapping(uint => MyStruct) myStructs;

    function f() public {
        // call _f with state variables
        _f(arr, map, myStructs[1]);

        // get a struct from a mapping
        MyStruct storage myStruct = myStructs[1];
        // create a struct in memory
        MyStruct memory myMemStruct = MyStruct(0);
    }

    function _f(
        uint[] storage _arr,
        mapping(uint => address) storage _map,
        MyStruct storage _myStruct
    ) internal {
        // do something with storage variables
        
    }

    // You can return memory variables
    function g(uint[] memory _arr) public pure returns (uint[] memory) {
        // do something with memory array
        for(uint i = 0; i < _arr.length; i++){
            unchecked{_arr[i] -= 1;}
        }
        return _arr;
    }

    uint[] public _arrh;
    function h(uint[] calldata _arr) external {
        // do something with calldata array
        // 修改的时候:TypeError: Calldata arrays are read-only.
        for(uint i = 0; i < _arr.length; i++){
            _arrh.push(_arr[i]);
        }
    }
}

注意输入数组的方式. ["1","2","3"]或者[1,2,3]才行
img

Function

有几种方法可以从函数返回输出。
公共(public, external)函数不能接受某些数据类型(比如map)作为输入或输出

// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

contract Function {
    //  Functions canreturn  multiple values.
    function returnMany() public pure returns(uint, bool, uint){
        return (1, true, 2);
    }

    // Return values can be named.
    function named() public pure returns(uint x, bool b, uint y){
        return (1, true, 2);
    }

    // Return values can  be assigned to their name.
    // In this case the return statement can be omitted. 
    function assigned() public pure returns(uint x, bool b, uint y){
        x = 1;
        b = true;
        y = 2;
    }

    // Use destructuring assignment when calling another
    // function that returns multiple values.
    function destructuringAssignments() 
        public
        pure 
        returns(uint, bool, uint, uint,  uint){
            (uint i,bool b,uint j)= returnMany();

            // values can be left out.
            (uint x, , uint y) = (4,5,6);
            return (i,b,j,x,y);
        }

    // Cannot use map for either input or output

    // Can use array for input
    function arrayInput(uint[] memory _arr) public {}

    // Can use array for output
    uint[] public arr;

    function arrayOutput() public view returns(uint[] memory){
        return arr;
    }

}

contract XYZ {
    function someFunWithManyInput(
        uint x,
        uint y,
        uint z,
        address a,
        bool b,
        string memory c
    ) public pure returns (uint) {}

    function callFunc() external pure returns(uint) {
        return someFunWithManyInput(1, 2, 3, address(0), true, "c");
    }

    function callFuncWithKeyValue() external pure returns (uint){
        return 
            someFunWithManyInput({a: address(0),  b: true, c: "c", x: 1, y: 2, z: 3});
    }
}

View and Pure

Getter类型的函数可以声明为viewpure

  • view函数声明状态不会被改变
  • pure函数声明不改变或读取状态变量。

Remix IDE会告诉你什么时候用哪个关键词.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract ViewAndPure {
    uint public x = 1;

    // Promise not to modify the state.
    function addToX(uint y) public view returns (uint) {
        return x + y;
    }

    // Promise not to modify or read from the state.
    function add(uint i, uint j) public pure returns (uint) {
        return i + j;
    }
}

Error

error将撤销在transaction期间对state所做的所有更改。
可以通过调用require、revert或assert抛出错误。

  • require用于在执行之前验证输入和条件。
  • revert类似于require。有关详细信息,请参阅下面的代码。
  • assert用于检查不应该为false的代码。断言失败可能意味着存在错误。

使用自定义错误(custom error, 0.8版本新特性)来节省燃气。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

contract Error {
    function testRequire(uint _i) public pure {
        // Require should be used to validate conditions such as:
        // - inputs
        // - conditions before execution
        // - return values from calls to other functions
        require(_i > 10, "input must be greater than 10");
    }

    function testRevert(uint _i) public pure {
        // Revert is useful when the condition to check is complex.
        // This code does the exact same thing as the example above
        if(_i <= 10){
            revert("input must be greater than 10");
        }
    }

    uint public num;

    function testAssert() public view {
        // Assert should only be used to test for internal errors,
        // and to check invariants.

        // Here we assert that num is always equal to 0
        // since it is impossible to update the value of num
        assert(num == 0);
    }

    error InsufficientBalance(uint balance, uint withdrawAmonut);

    function testCustomError(uint _withdrawAmount) public view {

        uint bal = address(this).balance;
        if(bal < _withdrawAmount){
            // 测试可以用0和1去看输出的区别
            revert InsufficientBalance({balance: bal, withdrawAmonut: _withdrawAmount});
        }
    }
}