1.状态变量
是被永久地保存在合约中
contract Example {
// 这个无符号整数将会永久的被保存在区块链中
uint myUnsignedInteger = 100;
}
2.uint = uint256,一个东西。无符号整数类型。uint8, uint16, uint32 等……
3.两种数组: 静态数组
和动态数组
// 固定长度为5的string类型的静态数组:
string[5] stringArray;
// 动态数组,长度不固定,可以动态添加元素:
uint[] dynamicArray;
状态变量被永久保存在区块链中。所以创建动态数组来保存成结构的数据是非常有意义的,省gas。
公共数组:定义 public
数组, Solidity 会自动创建 getter
方法
Person[] public people;
//其它的合约可以从这个数组读取数据(但不能写入数据),所以这在合约中是一个有用的保存公共数据的模式。
4.函数里的变量都是以(_
)开头 (但不是硬性规定) 以区别全局变量,好习惯。
5.Ethereum 内部有一个散列函数keccak256
,它用了SHA3
版本。一个散列函数基本上就是把一个字符串转换为一个256位的16进制数字。字符串的一个微小变化会引起散列数据极大变化。现在不能直接用了,要使用abi.encodePacked()
keccak256(abi.encodePacked(now, msg.sender, randNonce))
6.显性变换数据类型再操作,即数据类型一样才可以进行运算
uint8 a = 5;
uint b = 6;
// 将会抛出错误,因为 a * b 返回 uint, 而不是 uint8:
uint8 c = a * b;
// 我们需要将 b 转换为 uint8:
uint8 c = a * uint8(b);
7.事件
是合约和区块链通讯的一种机制。你的前端应用“监听”某些事件,并做出反应,很有用。
// 这里建立事件
event IntegersAdded(uint x, uint y, uint result);
function add(uint _x, uint _y) public {
uint result = _x + _y;
//触发事件,通知app
emit IntegersAdded(_x, _y, result);//这里使用
return result;
}
8.Mapping(映射
是个好东西
//对于金融应用程序,将用户的余额保存在一个 uint类型的变量中:
mapping (address => uint) public accountBalance;
//或者可以用来通过userId 存储/查找的用户名
mapping (uint => string) userIdToName;
映射本质上是存储和查找数据所用的键-值
对。在第一个例子中,键是一个 address
,值是一个uint
,在第二个例子中,键是一个uint
,值是一个 string
。
9.msg.sender
也是个好东西
在 Solidity 中,有一些全局变量可以被所有函数调用。 其中一个就是 msg.sender
,它指的是当前调用者(或智能合约)的 address
。
10.require
使得函数在执行过程中,当不满足某些条件时抛出错误,并停止执行。好东西。
11.继承
也好东西。常和Import
一起玩,当然也可不跟它玩。
//A 继承 B,A便可用B里的想让你用的方法
contract A is B {
}
12.在 Solidity 中,有两个地方可以存储变量 —— storage
或 memory
。
Storage
变量是指永久存储在区块链中的变量。 Memory
变量则是临时的,当外部函数对某合约调用完成时,内存型变量即被移除。
需要手动声明存储类型,主要用于处理函数内的 结构体
和 数组
时,其他时候,编辑器会提示。
13.函数可见性-----重点!
public 公有 和 private 私有 属性
internal 和 private 类似,不过, 如果某个合约继承自其父合约,这个合约即可以访问父合约中定义的“内部”函数。
external 与public 类似,只不过这些函数只能在合约之外调用 - 它们不能被合约内的其他函数调用。稍后我们将讨论什么时候使用 external 和 public。
14.与其他合约的交互,要先定义交互合约的 interface
(接口)
接口没函数体{}
,用;
结束
15.solidity能返回多个值,没错,非常酷,不想要的值可以用,,,,,
忽略,和go语言中的_
有点像。如
(,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);//一定要查好个数....
16.函数修饰符modifier
,好东西啊
函数修饰符看起来跟函数没什么不同,不过关键字modifier
告诉编译器,这是个modifier
(修饰符),而不是个function
(函数)。它不能像函数那样被直接调用,只能被添加到函数定义的末尾,用以改变函数的行为。执行时先执行modifier
内的判断,在_
的位置正常执行被修饰的函数。
17.真金白银gas
以太坊就像一个巨大、缓慢、但非常安全的电脑。当你运行一个程序的时候,网络上的每一个节点都在进行相同的运算,以验证它的输出 —— 这就是所谓的去中心化
由于数以千计的节点同时在验证着每个功能的运行(POW),这可以确保它的数据不会被被监控,或者被刻意修改。
可能会有用户用无限循环堵塞网络,抑或用密集运算来占用大量的网络资源,为了防止这种事情的发生,以太坊的创建者为以太坊上的资源制定了价格,想要在以太坊上运算或者存储,你需要先付费
。
省gas的招数
如果一个 struct
中有多个 uint
,满足需求的情况下,要考虑安全,别溢出了,则尽可能使用较小的 uint
, Solidity 会将这些 uint
打包在一起,从而占用较少的存储空间。
18.由于 struct
的存储指针可以以参数的方式传递给一个 private
或 internal
的函数,因此结构体可以在多个函数之间相互传递。
19.view
函数不花 gas
哦
当玩家从外部调用一个view
函数,是不需要支付一分gas
的。
Solidity 使用 storage(存储)
是相当昂贵的,”写入“操作尤其贵
在数组后面加上 memory
关键字, 表明这个数组是仅仅在内存中创建,不需要写入外部存储,并且在函数调用结束时它就解散了。与在程序结束时把数据保存进 storage
的做法相比,内存运算可以大大节省gas开销 ,再把这数组放在view
里用,完全不用花钱。
20.for
循环
function getEvens() pure external returns(uint[]) {
uint[] memory evens = new uint[](5);
// 在新数组中记录序列号
uint counter = 0;
// 在循环从1迭代到10:
for (uint i = 1; i <= 10; i++) {
// 如果 `i` 是偶数...
if (i % 2 == 0) {
// 把它加入偶数数组
evens[counter] = i;
//索引加一, 指向下一个空的‘even’
counter++;
}
}
return evens;
}
21.函数修饰符,,来个概览:
1、我们有决定函数何时和被谁调用的可见性修饰符: private 意味着它只能被合约内部调用; internal 就像 private 但是也能被继承的合约调用; external 只能从合约外部调用;最后 public 可以在任何地方调用,不管是内部还是外部。
2、我们也有状态修饰符, 告诉我们函数如何和区块链交互: view 告诉我们运行这个函数不会更改和保存任何数据; pure 告诉我们这个函数不但不会往区块链写数据,它甚至不从区块链读取数据。这两种在被从合约外部调用的时候都不花费任何gas(但是它们在被内部其他函数调用的时候将会耗费gas)。
3、然后我们有了自定义的 modifiers, 对于这些修饰符我们可以自定义其对函数的约束逻辑。
payable修饰符
payable
方法是让 Solidity 和以太坊变得如此酷的一部分 —— 它们是一种可以接收以太的特殊函数。
22.用keccak256
来制造随机数
Solidity 中最好的随机数生成器是 keccak256
哈希函数,不安全,但一般时候够用,只要攻击远远得不偿失就没事。安全的就得用第三方随机函数了。
23.以太坊上的代币
一个 代币 在以太坊基本上就是一个遵循一些共同规则的智能合约——即它实现了所有其他代币合约共享的一组标准函数,例如transfer(address _to, uint256 _value)
和 balanceOf(address _owner)
在智能合约内部,通常有一个映射, mapping(address => uint256) balances
,用于追踪每个地址还有多少余额。
所以基本上一个代币只是一个追踪谁拥有多少该代币的合约,和一些可以让那些用户将他们的代币转移到其他地址的函数。
代币分ERC20和ERC721
具体实现参考:ERC20、ERC721
24.合约安全是很重要很重要非常非常重要
防止溢出
比如uint8
, 只能存储8 bit
数据。这意味着我们能存储的最大数字就是二进制 11111111
(或者说十进制的 2^8 - 1 = 255).
uint8 number = 255;
number++; // number 等于 0了。给二进制 11111111 加1, 它将被重置为 00000000
使用SafeMath
,OpenZeppelin 建立了一个库(library),默认情况下可以防止这些问题。
25.assert
和require
区别
assert 和 require 相似,若结果为否它就会抛出错误。 assert 和 require 区别在于,require 若失败则会返还给用户剩下的 gas, assert 则不会。所以大部分情况下,你写代码的时候会比较喜欢 require,assert 只在代码可能出现严重错误的时候使用,比如 uint 溢出。
26.事件
当定义的事件触发时,我们可以将事件存储到EVM
的交易日志中,日志是区块链中的一种特殊数据结构。日志与合约关联,与合约的存储合并存入区块链中。只要某个区块可以访问,其相关的日志就可以访问。但在合约中,我们不能直接访问日志和事件数据(即便是创建日志的合约)。
Indexed属性
可以在事件参数上增加indexed
属性,最多可以对三个参数增加这样的属性。加上这个属性,可以允许你在web3.js
中通过对加了这个属性的参数进行值过滤
event transfer(address indexed _from, address indexed _to, uint indexed value);
var event = myContract.transfer({value: "100"});//实现的是对value值为100的日志,过滤后的返回
//同时匹配多个值,还可以传入一个要匹配的数组
var event = myContract.transfer({value: ["99","100","101"]});
增加了indexed
的参数值会存到日志结构的Topic
部分,便于快速查找。而未加indexed
的参数值会存在data
部分,成为原始日志。需要注意的是,如果增加indexed
属性的是数组类型
(包括string
和bytes
),那么只会在Topic
存储对应的数据的web3.sha3哈希值
,将不会再存原始数据。因为Topic
是用于快速查找的,不能存任意长度的数据,所以通过Topic实际存的是数组这种非固定长度数据哈希结果。要查找时,是将要查找内容哈希后与Topic内容进行匹配,但我们不能反推哈希结果,从而得不到原始值
。
事件还支持
传入其它参数对象来限定可检索的范围,支持fromBlock
,toBlock
等过滤条件
var event = myContract.transfer({value: "100"}, {fromBlock: 0, toBlock: 'latest'});
事件可以被继承