dd说明:基于solidity 0.8.0
参考地址:> https://www.bilibili.com/video/BV1Ra411x7Gv/?spm_id_from=pageDriver
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
}
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];
}
}
参数为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费
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;
}
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{
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();//不指定,如果多个父合约都有同样的方法,会依次调用,如果两个父合约都继承自祖父合约,祖父合约的同一方法只会执行一次
此关键词也可以定义常量,但是可以延迟赋值(构造方法或者定义时),比如访问到msg.sender
这个关键词较为节约gas费
但是constant关键词不可以延时赋值,只能立刻赋值
函数添加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);
}
}
new 一个contract对象,会返回一个address(新合约的地址),然后保存起来,相当于一个合约中创建另一个合约,都保存到链上,可以供后期调用等,正常情况下每次部署一个合约,这种情况下一次性可以部署多个合约
定义一个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类型
}
}
某些操作可以先签名,后面实际执行时验证,不需要付费也可以验证身份信息
用到的时候再研究吧
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,这样就达到了替换业务合约的目的,达到了合约的升级