Solidity入门(2022/4/24-)

465 阅读14分钟

EVM

EVM运行在以太坊节点上。 对标JVM,在EVM上运行的是智能合约的字节码形式

搭建测试网络

Ganache geth?忽然好奇怎么搭建区块链的。。源代码是啥样的。。 记得买本计算机网络

1ETH=1e9GWei=1e18Wei

smart contract 法律条文,只有有人触发交易才会自动执行

learnblockchain.cn/2018/01/04/… 区块链实际上不记录余额,账本记录的是每个交易记录,要确定余额,需要计算网络上的所有交易信息

状态变量,局部变量

contract Person{
    int public _age;
    string public _name;
    
    function Person(int age,string name){
        _age = age;
        _name = name;
}

    fuction changePerson(string name){
        var age1=age;
}
}


//其中_age,_name为状态变量,为storage存储
//函数中的形参age,name为局部变量,memory存储
  • 写的还挺好的 blog.51cto.com/zero01/2351…
  • 区块链学习路线图 blog.csdn.net/xilibi2003/…
  • doc docs.soliditylang.org/en/v0.8.13/…
  • solidity语法规范 blog.csdn.net/xilibi2003/… 呃但是在掘金里代码有空行代码就不特殊显示了,算了这样也很难看,有空换成独立代码块 - [ ] 回退函数(fallback fucntion是什么?) 一个合约里(只?)能有一个匿名函数,。。。then?合约的构造函数意义是啥,感觉solidity好像也是面向对象的coding?
  • 参数解构、元组tuple
  • 记得重新学一下分布式
  • 设计模式全忘了捏

view constant pure区别,【再找点别的文章读一下,比如在实际coding的时候什么情况更适合加这些view和pure。】

三个关键词都是不消耗gas,不读取/不改变状态变量 view和constant都是只可读不可改 pure是不可读也不可改

- [ ] external是什么关键字?

以太坊有三个可以存储项目的区域(花的gas是不同的)

1.存储storage:所有合约状态变量所在的位置。 2.内存memory:保存临时值并且在(外部)函数之间调用,便宜。 3.堆栈stack:保存小的临时局部变量,几乎免费,但只能保存有限数量的值 zhuanlan.zhihu.com/p/54167802 4.调用数据calldata:(?有的是123,有的是124,因为版本嘛?) 5.内存数据存储结构,插槽slot blog.csdn.net/rfrder/arti… 6.映射mapping只能存在storage中,数组和结构体可以声明storage或memeory

stroage和memeory的转换问题(?但是我好像没分清楚拷贝和存储和引用的区别,底层概念不清,囫囵吞枣)

1.storage转换为storage,更改的是内存,内存随之改变。 2.storage转换为memory,将数据从storage拷贝到memory中,不会改变原storage变量 3.memory转换为storage,前提是必须吧storage转换为memory类型的,否则会出错 (噢这么说好像很难理解,应该是这个样子a.memory赋值给默认storage的状态变量,可以,实际是将内存变量拷贝到存储中。 !!(高亮) b.memeory赋值给设置为storage的局部变量(默认memeory),不可以。 4.memory转换为memory,引用传递。

solidity的值类型(以下类型在传递时采用值传递)

1.boolean 2.integer:int/uint(uint8-uint256)默认为unit256 uint8=255 3.strinng(Solidity并不支持原生的字符串比较,需要使用keccak256哈希值来进行比较

require(keccak256(_valueName1) == keccak256(_valueName2));
         //如果上述语句为true,则运行下述语句返回hi。否则停止并抛异常
         return "hi"

4.address a.以太坊长度的地址为20个字节 20*8=160位(//40位的16进制)。可以用uint160位的编码。 b.分为外部地址和合约地址(?不太清除具体是啥还,o调用它的地址和合约本身地址?) c.地址之间支持大小比较 d.地址是所有合约的基础,所有合约都会继承地址对象,通过合约的地址串,调用合约的内部函数。//balance是属性,transefer/send等是方法)

每种区块链地址的长度是不同的

a.btc:34 b.eth:42(包含了前缀的0x) c.solana:44 ...

四种类型运算符

1.算术运算 2.逻辑运算 3.增量运算 4.按位运算

msg.sender/msg.value

solidity中最常见的api。还有很多常见的,可见blog.51cto.com/zero01/2351… 1.在solidity中,有些全局变量可以被所有函数调用,msg.sender就是其中之一,它是指当前合约或调用者的address。 2.在solidity中,功能的执行者始终需要从外部执行者来开始。一个合约在公链上不做事情(?是我理解的就是别人只做读操作?没啥用吗),除非有人调用其中的函数,所以msg.sender总是存在。

//更新合约余额
contract MappingExample{
    mapping(address=>uint) public balances;
    //一个更新合约余额的函数,将合约余额更新为newBalance。其中因为uint,所以前面需要mapping函数来从地址映射到uint
    function update(uint newBalance){
       balances[msg.sender] = newBalance;
   }
}

contract MappingUser{//创建用户实例,返回合约更新后的余额
   //通过函数来实现
   fuction getUpdate() returns(uint){
       MappingExample m = new MappingExample();
       m.update(100);
       return m.balance(this);
   }
}

solidity的数据结构

1.struct a.自定义类型,最多16个成员

  • b.*若函数以结构体作为参数,则函数修饰符必有private/internal(??为什么啊) c.结构体内部不可以包含自己本身,但可以有结构体和mapping(?虽然不知道放mapping可以干啥,懒得找例子。。好困。。。再也不熬夜了4/27),并且可以不初始化他们。 2.array a.编译时可以动态,可以固定长度 b.可以是struct类型 c.将array设为public 时自动提供getter方法 【

    一些关于数组的细碎,没找到地儿放

    1.注意固定长度array不能直接转换为动态数组(不能直接returns(bytes(name),先设置动态数组,把数据复制进动态数组,返回该动态数组) 2.动态数组可以直接转(returns(string)),固定长度array不能直接转string,先转动态array再转String 3.solidity支持二维数组,但是懒得仔细看,二维不支持put方法加数据。有空再细看。 】 3.mapping a.mapping(键类型=>值类型),建立键值对的对应关系。 b.例如在msg.sender中的代码里 balances[msg.sender]=newBalance;是指将newBalance这个参数的值和msg.sender这个地址对应起来。 c.key类型有限制,struct不能作为key,实际上key的种类是有限的,比如struct和动态数组,枚举,contract等等都不能作为key d.value类型没有限制,甚至mapping类型也可以 e.solidity中mapping的key存储并不在其中,而是用的这个key的keccak256的值,所以solidity的mapping并没有length的概念,也没有set方法来添加或者设置映射(?其实不太理解为啥没有set,是因为之是自身直接写代码去映射嘛?不需要set来添加新的key-value对应?)。 f.mapping被申明为storage而非memeory(?不懂//嗷好像懂了因为mapping是也是一个合约里声明的数据结构,本质上和integer之类的没区别,所以sturct也是storage类型?) - [ ] stroage/memeory的值传递/指传递并没搞明白(24/4),等下再看,默认局部变量如的函数参数(包括返回值)是memeory,默认状态变量是storage没错吧?晕了怎么写的都不一样,stroage和memory的转换还没搞明白,先搞明白这个吧,//搞明白了已经,写在了上边(25/4)。
//map例子:合约把地址,id和名字联系到一起
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract MappingDemo{
    mapping(address=>uint) mapATU;
    mapping(uint=>string) mapUTS;
    uint public num=0;
    
    //当用户注册时。
    function register(string memory name) public {
        //用户的地址是已知的
        address account = msg.sender;
        num++;
        mapATU[account] = num;
        mapUTS[num] = name;
    }
    

    
    //获取地址所绑定的id
    function getId(address a) returns(unit){
        return mapATU[a];
        //!!和Java其实一样的,不是唯一映射,不能反推,只是键值对,从key推value,这里因为代码的id是直接++,设计的时候逻辑重复,就是不能从id反推address。(所以咋设计id捏。。。忽然愣住)(如下设计)
    } 
}

//oooo噢噢噢噢4/26日,利用后面的modifier函数可以解决这个地址重复注册id的问题
contract MappingModifierDemo{
    mapping(address=>unit) mapATU;
    mapping(unit=>string) mapUTS;
    unit num=0;
    
    //如果是新用户,从来没有注册过id,那么利用他的addres的映射value应该为空
    modifier isnew{
       //mapATU(msg.sender)==null; 。。。呃,这样写对不对,是modifier必须用require嘛??//傻了?我上午在想什么呢??只写一个语句当然不对,这不就直接赋值为null嘛。或许。。我猜if(){}break;可以?但是require只需要一行。
       require(manATU(msg.sender))==0;
    }
    
    //首先把当前地址和unit结合。
    function combine() isnew{
        num++;
        mapATU(msg.sender)=num;
    }
}
  • 变量的作用域

solidity的控制结构

if,else,while,for(;;),do,break,continue...(和java/c之类的没什么区别)

构造函数

5.x以上的版本可以constructor(参数列表){} 4.x的版本只能function 合约名(参数列表){}

miodifier函数

我觉得把本质上可能就是为了能够简单的重用代码而已,也支持有(多个)参数,在调用时传参。但是当其他函数在调用多!个!modifier函数的时候应该注意一下他们语句之间的编译顺序,并不是简单的按顺序执行,会按每个函数第一个语句执行然后再倒叙,说不清楚。。代码能看的很简单。下次遇到再仔细看把。

constract demo01{
     addrress public owner;
     
     function demo01() public{//?不知道构造是不是一定要publci
        owner=msg.sender;
        }
        
     //modifier函数!可以重复使用代码。
     modifier confitAddress{
         reqiire(msg.sender=owner);
      }
      
     fuction a() public confirAddress{
     //会先执行require语句判断是否是当前合约调用者,如果不是则回滚
     //如果是就执行下列语句
         unit a;
     ...
     }  
} 

继承

1.关键字:is 2.继承权限:默认不写、public、internal、external(属性没有这个关键字,方法有)的属性和方法都可以被子类继承和调用。但private修饰的不能 (回忆一下java里其实各个关键字也是,但本质上子类也继承了父类的private的属性方法,只是这些被private修饰的属性和方法都不允许被使用被更改被调用,不知道solidity是不是也是这样) 3.*注意一下public,external和internal的继承范围, a.external可以被合约外部继承,在合约内部不行。 b.internal可以被合约内部继承,在合约外部无效。 (这个内外部??。。emmm不是很理解。所谓外部和内部,看到一句话是这样说的“以remix举例,在内部就是指合约内部可以调用这个函数,在外部就是指合约部署之后可以在旁侧看到这个函数的按钮。”) 4.支持多重继承 contract child is father,mather{} 5.继承external的使用方法 使用this

//创建带external方法的父类
contract AnimalDemo{
    function eat() external return(string){
        return "eat sth";
    }
    
    //external关键字不允许直接在合约内部调用,想要调用要用到this关键字
     function eatExternal return(string){
        return this.eat();    
}   
}

//创建调用external方法的子类
contract CatDemo is AnimalDemo{

    //使用父类对象调用external方法
    function eatExternal() return(string){
        AnimalDemo a = new AnimalDemo();
        a.eat();//..java里私有成员被继承后,不能通过对象访问父类私有属性和私有方法来着对吧……这样solidity是简单了一点,但底层逻辑是什么捏,对象为啥能调用??。。忽然想到,external他不是本来就可以在合约外部调用嘛,。。可以不用这么绕的。。这有什么必要吗。。。可以但没必要的感觉?也可能是我太naive。。如下,可以直接this
    
    //使用this关键字在合约外部调用eaxternal方法
     function eatExternal return(string){
        return this.eat();
}
    }
}

payable关键字

涉及币的转移时要用到,表明调用该函数时可以附带以太币 function pay() public payable{}

构造函数

*别忘了构造里不能用this就行,当前并没有完成创建

fallback function回调函数

1.一个合约里有且只有一个fallback function 2.没名字,没参数,external修饰 3.自己调用不了,子合约调用不了,只有当异常的时候 3.涉及转账的带payable的合约中必须有fallback function,没有paybable修饰的函数,则该合约无法接受别人转账 function() public payable{}

看一下详解www.cnblogs.com/wanghui-gar…

transfer、send异同

1.account.trasfer(msg.sender)/send(1)/call(1);都是向account账户转账,就这点比较反人类。。 2.看烦了。。0.5.x以后的版本好像有大改动。。算了别烦了好像这个很重要。。。。明天看。。。(4/26)

合约间的调用call delegateCall、callCode

gas

1.参考了一下zhuanlan.zhihu.com/p/380038608 2.eth的gas limited的默认金额是21000gas 3.fee=gas used*gas price 4.使用external的函数所消耗的gas少于使用public的,所以当一个函数只能被外部调用时最好使用external关键字 5.有时候觉得modifier和if语句都只有一句话,那么哪个更费钱啊?? 比如说

modifier control(){
    require(msg.sender == owner);
  }
  
function kill() conrtol{
    destruct(msg.sender);
    }

function kill){
    if(msg.sender==owner){
        destruct(msg.sender);
        }
}

。。原来这么看还是能少代码的?这样算少钱了嘛?还是说其实是一样的?比如说pure之类的不是不花gas?如果省钱了的话,那以后要注意不能直接主观臆断modifier更简单省钱。没省钱也注意一下代码的简洁性。

6.不太清楚各个代码都要花费多少,比如新建一个简单函数贵还是新建一个数据贵呢?我猜storage数据更贵?明天再看。。累了。。(2022/4/26) (4/27日view,constant,pure函数都是不消耗gas的,还有external函数消耗gas少)

abi编码

调用合约函数是在以太网中提交了一个行为,这个行为会附带一个数据,即abi的编码数据。solidity提供了api来获取这个编码数据。常用abi.encodeWithSignature【等会可以看一下官方abi

异常处理

比较特殊,没有try/catch。有assert,require,revert。solidity是通过回退来处理异常的(类似数据库事务) 1.发生异常时,会回退当前所有调用及其子调用所改变的状态,并给调用者一个标识,但是所花费的gas是不会退回的。 2.assert:判断内部错误 require:判断输入或外部错误 (呃。。看了半天还是觉得assert和require区别不大。。。主要就是内外部。?但是之前看到得代码里大家内部也用的require?) 3.assert类型异常时,会消耗掉所有gas(?是啥意思,把gas limited全用了??例如死循环啥的?),solidity会执行一个无效操作(指令0xfe) 4.require类型异常时,不会消耗?(为啥不..不是说不退回嘛。。饿了。。等会再看),solidity执行一个回退操作(指令0xfd) 5.在子调用中发生异常时自动上抛,但也有例外,如send和底层的函数调用call,delegatecall,callcode,发生异常时,这些函数返回false。 6.*注意,在一个不存在的地址上调用底层函数call,delegatecall,callcode时也会返回成功,所以在调用时,一定要先进行函数存在性检查

合约销毁/selfdestruct函数

1slefdestruct(address reciepient)将合约销毁并把余额发送到指定地址 function kill() { selfdestruct(this); } 2.允许合约在某个时间时自动停止,然后其他代码放到modifier声明的函数利用require来设置一个expired date。。(有别的关键字嘛,感觉这个嵌套不太合理还好费钱。。。)

  • csdn回我的那个bug评论,关于uint与address 的转换,uint允许2,10,16进制,address是uint160标识,那不是可以直接转换?其他的unit类型通过先uint160来转换成address,那如果小于unit160,会不会丢数据啊。。。?可以强转嘛,明天再看。

ERC20/ERC721/ERC1155

ERC20 zhuanlan.zhihu.com/p/394288720 ERC1155 zhuanlan.zhihu.com/p/389331603 erc接口实现实例blog.csdn.net/BBinChina/a…

SOLIDITY的代理proxy

accessControl

www.cnblogs.com/farwish/p/1…

enum枚举

zhuanlan.zhihu.com/p/89116947

事件

当智能合约被调用,会触发参数存储到日志中。每个交易包含0-n个日志记录,日志代表智能合约所触发的事件。 在Dapp中,如果监听了某事件,当某事件发生时,会进行回调。 我的理解里事件其实就是用在和web3.js的交互。类似java里的监听与前端页面。比如说如果没有事件,当我们在前端登录页面提交了用户名和密码,页面也没有刷新。但是有事件和监听的话,设置触发后,页面会随之刷新。

contract eventDemo{
    string name;
    uint age;
    
    //定义一个事件
    event setInfo(string name,uint age);
    
    function setInfo(string _name,uint _age){
        name=_name;
        age=_age;
        
        //触发这个事件
        emit setInfo(_name,_age);
    }
    
    function getInfo returns(string,uint){
        return name,age;
    }

}

receive函数

using Counters for Counters.Counter;

function random() private view returns (uint256) {
    return uint256(
        keccak256(
            abi.encodePacked(
                block.timestamp
                + block.difficulty
                + uint256(keccak256(abi.encodePacked(block.coinbase))) / block.timestamp
                + block.gaslimit
                + uint256(keccak256(abi.encodePacked(msg.sender))) / block.timestamp
                + block.number
            )
        )
    );
}
for (uint256 i = 0; i < payees.length; i++) {
    (bool sent,) = payees[i].call{value : balance}("");
    require(sent, "withdraw: Failed to send Ether");
}

openzeppling的library都提供了啥。。readMe里面好像有,再看一下

bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

oprnzeppling库里的onlyRole函数源码,提供的OwnerShip blog.csdn.net/super_lixia…

solidity的override也是重写嘛?

编译到部署上链的数据流程

实例blog.csdn.net/m0_46262108… www.cnblogs.com/hushuning/p…

solidity的接口区别不大