YouTube Solidity0.8
1⃣️Data types:(值类型)
values:数据存储一个值(布尔、int存储了一个数值)
references:引用类型,不存储值,存储了实际数据的存储位置
values
contract ValueTypes{
bool public = true;
uint public u = 123; // uint = uint256 0 to 2**256 - 1
int public i = -123; //int =int 256 -2**255 to 2**255-1
int public minInt = type(int).min;
int public maxInt = type(int).max;
address public addr = 0x0000000000000000000000000000000000000000;
bytes32 public b32 = 0x0000000000000000000000000000000000000000000000000000000000000000;
}
2⃣️Functions
3⃣️Variables:(变量类型)
1⃣state variables(状态变量):在合约内部声明,存储在函数外部。
uint public myUint = 123
变量存储在了区块链上
2⃣local variables(局部变量): 定义在函数内部的变量,仅在函数执行的过程中存在。
3⃣global variables(全局变量):存储例如区块链交易、调用函数的账户等。
address sender = msg.sender; //消息发送者
uint timestamp = block.timestamp; // 调用此函数的unix时间戳
uint blockNum = block.number; // 存储当前区块序号
4⃣️pure和view函数
如果函数要从state variables\other contracts\Block chain读取数据,就要设置为view函数,否则为pure函数。
5⃣️Write a Counter
6⃣️default values
状态变量和局部变量拥有默认值
bool : false
uint : 0
int : 0
address : 0x0000000000000000000000000000000000000000 //40个
bytes32 : 0x0000000000000000000000000000000000000000000000000000000000000000//64个
mapping/structs/enums/fixed sized arrays // 后面写
7⃣️constant
对于明知道不会改变的状态变量可以声明为常量而不是常规的状态变量,这样可以节约gas。
address public myAddress = 0x0000000000000000000000000000000000000000; //23553 gas
address public constant MY_ADDRESS = 0x0000000000000000000000000000000000000000; //21442 gas
8⃣️ifelse
9⃣️for/while
continue:停止当前循环中后续的部分,进入下一次循环
break:跳出所有循环
越多的循环会消耗越多的gas,尽量控制循环的次数。
🔟error
当调用函数的时候,有三种方式引发错误:require、revert、assert。当交易中发生错误时,剩余的gas将被返还,所有更新过的状态变量将会还原。
require:主要用于验证输入和访问控制
revert:常用于if语句中
assert:用于检验永远为true的语句,如果为false会报错
自定义错误:只能使用revert触发。可以节约gas
点击查看代码
error MyError(address caller, uint i)
function testCustomError(uint _i) public view{
if (_i > 10){
revert MyError(msg.sender,_i)
}
}
1⃣️1⃣️modifier
modifier允许开发者重用代码。
点击查看代码
bool public paused;
uint public count;
modifier whenNotPaused(){
paused = _paused;
_; //代表使用了此modifier的函数,函数中的代码拼接到这个位置;很关键,不能丢
}
function inc1() external{ //和下面的函数相同
require(!paused,"paused"); //不使用modifier
count +=1;
}
function inc2() external whenNotPaused{ //和上面的函数相同
//require(!paused,"paused"); //使用了modifier
count +=1;
}
1⃣️2⃣️constructor
在部署合约时只调用一次,主要用于初始化状态变量
点击查看代码
contract Constructor{
address public owner;
uint public x;
constructor(uint _x){
owner = msg.sender;
x = _x;
}
}
1⃣️3⃣️owanable
点击查看代码
contract Ownable{
address public owner;
constructor(){
owner = msg.sender;
}
modifier onlyOwner(){
require(msg.sender == owner,"not owner");
_;
}
function setOwner(address _newOwner) external onlyOwner {
require (_newOwner != address(0),"invalid address");
owner = _newOwner;
}
function onlyOwnerCanCallThisFunc() external onlyOwner {
//code
}
function anyOneCanCall() external {
//code
}
}
1⃣️4⃣️function outputs
Solidity中函数可以输出多个值
function returnMany() public pure returns (uint ,bool){ return(1,true); //返回值不知道叫什么 }
function named() public pure returns (uint x ,bool b){ return(1,true); //对返回值进行隐式返回,知道名字 }
function assigned() public pure returns (uint ,bool){ x = 1; b = true; //对返回值进行显示返回,知道名字 }
结构化返回(destructuring assignment)
点击查看代码
function destructingAssigments() public pure{
(uint x,bool b) = returnMany();
(,bool _b) = returnMany(); //可以通过省略第一个输出,表示只输出一个
}
1⃣️5⃣️array
// Array - dynamic or fixed size
// Initialization
// insert(push)/get/update/delete/pop(remove the last)/length
// creating array in memory
// returning array from function
点击查看代码
contract Array{
uint[] public nums = [1,2,3]; //动态数组,可以push/pop来改变大小
uint[3] public numsFixed = [4,5,6]; //固定大小的数组
function examples() external{
nums.push(4); //[1,2,3,4]
uint x = nums[1]; //2
nums[2] = 777; //[1,2,777,4]
delete nums[1]; //[1,0,777,4] delete不会改变数组长度
nums.pop(); //[1,0,777] pop可以改变数组长度
uint len = nums.length; //3
//create array in memory
uint[] memeory a = new uint[](5); //内存中的数组必须是固定大小(不能pop/push),此处数组长度为5
a[1] = 123
}
}
可以从函数中返回数组,但不推荐。浪费gas。
✨从 [1,2,3] 得到 [1,3] ,而不是 [1,0,3] 的方法。
法一:[1,2,3,4,5,6] -- > remove(2) -- > [1,2,4,5,6,6] -- > [1,2,4,5,6] //想移除第三个元素,要让后面的元素全部左移,再pop
点击查看代码
function remove(uint _index) public {
require(_index < arr.length,"index out of bound");
for (uint i = _index; i<arr.length-1; i++){
arr[i] = arr[i+1];
}
arr.pop();
}
法二:用最后的元素复制给要删除的元素,然后pop。
1⃣️6⃣️mapping ( like dectionary in Python)
//mapping
//how to declare a mapping (simple and nested)
//set/get/delete
如果我们想查找某个字符串元素是否在一个数组内,需要迭代,来检查数组内元素是否是要找的。(n维数组中需要迭代n/2次)
但在mapping中寻找只需要一次查找。
点击查看代码
contract Mapping{
mapping(address => uint) public balances; //可以用于快速查询目标地址的余额
mapping(adderss => mapping(address => bool)) public isFriend; //映射嵌套,查看一个地址是否是另一个地址的好友
function examples() external { //
balances[msg.sender] = 123;
uint bal = balances[msg.sender];
uint bal2 = balances[address(1)]; //从还未设置的键中获取值,此处bal2 = 0
balances[msg.sender] += 456; //更新为579
delete balances[msg.sender]; //0
isFriend[msg.sender][address(this)] = true;
}
}
✨对于mapping不可以通过迭代的方式获取其内部所有的元素
此处有方法可以通过for来获取mapping的大小并获取mapping内所有的元素
点击查看代码
contract IterableMapping{
mapping(address => uint) public balances; //需要一些额外的手段来获取映射中的所有元素和映射的大小。
mapping(address => bool) public inserted; //需要一个映射来跟踪是否插入,当一个地址插入balances时,在inserted中给这个地址设置为true。
address[] public keys; //通过keys可以知道balances中都有什么地址
function set(adderss _key,uint _val) external { //每当balances中新增一个key,将key的值赋给mapping中对应的uint,并且如果inserted中不存在key,使其为存在,并将key push到address类型的数组keys中
balances[_keys] = _val;
if(!inserted[_key]){
inserted[_key] = true;
keys.push(_key);
}
}
function getSize() external view returns (uint){ //获取映射的长度
return keys.length;
}
function get(_i) external view returns (uint){ //获取映射的键(值从mapping中获取)
returns balances[keys[_i]];
}
}
1⃣️7⃣️struct(结构体)
点击查看代码
contract Structs {
struct Car {
string model;
uint year;
address owner;
}
Car public car; //可以从结构体定义出状态变量、数组、映射
Car[] public cars;
mapping(address => Car[]) public carsByOwner;
function examples() external {
Car memory toyota = Car("toyota",1990,msg.sender) //在内存中初始化结构体,代表只有在调用这个函数时存在
Car memory lambo = Car({model:"lamborghini",year:1990,owner:msg.sender}) //多种初始化结构体的方法,这个可以不考虑顺序
Car memory tesla;
tesla.model = "Tesla";
tesla.year = 2020;
tesla.owner = msg.sender;
cars.push(toyota); //此前存储在内存中,此时可以把他们放在状态变量中,以便可以从外部读取。
cars.push(lambo);
cars.push(tesla);
cars.push(Car("Ferrari",2021,msg.sender)) //等价
Car storage _car = cars[0]; //开始尝试修改,此处要用storage。memory的话只在函数运行时有效,不能修改。
_car.year = 1999;
delete _car.owner;
delete cars[1]
}
}
1⃣️8⃣️enum(枚举)
点击查看代码
contract Enum{
enum Status { //默认值是None
None,
pending,
Shipped,
Completed,
Rejected,
Canceled
}
Status public status;
struct Order {
address buyer;
Status status;
}
Order[] public orders;
}
1⃣️9⃣️deploy any contract
点击查看代码
contract Proxy{ //使用代理来部署合同,代理通过字节码部署;合同的所有者会有所不同。
event Deploy(address);
function deploy(bytes memory _code) external payable returns (address){
assembly{ //在部署合约时发出事件,内联汇编
//create(v,p,n)
//v = amount of ETH to send
//p = pointer in memory to start of code
//n = size of code
addr :=create(callvalue(),add(_code,0x20),mload(_code))
//上面等价于:先定义一个address addr,addr=create(......),再返回addr
}
require(addr !=address(0),"failed") //0地址代表部署失败
}
}
2⃣️0⃣️Data locations - storage,memory,calldata
使用动态数据类型时需要声明数据位置,storage,memory,calldata
storage代表变量是状态变量
memory表示数据将被加载到内存,数据不会被实际更改
calldata类似memory,但只能被用于函数输入,可以节约gas(如果使用memory会有一次复制的过程)
点击查看代码
contract SimpleStorage {
string public text;
function set(string calldata_text) external { //此处如果使用memory会消耗更多的gas
text = _text;
}
function get() external view returns (string memory) {
return text;
}
}
ToDo List
constract ToDoList { //memory 比 storage 更费gas
struct todo {
string text;
bool completed;
}
Todo[] public todos;
function create(string calldata _text) external {
todos.push(Todo({
text:_text;
completed:false;
}));
}
}
2⃣️1⃣️Event
点击查看代码
contract Event{
event Log(string message,uintb val);
event IndexedLog(address indexed sender,uint val);
function example() external{
emit Log("foo",1234);
emit IndexedLog(msg.sender,789);
}
}
2⃣️2⃣️inheritance(继承)
有合约A和合约B,B想复用A中的多数代码,可以继承。
A是父合约,B是子合约
点击查看代码
contract A {
function foo() public pure virtual returns (string memory){ //virtual代表这个函数可以被继承
return "A";
}
function bar() public pure virtual returns (string memory){
return "A";
}
}
contract B is A{ //B继承了A,虽然B中只写了bar(),但仍可以调用foo()
function bar() public pure override returns (string memory){ //B虽然继承了A,但可以对A中的函数进行重写--override
return "B";
}
}
Multi lnheritance-->多重继承
多重继承时继承的顺序:从基础到派生(顺序错了则无法编译)
X
/ I
/ I
Y I
\ I
\ I
Z
顺序:X--Y--Z
override(X,Y)和override(Y,X)没有区别,都能正常编译。
✨多重继承时的constructor
U继承自S和T,S和T都有自己的构造函数
点击查看代码
contract S {
string public name;
constructor(string memory _name){
name = _name;
}
}
contract T {
string public text;
constructor(string memory _text){
text = _text;
}
}
contract U is S,T {
constructor(string memory _name,string memory _text) S(_name) T(_text){
//此处继承了name和text
}
}
✨引用父函数
两种方式(F\G继承自E,H继承自F&G)
1⃣直接调用
2⃣super关键字
E.foo() //F调用E中的foo()
super.foo() //F调用E中的foo()
//对于继承自F和G的H来说
F.foo()只会调用F中的foo()
super.foo()会调用F和G中所有的foo()
2⃣️3⃣️visibility(可见性)
函数的可见性包括:
private:只有合约内可见
internal:只有合约内及其子合约可见
public:合约内外都可见
external:合约外可见
2⃣️4⃣️immutable(不可变)
不可变量会比状态变量节约gas
address public immutable owner = msg.sender
2⃣️5⃣️payable
payable属性允许接受和发送ether
点击查看代码
contract Payable {
address payable public owner;
constructor(){
owner = payable(msg.sender);
}
function deposit() external payable{} //可以发送ether,如果没有payable属性,合约将无法接受ether
function getBalance() external view returns(uint) {
return address(this).balance; //返回账户余额
}
}
2⃣️6⃣️Fallback
fallback 函数会在以下时候执行:
要调用的函数在合约内不存在
主要用例:
启用直接发送ether
点击查看代码
contract Fallback{
event Log(string func, address sender, uint value,bytes data);
fallback() external payable{ //假设调用了一个不存在的函数foo,这时fallback()会被执行
emit Log("fallback",msg.sender,msg.value,msg.data);
}
receive() external payable{ //receive和fallback类似,具体不同在下面
emit Log("receive",msg.sender,msg.value,"");
}
//remix中使用low level interactions
}
fallback和receive的不同:
Ether被发送到了一个合约
如果msg.data不是空的:启用fallback()
如果msg.data是空的,则判断:
如果receive()存在:启用receive()
如果receive()不存在:启用fallback()
✨fallback函数是一个不接受任何参数也不返回任何值的特殊函数
调用了不存在的函数时,会退函数被调用
每当接受以太币时,如果没有receive函数,fallback函数会被调用
一个合约最多存在一个fallback函数
2⃣️7⃣️send ether
有三种方式发送以太币
1⃣transfer:消耗2300gas,如果失败,触发revert
2⃣send:消耗2300gas,如果失败,返回bool指示是否成功
3⃣call:消耗所有gas,如果失败,返回bool和data
transfer和和send相比,推荐transfer,会有错误提示。
send失败会继续执行后面代码,因此要保证send一定成功
点击查看代码
contract SendEther{
constructor() payable{} //构造函数引入payable,确保合约可以收到ether
receive() external payable{} //用于调用错误(不存在的)函数时的处理
//以上两个函数是将以太币发送到合约中的两种方式(部署合约时发送以太币,或者通过接受回退函数直接发送以太币)
//以下是一些发送后以太币的函数
function sendViaTransfer(address payable _to) external payable {
_to.transfer(123);
}
function sendViaSend(address payable _to) external payable {
bool sent = _to.send(123); //因为失败会返回bool
require(sent,"send failed");
}
function sendViaCall(address payable _to) external payable {
(bool success,) = _to.call{value:123}("");
require(success,"call failed");
}
}
contract EthReceiver {
event Log(uint amount ,uint gas);
receive() external payable {
emit Log(msg.value,gasleft());
}
}
2⃣️8⃣️ether wallet(创建一个合约,所有人都可以往里面转钱,但只有拥有者可以往外转)
点击查看代码
contract EtherWallet {
address payable public owner; //要加上payable,否则后面不能向外转帐。
constructor() { //合约的拥有者为合约的创建者
owner = payable(msg.sender);
}
receive() external payable { //合约可以接受付款
}
function withdraw(uint _amount) external { //确保只有合约的拥有者可以调用此函数向外转钱
require(msg.sender == owner,"caller is not owner");
owner.transfer(_amount);
}
}
2⃣️9⃣️call other contract
点击查看代码
contract callTest{
function setX(address _test,uint _x) external { //此处_test为合约Test的合约地址
//也可以写成
//function setX(Test _test,uint _x) external
//_test.setX(_x);
Test(_test).setX(_x);
}
function getX(address _text) external view returns (uint x) {
x = Test(_test).getX();
}
}
contract Test{
uint public x;
uint public value = 123;
function setX(uint _x) external {
x = _x;
}
fuction getX() external {
return x
}
}
3⃣️0⃣️interface(接口)
solidity允许使用接口调用其他合约,而无需其余代码。
(不知道另一个合约的代码或另一个合约的代码特别庞大时好用)
抽象合约和接口:
1⃣抽象合约无法被编译,但可作为父合约
2⃣接口类似抽象合约
不使用接口
contract Counter{
int public count;
function inc() external{
count += 1;
}
function dec() external{
count -= 1;
} //不使用接口需要让两个合约在同一个文件内,才能调用
contract CallInterface {
function examples(address _counter) external {
Counter(_counter).inc(); //这是不使用接口的方式
}
}
使用接口
interface ICounter {
function count() external view returns (uint);
function inc() external;
} //使用接口,即使两个合约不在同一个文件,也可调用
contract CallInterface {
function examples(address _counter) external {
ICounter(_counter).inc(); //这是使用接口的方式
count = ICounter(_counter).count();
}
3⃣️1⃣️合约调用:send/call/delegatecall/callcode(已弃用)
call和delegate:都属于低级的调用,拼装交易(函数选择器+参数)
区别:delegatecall保证了合约不用传输自身状态的情况下,可以使用其他合约的代码
或者说:
✨call调用其他合约的时候,代码是在被调用的合约里执行;
✨delegatecall调用其他合约时,代码是在自己的合约里执行;(即不需要传输自身状态)
3⃣️2⃣️create/create2
从一个合约来创建另一个合约的两种方式:create/create2
区别:create2多了一个salt参数
create2只需要确定了合约代码和salt,就可以确定合约地址
create则需要确定nonce才能确定合约地址
New
contract Account {
address public bank;
address public owner;
constructor(address _owner) payable {
bank = msg.sender;
owner = _owner;
}
}
contract AccountFactory {
Account[] public accounts; //创建一个状态变量用来存放新合约
function createAccount(address _owner) external payable{
Account account = new Account{value:111}(_owner);
accounts.push(account); //用数组来存放新合约字节码,accounts[0]代表了合约地址
}
}
3⃣️3⃣️function selector
当我们引用函数时,到底传输了什么消息
1⃣函数选择器:0x00000000
2⃣传输的参数
3⃣️4⃣️Kill
有一个叫做selfdestruct的函数,调用后可以在区块链上删除合约,并且可以将以太币发送到任意地址(即使是一个没有fallback()的合约地址)。
点击查看代码
contract Kill{
function kill() external {
selfdestruct(msg.sender; //删除合约,并且将合约中锁定的以太币发送给msg.sender
//如果msg.sender是一个没有fallback()的合约地址,我们依然可以强制发送
}
}
3⃣️5⃣️ABIDecode
点击查看代码
contract AbiDecode {
//function encode() external pure returns (bytes memory){}
//function decode(bytes calldata data) external pure returns{}
struct MyStruct {
string name;
uint[2] nums;
}
function encode(
uint x,
address addr,
uint[] calldata arr,
Mystryct calldata myStruct
) external pure returns (bytes memory){
returns abi.encode(x,addr,arr,myStruct);
}
function decode(bytes calldata data) external pure returns (
uint x,
address addr,
uint[] calldata arr,
Mystryct calldata myStruct
) {
(x,addr,arr,myStruct) = abi.decode(data,(uint,address,uint[],MyStruct));
}
}
3⃣️6⃣️Library(库)
可以在文件里写一个库
可以引用外部库
3⃣️7⃣️keccak256
Solidity中广泛应用的一个哈希加密函数,keccak256(/ke chek/)
例如用于签署签名,获得唯一的id
也可以创建一个被保护的合约
点击查看代码
contract HashFunc{
function hash(string memory text,uint num,address addr) external pure returns(bytes32){
return keccak256(abi.encodePacked(text,num,addr));
}
}
3⃣️8⃣️Verify Signature(签署签名)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix