timezone: Asia/Shanghai
- 自我介绍 我是李磊,很高兴可以参加这次集训
- 你认为你会完成本次残酷学习吗? 肯定会
// SPDX-License-Identifier: MIT pragma solidity ~0.8.21; //声明编译的版本,也可以写成 pragma solidity >0.8.21 <'0.9.0' contract helloworld{ //声明合约名称 string public name = 'helloworld'; //声明属性,必须设置为public,不然不可见 }
第一小节,学习了sol文件的基本格式,编译和部署,第一行是solidity文件的固定格式,下边的都已经添加注释,我目前用的remix客户端进行编写
// SPDX-License-Identifier: MIT pragma solidity ~0.8.21;
contract valueType{
//布尔类型 bool public bol = true; bool public bol2 = !bol; //false bool public bol3 = bol && bol2; //false bool public bol4 = bol || bol; //true bool public bol5 = bol == bol3; //false bool public bol6 = bol !=bol3; //true;
//整数类型 int public num = -1; //整数包含负值 uint public num1 =2; //正整数 uint256 public num2 = 3; //范围大的正整数 uint256 public num3 = num1+1; uint256 public num4 = 2*2; //4 uint256 public num5 = num4%2; //0 bool public bol8 = num4>num5; //true bool bol7 = num4>num5; //无法读取因为没有设置public
//地址类型
address public addr =0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
address payable public addr1 = payable(addr);
uint256 public balance = addr1.balance;
//定长字节数组
bytes32 public byt = "creayzysolidity";
bytes1 public byt1 = byt[0] ;
//枚举类
enum actionList{Buy,Sell,Hold}
actionList public action = actionList.Buy;
function getActionIndex() public view returns(uint idx){
idx = uint(action); //将uint8转为
}
} 第二小节学习了solidity基本的数据类型,如果不声明为public,外界防问不到 bool 布尔类型,判断true,false用来做逻辑运算 uint 正整数,不申明长度就默认为uint256,可以用来做基本的运算 address 地址类型,地址唯一,payable修饰后可以接收转账,比普通用户多了transfer和send方法 余额属于地址的一个变量,可以直接获取 bytes 定长数组,声明时需要定义长度 枚举类型enum 目前不常用
// SPDX-License-Identifier: MIT pragma solidity ~0.8.21;
contract testFunction{
uint256 public number = 5;
constructor() payable {}
function add() external {//外部可以调用
number = number+100;
}
function addpure(uint256 a) external pure returns(uint256 num){//不能看也不能操作链上的元素
num = a;
}
function addpure() external view returns(uint256){
return number;
}
function addview() external view returns(uint256 num){ //只能看不能修改
num = number;
}
function minus() internal { //只能内部调用
number = number-1;
}
function minusCall() external { //只能外部调用
minus();
}
function minusPayable() external payable returns(uint256 balance){
minus();
balance = address(this).balance;
}
} 第三小节,学习了函数的定义,函数的作用范围和权限的概念。 函数可见性关键字: public 内部外部都可见 private 内部可见,不可继承 external 只能外部可见 internal 只能内部可见与private的区别是可继承 权限关键字:pure不能看也不能修改 view只能看 不显式定义的话既可以看也可以修改 payable修饰的话可以支付etl returns 定义返回的类型(不是必须的),方法体内可以return,也可以用returns后边定义的参数结合return使用
// SPDX-License-Identifier: MIT pragma solidity ~0.8.21; contract testReturn{ //返回多个参数 function returnMuliti() external pure returns(uint256 number,string memory name,bool b){ return (1,"test",false); } //命名式返回 function returnNamed() public pure returns(uint256 number,string memory name,bool b){ number =2; name = "test"; b = false; } //解构式读取 function readName() external pure returns(uint256 number,string memory name,bool b){ (number,name,b) = returnNamed(); //返回部分值 (,name,) = returnNamed(); } } 第四小节主要是返回值的相关操作,可以返回多个值,也可以读取别的方式的返回值再选择性的返回 注意点:1.调用别的方法时,被调用方法不能申明成external,否则内部方法无法访问 2.string和array数组,定义时需要用memory修饰
contract testStorage{ uint[] public arr = [2,3,4];
function fstorage() public{ //存储在链上,修改后的值会同步到链上
uint[] storage arr2 = arr;
arr2[0] =100;
}
function fmemory() public view{ //存放在内存上,修改后的值不会同步到链上
uint[] memory arr3 = arr;
arr3[1] =50;
}
function fcalldata(uint[] calldata data) public pure returns(uint[] calldata){//只用来读取参数
return data;
}
function readData() public view returns(uint[] memory a){ //读取arr的值,看是否被修改
a = arr;
}
} 第五小节,变量存储的位置 storage 存储在链上,修改后的值会同步到链上 memory 存储在内存中,修改的值不会同步到链上 calldata 存储在内存中,且不能更改,所以一般用到做参数
contract testStruct{ uint256[] arr1 = new uint256; //动态数组,后边申明的是长度 uint256[3] arr2 = [1,2,3]; //定长数组,申明的是值 function initArray() external pure returns(uint[] memory arr){ uint[] memory a = new uint; a[0] =1; a[1] =2; a[2] =3; arr = a; } uint[] public arr4; function pushpopArray() public returns(uint[] memory ){ arr4.push(2); arr4.push(4); return arr4; } struct student{ string name; uint256 age; }
student stu;
function initStudent()external {
stu = student({name:"test",age:10});
}
function initStudent2() external {
stu.name = "lala";
stu.age = 20;
}
} 学习了动态数组和定长数组的创建和赋值方式,动态数组如果用memory修饰需要定义长度,不能使用pop,push方法 因为pop,push的数组必须存在链上 还学习了结构体的声明,主要写了常用的初始化方式,如果部署后想知道初始化的结果,需要将stu声明成public
// SPDX-License-Identifier: MIT pragma solidity ~0.8.21;
contract Mapping{
mapping(uint=>address) public iptoaddress;
function getData(uint ip) public view returns(address){//根据key获取value
address addr = iptoaddress[ip];
return addr;
}
function writeMap() public {//map赋值
iptoaddress[123] = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
}
} 第七小节mapping的赋值和根据key获取value,相当于java的map,key不能使用自定义的类型 存储位置必须是storage
第八小节学习了不同类型变量的默认值 bool: false,string:"",int uint :0,enum:枚举的第一个值,address:address(0),struct是各个元素的默认值
第九个小节 常量的申明 constant 声明后必须初始化,初始化后不可变 immutable 可在声明货构造器中初始化,更加灵活
第10个小节学习了循环,因为跟java相差不大,只写示例
// SPDX-License-Identifier: MIT pragma solidity ~0.8.21;
contract looptest{ function ifelsetest(uint num) public pure returns(bool){ if (num>0){ return true; }else{ return false; } } function fortest() public pure returns(uint sum){ for (uint i=0;i<10;i++){ sum+=i; } } function forwhile() public pure returns(uint){ uint sum =0 ; uint i=0; while(i<10){ sum+=i; i++; } return sum; } function tenarytest(uint x,uint y) public pure returns(uint a){ return x>y?x:y; } //插入排序 function insertSort(uint[] memory a) public pure returns(uint[] memory){ for(uint i=1;i<a.length;i++){ uint temp = a[i]; uint j=i; while((j>=1)&&temp<a[j-1]){ a[j] = a[j-1]; j--; } a[j] = temp; } return a; } }
contract modifiertest{
address public ownAddress;
constructor (address addr){
ownAddress = addr;
}
modifier onlyOwner{
require (msg.sender == ownAddress); //如果校验通过会进行后边的业务操作
_;
}
function changeOwner(address newAddr) external onlyOwner{
ownAddress = newAddr;
}
} 学习了修饰器和构造器的创建方式,构造器在初始化的时候会并且只会运行一次 修饰器一旦定义可以多次使用,简化了代码,常用来校验权限
contract testEvent{ mapping(address=>uint) public balanceMap; event Transfer(address indexed from,address indexed to,uint value);
function transfer(address from,address to,uint value) public {
balanceMap[from] = 1000;
balanceMap[from] -= value;
balanceMap[to] +=value;
emit Transfer(from,to,value);
}
} 学习了事件的创建和简单使用,使用事件的好处:1.节省gas 2.如果用indexed修饰,会根据索引生成一个topic,保存到日志中,便于查询,最多三个 3.前端可以对该事件进行订阅监听,相应。 4.事件和数据一起保存,保证了安全性
// SPDX-License-Identifier: MIT pragma solidity ~0.8.21; contract older{ event log(string message); function pip()public virtual { //virtual表示需要被继承 emit log("your grandpa is pip"); } function pop() public{ emit log("your grandpa is pop"); } } contract old is older{ function pip() public virtual override {//实现了父类函数,并且需要子类继承 emit log("your baba is pip"); } function popping() public { emit log("popping"); } } contract young is older,old{ //多重继承,从高到低依次申明 function pip() public override(older,old) { emit log("the young is pip"); } function callParentPip() public { super.popping(); //调用的是直接上级的方法 } }
修饰器的继承 contract Base{ modifier exactDividedBy2And3(uint a) virtual{ require(a%3==0 && a%2==0); //校验通过后再进行后边的操作 _; } } contract cal is Base{ function getData(uint num) public exactDividedBy2And3(num) pure returns(uint,uint){ return getRes(num); } function getRes(uint num) public pure returns(uint x,uint y){ x = num%2; y = num%3; } }
继承:函数,构造器和修饰器都可以继承,简化了代码 需要被继承时函数声明为virtual,实现时加override 关键字 多次继承的话需要按优先级申明继承的顺序,如果继承的多个合约都有一个共同的函数,super.函数()会调用上一级的方法 修饰器继承后使用时,跟的参数需要与前边函数调用的参数保持一致
contract errtest{ error transferOwner(); mapping(uint=>address) private owners; function transferNotOwner(uint token,address newOwner) public { if(owners[token] != msg.sender){ revert transferOwner(); } owners[token] = newOwner; } function testRequire(uint token,address newOwner) public view{ require(owners[token] == msg.sender,"transfer not owner"); owners[token] == newOwner; } function testAssert(uint token,address newOwner) public{ assert(owners[token]==msg.sender); owners[token]= newOwner; } }
error 搭配revert使用,可以返回报错信息,gas最少 require 可以自定义返回的报错信息 assert gas最多,返回的错误信息未知 上边mapping没初始化,所以都会报错
pragma solidity ~0.8.21; contract overrideTest{
function getString() public pure returns(string memory){
return "hello";
}
function getString(string memory msg) public pure returns(string memory){
return string.concat("hello",msg);
}
function f(uint8 _in) public pure returns(uint8 out){
out=_in;
}
function f(uint256 _in) public pure returns(uint256 out){
out=_in;
}
}
重载就是可以声明多个相同名字不同参数类型的方法
import "./Strings.sol"; using Strings for uint256; contract LibraryTest{ function getString(uint256 number) public pure returns(string memory){ return number.toHexString(); }
function getString2(uint256 number) public pure returns(string memory){
return Strings.toHexString(number);
}
} 使用库合约跟java的工具类一样,可以直接使用已经定义好的方法 将library导入,在通过using A for B 将A的函数赋予B
// SPDX-License-Identifier: MIT pragma solidity ~0.8.21;
contract otherContract{
uint256 private x=0;
event log(uint amount,uint gas);
function setx(uint256 amount) public payable {
x = amount;
if(amount>0){
emit log(msg.value,gasleft());
}
}
function getx() public view returns(uint256){
return x;
}
function getBalance() public view returns(uint256){
return address(this).balance;
}
}
contract callother{ function callSetx(address addr,uint x)external { // otherContract con = otherContract(addr); // con.setx(x); otherContract(addr).setx(x); } function callGetx(otherContract addr) public view returns(uint){ return addr.getx(); } } 调用其他的合约,跟上边的库合约类似
contract receiveEth{ event recEth(address from,uint256 value); event fallbackEth(address,uint256 value,bytes data); //因为有gas限制,receive和fallback逻辑不能太复杂 receive() external payable { emit recEth(msg.sender,msg.value); } fallback() external payable{ emit fallbackEth(msg.sender, msg.value, msg.data); }
function getBalance() public view returns(uint) {
return address(this).balance;
}
}
contract callEth{ error sendfailed(); //搭配revert使用 error callfailed(); //三种发送eth的方法:call,transfer,send constructor() payable {} receive() external payable { }
function transferEth(address payable to,uint amount) external {
to.transfer(amount);
}
function sendEht(address payable to ,uint amount) external {
bool success = to.send(amount);
if(!success){
revert sendfailed();
}
}
function callerEth(address payable to,uint amount)external {
(bool success, )= to.call{value: amount}("");
if(!success){
revert callfailed();
}
}
} 发送eth和接收eth,地址和结构体需要用payable修饰 发送可以用transfer,call,send transfer 有gas限制2300,失败后会revert send 有gas限制,失败后不会自动revert,一般不会用 call 没有gas限制,常用
import "./other.sol"; contract callOther{ event log(string message);
function callSetx(address payable addr,uint amount) public payable {
(bool success,bytes memory data) = addr.call{value:msg.value}(abi.encodeWithSignature("setx(uint256)", amount));
if(success){
emit log("success");
}
}
function callGetx(address payable addr) public returns(uint256){
(bool success,bytes memory data) = addr.call(abi.encodeWithSignature("getx()"));
if(success){
emit log(string(abi.encodePacked(abi.decode(data, (uint256)))));
}
return abi.decode(data, (uint256));
}
function callnotexist(address payable addr)public{
(bool success,bytes memory data) = addr.call(abi.encodeWithSignature("selects()"));
if(!success){
emit log("the method is not exist");
}
}
} 使用call调用其他的合约方法,abi.encodeWithSignature("setx(uint256)")会找到对应的方法,如果有入参的话在后边指定 call还可以发送eth,addr.call(value:msg.value) 这个value指的是当前合约拥有的eth,也可以手动指定数值 调用不存在的方法时,会自动fallback()
pragma solidity ~0.8.21;
contract c{ uint256 public number; address public addr; function setVars(uint256 num) external{ number = num; addr = msg.sender; } } contract B{ uint public number; address public addr; function callSetVars(address _addr,uint amount) public { (bool success,bytes memory data) = _addr.call(abi.encodeWithSignature("setVars(uint256)", amount)); } function callVars(uint amount,address _addr) public{ (bool success,bytes memory data) = _addr.delegatecall(abi.encodeWithSignature("setVars(uint256)", amount)); } } call调用目标方法直接修改目标的方法的属性 delegatecall 是A调用B资产执行c的代理方法,修改的B的属性值,这会B相当于目标方法,c成了代理方法。
// SPDX-License-Identifier: MIT pragma solidity ~0.8.21;
contract Pair{ address public factory; address public token1; address public token2;
constructor(){
factory = msg.sender;
}
function init(address _token1,address _token2) public{
require(factory == msg.sender);
token1 = _token1;
token2 = _token2;
}
}
contract pairFactory{
mapping(address=>mapping(address=>address)) public getPair;
address[] public allPair;
function createPair(address tokenA,address tokenB)public returns(address addPair) {
Pair pp = new Pair();
pp.init(tokenA, tokenB);
addPair = address(pp); //生成一个当前初始化的地址
allPair.push(addPair);
getPair[tokenA][tokenB] = addPair; //修改map的key,value
getPair[tokenB][tokenA] = addPair;
}
}
contract pairCreate2{ mapping(address=>mapping(address=>address)) public getPair; address[] public allPair;
function createPair2(address tokenA,address tokenB) public returns(address pairAdd){
//create2跟create不一样的地方是多了salt参数
(address token1,address token2) = tokenA>tokenB?(tokenB,tokenA):(tokenA,tokenB);
bytes32 salt = keccak256(abi.encodePacked(token1,token2));
Pair pp = new Pair{salt:salt}();
pp.init(tokenA,tokenB);
pairAdd = address(pp);
allPair.push(pairAdd);
getPair[tokenA][tokenB] = pairAdd;
getPair[tokenB][tokenA] = pairAdd;
}
} create跟create2的区别就是create2需要sa
contract deleteContract{ receive() external payable {
}
event log(address addr);
uint public value=10;
constructor() payable {}
event log(string mesg);
function deletecontract() public{
emit log(msg.sender);
selfdestruct(payable (msg.sender));
}
function getBalance() external view returns(uint){
return address(this).balance;
}
} 删除合约,删除后eth会返回到调用方 import "./deleteContray.sol"; contract deployContract{ constructor() payable{} event log(address addr,uint balance,uint value); error callfailed();
struct DemoResult{
address addr;
uint balance;
uint value;
}
function getBalance() external view returns(uint){
return address(this).balance;
}
//先给deleteContract发送ETH
function callEth(address payable addr,uint amount) public{
(bool success,bytes memory data) = addr.call{value:amount}("");
if(!success){
revert callfailed();
}
}
function deploycontract() public payable returns(DemoResult memory){
deleteContract ddl = new deleteContract{value:msg.value}();
DemoResult memory res = DemoResult({
addr:address(ddl),
balance: ddl.getBalance(),
value:ddl.value()
});
emit log(res.addr,res.balance,res.value);
ddl.deletecontract();
return res;
}
} 先发送eth给deleteContract,在调用删除合约的方法,看etl是否会返回当前合约的balance
contract encode{ uint x =10; string name ='leiii'; uint[2] arr = [5,6];
function encoded() public view returns(bytes memory){ //可以与合约进行交互
return abi.encode(x,name,arr);
}
function encodePacked()public view returns(bytes memory){ //节省空间但是不能与合约交互
return abi.encodePacked(x,name,arr);
}
function encodeWithSignature() public view returns(bytes memory){
return abi.encodeWithSignature("foo(uint256,string,uint256[2])",x,name,arr);
}
function encodeWithSelector() public view returns(bytes memory result){
result = abi.encodeWithSelector(bytes4(keccak256("foo(uint256,string,uint256[2])")), x, name, arr);
}
function decode(bytes memory data) public pure returns(uint256 dx,string memory dname,uint256[2] memory adrr ){
(dx,dname,adrr) = abi.decode(data, (uint256,string,uint256[2]));
}
} 加密和解密常用的方法 encode() 将参数都编译成32字节的数据在拼接到一起,可以调用其他的合约,安全性高,省资源 encodePacked 常用来取hash值 encodeWithSelector和encodeWithSignature一样,但是select在选择方法时更精准