智能合约入门
区块链我来啦
智能合约入门
Solidity
Solidity中智能合约的含义就是一组代码(它的 功能 )和数据(它的 状态 )的集合,并且它们是位于以太坊区块链的一个特定地址上的。
Solidity也是一种编程语言,不过它针对智能合约项目进行了全面的支持,要想了解一门语言当然要从编写程序开始咯,那么我们就来写我们的第一个Solidity合约吧
//Name.sol
pragma solidity ^0.4.16;//Solidity必须开头,生命合约版本
contract Name{
string name="Jlan";//设定状态
function getName() public returns(string)//设定功能
{
return name;
}
function changeName(string _newName) public
{
name=_newName;
}
}
以上就是一个非常基础的Solidity合约了,可以看的出合约很像是我们在别的语言中的一个类,确实如此,所以一些面向对象编程的内容也可以套用到Solidity上
在Solidity中,许多的操作都需要花费资源,又名gas,也就是虚拟货币,这些内容将在后面讲到
在给属性增加public属性之后自动生成get方法
数据类型
- int
- uint=一个256位长度的无符号int
- bytes1……bytes32 一个byte类型是8位
关键字
- public
- view
- pure
- returns
- return
- payable关键字修饰函数,可以通过这个函数给我们的合约地址充值
回滚函数(fallback)
全局属性
block.blockhash(uint blockNumber) returns (bytes32)
:指定区块的区块哈希——仅可用于最新的 256 个区块且不包括当前区块;而 blocks 从 0.4.22 版本开始已经不推荐使用,由blockhash(uint blockNumber)
代替block.coinbase
(address
): 挖出当前区块的矿工地址block.difficulty
(uint
): 当前区块难度block.gaslimit
(uint
): 当前区块 gas 限额block.number
(uint
): 当前区块号block.timestamp
(uint
): 自 unix epoch 起始当前区块以秒计的时间戳gasleft() returns (uint256)
:剩余的 gasmsg.data
(bytes
): 完整的 calldatamsg.gas
(uint
): 剩余 gas - 自 0.4.21 版本开始已经不推荐使用,由gesleft()
代替msg.sender
(address
): 消息发送者(当前调用)msg.sig
(bytes4
): calldata 的前 4 字节(也就是函数标识符)msg.value
(uint
): 随消息发送的 wei 的数量now
(uint
): 目前区块时间戳(block.timestamp
)tx.gasprice
(uint
): 交易的 gas 价格tx.origin
(address
): 交易发起者(完全的调用链)
address成员变量
<address>.balance
(uint256
):以 Wei 为单位的 地址类型 的余额。
<address>.transfer(uint256 amount)
:向 地址类型 发送数量为 amount 的 Wei,失败时抛出异常,发送 2300 gas 的矿工费,不可调节。
<address>.send(uint256 amount) returns (bool)
:向 地址类型 发送数量为 amount 的 Wei,失败时返回
false
,发送 2300 gas 的矿工费用,不可调节。<address>.call(...) returns (bool)
:发出低级函数
CALL
,失败时返回false
,发送所有可用 gas,可调节。<address>.callcode(...) returns (bool)
:发出低级函数
CALLCODE
,失败时返回false
,发送所有可用 gas,可调节。<address>.delegatecall(...) returns (bool)
:发出低级函数
DELEGATECALL
,失败时返回false
,发送所有可用 gas,可调节。
mapping哈希表
使用语法
mapping(映射关系) 哈希表名称;
pragma solidity^0.4.16;
contract mappingTest{
mapping(address => uint) idmap;
mapping(uint => string) namemap;
uint sum=0;
function register(string username){
address account=msg.sender;
sum++:
idmap[account]=sum;
namemap[sum]=username;
}
}
函数重载
当使用不同的参数(数量,类型)时可以让函数重载,并且如同uint160与address这样的参数类型也是算不同参数类型,可以编译通过,但会有问题
当使用确定参数调用函数时,如果能同时匹配多个重载的函数,会直接报错,如下例:
在这里test函数调用fun函数时传入了一个确定的参数1,同时匹配了uint类型和uint8类型,此时导致编译失败
而在下面这个例子中,传入的是msg.sender
,此时编译通过,运行发现结果返回调用了以address为参数类型的fun函数重载,为啥啊我也不知道啊啊啊啊啊啊啊啊啊啊,后面再看吧😭
函数传参
类似python的两种方法,一是按照函数定义的顺序直接填入,二是通过花括号指定传入哪个形参,如下例:
pragma solidity ^0.4.16;
contract parma{
uint public num;
string public name;
function setParam(uint _num,string _name){
num=_num;
name=_name;
}
function T1(){
setParam(18,"J1an");
}
function T2(){
setParam({_name:"Ben",_num:20});
}
// function T3(){
// setParam(100);
// }
}
在上面这个例子中,T1,T2形象的说明了函数传参的方式,并且如果加入T3函数代码会发现编译无法通过,这是因为在合约内部的函数调用其他函数时必须严格按照函数定义的形参传入参数,但我们在尝试让用户直接对setParam(100)
进行调用时会发现调用成功了,并且_name
被自动赋值为空,如下图所示:tmd这种睿智参数传递多少有点大病
函数返回值
solidity中的函数可以有多个返回值
如果给了函数返回值的参数名,那么这个参数会以类似形参的方式传入到函数中,我们可以不通过return而直接给这个形参赋值来达到返回值的效果,而如果形参传递和return同时存在,以return为准,这不废话吗肯定return优先级高啊,return相当于赋值了还把函数给结束了,后面没操作了可不它高吗
pragma solidity ^0.4.16;
contract funReturn{
function T1() returns(uint num){
return 1;
}
function T2() returns(uint num){
num=2;
}
function T3() returns(uint num){
num=3;
return 4;
}
}
变量作用域和值传递
作用域以小的为准
修饰符
private
函数只能被合约独立使用,不能被继承
public
函数可以被任意调用,可以被继承
internal
函数只能在合约内部调用,不能直接在外部调用,可以被继承
external
函数不能在合约内部进行调用,只能在合约外部被调用(在函数内部可以通过this来间接调用,也可以通过new创建新合约的方法调用),可以被继承
pure
不消耗gas,不会读取全局变量,也不会修改全局变量,一个固定输入就会有固定输出
view
读取全局变量但不修改,不消耗gas
payable
进行金钱交易时所必须的修饰符
值传递和副本拷贝
直接如同a=b这样的传递都是值传递,传入函数都是副本拷贝
const常量定义(已废弃)
函数中使用constant(版本为0.4.16)修饰,不消耗gas(与view修饰等价,并且将在0.5.0版本中废弃)
对于全局变量(局部不可用)使用constant修饰,无法被更改(就是常量定义嘛~~~)
变量并非所有类型都可以使用常量显示,可使用的类型有int,uint,string,bytes1-32
构造函数
构造函数与合约同名或者命名为constructor
(新版本),首先被执行,构造函数传参需要在合约部署时赋予
函数modifire(函数修改器)
require判断,如果require中的内容不成立,那么后面的代码都不会被执行并且回滚
此处例子中,在合约部署时设定了部署者为合约拥有者,在modifier中验证了函数change的身份,首次我们让函数拥有者5B3进行调用发电函数调用成功,a的值被修改,而当我们切换用户Ab8再进行调用时,会发现调用失败并且报错
pragma solidity ^0.4.16;
contract modifierTest{
address public owner;
uint public a=100;
function modifierTest(){
owner=msg.sender;
}
modifier OnlyOwner{
require(msg.sender==owner);
_;
}
function change(uint _num)OnlyOwner{
a=_num;
}
}
modifier modifierName{
require(执行函数要求条件);
_;//占位函数
}
//函数调用
function funName() modifierName{}
//该函数执行时会先进入modifier进行判断
第二个应用,判断用户注册
这个例子中我们在注册前先对其要求该address对应的id为0也就是未注册过才可以进入注册函数
pragma solidity ^0.4.16;
contract modifierTest{
address public owner;
uint sum=0;
mapping(address=>uint) idmap;
mapping(uint=>string) namemap;
function modifierTest(){
owner=msg.sender;
}
modifier OnlyOwner{
require(msg.sender==owner);
_;
}
modifier regConfirm{
require(idmap[msg.sender]==0);
_;
}
function register(string name) regConfirm{
address account=msg.sender;
sum++;
idmap[account]=sum;
namemap[sum]=name;
}
}
第三个应用,权限管理,代码就不放了自己意会吧
modifier执行顺序,在有多个modifier被使用时,后面的modifier会与函数一起被放入前一个modifier的_;
占位符中
合约继承
继承语法
pragma solidity ^0.4.16;
contract father{
}
contract son is father{
}
可以连续继承,可多重继承,如果有同名属性以最后的合约为准
对于属性和函数来说private不可被继承
getter函数
public属性的变量会自动生成一个同名属性为external的函数,对于mapping类型生成一个有参数的函数,传
回退函数fallback
fallback函数,回退函数,是合约里的特殊无名函数,有且仅有一个。它在合约调用没有匹配到函数签名,或者调用没有带任何数据时被自动调用。回退函数是合约里的特殊函数,没有名字,不能有参数,没有返回值。下面来看一个简单的回退函数例子。
pragma solidity ^0.4.0;
contract SimpleFallback{
function(){
//fallback function
}
}
当调用的函数找不到时,就会调用默认的fallback函数。由于Solidity中,Solidity提供了编译期检查,所以我们不能直接通过Solidity调用一个不存在的函数。但我们可以使用Solidity的提供的底层函数address.call
来模拟这一行为,关于call()
函数详见:http://me.tryblockchain.org/Solidity-call-callcode-delegatecall.html 。我们来看个例子:
- send()函数发送ether
当我们使用address.send(ether to send)
向某个合约直接转帐时,由于这个行为没有发送任何数据,所以接收合约总是会调用fallback函数,我们来看看下面的例子
合约销毁
selfdestruct(owner);
storage和memory
全局变量被存在链上,属于storage中的内容,在函数中运行的值都只存在本机中,属于memory中内容,用后即销毁
但是在函数中定义的可变数组是storage类型