solidity学习-cryptoPunks为实例

在这里使用cryptoPunks为实例来进行solidity的介绍,通常这些内容理解了就可以进行相对简单的智能合约的编写了,同时会添加一些我认为也十分重要的内容
学习文档为http://solidity-cn.readthedocs.io/zh/develop/layout-of-source-files.html

()pragma solidity ^0.4.0;
这样,意味着源文件将既不容许低于 0.4.0 版本的编译器编译, 也不容许高于(包含) 0.5.0 版本的编译器编译(第二个条件因使用 ^ 被添加)
还有pragma solidity >0.4.23 <0.5.0;

()声明状态变量
string public name;
通常我都会显示地将他们定义为public,虽然你不这么定义,调用合约时也能调用,可是用remix-ide的就知道区别,当你在run中deploy后,出现的变量按钮都是你声明为public

()变量声明后将有默认初始值,其初始值字节表示所有为零。任何类型变量的“默认值”是其对应类型的典型“零状态”,因此并不须要本身赋值,声明便可
例如, bool 类型的默认值是 false 。 uint 或 int 类型的默认值是 0 。对于静态大小的数组和 bytes1 到 bytes32 ,每一个单独的元素将被初始化为与其类型相对应的默认值。 最后,对于动态大小的数组, bytes 和 string 类型,其默认缺省值是一个空数组或字符串。
因此其实代码中mapping (uint => Bid) public punkBids的punkBids被声明为{false,0,0x0,0}

()映射
mapping (address => uint256) public balanceOf;
当你想要将两个变量设定必定关系时,如该例子,想要经过帐户的地址来查出帐户拥有的token数量
也能够写得复杂一点,好比:
mapping (address => mapping (address => uint256)) public allowance;
访问就是allowance[‘’][‘’]

()结构类型¶
结构是能够将几个变量分组的自定义类型
1》structure结构体
struct Bid {
        bool hasBid;//是否正在竞标
        uint punkIndex;
        address bidder;
        uint value;
    }
这样你能够将一组相关的信息写在一块儿,而后再结合语句:
mapping (uint => Bid) public punkBids;
这样你就能够经过punkBids[5]的映射方式去获取结构体Bid的信息了

2》enum枚举类型
枚举可用来建立由必定数量的“常量值”构成的自定义类型
举例:enum Gender {Male,Female}
Male = 0 , Female = 1
访问枚举方式 Gender.Male 实际等于数字 0
性别枚举的值就有男和女,因此当你声明一个枚举类型的变量的时候,它的值要么是男,要么是女这样,枚举下标定义从左至右从零开始css


()event
用于事件监听,好比
event PunkTransfer(address indexed from, address indexed to, uint256 punkIndex);//声明
function transferPunk(address to, uint punkIndex) {

    emit PunkTransfer(msg.sender, to, punkIndex); //监听事件

}

该event的做用就是你可使用instance.PunkTransfer(filter,callback)函数,经过设置过滤条件filter,如{from:web3.eth.accounts[0]}来在调用transferPunk的同时监听该函数的特定条件的交易是否成功及其返回内容。
监听时的emit要加上,没有回警告


() if (msg.sender != owner) throw;  
这是在合约中咱们常见到的条件判断语句,可是如今用这个的时候会有警告,如今都不用这种写法了,能够换成
•     if(msg.sender != owner) { revert(); } 

    •    assert(msg.sender == owner);
    •    require(msg.sender == owner);
assert和require正确才可往下走
revert() 和 require() 都会返还剩余 gas,并且容许返回一个message,好比:
revert(‘Something bad happened’);
或者
require(condition, ‘Something bad happened’);
就是若是当判断的条件不成立的时候就会输出的错误信息
三者使用状况:
    •    通常地,尽可能使用 require 函数
    •    通常地,require 应该在函数最开始的地方使用
若是有复杂的 if/else 逻辑流,那么应该考虑使用 revert() 函数而不是require()。
    •    通常地,尽可能少使用 assert 调用
    •    通常地,assert 应该在函数结尾处使用
基本上,require() 应该被用于函数中检查条件,assert() 用于预防不该该发生的状况,但不该该使条件错误。
另外,“除非认为以前的检查(用 if 或 require )会致使没法验证 overflow,不然不该该盲目使用 assert 来检查 overflow”——来自于@chriseth
举例,在下例中,你能够看到如何轻松使用``require``检查输入条件以及如何使用``assert``检查内部错误,注意,你能够给 require 提供一个消息字符串,而 assert 不行:
contract Sharer {
    function sendHalf(address addr) public payable returns (uint balance) {
        require(msg.value % 2 == 0, "Even value required.");
        uint balanceBeforeTransfer = this.balance;
        addr.transfer(msg.value / 2);
                    //因为转移函数在失败时抛出异常而且不能在这里回调,所以咱们应该没有办法仍然有一半的钱。
        assert(this.balance == balanceBeforeTransfer - msg.value / 2);
        return this.balance;
    }

因此我通常就简单地记为,当想要在调用函数以前进行条件的限制时,就使用require或者if-revert,并且能够写message来输出错误信息以肯定是哪里的判断限制了交易的进行。在remix-ide中,message会在触发后在控制台显示错误信息,但如果使用本身配置的私有链,就须要去本身查看了(没试过,以后有空试试)

()函数
function (<parameter types>) {internal|external} [pure|constant|view|payable] [returns (<return types>)]

1》参数类型<parameter types>能够为空();返回类型<return types>与参数类型相反,返回类型不能为空 —— 若是函数类型不须要返回,则须要删除整个 returns (<return types>) 部分。
solidity与 Javascript 和 C 不一样的是,它们可能返回任意数量的参数做为输出,但solidity不是,它返回多少个变量是定义死的,returns (uint a, uint b)。
可是参数的名称是能够省略的。在函数中未使用参数的名称(特别是返回参数)能够省略。好比一个函数return(false,2),那它的返回参数就能够写为returns(bool,uint)。这些参数仍然存在于堆栈中,但它们没法访问。
举例说明:
contract C {
    // 省略参数名称
    function func(uint k, uint) public pure returns(uint) {
        return k;
    }


2》被声明为internal|external,是内部函数和外部函数的区别
有两种方法能够访问当前合约中的函数:一种是直接使用它的名字,f ,另外一种是使用 this.f 。 前者适用于声明为内部函数internal,后者适用于声明为外部函数external的函数

请注意,当前合约函数被声明为 public 函数(默认也为public),则该函数既能够被看成内部函数也能够被看成外部函数使用。 若是想将一个函数看成内部函数使用,就用 f 调用,若是想将其看成外部函数,使用 this.f

3》pure|constant|view|payablehtml


1)带上payable关键字,说明这个函数须要接受msg.value,好比
function buyPunk(uint punkIndex) payable {}
那么在你部署好合约后,想要调用这个函数,你的调用形式必定是:
contract.buyPunk(punkIndex,{from:web3.eth.accounts[0],value:30,gas:30000});
这里的value传入的值就是在合约中使用的msg.value,from传入的值就是合约中的msg.sender
这里的gas的传入也是一个十分重要的点,详细分析请看


2)view是不能修改状态(至关于constant)
1. 写入状态变量;
2. (emitting??)发送事件;
3. 建立其余合约;
4. 使用selfdestruct;
5. 经过调用发送Ether;
6. 调用没有被声明为view和pure的函数
7. 使用低级调用;
8. 使用包含特定操做码的内联程序集。

3)pure的限制更多一点,它是不能修改状态也不能读取状态
1. 读取状态变量;
2. 访问this.balance 或者<address>.balance;
3. 访问block,tx,msg(除了msg.sig和msg.data);
4. 调用没有标记为pure的函数;
5. 使用包含特定操做码的内联程序集。


()cryptoPunks函数withdraw函数中有一句msg.sender.transfer(amount)
这句话的意思是,当咱们在合同中有写payable的函数(如buyPunk)的时候,当调用这个函数时,msg.sender就须要付必定的msg.value,在这里,这个msg.value的去向to是合约的地址。
可是通常在另外一个函数(withdraw)中,咱们收取这个value是但愿它最终是会流向某一个帐户的地址的,因此在这里咱们通常都会定义一个数组,即一个临时帐户pendingWithdrawals来记录你付的value的值, uint amount = pendingWithdrawals[msg.sender]。
当最后想要把这个值给一个帐户地址的时候,咱们就会使用语句msg.sender.transfer(amount)来将合约地址上的这笔钱转到msg.sender这个帐户地址中去


()地址类型address
address:地址类型存储一个 20 字节的值(以太坊地址的大小)
可使用 balance 属性来查询一个地址的余额, 也可使用 transfer 函数向一个地址发送 以太币(以 wei 为单位),即上文的msg.sender.transfer(amount)


()在remix-ide中编译合约的时候有出现过下面的警告:
Warning: Variable is declared as a storage pointer. Use an explicit "storage" keyword to silence this warning.

解决办法是: 加一个显式声明storage(搞清楚其与memory的区别)
Bid storage exist = bidForPicture[pictureId];

() 数据位置——详细说明:
有三种类型,memory,storage和calldata,通常只有外部函数的参数(不包括返回参数)被强制指定为calldata。这种数据位置是只读的,不会持久化到区块链

storage存储或memory内存
memory存储位置同咱们普通程序的内存相似,即分配,即便用,动态分配,越过做用域即不可被访问,等待被回收。
而对于storage的变量,数据将永远存在于区块链上。

总结¶
强制指定的数据位置:
    •    外部函数的参数(不包括返回参数): calldata,效果跟 memory 差很少
    •    状态变量: storage
默认数据位置:
    •    函数参数(包括返回参数): memory
    •    全部其它局部变量: storage

举例说明:python

contract C {
    uint[] x; // x 的数据存储位置是 storage,状态变量

    // memoryArray 的数据存储位置是 memory,函数参数
    function f(uint[] memoryArray) public {
        x = memoryArray; // 将整个数组拷贝到 状态变量storage 中,可行
        var y = x;  // 分配一个指针(其中 y 的数据存储位置是 storage),可行,其余局部变量
        y[7]; // 返回第 8 个元素,可行
        y.length = 2; // 经过 y 修改 x,可行
        delete x; // 清除数组,同时修改 y,可行
        // 下面的就不可行了;须要在 storage 中建立新的未命名的临时数组
        // 但 storage 是“静态”分配的:
        // y = memoryArray;


        // 下面这一行也不可行,由于这会“重置”指针,
        // 但并无可让它指向的合适的存储位置。
        // delete y;

    }
}



三种数据之间的相互赋值行为:
(1)当咱们把一个storage类型的变量赋值给另外一个storage时,咱们只是修改了它的指针,一个变,另外一个也变
struct S{string a;uint b;}
  S s;
    
  function convertStorage(S storage s) internal{
    S tmp = s;
    tmp.a = “Test”;//s的a的值也改变了
  }

(2)memory转为memory,memory之间是引用传递,并不会拷贝数据,一个变,另外一个也跟着变

(3)memory转换为storage
由于局部变量和状态变量的类型均可能是storage。因此咱们要分开来讲这两种状况:web


1. memory赋值给状态变量
将一个memory类型的变量赋值给一个状态变量时,实际是将内存变量拷贝到存储中,后续二者不会有任何关系
  S s;

  function memoryToState(S memory tmp) internal{
    s = tmp;//从内存中复制到状态变量中。

    //修改旧memory中的值,并不会影响状态变量
    tmp.a = “Test”;//s的就不会变了
  }

2.memory赋值给局部变量是不能够的
因为在区块链中,storage必须是静态分配存储空间的。局部变量虽然是一个storage的,但它仅仅是一个storage类型的指针。若是进行这样的赋值,实际会产生一个错误。
function assign(S s) internal{ //默认的变量是storage的指针
//error:Type struct MemoryToLocalVar.S memory is not implicitly convertible to expected type struct MemoryToLocalVar.S storage pointer.
S tmp = s; //修改变量为memory类型S memory tmp = s;便可

}

(4)storage转为memory,会建立一份独立的拷贝,两两互不影响


()在合约中有不少地方即有uint8/uint256/uint,区别是:
声明一个类型为 uint (256位无符号整数)
因此在solidity中,若是你声明了一个变量类型是uint的,其实就是声明了一个uint256的
int / uint :分别表示有符号和无符号的不一样位数的整型变量。 支持关键字 uint8 到 uint256 (无符号,从 8 位到 256 位)以及 int8 到 int256,以 8 位为步长递增。 uint 和 int 分别是 uint256 和 int256 的别名

()判断语句
JavaScript 中的大部分控制结构在 Solidity 中都是可用的,除了 switch 和 goto。 所以 Solidity 中有 if,else,while,do,for,break,continue,return,``? :``这些与在 C 或者 JavaScript 中表达相同语义的关键词。
用于表示条件的括号 不能够 被省略,单语句体两边的花括号能够被省略。

注意,与 C 和 JavaScript 不一样, Solidity 中非布尔类型数值不能转换为布尔类型,所以 if (1) { ... } 的写法在 Solidity 中 无效 。

()
Gas requirement of function CryptoPicturesMarket.imageHash() high: infinite. If the gas requirement of a function is higher than the block gas limit, it cannot be executed. Please avoid loops in your functions or actions that modify large areas of storage (this includes clearing or copying arrays in storage)
可是我查了一下资料,发现这个好像不是什么问题,但愿之后可以看到能够解决它的方法吧数据库

 

以上是在cryptoPunks中须要知道的内容,下面是一些添加内容:json


其余内容:
()一个合约如何调用另外一个合约数组

contract MappingExample {
    mapping(address => uint) public balances;

    function update(uint newBalance) public {
        balances[msg.sender] = newBalance;
    }
}

contract MappingUser {
    function f() public returns (uint) {
        MappingExample m = new MappingExample();
        m.update(100);
        return m.balances(this);//this是该合约的地址
    }
}

经过 new 建立合约
使用关键字 new 能够建立一个新合约。待建立合约的完整代码必须事先知道,因此在MappingUser合约中建立合约MappingExample,MappingExample合约必须在MappingUser合约以前声明。所以递归的建立依赖是不可能的。
即一个合约中建立另外一个合约,另外一个合约必定要在该合约以前就已经声明了

()合约相关¶
this (current contract's type):
当前合约,能够明确转换为 地址类型。
selfdestruct(address recipient):
销毁合约,并把余额发送到指定 地址类型。
suicide(address recipient):
与 selfdestruct 等价,但已不推荐使用。

()导入
import * as symbolName from “filename”;//或“.sol”文件
等同于import "filename" as symbolName;

()注释
单行(//)、多行注释(/*…*/)和(/** ... */)在函数开头或内部作的注释
/** @title 形状计算器。 */
contract shapeCalculator {
    /** @dev 求矩形代表面积与周长。
    * @param w 矩形宽度。
    * @param h 矩形高度。
    * @return s 求得表面积。
    * @return p 求得周长。
    */
    function rectangle(uint w, uint h) returns (uint s, uint p) {
        s = w * h;
        p = 2 * (w + h);
    }
}

()定长字节数组¶
关键字有:bytes1, bytes2, bytes3, ..., bytes32。byte 是 bytes1 的别名。
.length 表示这个字节数组的长度(只读)
注解
能够将 byte[] 看成字节数组使用,但这种方式很是浪费存储空间,准确来讲,是在传入调用时,每一个元素会浪费 31 字节。 更好地作法是使用 bytes。

()变长字节数组¶
bytes:
变长字节数组,参见 数组。它并非值类型。
string:
变长 UTF-8 编码字符串类型,参见 数组。并非值类型。


()字面常数-须要好好看看
Solidity 中是没有八进制的,所以前置 0 是无效的
注解
数值字面常数表达式只要在非字面常数表达式中使用就会转换成非字面常数类型。 在下面的例子中,尽管咱们知道 b 的值是一个整数,但 2.5 + a 这部分表达式并不进行类型检查,所以编译不能经过。
//在remix中编译的时候的确是会报错
browser/test.sol:4:17: TypeError: Operator + not compatible with types rational_const 5 / 2 and uint128
    uint128 b = 2.5 + a + 0.5;
                ^-----^
uint128 a = 1;
uint128 b = 2.5 + a + 0.5;

意思应该就是说由于在字面上咱们能够看见不进行类型检查时a是非字面常数类型

十六进制字面常数以关键字 hex 打头,后面紧跟着用单引号或双引号引发来的字符串(例如,hex”001122FF")


()library ArrayUtils {
  // internal内部函数能够在内部库函数中使用,
  // 由于它们会成为同一代码上下文的一部分
…}
contract Pyramid {
  using ArrayUtils for *;//使用,而后经过ArrayUtils.函数名()调用
}

()数组:
能够在声明时指定长度,也能够动态调整大小。
对于 存储storage的数组来讲,元素类型能够是任意的(即元素也能够是数组类型,映射类型或者结构体)。
 对于 内存memory的数组来讲,元素类型不能是映射类型,若是做为 public 函数的参数,它只能是 ABI 类型。

(1)在memory中:一经建立,内存memory数组的大小就是固定的(但倒是动态的,也就是说,它依赖于运行时的参数
1.内存中的数组是不容许建立变长数组的
uint[] memory a;//wrong
2.数组字面常数是一种定长的 内存memory数组类型
在memory中,声明定长数组:
uint[] memory a = new uint[](2);

在memory中,声明定长数组并初始化:
uint[2] memory a = [uint(1),2];
uint[] memory a = [uint(1),2];
//wrong,定长的 内存memory数组并不能赋值给变长的 内存memory数组
为何要在第一个元素一前面加上uint的类型,这是由于咱们前面声明的是uint的数组,而在solidity中有uint8,uint248等多种不一样位数的无符号int型常量,在不声明的状况下,默认使用uint256,而咱们初始化的数组元素是1和2,系统会断定为uint8,因此须要咱们在第一个元素1前面强制转化为uint型

固然,咱们也能够以下的声明方式:
uint8[2] memory a = [1,2];
但要注意,那么a数组里面就不能够出现2进制里面大于8位的数了

定长数组是没法经过length和push方法更改长度或插入的

(2)在storage中

1.在storage中,声明定长数组,并初始化变量:
uint[2] a = [1,2];

2.在storage中,声明变长数组,并初始化变量:

uint[] a = [1,2];

3.二维数组初始化,行列是反过来定义的:
uint[2][3] a = [[1,1],[2,2],[3,3]];
但访问是相同的:
访问第三个动态数组的第二个元素,你应该使用 a[2][1]


(3)
bytes 和 string 类型的变量是特殊的数组。 bytes 相似于 byte[],但它在 calldata 中会被“紧打包”(译者注:将元素连续地存在一块儿,不会按每 32 字节一单元的方式来存放)。 string 与 bytes 相同,但(暂时)不容许用长度或索引来访问。
(4)变长的 存储storage数组以及 bytes 类型(而不是 string 类型)都有一个叫作 push 的成员函数

()delete的做用:
delete a 的结果是将 a 的类型在初始化时的值赋值给 a。即对于整型变量来讲,至关于 a = 0, 但 delete 也适用于数组,对于动态数组来讲,是将数组的长度设为 0,而对于静态数组来讲,是将数组中的全部元素重置。 若是对象是结构体,则将结构体中的全部属性重置。
delete 对整个映射是无效的(由于映射的键能够是任意的,一般也是未知的)。 所以在你删除一个结构体时,结果将重置全部的非映射属性,这个过程是递归进行的,除非它们是映射。 然而,单个的键及其映射的值是能够被删除的。

安全

contract DeleteExample {
    uint data;
    uint[] dataArray;

    function f() public {
        uint x = data;
        delete x; // 将 x 设为 0,并不影响数据
        delete data; // 将 data 设为 0,并不影响 x,由于它仍然有个副本
        uint[] storage y = dataArray;
//能够经过改变dataArray来改变y,也能够经过改变y来改变dataArray
        delete dataArray;
        // 将 dataArray.length 设为 0,但因为 uint[] 是一个复杂的对象,y 也将受到影响,
        // 由于它是一个存储位置是 storage 的对象的别名。
        // 另外一方面:"delete y" 是非法的,引用了 storage 对象的局部变量只能由已有的 storage 对象赋值。
    }
}

 



()基本类型之间的转换
(1)隐式转换
int8 不能转换成 uint256(由于 uint256 不能涵盖某些值,例如,-1)。 更进一步来讲,无符号整型能够转换成跟它大小相等或更大的字节类型,但反之不能。 任何能够转换成 uint160 的类型均可以转换成 address 类型。
(2)显式转换
但有些时候会出问题
int8 y = -3;
uint x = uint(y);
这段代码的最后,x 的值将是 0xfffff..fd (64 个 16 进制字符),由于这是 -3 的 256 位补码形式。

若是一个类型显式转换成更小的类型,相应的高位将被舍弃
uint32 a = 0x12345678;
uint16 b = uint16(a); // 此时 b 的值是 0x5678

类型推断
为了方便起见,没有必要每次都精确指定一个变量的类型,编译器会根据分配该变量的第一个表达式的类型自动推断该变量的类型
uint24 x = 0x123;
var y = x;
这里 y 的类型将是 uint24。不能对函数参数或者返回参数使用 var。

()时间seconds、 minutes、 hours、 days、 weeks 和 years
years 后缀已经不推荐使用了,由于从 0.5.0 版本开始将再也不支持。

这些后缀不能直接用在变量后边。若是想用时间单位(例如 days)来将输入变量换算为时间,你能够用以下方式来完成:
function f(uint start, uint daysAfter) public {
    if (now >= start + daysAfter * 1 days) {
        // ...
    }
}

()赋值¶
解构赋值和返回多值¶
Solidity 内部容许元组 (tuple) 类型,也就是一个在编译时元素数量固定的对象列表,列表中的元素能够是不一样类型的对象。这些元组能够用来同时返回多个数值,也能够用它们来同时给多个新声明的变量或者既存的变量(或一般的 LValues):网络

pragma solidity >0.4.23 <0.5.0;

contract C {
    uint[] data;

    function f() public pure returns (uint, bool, uint) {
        return (7, true, 2);
    }

    function g() public {
        //基于返回的元组来声明变量并赋值
        (uint x, bool b, uint y) = f();
        //交换两个值的通用窍门——但不适用于非值类型的存储 (storage) 变量。
        (x, y) = (y, x);
        //元组的末尾元素能够省略(这也适用于变量声明)。
        (data.length,,) = f(); // 将长度设置为 7
        //省略元组中末尾元素的写法,仅能够在赋值操做的左侧使用,除了这个例外:
        (x,) = (1,);
        //(1,) 是指定单元素元组的惟一方法,由于 (1)
        //至关于 1。
    }
}

 

()错误处理:
下列状况将会产生一个 assert 式异常:
    1    若是你访问数组的索引太大或为负数(例如 x[i] 其中 i >= x.length 或 i < 0)。
    2    若是你访问固定长度 bytesN 的索引太大或为负数。
    3    若是你用零当除数作除法或模运算(例如 5 / 0 或 23 % 0 )。
    4    若是你移位负数位。
    5    若是你将一个太大或负数值转换为一个枚举类型。
    6    若是你调用内部函数类型的零初始化变量。
    7    若是你调用 assert 的参数(表达式)最终结算为 false。
下列状况将会产生一个 require 式异常:
    1    调用 throw 。
    2    若是你调用 require 的参数(表达式)最终结算为 false 。
    3    若是你经过消息调用调用某个函数,但该函数没有正确结束(它耗尽了 gas,没有匹配函数,或者自己抛出一个异常),上述函数不包括低级别的操做 call , send , delegatecall 或者 callcode 。低级操做不会抛出异常,而经过返回 false 来指示失败。
    4    若是你使用 new 关键字建立合约,但合约没有正确建立(请参阅上条有关”未正确完成“的定义)。
    5    若是你对不包含代码的合约执行外部函数调用。
    6    若是你的合约经过一个没有 payable 修饰符的公有函数(包括构造函数和 fallback 函数)接收 Ether。
    7    若是你的合约经过公有 getter 函数接收 Ether 。
    8    若是 .transfer() 失败。
在内部, Solidity 对一个 require 式的异常执行回退操做(指令 0xfd )并执行一个无效操做(指令 0xfe )来引起 assert 式异常。 在这两种状况下,都会致使 EVM 回退对状态所作的全部更改。回退的缘由是不能继续安全地执行,由于没有实现预期的效果。 由于咱们想保留交易的原子性,因此最安全的作法是回退全部更改并使整个交易(或至少是调用)不产生效果。 请注意, assert 式异常消耗了全部可用的调用 gas ,而从 Metropolis 版本起 require 式的异常不会消耗任何 gas。
下边的例子展现了如何在 revert 和 require 中使用错误字符串:
pragma solidity ^0.4.22;

contract VendingMachine {
    function buy(uint amount) payable {
        if (amount > msg.value / 2 ether)
            revert("Not enough Ether provided.");
        // 下边是等价的方法来作一样的检查:
        require(
            amount <= msg.value / 2 ether,
            "Not enough Ether provided."
        );
        // 执行购买操做
    }
}
这里提供的字符串应该是通过 ABI 编码 以后的,由于它其实是调用了 Error(string) 函数。在上边的例子里,revert("Not enough Ether provided."); 会产生以下的十六进制错误返回值:
0x08c379a0                                                         // Error(string) 的函数选择器
0x0000000000000000000000000000000000000000000000000000000000000020 // 数据的偏移量(32)
0x000000000000000000000000000000000000000000000000000000000000001a // 字符串长度(26)
0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // 字符串数据("Not enough Ether provided.")

()可见性
contract C {
    uint private data;

    function f(uint a) private returns(uint b) { return a + 1; }//不可继承
    function setData(uint a) public { data = a; }
    function getData() public returns(uint) { return data; }
    function compute(uint a, uint b) internal returns (uint) { return a+b; }//可继承
}

contract D {//这能访问public
    function readData() public {
        C c = new C();
        uint local = c.f(7); // error: member `f` is not visible
        c.setData(3);
        local = c.getData();
        local = c.compute(3, 5); // error: member `compute` is not visible
    }
}

contract E is C {//继承至C,因此能够访问internal,public
    function g() public {
        C c = new C();
        uint val = compute(3, 5); // access to internal member (from derived to parent contract)
    }
}


()getter函数
(1)就是在合约中声明的状态变量如data,其实都自动地生成了getter函数,就是能够像访问函数同样访问它的值,在合约外访问时就能够直接 合约名.data()
pragma solidity ^0.4.0;

contract C {
    uint public data = 42;
}

contract Caller {
    C c = new C();
    function f() public {
        uint local = c.data();
    }
}
(2)若是是在合约内部访问,它有两种访问的形式:internal和external
internal则是直接变量名访问便可
external则是使用this.data()
pragma solidity ^0.4.0;

contract C {
    uint public data;
    function x() public {
        data = 3; // internal access
        uint val = this.data(); // external access
    }
}


()
contract Complex {
//Note that the mapping in the struct is omitted because there is no good way to provide the key for the mapping.
    struct Data {
//若是在一个结构体中声明了一个映射,通常赋值时都先省略,而后在赋值mapping,由于它的key是不固定的
        uint a;
        bytes3 b;
        mapping (uint => uint) map;
    }
    mapping (uint => mapping(bool => Data[])) public data;
//调用方法:data[arg1][arg2][arg3].a
}

()修饰器modifier
继承后能够直接使用被继承处的修饰器

使用修改器实现的一个防重复进入的例子。
pragma solidity ^0.4.0;
contract Mutex {
    bool locked;
    modifier noReentrancy() {
        if (locked) throw;
        locked = true;
        _;//函数体f()return前的内容执行的区域
        locked = false;
    }

    /// This function is protected by a mutex, which means that
    /// reentrant calls from within msg.sender.call cannot call f again.
    /// The `return 7` statement assigns 7 to the return value but still
    /// executes the statement `locked = false` in the modifier.
    function f() noReentrancy returns (uint) {
        if (!msg.sender.call()) throw;
        return 7;
    }
}
例子中,因为call()方法有可能会调回当前方法,修改器实现了防重入的检查。
若是同一个函数有多个修改器,他们之间以空格隔开,修饰器会依次检查执行。
须要注意的是,在Solidity的早期版本中,有修改器的函数,它的return语句的行为有些不一样。
在修改器中和函数体内的显式的return语句,仅仅跳出当前的修改器和函数体。返回的变量会被赋值,但整个执行逻辑会在前一个修改器后面定义的”_"后继续执行。

()fallback function \ call()\send()数据结构

(1)fallback function

contract ExecuteFallback{

  //回退事件,会把调用的数据打印出来
  event FallbackCalled(bytes data);
  //fallback函数,注意是没有名字的,没有参数,没有返回值的
  function(){
//msg.data其实就是你调用一个函数后,函数名,参数进行keccak256哈希后链接起来的data FallbackCalled(msg.data);//event }
//调用已存在函数的事件,会把调用的原始数据,请求参数打印出来 event ExistFuncCalled(bytes data, uint256 para); //一个存在的函数 function existFunc(uint256 para){ ExistFuncCalled(msg.data, para); } // 模拟从外部对一个存在的函数发起一个调用,将直接调用函数 function callExistFunc(){ bytes4 funcIdentifier = bytes4(keccak256("existFunc(uint256)")); this.call(funcIdentifier, uint256(1)); } //模拟从外部对一个不存在的函数发起一个调用,因为匹配不到函数,将调用回退函数 function callNonExistFunc(){ bytes4 funcIdentifier = bytes4(keccak256("functionNotExist()")); this.call(funcIdentifier); } }

回退函数(fallback function):则是当调用一个合约里的某个函数时,若是该函数并不存在,那么就会去调用该回调函数,回调函数无参,无名,无返回值,你能够经过在里面返回个什么或者emit一个事件来显示调用了该回退函数,该函数仍是颇有用的

(2)Call()

Call():call()是一个底层的接口,用来向一个合约发送消息,进行合约之间的交互,可是很不安全,通常是不用的。这个函数是这样的,第一个参数是该访问的函数的名字,后面的参数则是该函数所需的参数,调用它的是一个合约的地址


(3)send()
send()函数发送ether
当咱们使用address.send(ether to send)向某个合约直接转账时,因为这个行为没有发送任何数据,因此接收合约老是会调用fallback函数
function() payable{fallbackTrigged(msg.data);}

  function deposit() payable{//用来给合约存点钱
  }
在上述的代码中,咱们先要使用deposit()合约存入一些ether,不然将会因为余额不足,调用send()函数将报错。


查看事件fallbackTrigged获得:
fallbackTrigged[
  "0x"
]
能够看到,咱们成功使用send()发送了1wei到合约,触发了fallback函数,附带的数据是0x(bytes类型的默认空值),空数据。
这里须要特别注意的是:
    1.    若是咱们要在合约中经过send()函数接收,就必须定义fallback函数,不然会抛异常。
    2.    fallback函数必须增长payable关键字,不然send()执行结果将会始终为false。


fallback中的限制
send()函数老是会调用fallback,这个行为很是危险,著名的DAO被黑也与这有关。若是咱们在分成时,对一系列账户进行send()操做,其中某个作恶意账户中的fallback函数实现了一个无限循环,将由于gas耗尽,致使全部send()失败。为解决这个问题,send()函数当前即使gas充足,也只会附带限定的2300gas,故而fallback函数内除了能够进行日志操做外,你几乎不能作任何操做。
下述行为消耗的gas都将超过fallback函数限定的gas值:
    •    向区块链中写数据
    •    建立一个合约
    •    调用一个external的函数
    •    发送ether
因此通常,咱们只能在fallback函数中进行一些日志操做:

()底层的日志接口(Low-level Interface to Logs):能够代替event
经过函数log0,log1,log2,log3,log4,能够直接访问底层的日志组件。logi表示总共有带i + 1个参数(i表示的就是可带参数的数目,只是是从0开始计数的)。其中第一个参数会被用来作为日志的数据部分,其它的会作为主题(topics)。前面例子中的事件可改成以下:
log3(
    msg.value,
    0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20,
    msg.sender,
    _id
);
其中的长16进制串是事件的签名,计算方式是keccak256("Deposit(address,hash256,uint256)")


()继承

pragma solidity ^0.4.0;

contract mortal is owned {
    function kill() {
        if (msg.sender == owner) selfdestruct(owner);
    }
}


contract Base1 is mortal {
    function kill() { /* do cleanup 1 */ mortal.kill(); }
}


contract Base2 is mortal {
    function kill() { /* do cleanup 2 */ mortal.kill(); }
}


contract Final is Base1, Base2 {
}


对Final.kill()的调用只会调用Base2.kill(),由于派生重写,会跳过Base1.kill,由于它根本就不知道有Base1。一个变通方法是使用super。

pragma solidity ^0.4.0;

contract mortal is owned {
    function kill() {
        if (msg.sender == owner) selfdestruct(owner);
    }
}


contract Base1 is mortal {
    function kill() { /* do cleanup 1 */ super.kill(); }
}


contract Base2 is mortal {
    function kill() { /* do cleanup 2 */ super.kill(); }
}


contract Final is Base2, Base1 {
}


若是Base1调用了函数super,它不会简单的调用基类的合约函数,它还会调用继承关系图谱上的下一个基类合约,因此会调用Base2.kill()。须要注意的最终的继承图谱将会是:Final,Base1,Base2,mortal,owned。使用super时会调用的实际函数在使用它的类的上下文中是未知的,尽管它的类型是已知的。这相似于普通虚函数查找(ordinary virtual method lookup)

当基类的构造函数中若是须要传参,那么继承它时的方式是:

contract Base {
    uint x;
    function Base(uint _x) { x = _x; }
}


contract Derived is Base(7) {
    function Derived(uint _y) Base(_y * _y) {
    }
}

继承写的顺序是很重要的,从继承少的到多的

()抽象(Abstract Contracts)
抽象函数是没有函数体的的函数。以下:

pragma solidity ^0.4.0;

contract Feline {
    function utterance() returns (bytes32);
}

这样的合约不能经过编译,即便合约内也包含一些正常的函数。但它们能够作为基合约被继承。

pragma solidity ^0.4.0;

contract Feline {
    function utterance() returns (bytes32);
    
    function getContractName() returns (string){
        return "Feline";
    }
}

contract Cat is Feline {
    function utterance() returns (bytes32) { return "miaow"; }
}

若是一个合约从一个抽象合约里继承,但却没实现全部函数,那么它也是一个抽象合约。

()接口(interface)
接口与抽象合约相似,与之不一样的是,接口内没有任何函数是已实现的,同时还有以下限制
    1.    不能继承其它合约,或接口。
    2.    不能定义构造器
    3.    不能定义变量
    4.    不能定义结构体
    5.    不能定义枚举类
其中的一些限制可能在将来放开。
接口基本上限制为合约ABI定义能够表示的内容,ABI和接口定义之间的转换应该是可能的,不会有任何信息丢失。
接口用本身的关键词表示:
interface Token {
    function transfer(address recipient, uint amount);
}
合约能够继承于接口,由于他们能够继承于其它的合约。

pragma solidity ^0.4.11;

interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; }

//使用则是在合同函数中直接使用:
function approveAndCall(address _spender, uint256 _value, bytes _extraData)
        public
        returns (bool success) {
    tokenRecipient spender = tokenRecipient(_spender);

 


()library库
使用库合约的合约,能够将库合约视为隐式的父合约(base contracts),固然它们不会显式的出如今继承关系中。意思就是不用写is来继承,直接能够在合约中使用:
library Set {
  struct Data { mapping(uint => bool) flags; }
}

contract C {
    Set.Data knownValues;
}
但调用库函数的方式很是相似,如库L有函数f(),使用L.f()便可访问。此外,internal的库函数对全部合约可见,若是把库想像成一个父合约就能说得通了。固然调用内部函数使用的是internal的调用惯例,这意味着全部internal类型能够传进去,memory类型则经过引用传递,而不是拷贝的方式。

library Set {
  // We define a new struct datatype that will be used to
  // hold its data in the calling contract.
  struct Data { mapping(uint => bool) flags; }

  // Note that the first parameter is of type "storage
  // reference" and thus only its storage address and not
  // its contents is passed as part of the call.  This is a
  // special feature of library functions.  It is idiomatic
  // to call the first parameter 'self', if the function can
  // be seen as a method of that object.
  function insert(Data storage self, uint value)
      returns (bool)
  {
      if (self.flags[value])
          return false; // already there
      self.flags[value] = true;
      return true;
  }

  function remove(Data storage self, uint value)
      returns (bool)
  {
      if (!self.flags[value])
          return false; // not there
      self.flags[value] = false;
      return true;
  }

  function contains(Data storage self, uint value)
      returns (bool)
  {
      return self.flags[value];
  }
}


contract C {
    Set.Data knownValues;

    function register(uint value) {
        // The library functions can be called without a
        // specific instance of the library, since the
        // "instance" will be the current contract.
        if (!Set.insert(knownValues, value))
            throw;
    }
    // In this contract, we can also directly access knownValues.flags, if we want.
}

上面的例子中:
    •    Library定义了一个数据结构体struct Data,用来在调用的合约中使用(库自己并未实际存储的数据)。若是函数须要操做数据,这个数据通常是经过库函数的第一个参数传入(Data storage self),按惯例会把参数名定为self。
    •    另一个须要留意的是上例中self的类型是storage,那么意味着传入的会是一个引用,而不是拷贝的值,那么修改它的值,会同步影响到其它地方,俗称引用传递,非值传递。
    •    库函数的使用不须要实例化,c.register中能够看出是直接使用Set.insert。但实际上当前的这个合约自己就是它的一个实例。
    •    这个例子中,c能够直接访问,knownValues。虽然这个值主要是被库函数使用的

对比普通合约来讲,库的限制:
    •    无状态变量(state variables)。
    •    不能继承或被继承
    •    不能接收ether。


附着库(Using for)
指令using A for B;用来附着库里定义的函数(从库A)到任意类型B。这些函数将会默认接收调用函数对象的实例做为第一个参数。语法相似,python中的self变量同样。
using A for *的效果是,库A中的函数被附着在作任意的类型上。
在这两种情形中,全部函数,即便那些第一个参数的类型与调用函数的对象类型不匹配的,也被附着上了。类型检查是在函数被真正调用时,函数重载检查也会执行。
using A for B;指令仅在当前的做用域有效,且暂时仅仅支持当前的合约这个做用域,后续也很是有可能解除这个限制,容许做用到全局范围。若是能做用到全局范围,经过引入一些模块(module),数据类型将能经过库函数扩展功能,而不须要每一个地方都得写一遍相似的代码了。
上面的例子就改为了:

contract C {
    using Set for Set.Data; // this is the crucial change
    Set.Data knownValues;

    function register(uint value) {
        // Here, all variables of type Set.Data have
        // corresponding member functions.
        // The following function call is identical to
        // Set.insert(knownValues, value)
        if (!knownValues.insert(value))
            throw;
    }
}

//其实就是原本要访问的话的语句是Set.insert(knownValues, value),如今是knownValues.insert(value),即将库中的函数都附着在告终构体struct Data上,由于以前库函数的第一个参数的声明其实也是这个结构体(self),附着后调用时就能够省略掉第一个参数了,就算没有结构体,那么附着的必定是函数的第一个参数声明的那个self的类型
如:

library Search {
    function indexOf(uint[] storage self, uint value)
}
contract C {
    using Search for uint[];
    uint[] data;
}

 

即 library 库,有两种做用
《1》定义 library,使用结构体,调用函数

pragma solidity ^0.4.20;

// This is the same code as before, just without comments
library Set {
  struct Data { mapping(uint => bool) flags; }

  function insert(Data storage self, uint value)
      public
      returns (bool)
  {
      if (self.flags[value])
        return false; // already there
      self.flags[value] = true;
      return true;
  }

  function remove(Data storage self, uint value)
      public
      returns (bool)
  {
      if (!self.flags[value])
          return false; // not there
      self.flags[value] = false;
      return true;
  }

  function contains(Data storage self, uint value)
      public
      view
      returns (bool)
  {
      return self.flags[value];
  }
}
//而后合约中调用库中的函数     
contract C {
    using Set for Set.Data; // this is the crucial change
    Set.Data knownValues;

    function register(uint value) public {
        // Here, all variables of type Set.Data have
        // corresponding member functions.
        // The following function call is identical to
        // `Set.insert(knownValues, value)`
        require(knownValues.insert(value));
    }
}

《2》 使用库来扩展数据类型

pragma solidity ^0.4.20;

library Search {
    function indexOf(uint[] storage self, uint value)
        public
        view
        returns (uint)
    {
        for (uint i = 0; i < self.length; i++)
            if (self[i] == value) return i;
        return uint(-1);
    }
}

contract C {
    using Search for uint[];//使得类型为uint[]的数值能够有函数indexOf进行使用
    uint[] data;

    function append(uint value) public {
        data.push(value);
    }

    function replace(uint _old, uint _new) public {
        // This performs the library function call
        uint index = data.indexOf(_old);
        if (index == uint(-1))
            data.push(_new);
        else
            data[index] = _new;
    }
}    

 

()若是你想要把数据写到交易中:

参考:https://blog.csdn.net/koastal/article/details/78794275

let Web3 = require("web3");
let fs = require("fs");
let web3 = new Web3();
web3.setProvider(new Web3.providers.HttpProvider("http://192.168.1.10:8545"));

let log = {
    time:(new Date).getTime(),
    type:"error",
    msg:"数据库链接失败"
};
let str = JSON.stringify(log);//将json转换为string
let data = Buffer.from(str).toString('hex');
data = '0x'+data;
console.log(data);

//将数据写入到交易中
let coinbase = "0x8a1C505f1ff14045c03622E9ab82EB19c730cef3";
let user1 = "0xb4B6338977078f1c355ee394D67D6DFE5C24dad8";
web3.personal.unlockAccount(coinbase, "coinbase");
let address = web3.eth.sendTransaction({
    from:coinbase,
    to:user1,
    value:'0x00',
    data:data
});

//从交易地址获取数据
let transaction = web3.eth.getTransaction(address);
let inputData = transaction.input;
let res_str = Buffer.from(inputData.replace('0x',''),'hex').toString();
let res_json = JSON.parse(res_str);//将string转换为json
console.log(transaction);
console.log(res_json);

web3.eth.sendTransaction(transactionObject [, callback])

参数:

  • transactionObject : Object - 要发送的交易对象。
    • from: String - 指定的发送者的地址。若是不指定,使用web3.eth.defaultAccount。
    • to: String - (可选)交易消息的目标地址,若是是合约建立,则不填.
    • value: Number|String|BigNumber - (可选)交易携带的货币量,以wei为单位。若是合约建立交易,则为初始的基金。
    • gas: Number|String|BigNumber - (可选)默认是自动,交易可以使用的gas,未使用的gas会退回。
    • gasPrice: Number|String|BigNumber - (可选)默认是自动肯定,交易的gas价格,默认是网络gas价格的平均值 。
    • data: String - (可选)或者包含相关数据的字节字符串,若是是合约建立,则是初始化要用到的代码。
    • nonce: Number - (可选)整数,使用此值,能够容许你覆盖你本身的相同nonce的,正在pending中的交易11。
    • Function - 回调函数,用于支持异步的方式执行7。

返回值:

    • String - 32字节的交易哈希串。用16进制表示。

 

web3.eth.getTransaction(transactionHash [, callback])

参数:

  • transactionHash: String - 交易的哈希值。
  • callback: Function - 回调函数,用于支持异步的方式执行7。

返回值:

  • Object - 一个交易对象
    • hash: String - 32字节,交易的哈希值。
    • nonce: Number - 交易的发起者在以前进行过的交易数量。
    • blockHash: String - 32字节。交易所在区块的哈希值。当这个区块处于pending将会返回null。
    • blockNumber: Number - 交易所在区块的块号。当这个区块处于pending将会返回null。
    • transactionIndex: Number - 整数。交易在区块中的序号。当这个区块处于pending将会返回null。
    • from: String - 20字节,交易发起者的地址。
    • to: String - 20字节,交易接收者的地址。当这个区块处于pending将会返回null。
    • value: BigNumber - 交易附带的货币量,单位为Wei。
    • gasPrice: BigNumber - 交易发起者配置的gas价格,单位是wei。
    • gas: Number - 交易发起者提供的gas。.
    • input: String - 交易附带的数据。

结果为:

0x7b2274696d65223a313531333135363433363137392c2274797065223a226572726f72222c226d7367223a22e695b0e68daee5ba93e8bf9ee68ea5e5a4b1e8b4a5227d
{ blockHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
  blockNumber: null,
  from: '0x8a1c505f1ff14045c03622e9ab82eb19c730cef3',
  gas: 90000,
  gasPrice: { [String: '18000000000'] s: 1, e: 10, c: [ 18000000000 ] },
  hash: '0x3149578fbb8cf75f264fc87426b7bfa2a89256763fe5194ccad8b724a0325470',
  input: '0x7b2274696d65223a313531333135363433363137392c2274797065223a226572726f72222c226d7367223a22e695b0e68daee5ba93e8bf9ee68ea5e5a4b1e8b4a5227d',
  nonce: 57,
  to: '0xb4b6338977078f1c355ee394d67d6dfe5c24dad8',
  transactionIndex: 0,
  value: { [String: '0'] s: 1, e: 0, c: [ 0 ] },
  v: '0x26',
  r: '0x742335f7b649c554a35baf624fe90c8e3fa3c9cfc116cfe858af420dc893359a',
  s: '0x20a63d760ab574736aaed4799f2a15bc727a988b4d41ee279e3b753b1c1f3913' }
{ time: 1513156436179, type: 'error', msg: '数据库链接失败' }