关于solidity知识总结

借此CSDN博客笔记来巩固一下对solidity知识的认识

1.helloworld

// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {
    string public hello = "HelloWorld";
}

public 修饰符自动创建一个get()函数用于返回内容

类似于

// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {
    string hello = "HelloWorld";
    function getHello() public view returns(string memory) {
        return hello;
    }
}

view:函数里的功能只查看了状态变量或者全局变量,不改变状态变量的内容,用view修饰。调用view修饰的函数,不会消耗gas,只有消耗gas的函数调用view修饰的函数,才会消耗gas。

memory:数据位置的修饰符,修饰数组,映射,字节,于calldata的区别是 memory 不能修改变量的内容,而calldata可以

returns:声明返回的数据类型,可以返回多个。

2.第一个应用程序

// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {
    uint256 count;
    function get() public view returns(uint256) {
        return count;
    }
    function inc() public {
        count += 1;
    }
    function dec() public {
        count -= 1;
    }
}

功能描述:用于加减状态变量count

细节:0.8.0以下的版本减到零之后就会返回最大值,也就是常说的整数溢出问题 使用safemath库可以解决或者更换到0.8.0以上的版本。

状态变量:状态变量就是具体要上链的数据

3.数据类型

boolean

有两个值 true和false 默认为false

uint

无符号的整型,最大值为uint256,默认为0

// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {
    uint256 i;
    function getMax() public pure returns(uint256) {
        return type(uint256).max;
    }
    function getMin() public pure returns(uint256) {
        return type(uint256).min;
    }
}

可以用type()函数查看最大值和最小值

pure:通常用pure修饰符的函数是工具类

int

有符号的整型,最大值为int256,默认为0

// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {
    int256 i;
    function getMax() public pure returns(int256) {
        return type(int256).max;
    }
    function getMin() public pure returns(int256) {
        return type(int256).min;
    }
}

 也可以用type()函数查看最大值和最小值

address

地址类型,是solidity的特有的数据类型,以32位的十六进制表示,用于接受或发送以太币,默认为0x0000000000000000000000000000000000000000

4. 变量

solidity的变量有三种分别是:局部变量(local)、状态变量(state)、全局变量(global)。可以理解为solidity的状态变量是传统编程语言的全局变量,solidity的全局变量是提供有关区块链的信息的函数,比如(block.timestamp用于获取当前的时间戳、msg.sender获取当前账户的地址)

// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {
    // 状态变量
    string text = "Hello";
    uint256 num = 123;
    function doSomething() public view returns(uint256,uint256,address){
        // 局部变量
        uint256 i = 456;
        // 全局变量
        uint256 timestamp = block.timestamp;
        address sender = msg.sender;
        return (i,timestamp,sender);
    }
}

5. 常量

// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {
    address constant MY_ADDRESS = 0x0000000000000000000000000000000000000000;
    function get() public pure returns(address) {
        return MY_ADDRESS;
    }
}

这里解释一下 使用函数返回常量的值为什么使用pure修饰而不是view

因为constant修饰的是常量,常量的值是硬编码的,而pure修饰的函数就通常返回一些数学运算或者硬编码的值,view修饰的函数多用于查看状态变量或者全局变量。真因为常量的值是硬编码,所以可以节省gas费用。

6.不可变的

// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {
    address immutable MY_ADDRESS;
    constructor() {
        MY_ADDRESS = msg.sender;
    }
    function get() public view returns(address) {
        return MY_ADDRESS;
    }
}

和常量很像,使用immutable修饰不可修改,多用于在部署合约时确定合约账户地址。

在定义常量和不可改变量时的标识符通常用大写来表示

7. 读取和改变状态变量

// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {
    uint256 num;
    function set(uint256 _num) public {
        num = _num;
    }
    function get() public view returns(uint256) {
        return num;
    }
}

和我们第一个应用程序很像,读取变态变量不消耗gas,修改状态变量消耗gas

通常函数里的形参名称开头用_开头

8. ether与wei

ether和wei是以太坊用于交易的单位

1 ether = 1 * 10的18 次方 wei

在solidity中定义的uint或int默认的单位都是Wei

// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {
    uint256 oneWei = 1 wei;
    uint256 oneEther = 1 ether; 
    function fun1() public view returns(bool) {
        bool isOneWei = oneWei == 1;
        return isOneWei;// 返回true
    }
    function fun2() public view returns(bool) {
        bool isOneEther = oneEther == 1e18;
        return isOneEther;// 返回true
    }
}

9. Gas

在以太坊网络上进行交易,都需要支付一定的gas费用,举例子说明一下

设置Gas Limit。假设小明在以太坊网络上要部署合约,就是相当于一次交易。在部署合约时需要设置一个交易的Gas Limit这是小明愿意为这个交易使用的最大Gas量。假设设置的Gas Limit为300000Gas

设置Gas价格。接下来,小明需要设置一个Gas价格,这表示他愿意为每单位Gas支付的以太币数量。假设设置的Gas价格是50Gwei

计算交易费用。当小明的交易被执行时,实际上可能并不需要消耗全部的300000Gas,假设部署合约这个交易消耗了80000Gas。那么小明实际需要支付的Gas费用是:"消耗的Gas * Gas价格 = 80000Gas * 50Gwei = 4000000Gwei或0.004ETH" 。

未消耗的Gas退还。由于小明设置的Gas Limit是300000,但实际消耗了80000,剩余的220000Gas会回滚。

区块的Gas Limit。区块的Gas Limit是由以太坊网络决定的,限制一个区块内所有交易可以消耗的总Gas量。假设当前的区块Gas Limit是15,000,000 Gas,这意味着所有包含在这个区块中的交易,它们消耗的Gas总和不能超过这个限制。如果一个单独的交易超出区块的Gas Limit,这意味着当前交易太大,不能被任何矿工在单个区块中处理,所以矿工会忽略这样的交易。直到它被发送方取消或替换,或者网络的条件改变(例如区块的Gas Limit增加)。即便交易的Gas消耗没有超过区块的Gas Limit,但如果区块内已经包含的其他交易的Gas总和加上这个交易的Gas超过了区块的Gas Limit,那么这个交易将不会被包含在当前区块中。它会留在交易池中,等待下一个或未来某个区块有足够的空间。

10.if else

// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {
    function fun(uint256 _num) public pure returns(uint256) {
        if(_num < 10) {
            return 0;
        } else if (_num < 20) {
            return 1;
        } else {
            return 2;
        }
    }
    function fun1(uint256 _x) public pure returns(uint256) {
        // if(_x < 10) {
        //     return 1;
        // } else {
        //     return 2;
        // }
        return _x < 10 ? 1 : 2; // 相当于上段代码
    }
}

solidity支持if,else if,else的写法,不过在区块链中进行判断多用于require(),因为可以进行回滚操作

11. for、while、dowhile

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

contract First {
    // 100以内的偶数和
    function fun() public pure returns (uint256) {
        uint256 sum = 0;
        for (uint256 i = 0; i <= 100; i++) {
            if (i % 2 == 0) {
                sum += i;
            }
        }
        return sum;
    }

    // 100以内的奇数和
    function fun1() public pure returns (uint256) {
        uint256 sum = 0;
        uint256 i = 0;
        while (i <= 100) {
            if (i % 2 != 0) {
                sum += i;
            }
            i++;
        }
        return sum;
    }
    // 100以内的数字之和
    function fun2() public pure returns(uint256) {
        uint256 sum = 0;
        uint256 i = 0;
        do {
            sum += i;
            i++;
        } while(i <= 100);
        return sum;
    }
}

同样solidity支持for、while、dowhile的写法。这个例子也很好的诠释了pure修饰符的用处

12. mapping

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

contract First {
    mapping(address => uint256) myMap;
    mapping(address => mapping(uint256 => bool)) nested;
    function get(address _addr) public view returns(uint256) {
        return myMap[_addr];
    }
    function set(address _addr,uint256 _i) public {
        myMap[_addr] = _i;
    }
    function remove(address _addr) public {
        delete myMap[_addr];
    }
    function get1(address _addr,uint256 _i) public view returns(bool) {
        return nested[_addr][_i];
    }
    function set1(address _addr,uint256 _i,bool _boo) public{
        nested[_addr][_i] = _boo;
    }
    function remove1(address _addr,uint256 _i) public {
        delete nested[_addr][_i];
    }

}

mapping映射的key可以是任何值类型、字节、字符串、或者合约类型

                        value可以是任何类型,包括另一个映射或数组

13.数组

solidity的数组分为动态数组、静态数组和内存数组。

动态数组

// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract Second {
    // 动态数组
    uint256[] arr;
    // 添加数组元素
    function push(uint256 _num) public {
        arr.push(_num);
    }
    // 根据索引获取数组元素
    function getIndexArr(uint256 _i) public view returns(uint256) {
        return arr[_i];
    }
    // 获取数组
    function getArr() public view returns(uint256[] memory) {
        return arr;
    }
    // 获取数组的长度
    function getArrLength() public view returns(uint256) {
        return arr.length;
    }
    // 删除数组中最后一个元素,会使数组的长度 -1
    function pop() public {
        arr.pop();
    }
    // 根据索引重置元素为默认值,不会删除数组的长度
    function remove(uint256 index) public {
        delete arr[index];
    }

}

动态数组是solidity中常用的形式

静态数组

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

contract Second {
    // 静态数组
    uint256[10] arr = [1, 2, 3, 4, 5];

    // 根据索引获取数组元素
    function getIndexArr(uint256 _i) public view returns (uint256) {
        return arr[_i];
    }

    // 获取数组
    function getArr() public view returns (uint256[10] memory) {
        return arr;
    }

    // 获取数组的长度
    function getArrLength() public view returns (uint256) {
        return arr.length;
    }

    // 静态数组添加元素
    function addArr() public {
        for (uint256 i = 0; i < 5; i++) {
            arr[i + 5] = i;
        }
    }
}

在静态数组中,不可以使用push pop函数等

内存数组

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

contract Second {
    function fun() external pure returns(uint256[] memory){
        uint256[] memory arr = new uint256[](5);
        return arr;
    }
}

这里的external代表着只能从合约外部调用,不能被合约外部的其他函数调用

小案例

我们使用solidity想要删除数组,无非是使用 delete 和pop函数,可它们的缺点也很明显,delete函数只能根据索引重置数组内容为默认值,pop函数只能删除数组中的最后一个。但是我们想要的效果是根据索引删除元素,该如何实现呢?

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

contract Frist {
    uint256[] arr;
    function remove(uint256 index) public {
        
        for(uint256 i = index;i< arr.length - 1;i++) {
            arr[i] = arr[i + 1];// 将想要被删除的数替换为想要被删除的数之后的一位
        }
        arr.pop();
    }
    function getArr() public view returns(uint256[] memory) {
        return arr;
    }
    function test() public {
        arr = [1, 2, 3, 4, 5, 6];
        remove(1);
        // [1, 3, 4, 5, 6]
        assert(arr.length == 5);
        assert(arr[0] == 1);
        assert(arr[1] == 3);
        assert(arr[2] == 4);
        assert(arr[3] == 5);
        assert(arr[4] == 6);
    }
}

这里为了测试用到了断言语句,assert如果值相等才会往下执行

14. 枚举

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

contract Frist {
    enum Status {
        Pending,
        Shipped,
        Accepted,
        Rejected,
        Canceled
    }
    Status status;
    function get() public view returns(Status) {
        return status;// 默认值为枚举类型的第一个值
    }
    function set(Status _status) public {
        status = _status;
    }
    function cancel() public {
        status = Status.Canceled;
    }
    function reset() public {
        delete status;// 重置枚举类型为默认值
    }
}

15. 结构体

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

contract Frist {
    struct Todo {
        string text;
        bool completed;
    }
    Todo[] todos;// 结构体数组
    function create(string calldata _text,bool _completed) public{
        // 添加结构体数组的三种方式
        // 第一种
        todos.push(Todo(_text,_completed));


        // 第二种
        /**
            todos.push(Todo({
            text: _text,
            completed: _completed
         }));
        **/


        // 第三种
        /**
            Todo memory todo;
            todo.text = _text;
            todo.completed = _completed;
            todos.push(todo);
        **/

        
    }
    // 根据数组索引找到对应的结构体数据
    function get(uint256 _index) public view returns(string memory text,bool completed) {
        Todo memory todo = todos[_index];
        return (todo.text,todo.completed);
    }
    // 返回整个结构体数组
    function getTodos() public view returns(Todo[] memory) {
        return todos;
    }
    // 更新结构体数据
    function update(uint256 _index,string memory _text) public {
        Todo storage todo = todos[_index];
        todo.text = _text;
        todo.completed = !todo.completed;
    }
}

结构体常常和数组结合用来存储某一类的数据。

我们常说区块链是透明,不可更改等等,都只是狭义上的理解,这些概念都需要一个精确的解释。我们知道在solidity中状态变量就相当于上链的数据。为什么在这段代码里就可以修改状态变量status里的值呢?区块链不可更改就意味着一旦数据被记录到区块链上,就无法删除或篡改,每一个新区块都包含前一个区块的哈希值,在逻辑上形成了链。如果尝试更改链中的任何信息,都会使后续所有的区块哈希值无效。然而,不可更改并不意味着区块链上的应用状态不能变化。在solidity中状态变量的值可以随着新的交易执行而改变,创建一个新的区块,更新的状态也就在记录在链上了。所以说区块链上的记录是一个不可更改的操作历史,但数据的当前状态是可以通过智能合约的逻辑来改变。这个例子也很好的证明了区块链是公开透明的,因为每次状态的改变都是可查看、可验证的。

16.  数据存储位置

数据存储位置有Storage,Memory,Calldata

Storage

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Frist {
    struct MyStruct {
        uint256 num;
        string name;
    }
    mapping(uint256 => MyStruct) myStructs;
    function getMyStruct1() public view returns(MyStruct memory) {
        return myStructs[0];
    }
    function fun1() public {
        MyStruct storage myStruct = myStructs[0];
        myStruct.num = 666;
        myStruct.name = "Hello";
    }
}

让我们来解释一下这段代码的意思。首先定义了一个MyStruct结构体和map映射。在函数fun1()里,定义了一个MyStruct实例,使用storage修饰,并引用了map键为0的映射。这样在修改MyStruct实例的时候,实际会改变状态变量myStruct键为0的映射。getMyStruct1()是为了测试查看map映射的结果

Memory

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Frist {
    struct MyStruct {
        uint256 num;
        string name;
    }
    function fun1() public pure returns(MyStruct memory){
        MyStruct memory myStruct = MyStruct({
            num: 666,
            name:"hello" 
        });
        return myStruct;
    }
}

memory声明的MyStruct实例,只有在函数运行期间有效,是一个临时的存储。

特别说明一下当结构体里的数据只有一个参数时,可以使用以下代码来初始化一个结构体

struct MyStruct {
        uint256 num;
    }
MyStruct memory myMemStruct = MyStruct(0);

Calldata

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract First {
    struct MyStruct {
        uint256 num;
        string name;
    }
    function fun1(MyStruct calldata myStruct) external pure returns(MyStruct calldata){
        return myStruct;
    }
}

calldata修饰的变量,是只读的,不能修改,通常和external搭配,使用外部的数据来给calldata修饰的变量赋值。

注意:在solidity0.8.0以后的版本才支持上述写法,在remix传入参数的值为数组形式[666,"hello"]

之后写的代码全是以0.5.0以上0.6.0以下版本书写

17. 函数

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

contract First {
    // 函数可以返回多个值
    function returnMany() public pure returns(uint256,bool,uint256) {
        return(1,true,2);
    }
    // 也可以给函数的返回值命名
    function named() public pure returns(uint256 x,bool b,uint256 y) {
        return(1,true,2);
    }
    // 可以将返回值赋给他的名称,这种情况下可以省略return
    function assigned() public pure returns(uint256 x,bool b,uint256 y) {
        x = 1;
        b = true;
        y = 3;
    }
    // 调用另一个函数时,也可以使用解构赋值
    function destructuringAssignments() public pure returns(uint256,bool,uint256,uint256,uint256) {
        // 用解构赋值的语法接收函数返回的值
        (uint256 i,bool b,uint256 j) = returnMany();
        // 返回的值也可以省略
        (uint256 x,,uint256 y) = returnMany();
        return(i,b,j,x,y);
    }
    // 不能使用map作为函数的输入值和输出值,但可以使用数组
    function arrayInput(uint256[] memory _arr) public {
    }

}

关于函数的细节还是挺多的,知道不用和不知道也完全时两码事

17. pure和view 

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

contract First {
    uint256 public x = 1;
    function addToX(uint256 y) public view returns(uint256) {
        return x + y;
    }
    function add(uint256 i,uint256 j) public pure returns(uint256) {
        return i + j;
    }

}

这两个修饰符也提到过很多次了

view修饰的函数只能查看状态变量或者全局变量

pure修饰的函数不能查看、修改状态变量或者全局变量

什么都不写代表着要更新状态变量

18.Error

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

contract First {
    uint256 x = 1;
    function getX() public view returns(uint256) {
        return x;
    }
    function testRequire(uint256 _i) public{
        x = 2;
        // 条件不成立时,会回滚之前的操作
        require(_i > 10, "输入的数字必须大于10");
    }

    // 这段代码和testRequire函数的作用完全一样
    function testRevert(uint256 _i) public {
        x = 3;
        if(_i <= 10) {
            revert("输入的数字必须大于10");
        }
    }
    // assert多用于测试内部的错误
    function testAssert() public view {
        assert(x == 1);
    }
}

上述代码说明了require、revert、assert的作用,在高版本的solidity出现了自定义的错误,这种自定义错误可以节省gas费。以小案例来演示以下自定义错误的语法

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract First {
    error InsufficientBalance(uint256 balance,uint256 withdrawAmount,string msg);
    function testCustomError(uint256 _withdrawAmount) public view returns(uint256){
        uint256 bal = address(this).balance;// 返回当前合约的账户余额
        if(bal < _withdrawAmount) {
            revert InsufficientBalance({
                balance:bal,
                withdrawAmount:_withdrawAmount,
                msg:unicode"当前的账户余额小于输入值"
            });
        }
        return 0;
    }
    // 返回当前的账户余额,用于测试,因为当前账户没有余额所以返回的是默认值0,
    function getBalance() public view returns(uint256) {
        return address(this).balance;
    }
}

这个小案例用到了address(this).balance,这就之前一直提到的全局变量,这条语句的作用是将当前合约实例转化为地址类型,从而可以调用balance属性来获取当前合约账户的余额。

这里要特别理清三个概念。智能合约账户,智能合约地址,外部账户。这三个概念在区块链世界中容易混淆。在实际使用中,当我们说到“智能合约地址”,我们可能是在讨论与之交互的方法,例如发送以太币到这个地址或是调用它的函数。而当我们提及“智能合约账户”,则更多地在关注合约的状态和行为——例如它的余额、它能执行哪些操作,以及它存储了哪些数据。而外部账户用于部署合约,发送交易。

19. Modifier

// SPDX-License-Identifier:MIT
pragma solidity ^0.5.0;
contract Second {
    address public owner;
    uint256 public x = 10;
    bool public locked;
    // 构造函数,在合约部署的时候自动调用
    constructor() public{
        owner = msg.sender;
    }
    modifier onlyOwner() {
        require(msg.sender == owner,"你不是合约部署者");
        // 执行完了require再执行剩余用onlyOwner修饰的函数代码
        _;
    }
    modifier validAddress(address _addr) {
        require(_addr != address(0),"无效的地址值");
        _;
    }
    // 只有合约拥有者且地址值有效才可以修改合约拥有者
    function changeOwner(address _newOwner) public onlyOwner validAddress(_newOwner){
        owner = _newOwner;
    }
    // 防止用noReentrancy修饰的函数在执行的过程中再次调用
    modifier noReentrancy() {
        require(!locked,"不可重入");// 确保当前没有其他操作锁定了合约
        locked = true;// 在执行操作前锁定合约
        _;// 执行被修饰的函数的体
        locked = false;// 执行完毕后解锁合约,允许其他操作
    }
    function decrement(uint256 _i) public noReentrancy returns(uint256){
        x -= _i;
        if(_i > 1) {
            decrement(_i - 1);
        }
        return x;
    }
}

Modifier修饰符是可以在函数调用之前和之后运行的代码,多用于限制访问验证和防止重入攻击

19. Events事件

// SPDX-License-Identifier:MIT
pragma solidity ^0.5.0;
contract Second {
    event Log(address indexed sender,string message);
    event AnotherLog();
    function test() public {
        emit Log(msg.sender,"Hello World");
        emit Log(msg.sender,"Hello EVM");
        emit AnotherLog();
    }
}

事件是一种智能合约内部记录和触发日志信息的机制,在执行操作时向外部发送信息,在外部就可以被应用程序监听和处理。

indexed表示为地址添加索引,而动态类型的参数(stringbytes),由于其数据大小的不确定性,不能被设置为indexed。

20. constructor

// SPDX-License-Identifier:MIT
pragma solidity ^0.5.0;
contract X {
    string public name;
    constructor (string memory _name) public {
        name = _name;
    }
}
contract Y {
    string public text;
    constructor(string memory _text) public{
        text = _text;
    }
}
contract B is X("继承X"),Y("继承Y"){}

contract C is X,Y {
    constructor(string memory _name,string memory _text) X(_name) Y(_text) public{}
}
contract D is X,Y{
    constructor() X("继承X") Y("继承Y") public {}
}
contract E is X,Y{
    constructor() Y("继承Y") X("继承X") public {}
}

注意

is关键字表示继承。构造函数 在部署合约的时候自动调用,注意就是在低版本的solidity中需要在constructor后面加上public修饰符,在高版本中就不需要加了。

21. 继承

继承函数

// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract A {
    function foo() public pure virtual returns(string memory) {
        return "A";
    }
}
contract B is A {
    function foo() public pure virtual override returns(string memory) {
        return "B";
    }
}
contract C is A {
    function foo() public pure virtual override returns(string memory) {
        return "C";
    }
}
contract D is B,C {
    function foo() public pure override(B,C) returns(string memory) {
        return super.foo();
    }
}
contract E is C, B {
    function foo() public pure override(C, B) returns (string memory) {
        return super.foo();
    }
}
contract F is A, B {
    function foo() public pure override(A, B) returns (string memory) {
        return super.foo();
    }
}

    /* 
    A
   / \
  B   C
 / \ /
F  D,E
    */


在solidity中支持多重继承,在高版本中父合约需要被重写的函数要加上virtual修饰,子合约中需要重写父合约函数要加上override修饰。调用父合约的函数需要super

继承状态变量 

// SPDX-License-Identifier:MIT
pragma solidity ^0.5.0;
contract A {
    string public name = "Contract A";
    function getName() public view returns(string memory) {
        return name;
    }
}
contract B is A {
    function setName() public returns(string memory) {
        name = "Contract B";
    }
}

继承下来的状态变量可以直接修改

调用父合约

// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract A {
    // 方便追踪函数
    event Log(string message);
    function foo() public virtual {
        emit Log("A.foo called");
    }
    function bar() public virtual {
        emit Log("A.bar called");
    }
}
contract B is A {
    function foo() public virtual override {
        emit Log("B.foo called");
        A.foo();
    }
    function bar() public virtual override {
        emit Log("B.bar called");
        super.bar();
    }
}
contract C is A {
    function foo() public virtual override {
        emit Log("C.foo called");
        A.foo();
    }
    function bar() public virtual override {
        emit Log("C.bar called");
        super.bar();
    }
}
contract D is B, C {
    function foo() public override(B, C) {
        super.foo();
    }

    function bar() public override(B, C) {
        super.bar();
    }
}



调用父合约可以使用合约名或者super关键字

22. 可见性

函数

// SPDX-License-Identifier:MIT
pragma solidity ^0.5.0;
contract A {
    // 私有函数只能被合约内部调用,继承后不能被调用
    function privateFunc() private pure returns(string memory) {
        return "私有函数被调用了";
    }
    function testPrivateFunc() public pure returns(string memory) {
        return privateFunc();
    }
    // 内部函数可以被合约内部调用,也可以被继承调用
    function internalFunc() internal pure returns(string memory) {
        return "内部函数被调用了";
    }
    function testInternalFunc() public pure returns(string memory) {
        return internalFunc();
    }
    // 公共函数可以被外部的账户或合约调用
    function publicFunc() public pure returns(string memory) {
        return "公共函数被调用了";
    }
    // 外部函数只能被外部的账户或者合约调用,合约内部不可以调用
    function externalFunc() external pure returns(string memory) {
        return "外部函数被调用了";
    }
}

之前的操作中或多或少都遇到过,现在把它们总结一下

函数的修饰符有public、private、internal、external

public:任何合约或者账户都可以调用

private:只有在合约内部才可以调用

internal:只有在合约内部或者继承的合约才可以调用

external:只有其他外部合约或者外部账户才可以调用

状态变量

// SPDX-License-Identifier:MIT
pragma solidity ^0.5.0;
contract A {
    string private privateVar = "私有状态变量";
    string internal internalVar = "内部状态变量";
    string public publicVar = "公共变量";
}

状态变量的修饰符只有三种分别是public,private,internal,没有external修饰符,作用和函数类似。

23. Interface

24.案例

合约投票

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

contract First {
    // 表示一个投票人
    struct Voter {
        uint256 weight; // 通过代理积累的权重
        bool voted; // 表示是否已经投过票
        uint256 vote; // 选择的提案编号
    }
    // 表示一个提案
    struct Proposal {
        bytes32 name; // 提案名称
        uint256 voteCount; // 积累的投票数量
    }
    address public chairperson; // 主席
    // 保存从地址到投票人数据的映射
    mapping(address => Voter) public voters;
    // 将提案保存在数组里
    Proposal[] public proposals;

    // 基于一组提案,构建一个投票协约
    function Ballot(bytes32[] memory proposalNames) public {
        chairperson = msg.sender; // 投票协约的创建人是主席
        voters[chairperson].weight = 1; // 主席的投票权重是1
        // 针对每一个提案名,创建一个对应的提案,并且保存在Proposals数组中
        for (uint256 i = 0; i < proposalNames.length; i++) {
            proposals.push(Proposal({
                name: proposalNames[i], 
                voteCount: 0
            }));
        }
    }
    // 主席给予一个人的投票权利
    function giveRightToVote(address voter) public{
        require(msg.sender == chairperson,"只有主席才能给予一个人有投票的权力");
        require(!voters[voter].voted,"选民已经投过票了");
        require(voters[voter].weight == 0);
        voters[voter].weight = 1;
    }
    // 进行投票
    function vote(uint proposal) public {
        // 使用当前账户进行投票
        Voter storage sender = voters[msg.sender];
        require(!sender.voted,"选民已经投过票了");
        sender.voted = true;
        sender.vote = proposal;
        proposals[proposal].voteCount += sender.weight;
    }
    // 计算出胜者的提案
    function winningProposal() public view returns(uint256 winningProposal_) {
        uint winningVoteCount = 0;
        for(uint256 p = 0; p< proposals.length;p++) {
            if(proposals[p].voteCount > winningVoteCount) {
                winningVoteCount = proposals[p].voteCount;
                winningProposal_ = p;
            }
        }
        return winningProposal_;
    }
    // 返回胜出提案的名称
    function winnerName() public view returns(bytes32 winnerName_) {
        winnerName_ = proposals[winningProposal()].name;
        return winnerName_;
    }
}

这个案例从github上找到的,写的很详细,很适合新手来钻研其中的逻辑,这里我去除了代理人的逻辑,因为比较难,后续有时间再加上。

这个案例有三个角色,分别是提案,投票者,主席。基于这三个角色的互动实现了基础的投票

 还有很多优化的地方比如使用构造器来确定主席的地址等等,后续再改

  • 12
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Solidity是一种用于编写智能合约的编程语言。它是专门为以太坊平台设计的,用于创建去中心化应用程序(DApps)。Solidity支持多种特性,包括枚举和结构体。 枚举(enum)是一种可用来创建由一定数量的常量值构成的自定义类型。在Solidity中,枚举类型可以用来定义一组相关的状态或选项。例如,你可以使用枚举来定义一个投票合约中的不同状态,如"Created"、"Locked"和"InValid"。\[1\] 结构体(struct)是一种可以将多个变量分组的自定义类型。在Solidity中,结构体可以用来定义一个包含多个属性的数据结构。例如,在一个投票合约中,你可以使用结构体来定义一个投票人的属性,如权重、是否已投票、委托地址和投票选项。\[3\] 通过使用Solidity的枚举和结构体,你可以更好地组织和管理智能合约中的数据和状态。这些特性使得Solidity成为开发去中心化应用程序的强大工具。如果你想学习更多关于Solidity的内容,可以查阅Solidity的官方文档和教程。 #### 引用[.reference_title] - *1* *2* *3* [Solidity入门(1)](https://blog.csdn.net/weixin_49489840/article/details/124184205)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值