Subscribe to 0xmind
Subscribe to 0xmind
Share Dialog
Share Dialog
<100 subscribers
<100 subscribers
声明版本
pragma solidity ^0.4.19;
contract HelloWorld {
}
声明状态变量
contract Example {
// 这个无符号整数将会永久的被保存在区块链中
uint myUnsignedInteger = 100;
}
// 其中 uint 实际上指 uint256, 又分 uint8/uint16/uint32 等位数
运算
加法: x + y
减法: x - y,
乘法: x * y
除法: x / y
取模 / 求余: x % y (例如, 13 % 5 余 3, 因为13除以5,余3)
乘方 x ** y = x^y
结构体
struct Person {
uint age;
string name;
}
数组
// 分为静态数组和动态数组
// 固定长度为2的静态数组:
uint[2] fixedArray;
// 固定长度为5的string类型的静态数组:
string[5] stringArray;
// 动态数组,长度不固定,可以动态添加元素:
uint[] dynamicArray;
// 公共数组
Person[] public people;
// 其它的合约可以从这个数组读取数据(但不能写入数据),所以这在合约中是一个有用的保存公共数据的模式
函数
function eatHamburgers(string _name, uint _amount) {
}
私有 / 公共函数
// 我们合约中的其它函数才能够调用这个函数
// 继承自父合约的子合约也不可以调用这个函数
uint[] numbers;
function _addToArray(uint _number) private {
numbers.push(_number);
}
函数更多属性
// 返回值
string greeting = "What's up dog";
function sayHello() public returns (string) {
return greeting;
}
// 修饰符 view 只能读取数据不能更改数据
function sayHello() public view returns (string) {}
// 修饰符 pure 这个函数甚至都不访问应用里的数据
function _multiply(uint a, uint b) private pure returns (uint) {
return a * b;
}
Keccak256 和 类型转换
// 散列函数,它用了SHA3版本。一个散列函数基本上就是把一个字符串转换为一个256位的16进制数字。字符串的一个微小变化会引起散列数据极大变化。
类型转换
uint8 a = 5;
uint b = 6;
// 将会抛出错误,因为 a * b 返回 uint, 而不是 uint8:
uint8 c = a * b;
// 我们需要将 b 转换为 uint8:
uint8 c = a * uint8(b);
事件
// 这里建立事件
event IntegersAdded(uint x, uint y, uint result);
function add(uint _x, uint _y) public {
uint result = _x + _y;
//触发事件,通知app
IntegersAdded(_x, _y, result);
return result;
}
// 前端监听
YourContract.IntegersAdded(function(error, result) {
// 干些事
})
使用 web3.js
// 下面是调用合约的方式:
var abi = /* abi是由编译器生成的 */
var ZombieFactoryContract = web3.eth.contract(abi)
var contractAddress = /* 发布之后在以太坊上生成的合约地址 */
var ZombieFactory = ZombieFactoryContract.at(contractAddress)
// `ZombieFactory` 能访问公共的函数以及事件
// 某个监听文本输入的监听器:
$("#ourButton").click(function(e) {
var name = $("#nameInput").val()
//调用合约的 `createRandomZombie` 函数:
ZombieFactory.createRandomZombie(name)
})
// 监听 `NewZombie` 事件, 并且更新UI
var event = ZombieFactory.NewZombie(function(error, result) {
if (error) return
generateZombie(result.zombieId, result.name, result.dna)
})
// 获取 Zombie 的 dna, 更新图像
function generateZombie(id, name, dna) {
let dnaStr = String(dna)
// 如果dna少于16位,在它前面用0补上
while (dnaStr.length < 16)
dnaStr = "0" + dnaStr
let zombieDetails = {
// 前两位数构成头部.我们可能有7种头部, 所以 % 7
// 得到的数在0-6,再加上1,数的范围变成1-7
// 通过这样计算:
headChoice: dnaStr.substring(0, 2) % 7 + 1,
// 我们得到的图片名称从head1.png 到 head7.png
// 接下来的两位数构成眼睛, 眼睛变化就对11取模:
eyeChoice: dnaStr.substring(2, 4) % 11 + 1,
// 再接下来的两位数构成衣服,衣服变化就对6取模:
shirtChoice: dnaStr.substring(4, 6) % 6 + 1,
//最后6位控制颜色. 用css选择器: hue-rotate来更新
// 360度:
skinColorChoice: parseInt(dnaStr.substring(6, 8) / 100 * 360),
eyeColorChoice: parseInt(dnaStr.substring(8, 10) / 100 * 360),
clothesColorChoice: parseInt(dnaStr.substring(10, 12) / 100 * 360),
zombieName: name,
zombieDescription: "A Level 1 CryptoZombie",
}
return zombieDetails
}
映射(Mapping)和地址(Address)
// 以太坊区块链由 _ account _ (账户)组成
// _映射_ 是另一种在 Solidity 中存储有组织数据的方法
映射可以理解为一个包含一个或多个 key-value 键值对的对象
Msg.sender
全局变量,指的是当前调用者(或智能合约)的 address
Require
// require 使得函数在执行过程中,当不满足某些条件时抛出错误,并停止执行
function sayHiToVitalik(string _name) public returns (string) {
// 比较 _name 是否等于 "Vitalik". 如果不成立,抛出异常并终止程序
// (敲黑板: Solidity 并不支持原生的字符串比较, 我们只能通过比较
// 两字符串的 keccak256 哈希值来进行判断)
require(keccak256(_name) == keccak256("Vitalik"));
// 如果返回 true, 运行如下语句
return "Hi!";
}
继承(Inheritance)
contract Doge {
function catchphrase() public returns (string) {
return "So Wow CryptoDoge";
}
}
contract BabyDoge is Doge {
function anotherCatchphrase() public returns (string) {
return "Such Moon BabyDoge";
}
}
引入(Import)
import "./someothercontract.sol";
contract newContract is SomeOtherContract {
}
Storage与Memory
// Storage 变量是指永久存储在区块链中的变量。
// Memory 变量则是临时的,当外部函数对某合约调用完成时,内存型变量即被移除。
internal(内部) 和 external(外部)
// internal 和 private 类似,不过, 如果某个合约继承自其父合约,这个合约即可以访问父合约中定义的“内部”函数。
// external 与public 类似,只不过这些函数只能在合约之外调用 - 它们不能被合约内的其他函数调用。
与其他合约的交互-接口
// 先定义一个 interface
// 不使用大括号({ 和 })定义函数体,我们单单用分号(;)结束了函数声明
contract NumberInterface {
function getNum(address _myAddress) public view returns (uint);
}
// 在自己的合约中使用
contract MyContract {
address NumberInterfaceAddress = 0xab38...;
// ^ 这是FavoriteNumber合约在以太坊上的地址
NumberInterface numberContract = NumberInterface(NumberInterfaceAddress);
// 现在变量 `numberContract` 指向另一个合约对象
function someFunction() public {
// 现在我们可以调用在那个合约中声明的 `getNum`函数:
uint num = numberContract.getNum(msg.sender);
// ...在这儿使用 `num`变量做些什么
}
}
处理多返回值
function multipleReturns() internal returns(uint a, uint b, uint c) {
return (1, 2, 3);
}
function processMultipleReturns() external {
uint a;
uint b;
uint c;
// 这样来做批量赋值:
(a, b, c) = multipleReturns();
}
// 或者如果我们只想返回其中一个变量:
function getLastReturnValue() external {
uint c;
// 可以对其他字段留空:
(,,c) = multipleReturns();
}
Ownable Contracts
// OpenZeppelin库的Ownable 合约
contract Ownable {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
// function Ownable()是一个 _ constructor_ (构造函数),构造函数不是必须的,它与合约同名,构造函数一生中唯一的一次执行,就是在合约最初被创建的时候。
function Ownable() public {
owner = msg.sender;
}
// 在其他语句执行前,为它检查下先验条件。 在这个例子中,我们就可以写个修饰符 onlyOwner 检查下调用者,确保只有合约的主人才能运行本函数
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0));
OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}
合约继承 Ownable
import "./ownable.sol";
contract ZombieFactory is Ownable {}
gas优化1
// 把 uint 绑定到 struct 里面
// 如果一个 struct 中有多个 uint,则尽可能使用较小的 uint, Solidity 会将这些 uint 打包在一起,从而占用较少的存储空间
struct NormalStruct {
uint a;
uint b;
uint c;
}
struct MiniMe {
uint32 a;
uint32 b;
uint c;
}
// 因为使用了结构打包,`mini` 比 `normal` 占用的空间更少
NormalStruct normal = NormalStruct(10, 20, 30);
MiniMe mini = MiniMe(10, 20, 30);
// 当 uint 定义在一个 struct 中的时候,尽量使用最小的整数子类型以节约空间。 并且把同样类型的变量放一起(即在 struct 中将把变量按照类型依次放置)
时间单位
// Solidity 使用自己的本地时间单位
// 变量 now 将返回当前的unix时间戳(自1970年1月1日以来经过的秒数)
// Solidity 还包含秒(seconds),分钟(minutes),小时(hours),天(days),周(weeks) 和 年(years) 等时间单位
// 必须使用 uint32(...) 进行强制类型转换,因为 now 返回类型 uint256。
// 所以我们需要明确将它转换成一个 uint32 类型的变量
gas优化2
// 使用 view 查询数据,但是注意:
// 如果一个 view 函数在另一个函数的内部被调用,
// 而调用函数与 view 函数的不属于同一个合约,也会产生调用成本。
//这是因为如果主调函数在以太坊创建了一个事务,它仍然需要逐个节点去验证。
//所以标记为 view 的函数只有在外部调用时才是免费的。(在所能只读的函数上标记上表示“只读”的“external view 声明)
修饰符 payable
一种可以接收以太的特殊函数
contract OnlineStore {
function buySomething() external payable {
// 检查以确定0.001以太发送出去来运行函数:
require(msg.value == 0.001 ether);
// 如果为真,一些用来向函数调用者发送数字内容的逻辑
transferThing(msg.sender);
}
}
随机数
这个方法首先拿到now的时间戳、msg.sender、 以及一个自增数nonce(一个仅会被使用一次的数,这样我们就不会对相同的输入值调用一次以上哈希函数了)。
// 生成一个0到100的随机数:
uint randNonce = 0;
uint random = uint(keccak256(now, msg.sender, randNonce)) % 100;
randNonce++;
uint random2 = uint(keccak256(now, msg.sender, randNonce)) % 100;
转移标准
ERC721 规范有两种不同的方法来转移代币
第一种方法是代币的拥有者调用transfer 方法,传入他想转移到的 address 和他想转移的代币的 _tokenId。
第二种方法是代币拥有者首先调用 approve,然后传入与以上相同的参数。接着,该合约会存储谁被允许提取代币,通常存储到一个 mapping (uint256 => address) 里。然后,当有人调用 takeOwnership 时,合约会检查 msg.sender 是否得到拥有者的批准来提取代币,如果是,则将代币转移给他。
预防溢出 假设我们有一个uint8, 只能存储8 bit数据。这意味着我们能存储的最大数字就是二进制11111111(或者说十进制的 2^8 - 1 = 255)
uint8 number = 255;
number++;
// number = 0
使用safeMath
import "safemath.sol"
contract Hello{
// 声明了 using SafeMath for uint 后,我们用来调用这些方法的 uint 就自动被作为第一个参数传递进去了
using SafeMath for uint;
using SafeMath32 for uint32;
using SafeMath16 for uint16;
uint test = 2;
test = test.mul(3); // 等于 test = test * 3
}
注释 - 使用 natspec 标准
/// @title 一个简单的基础运算合约
/// @author H4XF13LD MORRIS 💯💯😎💯💯
/// @notice 现在,这个合约只添加一个乘法
contract Math {
/// @notice 两个数相乘
/// @param x 第一个 uint
/// @param y 第二个 uint
/// @return z (x * y) 的结果
/// @dev 现在这个方法不检查溢出
function multiply(uint x, uint y) returns (uint z) {
// 这只是个普通的注释,不会被 natspec 解释
z = x * y;
}
}
声明版本
pragma solidity ^0.4.19;
contract HelloWorld {
}
声明状态变量
contract Example {
// 这个无符号整数将会永久的被保存在区块链中
uint myUnsignedInteger = 100;
}
// 其中 uint 实际上指 uint256, 又分 uint8/uint16/uint32 等位数
运算
加法: x + y
减法: x - y,
乘法: x * y
除法: x / y
取模 / 求余: x % y (例如, 13 % 5 余 3, 因为13除以5,余3)
乘方 x ** y = x^y
结构体
struct Person {
uint age;
string name;
}
数组
// 分为静态数组和动态数组
// 固定长度为2的静态数组:
uint[2] fixedArray;
// 固定长度为5的string类型的静态数组:
string[5] stringArray;
// 动态数组,长度不固定,可以动态添加元素:
uint[] dynamicArray;
// 公共数组
Person[] public people;
// 其它的合约可以从这个数组读取数据(但不能写入数据),所以这在合约中是一个有用的保存公共数据的模式
函数
function eatHamburgers(string _name, uint _amount) {
}
私有 / 公共函数
// 我们合约中的其它函数才能够调用这个函数
// 继承自父合约的子合约也不可以调用这个函数
uint[] numbers;
function _addToArray(uint _number) private {
numbers.push(_number);
}
函数更多属性
// 返回值
string greeting = "What's up dog";
function sayHello() public returns (string) {
return greeting;
}
// 修饰符 view 只能读取数据不能更改数据
function sayHello() public view returns (string) {}
// 修饰符 pure 这个函数甚至都不访问应用里的数据
function _multiply(uint a, uint b) private pure returns (uint) {
return a * b;
}
Keccak256 和 类型转换
// 散列函数,它用了SHA3版本。一个散列函数基本上就是把一个字符串转换为一个256位的16进制数字。字符串的一个微小变化会引起散列数据极大变化。
类型转换
uint8 a = 5;
uint b = 6;
// 将会抛出错误,因为 a * b 返回 uint, 而不是 uint8:
uint8 c = a * b;
// 我们需要将 b 转换为 uint8:
uint8 c = a * uint8(b);
事件
// 这里建立事件
event IntegersAdded(uint x, uint y, uint result);
function add(uint _x, uint _y) public {
uint result = _x + _y;
//触发事件,通知app
IntegersAdded(_x, _y, result);
return result;
}
// 前端监听
YourContract.IntegersAdded(function(error, result) {
// 干些事
})
使用 web3.js
// 下面是调用合约的方式:
var abi = /* abi是由编译器生成的 */
var ZombieFactoryContract = web3.eth.contract(abi)
var contractAddress = /* 发布之后在以太坊上生成的合约地址 */
var ZombieFactory = ZombieFactoryContract.at(contractAddress)
// `ZombieFactory` 能访问公共的函数以及事件
// 某个监听文本输入的监听器:
$("#ourButton").click(function(e) {
var name = $("#nameInput").val()
//调用合约的 `createRandomZombie` 函数:
ZombieFactory.createRandomZombie(name)
})
// 监听 `NewZombie` 事件, 并且更新UI
var event = ZombieFactory.NewZombie(function(error, result) {
if (error) return
generateZombie(result.zombieId, result.name, result.dna)
})
// 获取 Zombie 的 dna, 更新图像
function generateZombie(id, name, dna) {
let dnaStr = String(dna)
// 如果dna少于16位,在它前面用0补上
while (dnaStr.length < 16)
dnaStr = "0" + dnaStr
let zombieDetails = {
// 前两位数构成头部.我们可能有7种头部, 所以 % 7
// 得到的数在0-6,再加上1,数的范围变成1-7
// 通过这样计算:
headChoice: dnaStr.substring(0, 2) % 7 + 1,
// 我们得到的图片名称从head1.png 到 head7.png
// 接下来的两位数构成眼睛, 眼睛变化就对11取模:
eyeChoice: dnaStr.substring(2, 4) % 11 + 1,
// 再接下来的两位数构成衣服,衣服变化就对6取模:
shirtChoice: dnaStr.substring(4, 6) % 6 + 1,
//最后6位控制颜色. 用css选择器: hue-rotate来更新
// 360度:
skinColorChoice: parseInt(dnaStr.substring(6, 8) / 100 * 360),
eyeColorChoice: parseInt(dnaStr.substring(8, 10) / 100 * 360),
clothesColorChoice: parseInt(dnaStr.substring(10, 12) / 100 * 360),
zombieName: name,
zombieDescription: "A Level 1 CryptoZombie",
}
return zombieDetails
}
映射(Mapping)和地址(Address)
// 以太坊区块链由 _ account _ (账户)组成
// _映射_ 是另一种在 Solidity 中存储有组织数据的方法
映射可以理解为一个包含一个或多个 key-value 键值对的对象
Msg.sender
全局变量,指的是当前调用者(或智能合约)的 address
Require
// require 使得函数在执行过程中,当不满足某些条件时抛出错误,并停止执行
function sayHiToVitalik(string _name) public returns (string) {
// 比较 _name 是否等于 "Vitalik". 如果不成立,抛出异常并终止程序
// (敲黑板: Solidity 并不支持原生的字符串比较, 我们只能通过比较
// 两字符串的 keccak256 哈希值来进行判断)
require(keccak256(_name) == keccak256("Vitalik"));
// 如果返回 true, 运行如下语句
return "Hi!";
}
继承(Inheritance)
contract Doge {
function catchphrase() public returns (string) {
return "So Wow CryptoDoge";
}
}
contract BabyDoge is Doge {
function anotherCatchphrase() public returns (string) {
return "Such Moon BabyDoge";
}
}
引入(Import)
import "./someothercontract.sol";
contract newContract is SomeOtherContract {
}
Storage与Memory
// Storage 变量是指永久存储在区块链中的变量。
// Memory 变量则是临时的,当外部函数对某合约调用完成时,内存型变量即被移除。
internal(内部) 和 external(外部)
// internal 和 private 类似,不过, 如果某个合约继承自其父合约,这个合约即可以访问父合约中定义的“内部”函数。
// external 与public 类似,只不过这些函数只能在合约之外调用 - 它们不能被合约内的其他函数调用。
与其他合约的交互-接口
// 先定义一个 interface
// 不使用大括号({ 和 })定义函数体,我们单单用分号(;)结束了函数声明
contract NumberInterface {
function getNum(address _myAddress) public view returns (uint);
}
// 在自己的合约中使用
contract MyContract {
address NumberInterfaceAddress = 0xab38...;
// ^ 这是FavoriteNumber合约在以太坊上的地址
NumberInterface numberContract = NumberInterface(NumberInterfaceAddress);
// 现在变量 `numberContract` 指向另一个合约对象
function someFunction() public {
// 现在我们可以调用在那个合约中声明的 `getNum`函数:
uint num = numberContract.getNum(msg.sender);
// ...在这儿使用 `num`变量做些什么
}
}
处理多返回值
function multipleReturns() internal returns(uint a, uint b, uint c) {
return (1, 2, 3);
}
function processMultipleReturns() external {
uint a;
uint b;
uint c;
// 这样来做批量赋值:
(a, b, c) = multipleReturns();
}
// 或者如果我们只想返回其中一个变量:
function getLastReturnValue() external {
uint c;
// 可以对其他字段留空:
(,,c) = multipleReturns();
}
Ownable Contracts
// OpenZeppelin库的Ownable 合约
contract Ownable {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
// function Ownable()是一个 _ constructor_ (构造函数),构造函数不是必须的,它与合约同名,构造函数一生中唯一的一次执行,就是在合约最初被创建的时候。
function Ownable() public {
owner = msg.sender;
}
// 在其他语句执行前,为它检查下先验条件。 在这个例子中,我们就可以写个修饰符 onlyOwner 检查下调用者,确保只有合约的主人才能运行本函数
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0));
OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}
合约继承 Ownable
import "./ownable.sol";
contract ZombieFactory is Ownable {}
gas优化1
// 把 uint 绑定到 struct 里面
// 如果一个 struct 中有多个 uint,则尽可能使用较小的 uint, Solidity 会将这些 uint 打包在一起,从而占用较少的存储空间
struct NormalStruct {
uint a;
uint b;
uint c;
}
struct MiniMe {
uint32 a;
uint32 b;
uint c;
}
// 因为使用了结构打包,`mini` 比 `normal` 占用的空间更少
NormalStruct normal = NormalStruct(10, 20, 30);
MiniMe mini = MiniMe(10, 20, 30);
// 当 uint 定义在一个 struct 中的时候,尽量使用最小的整数子类型以节约空间。 并且把同样类型的变量放一起(即在 struct 中将把变量按照类型依次放置)
时间单位
// Solidity 使用自己的本地时间单位
// 变量 now 将返回当前的unix时间戳(自1970年1月1日以来经过的秒数)
// Solidity 还包含秒(seconds),分钟(minutes),小时(hours),天(days),周(weeks) 和 年(years) 等时间单位
// 必须使用 uint32(...) 进行强制类型转换,因为 now 返回类型 uint256。
// 所以我们需要明确将它转换成一个 uint32 类型的变量
gas优化2
// 使用 view 查询数据,但是注意:
// 如果一个 view 函数在另一个函数的内部被调用,
// 而调用函数与 view 函数的不属于同一个合约,也会产生调用成本。
//这是因为如果主调函数在以太坊创建了一个事务,它仍然需要逐个节点去验证。
//所以标记为 view 的函数只有在外部调用时才是免费的。(在所能只读的函数上标记上表示“只读”的“external view 声明)
修饰符 payable
一种可以接收以太的特殊函数
contract OnlineStore {
function buySomething() external payable {
// 检查以确定0.001以太发送出去来运行函数:
require(msg.value == 0.001 ether);
// 如果为真,一些用来向函数调用者发送数字内容的逻辑
transferThing(msg.sender);
}
}
随机数
这个方法首先拿到now的时间戳、msg.sender、 以及一个自增数nonce(一个仅会被使用一次的数,这样我们就不会对相同的输入值调用一次以上哈希函数了)。
// 生成一个0到100的随机数:
uint randNonce = 0;
uint random = uint(keccak256(now, msg.sender, randNonce)) % 100;
randNonce++;
uint random2 = uint(keccak256(now, msg.sender, randNonce)) % 100;
转移标准
ERC721 规范有两种不同的方法来转移代币
第一种方法是代币的拥有者调用transfer 方法,传入他想转移到的 address 和他想转移的代币的 _tokenId。
第二种方法是代币拥有者首先调用 approve,然后传入与以上相同的参数。接着,该合约会存储谁被允许提取代币,通常存储到一个 mapping (uint256 => address) 里。然后,当有人调用 takeOwnership 时,合约会检查 msg.sender 是否得到拥有者的批准来提取代币,如果是,则将代币转移给他。
预防溢出 假设我们有一个uint8, 只能存储8 bit数据。这意味着我们能存储的最大数字就是二进制11111111(或者说十进制的 2^8 - 1 = 255)
uint8 number = 255;
number++;
// number = 0
使用safeMath
import "safemath.sol"
contract Hello{
// 声明了 using SafeMath for uint 后,我们用来调用这些方法的 uint 就自动被作为第一个参数传递进去了
using SafeMath for uint;
using SafeMath32 for uint32;
using SafeMath16 for uint16;
uint test = 2;
test = test.mul(3); // 等于 test = test * 3
}
注释 - 使用 natspec 标准
/// @title 一个简单的基础运算合约
/// @author H4XF13LD MORRIS 💯💯😎💯💯
/// @notice 现在,这个合约只添加一个乘法
contract Math {
/// @notice 两个数相乘
/// @param x 第一个 uint
/// @param y 第二个 uint
/// @return z (x * y) 的结果
/// @dev 现在这个方法不检查溢出
function multiply(uint x, uint y) returns (uint z) {
// 这只是个普通的注释,不会被 natspec 解释
z = x * y;
}
}
No activity yet