钱包的选择与使用
- My Ether Wallet
记住密码: 123456!@#$%^qwertyQWERTY
密钥和公钥信息存储在 MyEtherWallet 文件夹中.
使用 MetaMask 进行恢复钱包
使用官方 Ethereum Wallet 恢复钱包
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;
字节数组之间的转换
- 定长字节数组的转换
定长转定长: 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];
}
- 变长字节数组的转换
变长转 string: bytes(string)
- 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);