solidity学习(一)

1、版本标识符 pragmas 标识合约适配的编译器版本
pragmas solidity >0.5.0;

2、ABI 合约的json描述,类比API

3、导入其他的源文件 类似JS ES6导入规则,不支持default export
import "./filename";
import * as bb from "./filename";
import './filename' as bb;
import {aa as _aa, bb} from './filename';

4、路径解析
路径建议使用相对引用
·import './filename';·

5、合约结构:状态变量、函数、修改器、事件、结构体、枚举、库、接口

  • 状态变量是永久地存储在合约存储中的值
  • 函数调用 可发生在合约内部或外部(可见性:provide受保护、internal内部调用、public、external外部调用)

6、类型

  • “undefined”或“null”值的概念在Solidity中不存在
  • 值类型:bool、int、uint、fixed(未完全支持)、address、contract、bytes1-32、bytes、string、字面常量(地址、有理数整数、字符串、十六进制)、enum、function类型(默认internal)
  • 引用类型:struct、数组、map(使用引用类型必须指定存储位置:memory、storage、calldata)
  • 所有的引用类型,如 数组 和 结构体 类型,都有一个额外注解 数据位置
  • storage 和 memory 赋值
    *在 存储storage 和 内存memory 之间两两赋值(或者从 调用数据calldata 赋值 ),都会创建一份独立的拷贝
    *从 内存memory 到 内存memory 的赋值只创建引用
    *从 存储storage 到本地存储变量的赋值也只分配一个引用。
    *其他的向 存储storage 的赋值,总是进行拷贝
  • 状态变量标记 public 的数组,Solidity创建一个 getter函数
  • 访问超出数组长度的元素会导致异常(assert 类型异常 )
  • 动态数组可以使用 .push() 方法在末尾追加一个新元素,其中 .push() 追加一个零初始化的元素并返回对它的引用
  • 数组切片 x[start:end]
  • mapping(_KeyType => _ValueType),_KeyType 可以是任何基本类型,映射、结构体、即除 bytes 和 string 之外的数组类型是不可以
  • 映射只能是 存储storage 的数据位置
点击查看代码
pragma solidity >=0.6.0 <0.9.0;

contract ArrayContract {
    uint[2**20] m_aLotOfIntegers;

    // 注意下面的代码并不是一对动态数组,
    // 而是一个数组元素为一对变量的动态数组(也就是数组元素为长度为 2 的定长数组的动态数组)。
    // 因为  T[] 总是 T 的动态数组, 尽管 T 是数组
    // 所有的状态变量的数据位置都是 storage
    bool[2][] m_pairsOfFlags;

    // newPairs 存储在 memory 中 (仅当它是公有的合约函数)
    function setAllFlagPairs(bool[2][] memory newPairs) public {

     // 向一个 storage 的数组赋值会对 ``newPairs`` 进行拷贝,并替代整个 ``m_pairsOfFlags`` 数组
        m_pairsOfFlags = newPairs;
    }

    struct StructType {
        uint[] contents;
        uint moreInfo;
    }
    StructType s;

    function f(uint[] memory c) public {
        // 保存引用
        StructType storage g = s;

        // 同样改变了 ``s.moreInfo``.
        g.moreInfo = 2;

        // 进行了拷贝,因为 ``g.contents`` 不是本地变量,而是本地变量的成员
        g.contents = c;
    }

    function setFlagPair(uint index, bool flagA, bool flagB) public {
        // 访问不存在的索引将引发异常
        m_pairsOfFlags[index][0] = flagA;
        m_pairsOfFlags[index][1] = flagB;
    }

    function changeFlagArraySize(uint newSize) public {
       // 使用 push 和 pop 是更改数组长度的唯一方法

        if (newSize < m_pairsOfFlags.length) {
            while (m_pairsOfFlags.length > newSize)
                m_pairsOfFlags.pop();
        } else if (newSize > m_pairsOfFlags.length) {
            while (m_pairsOfFlags.length < newSize)
                m_pairsOfFlags.push();
        }
    }

    function clear() public {
        // 这些完全清除了数组
        delete m_pairsOfFlags;
        delete m_aLotOfIntegers;
        // 效果相同(和上面)
        m_pairsOfFlags.length = new bool[2][](0);
    }

    bytes m_byteData;

    function byteArrays(bytes memory data) public {
        // 字节数组(bytes)不一样,它们在没有填充的情况下存储。
        // 可以被视为与 uint8 [] 相同
        m_byteData = data;
        for (uint i = 0; i < 7; i++)
            m_byteData.push();
        m_byteData[3] = 0x08;
        delete m_byteData[2];
    }

    function addFlag(bool[2] memory flag) public returns (uint) {
        m_pairsOfFlags.push(flag);
        return m_pairsOfFlags.length;
    }

    function createMemoryArray(uint size) public pure returns (bytes memory) {
        // 使用`new`创建动态内存数组:
        uint[2][] memory arrayOfPairs = new uint[2][](size);

        // 内联(Inline)数组始终是静态大小的,如果只使用字面常量,则必须至少提供一种类型。
        arrayOfPairs[0] = [uint(1), 2];

        // 创建一个动态字节数组:
        bytes memory b = new bytes(200);
        for (uint i = 0; i < b.length; i++)
            b[i] = byte(uint8(i));
        return b;
    }
}

7、单位 1 ether = 10**9 gwei = 10**18 wei

  • 时间单位:seconds、 minutes、 hours、 days 和 weeks

8、特殊变量和函数

  • blockhash(uint blockNumber) returns (bytes32):指定区块的区块哈希——仅可用于最新的 256 个区块且不包括当前区块
  • block.chainid (uint): 当前链 id
  • block.coinbase ( address ): 挖出当前区块的矿工地址
  • block.difficulty ( uint ): 当前区块难度
  • block.gaslimit ( uint ): 当前区块 gas 限额
  • block.number ( uint ): 当前区块号
  • block.timestamp ( uint): 自 unix epoch 起始当前区块以秒计的时间戳
  • gasleft() returns (uint256) :剩余的 gas
  • msg.data ( bytes ): 完整的 calldata
  • msg.sender ( address ): 消息发送者(当前调用)
  • msg.sig ( bytes4 ): calldata 的前 4 字节(也就是函数标识符)
  • msg.value ( uint ): 随消息发送的 wei 的数量
  • tx.gasprice (uint): 交易的 gas 价格
  • tx.origin (address payable): 交易发起者(完全的调用链)
  • ABI 编码及解码函数
  • 错误处理函数 require、assert、revert
  • 数学密码学函数:addmod、mulmod、kaccak256

9、地址address

  • 成员 balance、code、codehash、transfer、send、call、delegatecall、staticcall
  • transfer、send 发送固定2300gas
  • call、delegatecall调用关系

10、合约相关

  • this (当前的合约类型),可以显示转换为 地址类型 Address。
  • selfdestruct(address payable recipient)销毁合约,并把余额发送到指定 地址类型 Address。

11、类型信息 type(X)

  • type(Contract) : name、creationCode、runtimeCode、
  • type(Interface):interfaceId 接口ID
  • type(Int):min、max

12 表达式和控制结构

  • 没有switch、goto
  • try/catch 仅用于外部函数调用和合约创建调用
  • solidity的非布尔值不能转换成布尔值 if(1) 无效

13函数调用

  • 内部函数调用 只能在同一个合约实例的函数,可以进行内部调用
    *避免过多的递归调用,每个内部函数调用至少使用一个堆栈槽,最多有1024个
  • 外部函数调用 this.g(); 、 C.g();
    *函数将会通过一个消息调用来进行外部调用
    *不可以在构造函数里使用this,这时候合约实例还未创建
    *对于外部函数调用,所有的函数参数都需要被复制到内存
    *从一个合约到另一个合约的函数调用不会创建自己的交易,它是整个交易的一部分
    *使用extcodesize 检查调用合约是否存在
    *feed.info{value: 10, gas: 800}() 设置调用函数时可以使用的gas和发送的wei
  • 具名调用和匿名调用参数
    set({value: 2, key: 3});
    set(2, 3);
    function set(uint key, uint value) public { data[key] = value; }
  • 通过 new 创建合约
    D newD = (new D){value:amount}(arg);
  • 通过汇编指令 create2 创建合约
    *通过create2指令指定salt,可以计算出合约创建时的地址
/// 这个复杂的表达式只是告诉我们,如何预先计算地址。
/// 这里仅仅用来说明。
/// 实际上,你仅仅需要 ``new D{salt: salt}(arg)``.
address predictedAddress = address(uint160(uint(keccak256(abi.encodePacked(
    bytes1(0xff),
    address(this),
    salt,
    keccak256(abi.encodePacked(
        type(D).creationCode,
        arg
    ))
)))));
address pair;
assembly {
    //通过create2方法布署合约,并且加盐,返回地址到pair变量
    pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}

D d = new D{salt: salt}(arg);
require(address(d) == predictedAddress);
  • 表达式计算顺序
    *语句按顺序执行,布尔表达式的短路执行
  • 赋值
    *解构赋值 (uint x, bool b, uint y) = f();

14作用域和声明

  • 变量声明后初始值为零状态
  • 对于 enum 类型, 默认值是第一个成员
  • 变量将会从它们被声明之后可见,直到一对 { } 块的结束

15用assert检查异常(Panic) 和 require 检查错误(Error)

posted @ 2022-04-25 20:15  北京雨夜  阅读(406)  评论(0)    收藏  举报