solidity基础以及版本变化

本文详细介绍了Solidity中的修饰符(如常量、不可变、访问权限)、数据存储位置(storage、memory、calldata)、方法修饰符(view、pure、require-revert-assert)以及事件和继承机制。还涵盖了如何发送与接收以太坊、构造函数、调用其他合约和创建合约等内容,适合深入理解智能合约开发。

参考网址

此处主要是针对链接中,增加中文理解说明,有需要可查看原文

(文中参考代码主要摘自该链接)https://solidity-by-example.org/

修饰符

初始变量修饰符 constants-immutable

  • constants 常量,硬编码,不可修改
  • immutable 不可变, 可在构造函数中赋值,之后不能修改
  • public 外部可访问,public变量会默认有个getter
  • private/default 私有,外部访问不了,子类可访问

数据位置 storage-memory-calldata

  • storage 全局变量都属于storage,存储在区块中,方法中使用该声明,会对区块信息修改
  • memory 存储在内存中,仅调用函数体内有效,可修改值
  • calldata 主要针对external类方法的参数,和memory的区别是不可以修改该变量

方法修饰符 view-pure

  • view 不会更改任何状态(会读取区块内变量)
  • pure 不会更改任何状态且不读取区块内变量

错误 require-revert-assert

  • require 执行的前置条件,如果这个不满足,无法往下执行, 且有提示语的话,会出现在浏览器和estimateGas提示
  • revert 在某个if判断,可调用revrt,功能和require类似
  • assert 断言某个条件是否为真

常用是require/revert, 可以将错误信息返出去,便于知道错误在哪
assert存在意义在哪不确定,可以使用require替代

另外针对assert的区别。 0.8.x测试没发现和require有什么区别,但是在低版本区别很大
例:0.4.x eth-usdt,可以去查看他的源码
如果一个交易gasLimit设置的100w,实际消耗大概是5w,代码最后面增加(require,revert)/ assert 导致出错

如果使用require/revert, 实际gasUsed = gasPrice * 5w,然后回退了
如果使用assert, 实际gasUsed = gasPrice * 100w, 会把gas消耗完…!

方法修饰符 public-private-internal-external

  • public 公开的,本合约也可以调用,外部也可以调用
  • private 私有的, 仅本合约可以调用
  • internal 内部的, 当前合约和子合约可以调用
  • external 公开的,当前合约自身不可调用

mapping

key=>value 格式

 mapping(address => uint) public myMap;
 mapping(address => mapping(uint => bool)) public nested;
 myMap[_addr] = _i;
 myMap[_addr];
 delete myMap[_addr];
 

数组

pragma solidity ^0.8.7;
contract Array {
    // 初始化数组的几种方式
    uint[] public arr;//可变长度数组,初始长度0
    uint[] public arr2 = [1, 2, 3];//可变长度数组,初始长度3,有对应值
    // 固定长度数组,所有元素是默认值,当前例子为 0,  这个数组不可以push/pop改变长度
    uint[10] public myFixedSizeArr;
    //通过下标获取数组元素
    function get(uint i) public view returns (uint) {
        return arr[i];
    }

    //可返回整个数组,这种方法需要避免长度很长可增长的数组
    //查询方法也受gasLimit限制,查询过多内容时会超限制
    function getArr() public view returns (uint[] memory) {
        return arr;
    }
    
     //数量很长的分页/区间查询
    function getArr1(uint256 pageNo, uint256 pageSize)public view returns(uint256[]memory list) {
        uint len = arr.length;
        uint start = pageNo * pageSize;
        if(len == 0  || start >= len){
            return new uint[](0);
        }
        uint end = start + pageSize;
        if(end > len){
            end = len;
        }
        uint arrLen = end - start;
        list = new uint[](arrLen);
        uint index;
        for(;start < end ; start ++){
            list[index++] = start;
        }
    }
    
    

    function push(uint i) public {
        //追加到数组,数组长度加1
        arr.push(i);
    }

    function pop() public {
        //移除数组的最后一个元素
        arr.pop();
    }
    //返回数组长度
    function getLength() public view returns (uint) {
        return arr.length;
    }

    function remove(uint index) public {
       //delete 操作不对修改数组长度,只是把索引位置的值重置为默认值,当前例子为0
        delete arr[index];
    }
    //如果想移除一个值,且改变数组长度,
    //可以先替换值, 在pop
    //注: 该方式会导致数组值不是原来的插入顺序
    function remove2(uint index)public{
        arr[index] = arr[arr.length-1];
        arr.pop();
    }

    function examples() external {
        // 在内存中创建数组,只能创建固定大小
        uint[] memory a = new uint[](5);
    }
}

struct 结构体

通过struct将相关数据放一起


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

contract Todos {
    struct Todo {
        string text;
        bool completed;
    }

    // An array of 'Todo' structs
    Todo[] public todos;//public声明, 外部可以通过  todos[index],输入index会返回整个结构体

    function create(string memory _text) public {
        // 3种初始化结构的方式
        // 像方法一样顺序传参
        todos.push(Todo(_text, false));

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

        // 初始化一个空的结构体,并针对每个字段赋值, 如果结构体中存在数组/其他结构体时,这种方式合适
        Todo memory todo;
        todo.text = _text;
        //未显视赋值的变量为类型初始值
        // todo.completed initialized to false

        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);
    }
    //如果是低版本的,会要求在文件头部声明 pragma experimental ABIEncoderV2;
    function getObject(uint _index)public view returns(Todo memory){
        return todos[_index];
    }

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

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

modifier

可在函数调用前做一定处理,以及执行之后,再做一定处理

主要功能

  • 限制访问
  • 验证输入
  • 防止重入
    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
       //下划线是一个特殊字符,仅在modifier中, 标志执行方法的其他代码
        _;
    }
    //判断输入的地址不是0地址
    modifier validAddress(address _addr) {
        require(_addr != address(0), "Not valid address");
        _;
    }
    //防止重入,调用函数前先把状态改了, 函数执行完后, 再把状态改回来
    modifier noReentrancy() {
        require(!locked, "No reentrancy");

        locked = true;
        _;
        locked = false;
    }

Events 事件 indexed

简单理解就是日志,便于知道区块内部执行了什么

event Transfer(address indexed from, address indexed to, uint value);

日志定义里面有个indexed修饰符, 最多允许3个参数使用该修饰符
event的数据,在transaction中logs中存在两个不同位置,
indexed 修饰的 在topic中 而其他的在data中

"logs": [
            {
                "address": "0x55d398326f99059ff775485246999027b3197955",
                "topics": [
                    "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
                    "0x000000000000000000000000eb2d2f1b8c558a40207669291fda468e50c12345",
                    "0x000000000000000000000000bcdc55ce32a3d875d440c6fec0354919ab812345"
                ],
                "data": "0x00000000000000000000000000000000000000000000001b1ae4d6e2ef500000"
            }
        ]

logs中说明
address 表示该事件是哪个合约地址的(注意是实际发出的地址, 比如A合约调用B代币, address是B代币的地址,而不是A合约的地址)

topics 是个数组,topics[0] 表示事件名,后面的就是顺序取事件中的indexed修饰的参数
以下针对Transfer事件的说明

topics[0]对应的内容是 keccak256(bytes('Transfer(address,address,uint256)'))
topics[1]对应的内容是事件中的 from
topics[2]对应的内容是事件中的 to

data 是非indexed修饰的参数,顺序取

去掉前面的0x后, 每64位表示一个参数,解析的时候对应参数类型解析即可

Constructor 构造方法

继承多父类是从左到右的顺序

参考下面代码中注释, 链接中描述顺序是Y-X-child 是错的

// SPDX-License-Identifier: MIT
pragma solidity ^0.8
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值