看黎跃春写得教程的笔记1-Solidity系列

钱包的选择与使用

  1. My Ether Wallet

记住密码: 123456!@#$%^qwertyQWERTY

密钥和公钥信息存储在 MyEtherWallet 文件夹中.

  1. 使用 MetaMask 进行恢复钱包

  2. 使用官方 Ethereum Wallet 恢复钱包

  3. Parity Ethereum 钱包

参考文章: 如何通过 MyEtherWallet 创建钱包以及如何通过 Ethereum Wallet 和 MetaMask 恢复钱包账号

注意: 以下内容也全都是相应的笔记.

Solidity 合约结构

区块链程序 == 智能合约

// 版本声明
pragma solidity ^0.4.4;

/// 合约声明, class
contract Counter {

    // 状态变量, 类属性
    uint count = 0;
    address owner;

    /// 构造函数
    function Counter(uint c) {
       owner = msg.sender;
       counter = c;
    }

    /// 成员函数
    function increment() public {
        // 本地变量
        uint step = 10;
        if (owner == msg.sender) {
            count = count + step;
        }
    }

    function getCount() constant returns (uint) {
       return count;
    }

    function kill() {
       if (owner == msg.sender) {
          // 析构函数
          selfdestruct(owner);
       }
    }
}

使用官方 Ethereum Wallet 部署 Solidity 合约, 并且在合约页面操作合约.

数据类型

本地(局部)变量 vs 状态变量

本地变量: 函数内的临时变量; 状态变量: 合约(类)中的成员变量;

值类型: 拷贝之后修改不会影响原变量.

  • 布尔(Booleans), bool
  • 整型(Integer), intN/uintN
  • 地址(Address), address
  • 定长字节数组(fixed byte arrays), bytesN
  • 有理数和整型(Rational and Integer Literals,String literals)
  • 枚举类型(Enums)
  • 函数(Function Types)

引用类型: 地址传递

  • 不定长字节数组(bytes)
  • 字符串(string)
  • 数组(Array)
  • 结构体(Struts)

值&引用类型通过函数传递, 使用的是 memory, 拷贝内存, 函数内部修改不会对原值造成改动.

function f(string name) public { // ==> f(string memory name);
    // 使用 var 或者 string memory
    var n1 = name;
    string memory n2 = name;
}

如果想要引用类型传参, 必须显示指定.

function f(string storage name) internal|private {
    // 使用 var 或者 string storage
    var n1 = name;
    string storage n2 = name;
}

Note: 使用 storage 传参, 方法权限必须为 internal 或者 private 类型.

权限 & 继承

public 默认, internal, private

可以用于修饰属性和方法.

uint public age;
function getAge() public constant return (uint) {}

contract Child is Father {}

继承 public/internal 属性 和 public 方法 (不继承 private 属性 和 internal/private 方法)

重写 public 方法(无法重写 internal/private, 只能算是自己定义相同名字的方法), 只需要直接重新定义即可.

数据类型详解

Boolean 布尔 - 值类型

&& || ! == !=

前两个是惰性的.

Integer 整型 - 值类型

uint/int, 默认 uint256/int256

默认 10 进制, 16 进制以 0x 开头.

支持 int8N, N ∈ [1,32]

比较:<=,<,==,!=,>=,> 返回值为 bool 类型。
位运算符:&,|,^异或,~非
数学运算:+,-,一元+/-,*,/,%求余,**次方,<<左移,>>右移

字面量计算需要详细了解

Address 地址 - 值类型

20 字节(bytes) => 1byte = 8 bit => 160bits => uint160

显示成 16 进制字符串为 40 个字符? => 每个 int/uint 都是 4 个 bit => uint160 => 160bits => 40 位的 16 进制

常用 Address 地址

  • msg.sender 合约当前调用者.

在构造函数 address owner = msg.sender; 来记录合约拥有者.

  • this 为合约地址

比较: <=,<,==,!=,>=和>

成员变量 addr.balance vs. this.balance
转账 target.transfer(wei) vs. bool=target.send(wei)

Note: send 递归深度超过 1024 或者 gas 不足会引起失败, 使用此方法需要检查返回结果.

Fixed-size byte arrays 定长字节数组 - 值类型

bytesN, N ∈ [1,32], 位(bit)数为 8N

byte 默认为 bytes1

比较运算符:<=, <, ==, !=, >=, >
位操作符:&, |, ^(异或), ~ (取反), << (左移), >> (右移)
索引访问:x[k](0 < k < I), 只读
可以使用.length 获取长度, 只读

String 字符串 - 引用类型

"" 或者 '' 来表示, 无’\0’结尾. UTF-8 编码, 特殊的变长字节数组.

不能使用 length 方法, 参考 变长字节数组 部分.

Dynamically-sized byte array 变长字节数组

bytes vs. string

如果字节长度已知, 尽量使用定长 bytesN.

获取字符串长度:

string s = ""; // 可以是普通字符串, 特殊字符(每个字母对应一个字节), 汉字(1汉字对应3字节)..
bytes b = bytes(s);
uint len = b.length;

创建于操作:

bytes name = new bytes(8);
name.length
name.push(byte)
name[0] = byte;

字节数组之间的转换

  1. 定长字节数组的转换
  • 定长转定长: bytes8(bytes4) 扩展右侧加 0, bytes4(bytes8) 截断右侧删掉.

  • 定长不能转直接转 string

    bytes32 b, string s;
    bytes memory names = new bytes(b.length);

    for(uint i = 0; i < b.length; i++) {
    names[i] = b[i];
    }
    s = string(names);

  • 定长转变长

    bytes9 name9;
    bytes memory names = new bytes(name9.length);
    for(uint i = 0; i < name9.length; i++) {
    names[i] = name9[i];
    }

  1. 变长字节数组的转换

变长转 string: bytes(string)

  1. String 的转换

string 转变长: string(bytes)

contract C {

    function bytes32ToString(bytes32 x) constant returns (string) {
        bytes memory bytesString = new bytes(32);
        uint charCount = 0;
        for (uint j = 0; j < 32; j++) {
            byte char = byte(bytes32(uint(x) * 2 ** (8 * j)));
            if (char != 0) {
                bytesString[charCount] = char;
                charCount++;
            }
        }
        bytes memory bytesStringTrimmed = new bytes(charCount);
        for (j = 0; j < charCount; j++) {
            bytesStringTrimmed[j] = bytesString[j];
        }
        return string(bytesStringTrimmed);
    }

    function bytes32ArrayToString(bytes32[] data) constant returns (string) {
        bytes memory bytesString = new bytes(data.length * 32);
        uint urlLength;
        for (uint i = 0; i< data.length; i++) {
            for (uint j = 0; j < 32; j++) {
                byte char = byte(bytes32(uint(data[i]) * 2 ** (8 * j)));
                if (char != 0) {
                    bytesString[urlLength] = char;
                    urlLength += 1;
                }
            }
        }
        bytes memory bytesStringTrimmed = new bytes(urlLength);
        for (i = 0; i < urlLength; i++) {
            bytesStringTrimmed[i] = bytesString[i];
        }
        return string(bytesStringTrimmed);
    }
}

数组

定长

uint[5] arr; // 默认 [0,0,0,0,0]
arr[0] = 1;
不能 push, 或者给 length 赋值.

变长

uint[] arr = new uint;
可以 push 和 通过 .length 修改长度

二维数组

uint [2][3] T = [[1,2],[3,4],[5,6]]; 而非 uint [2][3] T = [[1,2,3],[4,5,6]];

数组的使用注意

  • 数组 默认是 storage 类型存储, 可以是基础类型, 其它数组, 结构体, 字典/映射等.
    但是作为 public 方法的参数时(肯定是 memory 类型), 不能是映射/字典, 并且必须是 ABI 类型.

  • [1,2,3] 默认是 uint8[3], 为定长数组, 不能作为 f(uint[3] arr) 的实参.
    需要进行 [uint(1),2,3] 处理.

  • 定长数组(memory 类型)不能直接赋值给变长数组(storage/memory 类型)

    uint[] memory x = [uint(1), 3, 4]; // error
    uint[] storage x = [uint(1), 3, 4]; // error
    uint[3] memory x = [uint(1), 3, 4]; // check

字符数组进阶

  • bytes0 ~ bytes32 定长字节数组, 值传递, memory, 长度不可变, 内容不可改, 有 length 属性(只读)
  • bytes 变长字节数组, 引用传递, storage, 长度可变, 内容可改, 有 length 属性(可写)
  • string 字符串(特殊变长字节数组), 引用传递, storage, 长度可变, 内容可改, 无 length 属性

声明方式

bytesN, byte[N] 声明定长数组, N ∈ [1,32]
byte[] 或者 new bytes(N) 声明变长数组, N > 0

bytesN 声明的长度不可变, 内容不可改
byte[N] 声明的长度不可变, 内容可以改

Note: 使用数组注意数组类型的转换(memory<=>storage, 不可变<=>可变<=>string), 传参时候注意的内容.

Enum 枚举

enum Keys { Up, Down, Left, Right }

默认 uint8, 长度不够会扩展成 uint16

Keys(3) == Keys.Right
uint(Keys.Up) == 0

Struct 结构体

struct Person {
    uint age;
    uint stuID;
    string name;
}

// 默认是storage
Person _person = Person(18,101,"liyuechun");
Person _person = Person({age:18,stuID:101,name:"liyuechun"});
// 指定为memory
Person memory _person = Person({age:18,stuID:101,name:"liyuechun"});

Mapping 字典

mapping(_KeyType => _ValueType)

结构体与字典综合案例

pragma solidity ^0.4.4;

contract CrowdFunding {
    // 定义一个`Funder`结构体类型,用于表示出资人,其中有出资人的钱包地址和他一共出资的总额度。
    struct Funder {
        address addr; // 出资人地址
        uint amount;  // 出资总额
    }

    // 定义一个表示存储运动员相关信息的结构体
    struct Campaign {
        address beneficiary;  // 受益人钱包地址
        uint fundingGoal;     // 需要赞助的总额度
        uint numFunders;      // 有多少人赞助
        uint amount;          // 已赞助的总金额
        mapping (uint => Funder) funders;  // 按照索引存储出资人信息
    }

    // 统计运动员(被赞助人)数量 => ? 如何解决并发的冲突问题 ? => 使用哈希怎么样?
    uint numCampaigns;
    // 以键值对的形式存储被赞助人的信息
    mapping (uint => Campaign) campaigns;

    // 新增一个`Campaign`对象,需要传入受益人的地址和需要筹资的总额
    function newCampaign(address beneficiary, uint goal) public returns (uint campaignID) {
        campaignID = numCampaigns++; // 计数+1, campaignID 是返回变量, 名字相同无需return
        // 在storage中创建一个`Campaign`对象, 无需给mapping类型初始化,并存储到`campaigns`里面
        campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
    }

    // 通过campaignID给某个Campaign对象赞助
    function contribute(uint campaignID) public payable {
        Campaign storage c = campaigns[campaignID];  // 通过campaignID获取campaignID对应的Campaign对象
        // 根据参数创建临时 memory 结构体, 将其值赋值给 storage; 也可以使用 Funder(msg.sender, msg.value) 来初始化
        c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value}); // 存储投资者信息
        c.amount += msg.value; // 计算收到的总款

        // 实时转款方式
        c.beneficiary.transfer(msg.value);
    }

    // 检查某个campaignID编号的受益人集资是否达标,不达标返回false,否则返回true
    function checkGoalReached(uint campaignID) public returns (bool reached) {
        Campaign storage c = campaigns[campaignID];
        if (c.amount < c.fundingGoal)
            return false;

        // 达成后转款方式
        uint amount = c.amount;
        c.amount = 0;
        c.beneficiary.transfer(amount);

        return true;
    }
}

单位及全局方法

  • ether,finney,szabo, wei

1 ether => 1e3 finney => 1e6 szabo => 1e18 wei

  • seconds, minutes, hours, days, weeks, years

1 == 1 seconds
1 minutes == 60 seconds
1 hours == 60 minutes
1 days == 24 hours
1 weeks == 7 days
1 years == 365 days

  • 交易

block.blockhash(uint blockNumber) returns (bytes32): 某个区块的区块链 hash 值
block.coinbase (address): 当前区块的挖矿地址
block.difficulty (uint): 当前区块的难度
block.gaslimit (uint): 当前区块的 gaslimit
block.number (uint): 当前区块编号
block.timestamp (uint): 当前区块时间戳
msg.data (bytes): 参数
msg.gas (uint): 剩余的 gas
msg.sender (address): 当前发送消息的地址
msg.sig (bytes4): 方法 ID
msg.value (uint): 伴随消息附带的以太币数量
now (uint): 时间戳,等价于 block.timestamp (uint)
tx.gasprice (uint): 交易的 gas 单价
tx.origin (address): 交易发送地址

  • 错误

assert(bool condition):不满足条件,将抛出异常
require(bool condition):不满足条件,将抛出异常
revert() 抛出异常

if(msg.sender != owner) { revert(); }
assert(msg.sender == owner);
require(msg.sender == owner);

Donate - Support to make this site better.
捐助 - 支持我让我做得更好.