1 sol文件结构 1.1 编译开关
在使用Solidity编写智能合约前,需要声明所使用的的编译器版本,编译器开关 pragma solidity ^0.6.0;
,该编译开关表明编译器版本需要不低于0.6.0且低于于0.5.0才可以编译。也可以指定编译器的版本范围:pragma solidity >= 0.6.0 < 0.7.0;
Solidity支持导入语句引入外部文件。
全局引入:import "filename";
,此语句将从“filename”中导入所有的全局符号到当前全局作用域中。
创建新的全局符号代表从文件中引入的所有成员,自定义命名空间引入符号“*”:import * as symbolName from "filename";
,这条语句表示创建一个新的全局符号symbolName,其成员全部来自filename中的全局符号。
也可以创建多个全局符号分别表示源文件中的其他符号,分别定义引入语句:import {symbol1 as alias,symbol2} form "filename";
,这句话表示创建新的全局符号alisa和symbol2,从filename分别引入symbol1和symbol2。
pragma solidity ^0.6.0;
contract hello {
string public name;
constructor() public {
name = "hello";
}
}
这里的contract可以理解为一个类。contact有构造函数、成员函数和成员变量。
1.4 库库和合约的区别在于库不能有Fallback函数以及payable关键字,同时也不可以定义storage变量,但是库可以修改和他们链接的合约的storage变量。
1.5 接口与java、C++一样,solidity接口只定义行为,没有实现。
1.6 代码注释Solidity可以使用单行注释(//)和多行注释(/···/),此外还有一种natepec注释,其文档尚未编写完成。它用三个反斜杠(///)和双星号开头的块编写(/**···*/),注释还可以使用Doxygen央视,以支持生成对文档的说明,参数验证的注解或者是在用户调用这个函数时d出的确认内容。
pragma solidity ^0.4.0;
/** @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 );
}
}
2 合约文件结构
一个合约一般包括以下组成部分:
状态变量(State variables)结构定义(Structure definitions)修饰符定义(Modifier definitions)事件声明(Event declaration)枚举定义(Enumeration definitions)函数定义(Function definitions) 3 变量类型 3.1 值类型(1)布尔型:值为true或者false。
(2)整型:int或者uint。
(3)地址类型:以太坊的地址(address)的长度大小为20B,160bit(以太坊地址的大小)。地址类型也有成员变量,是所有合约的基础。balanc属性,用来查询一个地址的余额;transfer函数,用来向一个地址发送以太币。
(4)定长字节数组:固定大小的字节数组。
(5)有理数和整型字面量。
(6)枚举类型:solidity中的一种用户自定义类型,它可以显式地与整型进行转化,但不能进行隐式转换。
(7)函数:
完整的函数定义为:
function <name>(<parameter types>){ internal | external } [ pure | constant | view | payable] [returns(<return type>)]
若不说明函数类型,默认函数类型为internal。如果函数没有返回值,则省略return关键字。
函数可以分为内部函数(internal)和外部函数(exinternal)。**内部函数只能在当前合约内被使用,**不能在当前合约的上下文环境以外的地方执行。如在当前的代码块内,包括内部库函数和继承的函数中,函数的默认类型就是internal,与之相反,合约中的函数默认是public,只有被当做类型名的时候,默认才是内部函数。 外部函数由地址和函数方法签名两部分组成。可作为外部函数调用的参数或者外部函数调用的返回。
注意,当前合约的public函数即可以当做内部函数使用也可以当做外部函数使用。如果想将一个函数当做内部函数使用,就用f调用;如果想当做外部函数调用就用this.f调用。此外public函数也有一个特殊的成员变量,称作selector,可以返回ABI函数选择器。
pragma solidity ^0.4.0;
contract Selector {
function f () public view returns (bytes4) {
return this.f. selector;
}
}
3.2 引用类型
相比值类型,处理复杂类型时因为其占用空间超过256位,需要更加谨慎,由于复制这些类型变量开销相当大,因此不得不考虑他们的存储位置,考虑他们是是保存在内存中(并不是永久存储)还是存储(保存状态变量的地方)。
3.2.1 数据位置所有复杂类型(即数组和结构类型)都有一个额外属性,即数据位置,说明数据是保存在内存中还是存储中。大多数时候数据有默认位置,但也可以通过类型名后增加关键字storage和memory进行修改。函数参数的数据默认是memory,局部变量的数据位置默认storage,状态变量数据位置默认为storage。
3.2.2 数组数组可以在声明时指定长度,也可以动态调整大小。对于存储位置是存储(storage)的数组来说,元素类型可以是任意的(即可以是数组类型,映射类型或结构体)。对于存储位置是内存(memory)的数组来说,元素类型不能是映射类型,如果作为public函数的参数,它只能是ABI类型。
一个元素类型为uint,固定长度为K的数组可以声明为uint[K],动态数组可以声明为uint[]。bytes和string类型的变量是特殊的数组。
可使用new关键字创建一个memory的数组。与storage的数组不同,不能用.length的长度来修改数组的大小属性。
pragma solidity ^0.4.0;
contract test1{
function fun (){
uint [] memory A = new uint[](7); //创建一个memory的数组
}
uint[] B;
function fun2 (){
B = new uint[](7);
B.length = 10;
B[9]=100;
}
}
(1)不定长字节数组:不定长字节数组是一个动态数组,能够容纳任意长度的字节。Bytes可以声明为一个设定长度的状态变量:bytes localBytes = new bytes(0);
,Bytes也可直接赋值:localBytes = "this is a test";
,元素可以被压缩入字节数组:localBytes.push(byte(10));
(2)字符串:在C语言中,字符串以“\0”结尾,在solidity中,字符串并不包含结束符。
结构体是用来实现用户定义的数据类型。结构是一个组合数据类型,包含多个不同数据类型的变量。但结构体里没有任何代码,仅仅由变量构成。struct funder {address addr;uint amount;}
。结构体目前仅支持在合约内使用,如果在参数和返回值中使用结构体,函数必须声明internal。
pragma solidity ^0.4.11;
contract CrowFunding {
struct Funder {
address addr;
uint amount;
}
struct Campaign {
address beneficiary;
uint fundingGoal;
uint numFunders;
uint amount;
mapping (uint => Funder) funders;
}
uint numCampaingns;
mapping (uint => Campaign) campaigns;
function newCampaign(address beneficiary,uint goal) public returns (uint campaignID){
campaignID = numCampaingns ++;
campaigns [campaignID] = Campaign(beneficiary,goal,0,0);
}
function contribute (uint campaignID) publice payable{
Campaign storage c = campaigns[campaignID];
c.funders[c.numFunders++] = Funder({addr:msg.sender, amount:msg.value});
a.amount += msg.value;
}
function checkGoalReached(uint campaignID) public returns (bool reached){
Campaign storage c = campaigns[campaignID];
if (c.amount < c.fundingGoal){
return false;
}
uint amount = c.amount;
c.amount = 0;
c.beneficiary.transfer(amount);
return true;
}
}
这是一个简化版的众筹合约。结构体类型可以作为元素用在映射和数组中,其自身也可以包含映射和数组作为成员变量。
3.2.5 映射映射类型的声明形式为mapping(_KeyType => _ValueType)。_KayType可以是除映射、变长数组、合约、枚举以及结构体之外的几乎所有类型,_ValueType可以是包括映射类型在内的任何类型。
可以将映射声明为public,然后让solidity创建一个getter。_KeyType将成为getter的必须函数,并且getter会返回_ValueType。_ValueType也可以是一个映射。在使用getter时将需要递归地传入每个_Keytype参数。
pragma solidity ^0.4.0;
contract MappingExample{
mapping (address => uint ) public balance;
function update(uint newBalance) public{
balance [msg.sender] = newBalance;
}
}
contract mappingUser{
function f() public returns(uint){
MappingExample.m = new MappingExample();
m.upadate(100);
return m.balance(this);
}
}
4 表达式和控制语句
solidity不支持switch和go…to语句。
4.2 输入参数和输出参数Solidity和JavaScript一样,函数可能需要参数作为输入,与之不同的是,它们可以返回任意数量的参数作为输出。
(1)输入参数
输入参数的声明和变量相同,未使用的参数可以省略参数名。例如如果希望合约接受有两个整数形参的函数的外部调用,可以
pragma solidity ^0.4.0;
contract Simple {
function taker(uint _a,uint _b) public pure {
//用_a和_b实现相关功能
}
}
(2)输出参数
输出参数的声明方式在关键词returns之后,与输入参数的声明方式相同。例如,如果需要返回两个结果,两个给定整数的和与积。
pragma solidity ^0.4.0;
contract Simple {
function arithmetics(uint _a,uint _b) public pure returns (uint sum,uint product){
sum = _a + _b;
product = _a * _b;
}
}
输出参数名可以被省略。输出值也可使用return语句指定。return语句也可以返回多值。返回的输出参数被初始化为0。
4.2 条件语句与传统语句一样,solidity同样支持if/else语句。需要注意if(1){…}在solidity中是无效的,因为solidity中非布尔类型不能转换成布尔类型。
4.3 循环语句循环语句while和for语句。
uint insertIndex = stack.length;
while(insertIndex > 0 && bid.limit <=stack[insertIndex-1].limit){
insertIndex--;
}
4.3 其他控制结构
(1)break:用来跳出现有的循环。
(2)continue:用来退出当前的循环。
(3)return:用来从函数/方法中返回。
(4)?::三元 *** 作符。如a>b? a:b,如果a>b返回a,否则返回b。
(1)内部函数调用
当前合约中的函数可以直接从内部调用,也可以递归调用,例如,
pragma solidity ^0.4.0;
contract Test {
function test1 (uint a) public pure returns (uint ret){
return test2();
}
function test2 internal public pure returns (uint ret){
return test1(7) + test2();
}
}
这些函数调用在EVM中被解释为简单的跳转。这样做的效果就是当前内存不会被清除,也就是说,通过内部调用在函数之间传递内存引用是非常有效的。
(2)外部函数调用
表达式“this.g(8)”和“c.g(2)”(其中c是合约实例)都是有效的函数调用,这种情况下,函数将会通过一个消息调用来被外部调用,而不是直接跳转。注意,不可以在构造函数中通过this来调用函数,此时真实的合约实例还没有建立。
如果想要调用其他合约的函数,需要外部调用。对于一个外部调用,所有的函数参数都需要被复制到内存。当调用其他合约的函数时,随函数调用发送的wei和gas的数量可以分别由特定选型.value和.gas指定。
(3)具名调用和匿名函数调用
函数调用参数也可以按照任意顺序由名称给出,如果他们被包含在{}中,如以下实例中所示。参数列表必须按名称与函数声明中的参数列表相符,但可以任意顺序排列。
pragma solidity ^0.4.0;
constant C {
function f (uint key,uint value) public {
//.....
}
function g () public {
f({value : 2,key : 4});
}
}
4.5 通过new创建合约
使用关键字new可以创建一个新合约。由于创建合约的完整代码必须事先知道,因此递归地创建智能合约是不可能的。
pragma solidity ^0.4.0;
contract T{
uint x;
function T (uint a) public payable{
x = a;
}
}
contract T2 {
T D = new T(4); //作为合约T2构造函数的一部分执行
function creatT (uint arg) public {
T newT = new T(arg);
}
function creatAndEndowT(uint arg, uint amount) public payable{
//随合约的创建发送ether。
T newT = (new T).value(amount)(arg);
}
}
使用.value选项创建T的实例时可以转发ether,但是不能限制gas的数量。如果创建失败(栈溢出,没有足够的余额等问题),就会引发异常。
5 类型转换Solidity同样支持类型转换,例如,将一个数字字符串转换为整型或浮点数。转换被分为隐式类型转换和显式类型转换。
5.1 隐式类型转换如果运算符支持两种不同的类型,那么编译器会尝试隐式类型转换,同理赋值时也是类似。通常,隐式类型转换要保证不丢失数据且语义通顺。例如uin8可以转换为uint256,int8不能转为uint256,因为uint不支持-1。任何无符号整数都可以转换为相同或更大长度的字节数组,任何可以转换为uint16的类型,也可以转换为address类型。
function add() public pure returns (uint){
uint8 i = 10;
uint16 j = 20;
uint16 k = i + j;
return k;
}
在运行uint16 k= i+j运算时,i会隐式转换为uint16,在return k时,k会隐式转换为uint256。
5.2 显式转换如果编译器不允许隐式的自动转换,但你知道转换没有问题时,可以进行显式类型转换。例如,将一个int8类型转换成uint类型。
int8 y = 8;
uint x = uint(y);
6 Solidity中的单位
6.1 货币单位
一个数字常量后面跟随一个后缀 wei, funney,szabo或ether,这个后缀就是货币单位。
6.2 时间单位一个数字常量后面跟随一个后缀seconds,minutes,hours,days,weeks,years,这个后缀就是时间单位。
7 全局变量在全局命名空间中已经存在了一些特殊的变量和函数,他们主要用来提供关于区块的信息或一些通用的工具函数。
7.1 区块和交易属性 block.coinbase(address):挖出当前区块的矿工地址。block.difficulty(uint):当前区块难度。block.gaslimit:当前区块gas限额。block.number:当前区块号。gasleft () returns(uint256):剩余的gas。msg.date(bytes):完整的calldate。msg.sender(address):当前发送者(当前调用)。msg.value(uint):随消息发送wei的数量。tx.gasprice(uint):交易gas的价格。tx.origin(address):交易发起者。 7.2 ABI编码函数这里给出部分ABI编码可以使用的全局函数,后面会详细介绍ABI的相关概念和 *** 作。
abi.encode(…) returns (bytes):对给定参数进行编码。abi.encodePacked(…) returns (bytes):对给定参数进行紧打包编码。这些编码函数可以用来构造函数调用数据,而不用实际进行调用。 7.3 错误处理 assert(bool condition):如果条件不满足,则当前交易没有效果,主要用于检查内部错误。require(bool condition):如果条件不满足,则撤销状态更改,主要用于检查由输入或者外部组件引起的错误。require(bool condition,string message):如果条件不满足,则撤销更改状态,用于检查由输入或者外部组件引起的错误,可以同时提供一个错误消息。revert():中止运行并撤销状态更改。revert(string reason):中止运行并撤销状态更改,同时提供一个解释性的字符串。
solidity使用“状态恢复”异常的处理方式来解决程序运行中出现的错误。这种异常处理方法是将撤销对当前调用中的状态所做的所有更改,并且还向调用者标记错误发生的位置。函数asser和require可用于检查条件并在条件不满足的时抛出异常。**assert函数只用于测试内部错误,并检查非变量。require函数用于确定条件的有效性,例如输入变量,或合约状态变量是否满足条件,或验证外部合约调用返回的值。**如果使用得当,分析工具可以评估用户的合约,并标出那些会使用assert失败的条件和函数调用。正常的语句不会导致assert语句的失败,如果出现则表示出现了一个需要修复的的bug。
还有另外两种触发异常的方法:一种是revert函数标记错误并恢复当前的调用,revert调用中包含有关错误的详细信息是可能的,这个消息会被返回给调用者;另一种第throw代替revert,但无法返回错误信息。从0.4.13版本开始throw已被弃用。
下面的例子中,可以看到如何使用require检查输入条件,以及如何使用assert检查内部错误。注意可以给require提供一个消息字符串,而assert不行。
pragma solidity ^0.4.22;
contract Sharer{
function sendHalf (address addr) public payable returns (uint balance){
require (msg.value % 2 == 0,"Even value require.");
uint balanceBeforTransfer = this.balance;
addr.transfer(msg.value / 2);
assert (this.balance == balanceBeforTransfer-msg.value/2);
return this.balance;
}
}
下列情况将产生一个assert异常:
如果访问数组的索引太大或为负
如果访问固定长度bytesN的索引太大或为负
如果用零当除数做除法或模运算
如果移位负数位
如果将一个太大或者负值转换为一个枚举类型
调用内部函数类型的零初始化变量
调用assert的参数最终结算为false。
下列情况将会产生一个require式异常:
调用throw
调用require的参数最终结算为false
如果通过消息调用某个函数,但该函数没有正确结束(耗尽gas,没有匹配函数,或者本身抛出一个异常),上述函数不包括call、send、callcode等低级 *** 作,低级 *** 作不会抛出异常,而通过返回false来指示失败。
使用new关键字创建合约,但合约没有被正确创建。
对不包含代码的合约执行外部函数调用。
如果合约通过一个没有payable修饰符的共有函数接收Ether
合约通过公有的getter函数接收Ether
如果.transfer失败。
在内部,solidity对一个require式的异常执行回退 *** 作并执行一个无效的 *** 作来引发assert式异常。在这两种情况下,都会导致EVM回退对状态所做的所有更改。回退的原因不能继续安全的执行,没有实现预期效果。因为我们想保留交易的原子性,所以最安全的做法是回退所有更改并使整个交易不产生效果。assert异常不会消耗任何gas。
下面的例子展示了require和revert中使用错误字符串:
pragma solidity ^0.4.22;
contract Sharer{
function buy (uint amount) payable {
if (amount > msg.value / 2 ether)
revert ("no enough Ether");
require (amount <= msg.value / 2 ether,"no enough Ether");
}
}
7.4 地址相关
针地址 *** 作的全局函数如下。
< address >.balance(uint256):以wei为单位的地址类型余额。< address >.tranfer(uint256 amount):向地址类型发送数量为amount的wei,失败时抛出异常。< address >.send(uint256 amount) returns(bool):向地址类型发送数量为amount的wei,失败时返回false。< address >.call(…) returns (bool):发出低级函数call,失败时返回false,发送所有可用gas,失败时可调节。< address >.callcode(…) return (bool):发送低级函数callcode,失败时返回false,发送所有可用gas,失败时可调节。< address >.delegatecall(…) returns (bool):发送低级函数delegatecall,失败时返回false,发送所有可用gas,失败时可调节。在使用send时,如果栈的深度已经达到1024(可以由调用者强制指定),转账就会失败;如果接受者用光了gas,转账也会失败。为了保证以太币转账安全,应该总是检查send的返回值,利用transfer或者更好的方式,例如使用接收者取回钱的方式。 7.5 合约相关 this(current contract’s type):当前合约可以显示转换为地址类型。selfdestruct (address recipient):销毁合约,并把余额发送到指定地址类型。
此外,当前合约内的所有函数都可被直接调用,包括当前函数。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)