• solidity笔记


    dd说明:基于solidity 0.8.0
    参考地址:> https://www.bilibili.com/video/BV1Ra411x7Gv/?spm_id_from=pageDriver

    1 变量

    常用变量类型

    bool int uint无符号整形 bytes32(最大为32位长度) address(地址类型) 
    
    常量定义,用constant关键字,一般用大写字母作为变量名
    int constant AAA=123;
    
    • 1
    • 2
    • 3
    • 4

    数组

    memory的数组只能是定长的
    remove方法没有,需要自己实现

    contract B{
    uint[] arr1=[1,2,3,4];
    uint[2] arr2=[1,2];
    function exeu() public{
        arr1.push(3);
        uint i1=arr1[1]; 
        arr1.pop();
        delete(arr1[1]); //重置为默认值
        uint len=arr1.length;
    }
    function exeu1() public pure{
        uint[] memory arr= new uint[](5);//new 关键字定义
        //不能使用pop和delete,memory变量必须是定长的 
    }
    function _get() external view returns(uint[] memory){
        return arr1;
    }
    function remove(uint index) public {
        require(index
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    }

    mapping 映射

    contract map{
        mapping (address=>uint) public _balance;
        mapping(address=>mapping(address=>bool)) public _isFriend;
        function balanceAdd(uint mount_)public returns(uint){
            _balance[msg.sender]+=mount_;
            return _balance[msg.sender];
        }
        function setFriend(address b,bool flag)public returns(bool){
            _isFriend[msg.sender][b]=flag;
            return _isFriend[msg.sender][b];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    struct srorage,calldata和memory类型

    参数为string, struct,数组等时,需要添加memory关键词,如果是简单类型则不需要添加此关键字
    calldata只能用于参数中,使用calldata可以节约gas

    contract struct_{
        struct Car{
            string name;
            uint year;
            }
        Car public _car;
        Car[] public _cars;
        function _d()public {
            _car = Car({name:"tesla",year:2000});
            _cars.push(Car('baoma',2001));
            Car memory tmp_car;
            tmp_car.name='baoma x';
            tmp_car.year=2012;
            _cars.push(tmp_car);
            //storage 定义的值可以直接修改状态变量,但是如果定义为memory变量,修改值之后只是再evm中修改,并不会修改链上数据
            Car storage sCar = _cars[0];
            sCar.year= 1000;
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    枚举

    contract Enum{
        enum Sta{
            Init,Suc,Fail
        }
        Sta public _sta;
        //实际处理时,枚举类型按照顺序,依次被赋值为0,1,2...,所以传入值为uint类型
        function setSta(Sta sta_)public{
            _sta=sta_;
        }
        function isSuc()view public returns(bool){
            return _sta==Sta.Suc;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    同一个变量,如果定义为常量会省gas费,而作为变量会更耗费gas费

    event emit

    emit 发送一个事件(需要写权限,会消耗gas)
    event定义一个事件
    event(address indexed,uint); indexed可以让事件可以被索引,事件会保存到log中,也可以检索,使用log保存数据更加节约gas,但是一般都保存一下日志信息

    基本知识

    定义协议

    // SPDX-License-Identifier: MIT     
    
    • 1

    设置编译版本

    pragma solidity ^0.8.8;
    
    • 1

    定义方法,其中
    external权限为可以再外部调用,
    pure表示不可读不可写链上数据的权限,
    returns定义了返回的数据类型

    function add(uint a,uint b)external pure returns(uint) {
        return a+b;
    }
    
    • 1
    • 2
    • 3

    returns三种写法,以及多返回值接收

       function a1() public pure returns(uint,address){
            return (1,address(1));
       } 
       function a2() public pure returns(uint i,address addr){
            return (1,address(1));
       } 
       function a3() public pure returns(uint i,address addr){
           i=1;
           addr = address(1);
       } 
        function a4()public pure  returns(uint,address){
       		(uint i1,address addr1) = a1();
       		//(,address addr1) = a1(); ***第一个返回值不需要可以不写***
      		 return (i1,addr1);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    }

    函数权限

    pure 不可读写状态变量,全局变量
    view可读状态变量,全局变量
    什么都不写,表示可以写状态变量

    可见性

    可以再状态变量和方法中使用
    private只在内部可见
    external 外部合约的方法才可以调用,内部方法不能调用
    public 任意合约(内部方法和外部方法中)都可以调用
    internal只能在内部,或者自类中使用,类似protect

    局部变量

    方法内定义的变量,只在虚拟机内存中存在,不会上链

    成员变量

    (状态变量),会保存在链上,可以修改其内容

    全局变量

    常用的只读的一些变量 msg.sender,block.number,block.timestamp

     function globs() external view returns(address,uint,uint){
        address addr = msg.sender;
        uint timestamp = block.timestamp;
        uint number = block.number;
        return (addr,timestamp,number);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    常用函数

    address() 把字符串或数字转为address类型

    报错

    require(a revert(‘error’) 直接报错
    assert(a 上面三个都支持gas退还

    修改器

    其中modifier 可以定义一个修改器,所有需要检查是否停止的函数都可以加上这个方法名,简化开发
    _; 表示检查后续代码运行的位置是检查条件之后
    修改器可以比较复杂,只要确定好_;放置的位置即可,对于重复性代码有很好的效果
    同一个方法可以带多个修改器,达到多次修改或判断的作用,按使用的先后顺序依次执行

    contract A {
       uint public a;
       bool public pause;
       modifier notPause(){
           require(!pause,"paused can not modify");
           _;
       }
       function  setPause(bool pause_)public{
           pause=pause_;
       }
       function incress()public notPause{
           a++;
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    构造方法

    只能在部署时执行一次

    常用接口contract Ownable

    管理员权限合约,继承后可以方便的使用管理员功能

    contract Ownable{
        address public owner;
        constructor(){
            owner = msg.sender;
        }
        modifier onlyOwner(){
            require(msg.sender==owner,"not owner");
            _;
        }
        function setOwner(address addr) onlyOwner external{
            owner = addr;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    继承

    is关键字,virtual关键字可以让方法可被重写,override关键字标识方法为重写方法

    多继承时,base合约要写在前面 contract A is Basexxx,B

    继承时构造函数传参,可以再继承时直接带参数,也可以再构造方法定义时传入参数,如下

    	contract X is A(111),B{
    		constractor(uint i)B(222);
    	}
    
    • 1
    • 2
    • 3

    调用父合约的方法

    farther.fun();//指定要调用那个父合约的方法
    super.fun();//不指定,如果多个父合约都有同样的方法,会依次调用,如果两个父合约都继承自祖父合约,祖父合约的同一方法只会执行一次
    
    • 1
    • 2

    immutable

    此关键词也可以定义常量,但是可以延迟赋值(构造方法或者定义时),比如访问到msg.sender
    这个关键词较为节约gas费
    但是constant关键词不可以延时赋值,只能立刻赋值

    payable关键词

    函数添加payable可以使得用户调用方法时发送eth
    address类型的状态变量加上payable可以让此地址可以发送eth
    address(this).balance 获取地址的eth数量

    contract Payable{
         address payable public owner;
         constructor(){
             owner = payable(msg.sender);
         }
         //加上payable关键字就可以向合约打款
         function deposit() public payable{
    
         }
         function banlance() public view returns(uint){
            return address(this).balance;
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    回退函数

    receive fallback

         receive() payable external{
         //发送eth并且不带数据时被调用
         //**此方法中不能有逻辑,只能收款,因为gas限制不能超过2300,所以此方法不应该出现在合约中**
     }
     fallback()payable external{
         //发送eth,带有数据时调用
         //receive不存在时,一定走此方法
         **//只能用call方法调用 address.call{value:}()**
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    发送主币

    payable 的地址才可以接受主币
    transfer 带2230gas,不管接收者(可能是合约,之后需要其他写入操作,从而消耗gas)运行时gas够不够,反正就给你留下这么多,不够了就报错revert
    send 也是剩余2230gas,但是返回是否成功,接收的(合约)如果gas不足,就会返回false
    call 剩余的所有gas,都给接受者(合约或普通地址),让接受者合约继续执行剩余逻辑,所以如果接受者需要执行较多的逻辑,用这个方法,导致失败的可能性就低,如果逻辑很少,2230gas就够用了,用前两个方法也可以,反正用这个方法准没错

         function send_eth() public{
             owner.transfer(123);
             bool suc = owner.send(123);
             require(suc,"fail to send eth");
             (bool suc1,)=owner.call{value:123}("");
             require(suc1,"fail to send eth");
     }
    //取出合约上的主币
    function withraw() external onlyOwner{
         owner.transfer(address(this).balance);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    调用其他合约,通过地址

    视频教程
    https://www.bilibili.com/video/BV1V3411W7Cx/?spm_id_from=pageDriver
    通过interface调用其他合约
    定义一个interface,然后使用一个地址实例化此接口,就可以直接调用接口合约的方法了

    interface ICon{
    function aaa(uint a) external view returns(uint);
    }
    contract X{
    //要调用的合约的地址需要知道的
        function callAaa(address constract_)public view returns(uint){
          return  ICon(constract_).aaa(123);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    delegatecall 委托调用

    工厂合约

    new 一个contract对象,会返回一个address(新合约的地址),然后保存起来,相当于一个合约中创建另一个合约,都保存到链上,可以供后期调用等,正常情况下每次部署一个合约,这种情况下一次性可以部署多个合约

    library

    定义一个library ,作为一个工具库,可以直接用名称点调用
    另外可以用useing MathLib for uint; 这样就会让所有的uint都可以使用Mathlib里面的方法

    library Math{
        function add(uint x,uint y)internal pure returns(uint){
            return x+y;
        }
    }
    contract testMath{
        using Math for uint;
        function test()public pure returns(uint){
            uint a = 3;
            return a.add(4);
            //变量名会自动替代add方法中的第一个参数,所以Math中的所有方法,第一个参数都一定是uint类型
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    hash 签名和验证签名

    某些操作可以先签名,后面实际执行时验证,不需要付费也可以验证身份信息
    用到的时候再研究吧

    自毁合约

    代理模式实现可升级合约

    proxy合约实现fallback函数
    fallback函数是当调用的函数不存在时,才会调用的,
    addressx.delegatecall(msg.data) 可以调用指定地址的合约的任意方法
    delegatecall 方式修改的数据都是proxy的数据,及入口合约的数据

    proxy合约和addressx合约的成员变量应该一致,实际操作数据是按插槽序号操作的,业务合约操作第几个插槽(成员变量),proxy就会对应操作第几个插槽(成员变量),而跟成员变量的名字无关(合约编译后没有名字,只有插槽序号),所以proxy合约可以省略定义数据,在业务合约定义数据结构体,实际发生改变的依然是proxy合约里面存储的数据,这个特性使得合约升级时可以扩展新的成员变量(相当于新的数据表) 这就要求proxy合约与业务合约要有相同的成员变量结构,主要是为了有相同的插槽位置

    综上:proxy模式可以升级业务逻辑,通过业务合约调用可以增加成员变量(数据表),这样就达到了既能修改业务逻辑由能新增成员变量

    proxy可以是一张白纸,数据和业务逻辑都由业务合约定义和实现

    proxy可以添加多个业务合约,用来实现不同的功能,比如A1合约可以扩展成token合约,A2合约可以把他扩展成一个nft合约,A3合约可以把它扩展成一个其他合约,这样写出的代码就可以无限扩展下去,拥有几乎无限的可能,需要注意的是业务合约需要有对应的插槽

    addressx合约是实际的业务合约,proxy合约调用addressx合约的方法,而addressx合约可以修改proxy合约的数据,做到了数据与业务分离

    而业务合约addressx的地址由proxy合约持有,可以修改,这样我们可以把持有的地址改成addressy,这样就达到了替换业务合约的目的,达到了合约的升级

  • 相关阅读:
    MySQL常用配置详解
    Modbus-RTU和Modbus-TCP如何进行协议解析和转换
    .NET开源全面方便的第三方登录组件集合 - MrHuo.OAuth
    Cassandra 安装部署
    Android Kotlin 协程初探 | 京东物流技术团队
    Java8新特性(Stream流)
    【数据分析】基于八叉树和损失编码的无损点云压缩附matlab代码
    游戏工作时d3dcompiler_47.dll缺失怎么修复?5种修复方法分享
    Android Studio如何取消与SVN的关联
    2022年9月深圳PMP®项目管理招生啦
  • 原文地址:https://blog.csdn.net/qq_31340657/article/details/125151104