solidity合约入门

  • Post author:
  • Post category:solidity

基本类型

int

  • 用intx和uintx来申明,其中x是一个8-256之间的8的倍数,表示有多少个bit。如int8 ,uint32。
  • 比较:<=,<,==,!=,>=,>
  • 位运算:&,|,^,~,<<,>>
  • 数值运算:加减乘除,%取余,指数。0的0次方等于1
  • type(x).min和type(x).max给出该类型的上下界
    溢出会被截断

address

  • address与address payable:储存160个bit长的信息,也就是一个地址。
  • address payable 相对于address多了可以向此地址转账的功能
  • .balance给出该地址的金额
  • .transfer()和.send()对于一个address payable代表向其转账
address payable x = address(0x123);
address myAddress = address(this);
if(myAddress.balance >= 10){
	x.transfer(10);// or x.send(10)//合约向x转账
	}

Bool

  • 用bool x来申明
  • 操作符 !,&&,||,==,!=
  • &&操作和||操作是短路运算

数组

  • 定长数组:bytes1,bytes3, … bytes32表示储存了1个,2个…32个byte的数组
  • bytes32 x:可以用下标访问
  • 具有位运算
  • 常规数组和C++类似:int a[10]
  • 也可以申明动长数组:int a[]。动长数组有a.length返回长度, a.push(x) 在最后添加一个新元素x并返回新长度

Char 和 String

  • 用char来申明字符变量
  • string相当于一个char的数组,但是目前的版本不支持使用.length以及下标- 访问,需要转化为bytes访问字节形式的内容
string s;
bytes(s).length
bytes(s)[7] = 'x'

Mapping

  • 用mapping(_keytype => _valuetype),基本上就是个哈希表
mapping(address => uint) public balance;
function update(uint newBalance) public {
	balances[msg.sender] = newBalance;
}

申明一个balance从地址映射的uint的值
uint没有具体申明,默认为uint256
申明一个函数(后面详细介绍)
在整体上,智能合约不是一个完成的程序,需要被其他人调用自己才能实现真正的动作
msg.sender是调用者地址(后面会详细介绍)
balance[address] = uint256(具体问题,具体分析)

Struct

结构体

struct Voter{//投票者
	uint weight;//权重
	bool voted;//是否投过票
	address delegate;//地址
	uint vote;//票数
	}

预定义变量

block

区块的相关信息

block.blockhash(uint blockNumber) retruns(bytes32) 指定区块的区块哈希,不推荐,建议使用blockhash(uint blockNumber)代替
block.coinbase(address):挖出当前区块的矿工地址
block.difficulty(uint):当前区块难度
block.gaslimit(uint):当前区块gas限额
block.number(uint):当前区块号
block.timestamp(uint):自uinx epoch其当前区块以秒计的时间戳

msg

msg.data(bytes):完整的calldata
msg.gas(uint):剩余gas,不推荐,建议使用gasleft()代替
msg.sender(address):当前调用的消息发送者
msg.sig(bytes4):calldata的前4个字节(也就是函数标识符)
msg.value(uint):随消息发送的wei的数量

Event

用来触发EVM中的log记录,使用如下语句声明和使用

event X(<parameter types>) //定义
emit X(<parameter>) //使用
event Send(address sender, address receive, uint amount); //事件

function sent() public payable{
	//....
	emit Send(msg.sender, msg.receive,msg.value);//触发事件
}

控制结构

if,else,while,do,for,break,continue,return,? :

在soldity中,其他类型是不会自动转换为bool的,所以if(1){}这样是不合法的

Units

EtherUnits

wei,szabo,finney,ether
1 ether = 1000 finney = 106 szabo = 1018 wei

TimeUnits

一个数值后跟seconds,minutes,hours,days,weeks是一个时间单位,默认秒(1 == 1seconds)

Contracts

合约Contract是一个代码的基本组成部分,和class类似
一个Contract里会包含状态变量,函数,函数修饰器和事件
Contract也有继承与多态

状态变量

储存在合约中的信息,如总量,个人的余额等等

contract C1{
	uint xxx; //State Variable
	}

函数

function (<parameter types>) [internal | external | public] [pure | view | payable] [returns (<return types>)]
{}
view:一个函数如果不会改变状态(包括send ether)则可以被申明为view
pure:一个函数如果是view,并且不会从状态中读取数据,则可以被申明为pure
如果一个函数什么都不return就相当于c++中的void 函数 不写returns即可
reutrn可以返回一个元组,用括号括起来
internal | external | public是不可选,必须写
pure | view | payable是可选的,可以不写
returns是可选的,可以不写

Payable

表示调用的时候可以发送币
msg.sender是发币地址,msg.value是数量

function deposit() payable returns(address addr,uint amount,bool success){
	return(msg.sender,msg.value,this.send(msg.value));
	}

没有申明可见性,默认是public
返回发送者地址,发了多少代币,是否发送成功
this代指本合约地址
这里是指sender向此合约打款

visibility

函数有四种可见性:external,public,internal,private
external表示只能从外部调用,如果想从内部调用,必须用this.fn
public 则表示内外都可以
internal 可以被自己或衍生类调用
private则只能自己调用
变量的visibility 不能是 external

internal ,private只是针对于外部合约而言,在区块链外部还是能查看相关数据的
visibility不写,默认为public

构造函数

一个合约有至多一个构造函数,在构造时会执行一次

Contract A{
	bytes32 name;
	constructor(bytes32 _name)public{
		name = _name;
		}
	}

Receive Ether

一个合约有至多一个Receive ether函数,用如下语句声明。
receive() external payable{…}
没有function关键字
当收到ether时执行(.send()和.transfer())

//This contract keeps all Ether sent to it with no way 
//to get it back
contract Sink{
	event Received(address, uint);
	receive() external payable{
		emit Received(msg.sender, msg.value);
	}
}

contract Source{
	function sending(uint amount) internal payable {
	// 向Sink合约发送币
		address payable x = address(Sink);
		x.transfer(10); // will call receive
	}
}

fallback

  • 一个合约至多有一个fallback函数,fallback函数是缺省函数,当调用了一个该合约不存在的函数时会执行fallback。
  • 用如下语句声明,同样没有function关键词
    fallback() external [payable]

要求某种条件必须成立
如果不满足,就全部不执行,所有操作将会回退(撤销)

函数修饰器function modifier

contract owned{
	address owner;
	mapping(address => bool) registeredAddresses;
	uint price;
	
	constructor()public {
		owner = msg.sender;
	}
	
	modifier onlyOwner{
		require(msg.sender == owner);
		_;//如果满足上述条件,函数将会在这里接着执行
	}
	
	modifier costs(uint price) {//modifier可以接受参数
		if(msg.value >= price){
			_;//满足条件,函数将会在这里接着执行
		}
	}
	
	function register() public payable costs(price){
		registeredAddressed[msg.sender] =true;
	}
	
	function changePrice(uint _price) public onlyOwner{
		price = _price;
	}
	
}

继承

  • 用is来实现合约继承
  • 继承的子合约会继承父合约所有非private的东西,包括modifier
Contract A{…}
Contract B is A {…}
  • 如果要override一个函数,在被覆盖的函数声明时必须加virtual关键字,在覆盖的函数后加override
  • 如果希望能被在此override,也要加virtual关键字
  • 函数默认调用最衍生的类定义的函数
  • 如果要调用父类的函数,使用super.f()
  • 函数override的时候必须保持同样的可见性,或从external变成public
  • 另一个参数只能变得更加严格,即non-payable(也就是无参数)可以被override为view,view可以变成pure。payable是特殊的,必须保持为payable
contract Base{
	function foo() virtual external view{}
}

contract Middle is Base{}

contract Inherited is Middle{
	function foo() virtual override public pure{}
}

external -> public
view -> pure
符合override后的函数修饰

Abstract Contracts

Abstract contracts是未实现的Contract。可以用来做父类

abstract contract A{
	function num() public pure virtual returns(uint);
}

contract B is A{
	function num() public pure override returns(uint){
		return 0;
	}
}

Interface

和Abstract Contract 差不多,但是更严格一点

不能实现任何函数
不能定义构造函数
不能定义任何变量
不能继承(可以被继承)
  • (1)合约中的方法默认为public

  • (2)合约中的状态变量默认为internal

  • (3)this是指当前的合约地址,this.() 只能去访问当前合约的public类型的

  • (4)状态变量为public的状态变量,在合约内部会自动生成一个public同名的方法

  • (5)如果在合约中,重写public的状态变量同名的方法,将会覆盖掉合约自动生成的同名方法

  • (6)在继承中,子合约只能继承父合约中的所有public类型的方法

  • (7)在继承中,子合约只能继承父合约中的所有public类型和internal类型的状态变量

  • (8)支持多继承,继承的权限同上

  • (9)业务需求,可重写从父类继承过来的方法

  • (10)constant与view功能相同,尽量就用view

  • (11)function 合约名()可以表示构造函数,但是一般不建议使用,就用constructor()表示构造函数

  • (12)在部署合约时,会运行带有view / constant 的方法

  • (1)智能合约中,不支持直接返回string[],其他类型的数组,可以直接返回

  • (2)智能合约中,不支持字符串直接拼接,如果想要实现拼接功能,可以转为bytes在去实现相关操作

  • (3)方法修饰符,如果加了view将不会消耗gas,并且调用此方法,不需要用户私钥,可直接调用

  • (4)拷贝合约:合约 新合约名 = 合约(地址)

  • (5)address(0)代表空地址

  • (6)library库合约,特定地址部署一次,就可以被其他合约反复调用

  • (7)如果用继承父合约的相同状态变量或方法,会消耗大量gas,如果用library库合约就可以解决这个问题

  • (8)library库合约没有状态变量,一般使用是创建一个结构体,结构体中mapping去管理相关状态和判断条件

  • (9)abi为空的合约,不能被部署(WeBASE-Front上测试的)

abstract用法

  • (1)abstract 定义抽象合约,供其他合约继承使用

  • (1)抽象合约将合约的定义与其实现脱钩,从而提供了更好的可扩展性和自文档性

  • (2)简化了诸如Template方法的模式,并消除了代码重复。

  • (1)如果未实现合约中的至少一项功能,则需要将合同标记为抽象。

  • (2)即使实现了所有功能,合同也可以被标记为抽象的。

  • (3)这样的抽象合同不能直接实例化。

  • (4)如果抽象合约本身实现了所有定义的功能,也不能直接实例化

  • (5)如果合约继承自抽象合约,并且没有通过覆盖实现所有未实现的功能,则也需要将其标记为抽象。

  • (6)抽象合同不能覆盖未实现的虚函数

冻结和交易属性

  • (1)blockhash(uint blockNumber)返回(bytes32):给定块的哈希-仅适用于256个最新块,不包括当前块
  • (2)block.chainid(uint):当前链号
  • (3)block.coinbase(应付地址):当前区块矿工的地址
  • (4)block.difficulty(uint):当前区块难度
  • (5)block.gaslimit(uint):当前块的gaslimit
  • (6)block.number(uint):当前块号
  • (7)block.timestamp(uint):当前块时间戳,以自unix纪元以来的秒数为单位
  • (8)gasleft()返回(uint256):剩余gas
  • (9)msg.data(字节calldata):完整的calldata
  • (10)msg.sender(地址):消息的发件人(当前通话)
  • (11)msg.sig(bytes4):调用数据的前四个字节(即函数标识符)
  • (12)msg.value(uint):与消息一起发送的wei数
  • (13)tx.gasprice(uint):交易的gas价格
  • (14)tx.origin(地址):交易的发送者(完整的调用链)

event用法

  • (1)事件是以太坊日志记录/事件监视协议的抽象。

  • (2)日志条目提供了合同的地址,一系列多达四个主题以及一些任意长度的二进制数据。

  • (3)事件利用现有功能ABI来将其(连同接口规范)解释为适当类型的结构。

  • (4)给定一个事件名称和一系列事件参数,我们将它们分为两个子系列:被索引的子序列和未被索引的子序列。

  • (5)已索引的索引(最多可为3个)与事件签名的Keccak哈希一起使用,以形成日志条目的主题。

  • (6)未索引的事件形成事件的字节数组。

  • (1)对于所有最大长度为32个字节的类型,EVENT_INDEXED_ARGS数组包含的值直接,填充或符号扩展(对于有符号整数)为32个字节,就像常规ABI编码一样。

  • (2)对于所有“复杂”类型或动态长度类型,包括所有数组,字符串,字节和结构,EVENT_INDEXED_ARGS将包含特殊就地编码值的Keccak哈希值(请参见索引事件参数的编码),而不是直接编码的值。

  • (3)这允许应用程序有效地查询动态长度类型的值(通过将编码值的哈希值设置为主题),但使应用程序无法解码尚未查询的索引值。

  • (4)对于动态长度类型,应用程序开发人员面临快速搜索预定值(如果参数已索引)和易读性任意值(这要求不对参数进行索引)之间权衡的问题。

  • (5)开发人员可以通过定义两个具有相同值的参数来定义事件,从而克服这种折衷并实现有效的搜索和任意的可读性。

modifier用法

  1. 使用 修饰器modifier 可以轻松改变函数的行为。

  2. 例如,它们可以在执行函数之前自动检查某个条件。

  3. 修饰器modifier 是合约的可继承属性, 并可能被派生合约覆盖。

  4. 如果同一个函数有多个 修饰器modifier,它们之间以空格隔开,修饰器modifier 会依次检查执行。

  5. 修饰器modifier 或函数体中显式的 return 语句仅仅跳出当前的 修饰器modifier 和函数体。

  6. 结尾必须加_;

  7. 返回变量会被赋值,但整个执行逻辑会从前一个 修饰器modifier 中的定义的 “_” 之后继续执行。

  8. 修饰器modifier 的参数可以是任意表达式,在此上下文中,所有在函数中可见的符号,在 修饰器modifier 中均可见

  9. 可继承,覆盖,但是不可以重载

存储区域

memory

内存memory,用于暂存数据。其中存储的内容会在函数被调用(包括外部函数)时擦除,所以其使用开销相对较小。

storage

存储storage,贮存了合约声明中所有的变量。 虚拟机会为每份合约分别划出一片独立的 存储storage 区域,并在函数相互调用时持久存在,所以其使用开销非常大。

stack

栈stack,用于存放小型的局部变量。使用几乎是免费的,但容量有限。

不同数据类型的变量会有各自默认的存储地点:

  • 状态变量总是会贮存在 存储storage 中
  • 函数参数默认存放在 内存memory 中
  • 结构、数组或映射类型的局部变量,默认会放在 存储storage 中
  • 除结构、数组及映射类型之外的局部变量,会储存在栈中

** 一个常见误区就是声明了一个局部变量,就认为它会创建在 内存memory 中,其实它会被创建在 存储storage 中: **

/// 这份合约包含一处错误

pragma solidity ^0.4.0;

contract C {
    uint someVariable;
    uint[] data;

    function f() public {
        uint[] x;
        x.push(2);
        data = x;
    }
}

局部变量 x 的数据类型是 uint[] storage,但由于 存储storage 不是动态分配的,它需要在使用前通过状态变量赋值。所以 x 本身不会被分配 存储storage 的空间,取而代之的是,它只是作为 存储storage 中已有变量的别名。
实际上会发生的是,编译器将 x 解析为一个 存储storage 指针,并默认将指针指向 存储插槽storage slot 0 。这就造成 someVariable (贮存在 存储插槽storage slot 0)会被 x.push(2) 更改。

注:在本例中,两个合约变量 someVariable 和 data 会被预先分配到两个 存储插槽storage slot 中,即 存储插槽storage slot 0 和 存储插槽storage slot 1 。上面的程序会使局部变量 x 变成指向保存了变量 someVariable 的 存储插槽storage slot 0 的指针。

/// 正确的做法
pragma solidity ^0.4.0;

contract C {
    uint someVariable;
    uint[] data;

    function f() public {
        uint[] x = data;
        x.push(2);
    }
}

pure用法

  • 函数声明关键字 ,pure,不读取或修改状态。
  • 编译器没有强制 pure 方法不能读取状态。
  • 读取状态变量。
  • 访问this.balance 或者 .balance
  • 访问block.tx msg中任意成员(除了msg.sig和msg.data之外)
  • 调用任何未标记为 pure 的函数。
  • 使用包含某些操作码的内联汇编。

library用法

  • 库与合约类似,只需要在特定的地址部署一次
  • 库函数被调用,它的代码在调用合约的上下文中执行,即 this 指向调用合约,特别是可以访问调用合约的存储
  • 每个库都是一段独立的代码,所以它仅能访问调用合约明确提供的状态变量(否则它就无法通过名字访问这些变量)
  • 我们假定库是无状态的,所以如果它们不修改状态(view 或者 pure 函数), 库函数可以通过直接调用来使用
  • 库可以看作是使用他们的合约的隐式的基类合约
  • 虽然在继承关系中不会显式可见,但调用库函数与调用显式的基类合约十分类似 (如果 L 是库的话,可以使用 L.f() 调用库函数)
  • 像库是基类合约一样,对所有使用库的合约,库的 internal 函数都是可见的
  • 需要使用内部调用约定来调用内部函数,这意味着所有内部类型,内存类型都是通过引用而不是复制来传递

与合约相比

  • 没有状态变量
  • 不能够继承或被继承
  • 不能接收以太币

using for用法

// 可用于在合约的上下文中,将库函数(来自库A)附加到任何类型(B)
using A for B

这些函数将接收被调用的对象作为它们的第一个参数
using A for *——库A的功能可以附加到任何类型。
附加库中的所有函数,即使那些第一个参数的类型与对象的类型不匹配的函数也是如此。
在调用函数时检查类型,并执行函数重载解析。
指令仅在当前合约内(包括其所有功能内)有效,并且在使用该合约的合约外无效。

struct用法

自定义结构体类型

 struct Voter {
        uint weight;
        bool voted;
        address delegate;
        uint vote;
    }
  • 在合约外部声明结构体可以使其被多个合约共享
  • 在合约内部定义结构体,这使得它们仅在此合约和衍生合约(继承 or 引用)中可见。
  • 在函数中使用结构体时,一个结构体赋值给另一个局部变量,需是存储storage 类型的局部变量;在这个过程中并没有拷贝这个结构体,而是保存一个引用,所以对局部变量成员的赋值实际上会被写入状态。

版权声明:本文为weixin_43840202原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。