Solidity函数声明和类型
- 函数的值类型有两类:内部(internal)类型和外部(external)类型
- 内部函数只可以在当前合约内部被调用(即在当前代码块内,包括内部库函数和继承函数),因为他们不能在当前合约的上下文中的外部被执行。调用一个内部函数是通过跳转到它的入口标签来实现的,就像在当前合约的内部调用一个函数
- 外部函数是由一个地址和一个函数的签名组成,可以通过外部函数调用传递或者返回
- 调用内部函数,直接使用名字f
- 调用外部函数:this.f(当前合约),a.f(外部合约)
- 除了使用外部函数调用外,还可以使用继承机制
例子(外部函数调用)
contract C{uint a;function f() public{}
}
contract D{function g() public{C c = new C();c.f();}
}
例子(继承机制)
- 可以使用internal函数/public函数,不可以使用private函数
- D继承C合约,只会把交叉的函数编译上传
contract C{uint a;function f() public{}
}
contract D is C{function g() public{f();}
}
函数的可见性
- 函数的可见性可以指定为external、public、internal或者private;对于状态变量,不可以设置为external,默认是internal
- external:外部函数作为合约接口的一部分,意味着我们可以从其他合约和交易中调用。一个外部函数f不可以从内部调用(即f不起作用,但是this.f可以)。当接收到大量的数据的时候,外部函数有时候会更有效率
- public:public函数是合约接口的一部分,相当于定义了一个view类型的可以返回参数的函数,可以在内部或者通过消息调用。对于public状态变量,会自动生成一个getter函数
例子
uint public a;//将a声明为public,就相当于为a定义了如下的函数形式
function a() public view returns (uint){return a;
}
- internal:这些函数和状态变量只能是内部访问(即从当前合约内部或者从其他派生的合约访问),不可以使用this
- private:private函数和状态变量仅在当前定义他们的合约中使用,并且不能被派生合约使用。
函数状态可变性
- payable:允许从消息调用中接收以太币Ether
- constant:和view相同,一般只修饰状态变量,不允许赋值(除了初始化之外)
constructor()public payable{}//创建合约的同时需要往合约上面转钱
- pure:纯函数,不允许修改或者访问状态
- view:不允许修改状态
函数状态可变性
- 修改状态变量
- 产生事件
- 创建其他合约
- 使用selfdestruct(自杀/自毁)
- 通过调用发送以太币
- 调用任何没有标记为view或者pure的函数
- 使用低级调用
- 使用包含特定操作吗的内联汇编
以下是被认为从状态中进行读取
- 读取状态变量
- 访问this.balance或者<address>.balance
- 访问block、tx、msg中任意成员(除了msg.sig和msg.data之外)
- 调用任何未标记为pure的函数
- 使用包含某些操作码的内联汇编
函数修饰器 modifier
- 使用修饰器modifier可以轻松改变函数的行为。例如,他们可以在执行函数之前自动检查某个条件。修饰器modifier是合约的可继承属性,并可能被派生合约覆盖
- 如果同一个函数有多个修饰器modifier,他们之间可以使用空格隔开,修饰器modifier会依次检查执行。
代码
contract Purchase{address public seller;constructor() public{seller = msg.sender;}modifier onlySeller(){require(msg.sender == seller,"Only seller");_;}function f() public view onlySeller returns(uint){return 200;}
}
效果展示
- msg.sender是合约的部署者,只有合约的部署者调用f函数,返回200,剩余的人会返回错误信息,only seller
- require(msg.sender == seller,"Only seller");逗号之前是检查条件,之后是返回报错信息,检查条件在代码流程之前执行
- _;将代码流程嵌套到这里,指代检查条件执行之后,执行代码的逻辑。(函数体)
回退函数(fallback)
- 回退函数(fallback function)是合约中的特殊函数;没有名字,不能有参数也不能有返回值
- 如果在一个到合约的调用中,没有其他函数与给定的函数标示符匹配(或者没有提供调用函数),你们这个函数(fallbacj函数)会被执行
- 每当合约收到以太币(没有任何数据),回退函数就会执行。此外,为了接收以太币,fallback函数必须标记为payable。如果不存在这样的函数,则合约不能通过常规交易接收以太币
- 在上下文中只有很少的gas可以用来完成回退函数的调用,所以使fallback函数的调用尽量廉价很重要
代码
pragma solidity >0.4.99 <0.6.0;
contract Sink{function() external payable{}
}
contract Test{function() external {x=1;}uint x;
}contract Caller{function calllTest(Test test)public returns(bool){(bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));require(success);address payable testPayable = address(uint160(address(test)));return testPayable.send(2 ether);}
}
注解
- 调用合约中不存在的函数和转钱的时候会调用回退函数
- 回退函数适用在合约5.0版本以上
事件(event)
- 事件是以太坊EVM提供的一种日志基础设施。事件可以用来做操作记录,存储为日志。也可以用来实现一些交互功能,比如通知UI,返回函数的调用结果
- 当定义的事件被触发时,可以将事件存储到EVM的交易日志中,日志是区块链中的一种特殊的数据结构;日志和合约相互关联,与合约的存储合并存入到区块链条中,只要某个区块可以访问,其相关的日志就可以访问,但是在合约中是不可以之间访问日志和事件数据
- 可以通过日志实现简单支付验证SPV,如果一个外部实体提供了一个带有这种证明的合约,它可以检查日志是否真实存在于区块链中
异常处理
- 适用状态恢复异常来处理异常。这样的异常将会撤销对于当前的调用(及其所有的子调用)中的状态所做的所有的更改,并且向调用者返回错误
- 函数的assert和require用于判断条件,并且在不满足条件的时候抛出异常
- assert()一般只应用于测试内部的错误,并且检查异常
- require()应用于确保满足有效的条件(如输入或合约的状态变量),或者验证调用外部合约的返回值
- revert()用于抛出异常,它可以标记一个错误并且将当前调用回退
单位
- 以太币单位之间的换算就是在数字后边加上wei、finney、szabo或者ether来实现,如果没有单位,缺省为wei
时间
- 秒是缺省单位,在时间单位之间,数字后面带有seconds、minutes、hours、days、weeks和years的可以进行换算 但是这些后缀不可以直接用在变量的后边,如果需要使用到时间单位(例如days)来将输入变量转换为时间,可以使用如下方式
function f(uint start,uint daysAfter)public{if (now >= start + daysAfter * 1 days)
}