Skip to content

Latest commit

 

History

History
237 lines (180 loc) · 8.12 KB

Solidity 101.md

File metadata and controls

237 lines (180 loc) · 8.12 KB

Solidity 101

Chapter 1

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
contract HelloWeb3{
    string public _string = "Hello Web3!";
}

Chapter 2: Value Type

bool, int, uint, uint256

address, address payable

// 地址
address public _address = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71;
address payable public _address1 = payable(_address); // payable address,可以转账、查余额
// 地址类型的成员
uint256 public balance = _address1.balance; // balance of address
  • 普通地址(address): 存储一个 20 字节的值(以太坊地址的大小)。

  • payable address: 比普通地址多了 transfersend 两个成员方法,用于接收转账。

  • 定长字节数组: 属于值类型,数组长度在声明之后不能改变。根据字节数组的长度分为 bytes1, bytes8, bytes32 等类型。定长字节数组最多存储 32 bytes 数据,即bytes32

  • 不定长字节数组: 属于引用类型(之后的章节介绍),数组长度在声明之后可以改变,包括 bytes 等。

// 固定长度的字节数组
bytes32 public _byte32 = "MiniSolidity"; 
bytes1 public _byte = _byte32[0]; 

enum

// 用enum将uint 0, 1, 2表示为Buy, Hold, Sell
enum ActionSet { Buy, Hold, Sell }
// 创建enum变量 action
ActionSet action = ActionSet.Buy;

枚举可以显式地和 uint 相互转换,并会检查转换的正整数是否在枚举的长度内,否则会报错:

// enum可以和uint显式的转换
function enumToUint() external view returns(uint){
    return uint(action);
}

Chapter 3: Function

function <function name>(<parameter types>) {internal|external|public|private} [pure|view|payable] [returns (<return types>)]
  • public:内部和外部均可见。
  • private:只能从本合约内部访问,继承的合约也不能使用。
  • external:只能从合约外部访问(但内部可以通过 this.f() 来调用,f是函数名)。
  • internal: 只能从合约内部访问,继承的合约可以用。
  1. public vs external 的区别:
  • public 函数:
    • 可以被合约内部调用
    • 可以被外部合约/账户调用
    • 当在合约内部调用时会复制函数参数到内存(消耗更多gas)
  • external 函数:
    • 只能被外部合约/账户调用
    • 不能被合约内部调用(除非使用this.function())
    • 参数可以直接从calldata读取(更省gas)
  1. private vs internal 的区别:
  • private 函数:
    • 只能在当前合约内部访问
    • 不能被继承的合约访问
    • 对合约外部完全不可见
  • internal 函数:
    • 可以在当前合约内部访问
    • 可以被继承的合约访问
    • 对合约外部不可见

payable(可支付的),带着它的函数,运行的时候可以给合约转入 ETH。pureview 的介绍见下一节。

包含 pureview 关键字的函数是不改写链上状态的,因此用户直接调用它们是不需要付 gas 的(注意,合约中非 pure/view 函数调用 pure/view 函数时需要付gas)。

在以太坊中,以下语句被视为修改链上状态:

  1. 写入状态变量。
  2. 释放事件。
  3. 创建其他合约。
  4. 使用 selfdestruct.
  5. 通过调用发送以太币。
  6. 调用任何未标记 viewpure 的函数。
  7. 使用低级调用(low-level calls)。
  8. 使用包含某些操作码的内联汇编。

Ch5: Variable's Storage/Memory/Calldata

Solidity数据存储位置有三类:storagememorycalldata。不同存储位置的gas成本不同。storage类型的数据存在链上,类似计算机的硬盘,消耗gas多;memorycalldata类型的临时存在内存里,消耗gas少。大致用法:

  1. storage:合约里的状态变量默认都是storage,存储在链上。
  2. memory:函数里的参数和临时变量一般用memory,存储在内存中,不上链。尤其是如果返回数据类型是变长的情况下,必须加memory修饰,例如:string, bytes, array和自定义结构。
  3. calldata:和memory类似,存储在内存中,不上链。与memory的不同点在于calldata变量不能修改(immutable),一般用于函数的参数。例子:
function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){
    //参数为calldata数组,不能被修改
    // _x[0] = 0 //这样修改会报错
    return(_x);
}

Solidity中变量按作用域划分有三种,分别是状态变量(state variable),局部变量(local variable)和全局变量(global variable)

  • State Variable 状态变量是数据存储在链上的变量,所有合约内函数都可以访问,gas消耗高。状态变量在合约内、函数外声明:

    contract Variables {
        uint public x = 1;
        uint public y;
        string public z;
    }
    
  • Local Variable 局部变量的数据存储在内存里,不上链,gas低。局部变量在函数内声明:

  • Global Variable 全局变量是全局范围工作的变量,都是solidity预留关键字。他们可以在函数内不声明直接使用:

    function global() external view returns(address, uint, bytes memory){
        address sender = msg.sender;
        uint blockNum = block.number;
        bytes memory data = msg.data;
        return(sender, blockNum, data);
    }
  • wei: 1

  • gwei: 1e9 = 1000000000

  • ether: 1e18 = 1000000000000000000

  • seconds: 1

  • minutes: 60 seconds = 60

  • hours: 60 minutes = 3600

  • days: 24 hours = 86400

  • weeks: 7 days = 604800

function secondsUnit() external pure returns(uint) {
    assert(1 seconds == 1);
    return 1 seconds;
}

function minutesUnit() external pure returns(uint) {
    assert(1 minutes == 60);
    assert(1 minutes == 60 seconds);
    return 1 minutes;
}

function hoursUnit() external pure returns(uint) {
    assert(1 hours == 3600);
    assert(1 hours == 60 minutes);
    return 1 hours;
}

function daysUnit() external pure returns(uint) {
    assert(1 days == 86400);
    assert(1 days == 24 hours);
    return 1 days;
}

function weeksUnit() external pure returns(uint) {
    assert(1 weeks == 604800);
    assert(1 weeks == 7 days);
    return 1 weeks;
}

Ch7 Mapping

  • 规则1:映射的_KeyType只能选择Solidity内置的值类型,比如uintaddress等,不能用自定义的结构体。而_ValueType可以使用自定义的类型。下面这个例子会报错,因为_KeyType使用了我们自定义的结构体:

    // 我们定义一个结构体 Struct
    struct Student{
        uint256 id;
        uint256 score; 
    }
    mapping(Student => uint) public testVar;

    Copy

  • 规则2:映射的存储位置必须是storage,因此可以用于合约的状态变量,函数中的storage变量和library函数的参数(见例子)。不能用于public函数的参数或返回结果中,因为mapping记录的是一种关系 (key - value pair)。

  • 规则3:如果映射声明为public,那么Solidity会自动给你创建一个getter函数,可以通过Key来查询对应的Value

  • 规则4:给映射新增的键值对的语法为_Var[_Key] = _Value,其中_Var是映射变量名,_Key_Value对应新增的键值对。

Ch8 Constant & Immutable

constant(常量)和immutable(不变量)。状态变量声明这两个关键字之后,不能在初始化后更改数值。这样做的好处是提升合约的安全性并节省gas

另外,只有数值变量可以声明constantimmutablestringbytes可以声明为constant,但不能为immutable

Ch 11

合约只能有最多1个constructor

// 定义modifier
modifier onlyOwner {
   require(msg.sender == owner); // 检查调用者是否为owner地址
   _; // 如果是的话,继续运行函数主体;否则报错并revert交易
}
function changeOwner(address _newOwner) external onlyOwner{
   owner = _newOwner; // 只有owner地址运行这个函数,并改变owner
}