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;

数组

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

}

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];
    }
}

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;

    }
}

枚举

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;
    }
}

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

event emit

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

基本知识

定义协议

// SPDX-License-Identifier: MIT     

设置编译版本

pragma solidity ^0.8.8;

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

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

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);

}

函数权限

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);
}

常用函数

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++;
   }
}

构造方法

只能在部署时执行一次

常用接口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;
    }
}

继承

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

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

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

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

调用父合约的方法

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

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;
     }
}

回退函数

receive fallback

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

发送主币

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);
 }

调用其他合约,通过地址

视频教程
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);
    }
}

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类型
    }
}

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,这样就达到了替换业务合约的目的,达到了合约的升级

你可能感兴趣的:(solidity,学习)