一、Solidity简介
1.介绍
solidity是一门面向合约的、为实现智能合约而创建的高级编程语言,能在以太坊虚拟机(EVM)上运行。它是一门静态语言。内含的类型除了常见编程语言中的标准类型,还包括address等以太坊独有的类型。solidity源码文件通常以.sol作为扩展名。
2.语言特性
它的语法接近于Javascript,是一种面向对象的语言。但作为一种真正意义上运行在网络上的去中心合约,它又有很多的不同,下面列举一些:
- 以太坊底层是基于帐户,而非UTXO的,所以有一个特殊的
Address
的类型。用于定位用户,定位合约,定位合约的代码(合约本身也是一个帐户)。 - 由于语言内嵌框架是支持支付的,所以提供了一些关键字,如
payable
,可以在语言层面直接支持支付,而且超级简单。 - 存储是使用网络上的区块链,数据的每一个状态都可以永久存储,所以需要确定变量使用内存,还是区块链。
- 运行环境是在去中心化的网络上,会比较强调合约或函数执行的调用的方式。因为原来一个简单的函数调用变为了一个网络上的节点中的代码执行,分布式的感觉。
- 最后一个非常大的不同则是它的异常机制,一旦出现异常,所有的执行都将会被回撤,这主要是为了保证合约执行的原子性,以避免中间状态出现的数据不一致。
3.编译器
目前solidity编程最好的方式是使用Remix(地址:https://remix.ethereum.org/),这是一个基于web浏览器的IDE,可在线使用而无需安装任何东西,可以编写solidity智能合约,然后编译、部署并运行该智能合约。
二、Solidity源码和智能合约
Solidity源代码成为可以运行的智能合约需要以下步骤:
1.源代码通过编译成字节码(Bytecode),同时会产生二进制接口规范(ABI);
2.通过交易将字节码部署到以太坊网络,部署成功会产生一个智能合约账户;
3.通过web3.js+ABI去调用智能合约中的函数来实现数据的读取和修改。
三、Solidity值类型
1.布尔(bool)
可能的取值为常量值true
和false
。
2.整型(int/uint)
分别表示的是有符号或无符号整型。支持从uint8
到uint256
,以及int8
到int256
,uint
和int
默认代表的是uint256
和int256
,变量支持的步长以8
递增。
3.浮点型(fixed/ufixed)
分别表示有符号或无符号的浮点型。
4.地址(address)
存储一个20字节的值(以太坊地址的大小)。地址类型也有成员变量,并作为合约的基础。
- balance(uint256)查询该地址的以太币余额,以Wei为单位
- transfer(uint256)向该地址发送指定数量的以太币,以Wei为单位,失败时抛出异常
- send(uint256)向该地址发送指定数量的以太币,以Wei为单位,失败时返回false
pragma solidity ^0.4.0;
contract sendMoney{
//payable关键字代表我们可以通过这个函数给我们的合约地址充值,转账。
function pay() payable{
}
//获取合约账户上的金额
function getBalance() view returns(uint){
return this.balance;
}
//合约地址
function getThis() view returns(address){
return this;
}
//获取任意账户地址的金额
function getRandomBanlance(address account) view returns(uint){
// address account = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
return account.balance;
}
//向指定账户(account)转账
//可以直接输入account.transfer(10 ether);就代表给账户转账
//如果函数内什么操作都没有,但是有payable属性,那么msg.value的值就会转到合约账户地址上
//如果转账金额大于10(eg:20),那么剩余的10会转到合约账户去,被两个账户瓜分
function transfer() payable{
address account = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
account.transfer(10 ether);
}
//向合约账户转账,这种方式必须要有一个回滚函数
function transfer2() payable{
this.transfer(msg.value);
}
//回滚函数,无函数名和返回值
function () payable{
}
}
5.定长字节数组(byte arrays)
bytes1
, … ,bytes32
,允许值以步长1
递增。byte
默认表示byte1
。
6.枚举(enum)
一种用户自定义类型。可以显示的转换与整数进行转换,默认从0开始递增,一般用来模拟合约的状态。枚举类型应至少有一名成员。
pragma solidity ^0.4.0;
contract enumTest{
//enum必须有成员
//不能有汉字
//不能加;
// enum girl{}
enum girl{a,b,c} //0,1,2...
girl dateGirl = girl.a;
function getEnum() view returns(girl){
return girl.b;
}
//用途:标志状态的转移
function first() returns(string){
require(dateGirl == girl.a);
dateGirl = girl.b;
return "date with a";
}
function second() returns(string){
require(dateGirl == girl.b);
return "date with b";
}
}
7.函数(function)
四、Solidity引用类型
数组(Array)
结构(Struct)
pragma solidity ^0.4.0;
contract structTest{
//结构体的定义
struct student{
uint grade;
string name;
mapping(uint => string) map;
}
//结构体的定义
struct student2{
uint grade;
string name;
// student2 stu; 结构体不能包含自己本身,但是可以是动态长度的数组,也可以是映射
student2[] stu;
mapping(uint => student2) map;
}
student a; //默认为storage类型,只能够用storage类型来操作我们结构体中的mapping类型;
//结构体的初始化
function init() view returns(uint,string,string){
//1.初始化结构体的时候,忽略掉mapping类型
student memory s = student(100,"zhang");
//2.memory的对象不能够直接的操作struct结构体中的mapping
// s.map[0] = "hi";
//将内存当中的s对象赋值给a对象
a = s;
//只能通过storage对象来操作我们的结构体中的mapping属性
a.map[0] = "hi";
return(s.grade,s.name,a.map[0]);
}
//结构体的初始化2
function init2() view returns(uint,string){
student memory s = student({grade:100,name:"zhang"});
return(s.grade,s.name);
}
//internal
function test(student s) internal{
//不能将memory赋值给storage
// student stu = s;
}
}
pragma solidity ^0.4.0;
contract struct1{
struct student{
uint grade;
string name;
}
student stu;
//引用代表会修改区块链上的值
function test(student storage s) internal{
student storage a = s;
a.name = "zhang";
}
function call() returns(string){
test(stu);
return stu.name;
}
}
pragma solidity ^0.4.0;
contract struct2{
struct student{
uint grade;
string name;
}
student stu;
//形参传递了指针引用
function test(student memory s) internal{
//把s的值赋值给区块链上的stu
stu = s;
//修改函数形参的s,只是修改了其内存中的空间,没有修改掉区块链上的空间,因为是两个完全独立的空间
s.name = "zhang";
}
function call() returns(string){
//内存中开辟空间
student memory tmp = student(100,"tmp");
test(tmp);
return stu.name;
}
}
pragma solidity ^0.4.0;
contract struct3{
struct student{
uint grade;
string name;
}
student stu = student(100,"stu");
//s形参是一个引用
function test(student storage s) internal{
//a是一个内存中的副本,把s引用的stu的内容拷贝给了a这个内存中的对象
student memory a = s;
//试图修改a的值不会修改stu的值,因为是属于两个不同的空间
a.name = "zhang";
}
function call() returns(string){
test(stu);
return stu.name;
}
}
映射(Mapping)
pragma solidity ^0.4.0;
contract Mapping{
mapping(address => uint) idMapping; // addr ==>id
mapping(uint => string) nameMapping; // id ==> name
uint public num = 0;
function register(string name){
address account = msg.sender;
num++;
idMapping[account] = num;
nameMapping[num] = name;
}
function getIdByAddress(address addr) returns(uint){
return idMapping[addr];
}
function getNameById(uint id) returns(string){
return nameMapping[id];
}
}
五、Solidity数据位置
所有的复杂类型(数组、结构、映射),都有一个额外属性就是数据位置,用来说明数据是存储在内存memory还是存储storage中。
根据上下文不同,大多数时候数据有默认的位置,但也可以通过在类型名后加上关键字memory或storage来进行修改。
函数参数(包括返回的参数)的数据位置默认是memory,局部变量默认是storage,状态变量默认是storage。
六、Solidity函数
1.函数可见性/访问权限
- public:public函数是合约接口的一部分,可以在内部或通过消息调用。对于public状态变量,会自动生成一个getter函数
- private:private函数和状态变量金在当前定义他们的合约中使用,并且不能被派生合约使用
- external:external函数是合约接口的一部分,可以从其他合约和交易中调用。一个外部函数f不能从内部调用(即f不起作用,但this.f()可以)。当收到大量数据的时候,外部函数有时候会更有效率
- internal:internal函数和状态变量只能是内部访问(即从当前合约内部或从它派生的合约访问),不能用this调用
2.函数状态可变性
- view:不允许修改状态
- pure:纯函数,不允许访问或修改状态
- payable:允许从消息调用中接收以太币
- constant:与view相同,一般只修饰状态变量,不允许赋值(除初始化以外)
3.回退函数
回退函数(fallback)是合约中的特殊函数:没有名字,没有参数,没有返回值;
如果在一个合约的调用中,没有其他函数与给定的函数标识符匹配(或没有提供调用数据),那么这个函数就会被执行;
每当合约收到以太币(没有任何数据),回退函数就会执行。此外为了接收以太币,fallback函数必须标记为payable。如果不存在这样的函数,则合约不能通过常规交易接收以太币;
在上下文中通常只有很少的gas可以用来完成回退函数的调用,所以使fallback函数的调用尽量廉价很重要。
七、Solidity事件
事件是以太坊EVM提供的易中日志基础设施。事件可以用来做操作记录,存储为日志,也可以用来实现一些交互功能。
当定义的事件触发时,我们可以将事件存储到EVM的交易日志中,日志是区块链中的一种特殊数据结构;日志与合约关联,与合约的存储合并存入区块链中;只要某个区块可以访问,其相关的日志就可以访问,但在合约中不能直接访问日志和事件数据。
可以通过日志实现简单支付验证SPV,如果一个外部实体提供了一个带有这种证明的合约,它可以检查日志是否真实存在于区块链中。
可以最多有三个参数被设置为indexed,来设置是否被索引。设置为索引后,可以允许通过这个参数来查找日志,甚至可以按特定的值过滤。
event Transfer(address indexed from,address indexed to,uint256 value);
.......
//转账,内部调用
function _transfer(address _from,address _to,uint _value)internal{
......
emit Transfer(_from,_to,_value);
//assert要求一定为真
assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
}
八、Solidity异常处理
Solidity使用“状态恢复异常”来处理异常。这样的异常将撤销对当前调用及其所有子调用中的状态所做的所有更改,并且向调用者返回错误。
函数assert和require可用于判断条件,并在不满足条件时抛出异常。
assert()一般只用于测试内部错误,并检查常量。
//转账,内部调用
function _transfer(address _from,address _to,uint _value)internal{
......
//assert要求一定为真(要求转出地址的余额)
assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
}
require()应用于确保满足有效条件(如输入或合约状态变量),或验证调用外部合约的返回值。
//转账,内部调用
function _transfer(address _from,address _to,uint _value)internal{
//禁止往0地址转账
require(_to != 0x0);
require(balanceOf[_from]>=_value);
//防止溢出
require(balanceOf[_to]+_value >= balanceOf[_to]);
......
}
revert()用于抛出异常,它可以标记一个错误并将当前调用回退。
九、示例合约
1.众筹合约
pragma solidity ^0.4.0;
//完成了从创建众筹事件、捐赠、提款的全部操作
contract crowdFunding{
struct funder{
address fundaddress;//捐赠者的地址
uint Tomoney;//捐赠者捐赠的金钱
}
struct needer{
address Neederaddress;//受益人的地址
uint goal;//受益人的目标值
uint amount;//当前的已经筹集到多少钱
uint funderAccount;//捐赠者的id
mapping(uint => funder) map;//将捐赠者的id与捐赠者绑定在一起,从而能够得知,是谁给当前的受益人捐钱了
}
uint neederAmount;//受益人的id数
mapping(uint => needer) needmap;//将受益人的id与受益人绑定在一起,从而能够管理受益人
//实现一个众筹的事件
function NewNeeder(address _Neederaddress,uint _goal){
//将受益人id与受益人绑定
neederAmount++;
needmap[neederAmount] = needer(_Neederaddress,_goal,0,0);
}
//捐赠者的地址,受益人的id
function contribute(address _address,uint _neederAmount) payable{
needer storage _needer = needmap[_neederAmount];
//筹集到的资金增加
_needer.amount += msg.value;
//捐赠人数增加
_needer.funderAccount++;
//将受益人id与受益人绑定
_needer.map[_needer.funderAccount] = funder(_address,msg.value);
}
//当筹集到的资金满足条件,就会给受益人的地址转账
//受益人的id
function isComplete(uint _neederAmount){
needer storage _needer = needmap[_neederAmount];
if(_needer.amount >= _needer.goal){
_needer.Neederaddress.transfer(_needer.amount);
}
}
function test() view returns(uint,uint,uint){
return(needmap[1].goal,needmap[1].amount,needmap[1].funderAccount);
}
}
2.代币合约
pragma solidity ^0.4.16;
//接口定义
interface tokenRecipient{
function receiveApproval(address _from,uint256 _value,address _token,bytes _extraData) external;
}
contract TokenERC20{
string public name;//代币的名字
string public symbol;//代币的符号,也就是代币的简称
uint8 public decimals = 18;//支持几位小数点后几位
uint256 public totalSupply;//发行代币的总量,所有智能合约发行的代币总量是一定的
mapping(address => uint256) public balanceOf;//输入地址,可以获取该地址代币的余额
mapping(address => mapping(address => uint256)) public allowance;//准许,允许别人以你的名义给别人转
//事件
event Transfer(address indexed from,address indexed to,uint256 value);
event Approval(address indexed _owner,address indexed _spender,uint256 _value);
event Burn(address indexed from,uint256 value);
//构造函数
function constructor(uint256 initialSupply,string tokenName,string tokenSymbol) public {
totalSupply = initialSupply * 10 ** uint256(decimals);
balanceOf[msg.sender] = totalSupply;
name = tokenName;
symbol = tokenSymbol;
}
//转账,内部调用
function _transfer(address _from,address _to,uint _value)internal{
//禁止往0地址转账
require(_to != 0x0);
require(balanceOf[_from]>=_value);
//防止溢出
require(balanceOf[_to]+_value >= balanceOf[_to]);
//保证原子性
uint previousBalances = balanceOf[_from] + balanceOf[_to];
balanceOf[_from]-=_value;
balanceOf[_to]+=_value;
emit Transfer(_from,_to,_value);
//assert要求一定为真
assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
}
function transfer(address _to,uint256 _value) public returns(bool success){
_transfer(msg.sender,_to,_value);
return true;
}
//从别人账户转到另一个人账户
function transferFrom(address _from,address _to,uint256 _value) public returns(bool success){
//allowance[_from][msg.sender]:from账户给合约调用者的账户的额度
require(_value <= allowance[_from][msg.sender]);
allowance[_from][msg.sender]-=_value;
_transfer(_from,_to,_value);
return true;
}
//授权
function approve(address _spender,uint256 _value) public returns(bool success){
//合约调用者给spender的授权额度
allowance[msg.sender][_spender] = _value;
emit Approval(msg.sender,_spender,_value);
return true;
}
function approveAndCall(address _spender,uint256 _value,bytes _extraData) public returns(bool success){
tokenRecipient spender = tokenRecipient(_spender);
if(approve(_spender,_value)){
spender.receiveApproval(msg.sender,_value,this,_extraData);
return true;
}
}
function burn(uint256 _value) public returns(bool success){
require(balanceOf[msg.sender] >= _value);
//调用者账户减掉_value
balanceOf[msg.sender] -= _value;
//代币总量减掉_value
totalSupply -= _value;
emit Burn(msg.sender,_value);
return true;
}
//在授权额度内销毁别人的代币
function burnFrom(address _from,uint256 _value) public returns(bool success){
require(balanceOf[_from] >= _value);
require(_value <= allowance[_from][msg.sender]);
balanceOf[_from] -= _value;
allowance[_from][msg.sender] -= _value;
totalSupply-=_value;
emit Burn(_from,_value);
return true;
}
}