solidity 合约文件结构
合约文件:
例子:
pragma solidity ^0.4.0;
import "";
contract Test {
//状态变量
uint a;
function setA(uint x) public {
a = x;
// 调用事件
emit Set_A(x)
}
// 定义事件
event Set_A(uint a);
// 定义结构类型
struct Pos {
int lat;
int lng;
}
//函数修改器
modifier owner () {
}
}
int 是有符号的整型,而uint是无符号的整型 uint8 表示无符号的8位整型。
整型的运算符:
pragma solidity ^0.4.0;
contract Test {
uint a;
uint256 b = 20;
uint256 c = 30;
function testadd() public returns (int) {
if(b>c){
return b + c
} else if(b == c){
return b * c
} else {
return b >> 2;
//结果是5 b>> 2 表示 20 / 2^2
}
}
}
uint是指只支持固定的位数的变量,而constant 是支持任意精度的,所以有时候会运用到常量
并且常量的运算是不会有溢出的
pragma solidity ^0.4.0;
contract Test {
function testLiterals() public constant returns (uint){
return 1; // 这个 1 就是一个常量
// return 1 + 1;
// return 1.0 + 1e10; 有理数的常量
}
function testStringLiterals() public constant returns(string){
return "abc"; //字符串常量会被转换成一个字节数组或者是字符串数组
}
function testHexLiterals() public constant returns(bytes2,bytes1,bytes1){
// 十六进制常量返回的是一个字节数组,十六进制常量是以hex开头的
bytes2 a = hex"abcd";
return (a,a[0],a[1]); // "0xabcd","0xab","0xcd"
}
}
contant 表示不会修改状态变量的常量,新版本也可以使用 view
是一种数据类型,比如数字 1 和 1.000 就是常量
字符串常量:就是字符串
表示一个账户的地址(20字节)
地址类型成员包括: 获取账户余额:`balance` ,转移账户余额:` transfer(金额数目)`
pragma solidity ^0.4.16;
contract AddrTest {
//当表示一个方法可以接受以太币时需要在函数声明的时候加上 payable
function deposit() public payable {
}
function getBalance() public constant returns (uint) {
return this.balance;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Xr2b3Ie-1572335194921)(.\img\1.png)]
实现转账功能
pragma solidity ^0.4.16;
contract AddrTest {
//当表示一个方法可以接受以太币时需要在函数声明的时候加上 payable
function deposit() public payable {
}
function getBalance() public constant returns (uint) {
return this.balance;
}
function transferEther(address towho) public {
//10 表示转10个单位的币种
towho.transfer(10)
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3lbMlMlh-1572335194922)(.\img\2.png)]
再来看一段代码:
pragma solidity ^0.4.4;
contract Test {
address public _owner;
uint public _number;
function Test() {
_owner = this;
_number = 100;
}
function msgSenderAddress() constant returns (address) {
return msg.sender;
}
function setNumberAdd1() {
_number = _number + 5;
}
function setNumberAdd2() {
if (_owner == msg.sender) {
_number = _number + 10;
}
}
function returnContractAddress() constant returns (address) {
return this;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-960C8IXc-1572335194926)(.\img\3.png)]
○ send() 函数
send 与transfer对应,但更底层。如果执行失败,transfer不会因异常停止,而send会返回false。
警告:send() 执行有一些风险:如果调用栈的深度超过1024或gas耗光,交易都会失败。因此,为了保证安全,必须检查send的返回值,如果交易失败,会回退以太币。如果用transfer会更好。
○ call(), callcode() 和 delegatecall() 函数
call函数返回一个bool值,以表明执行成功与否。正常结束返回true,异常终止返回false。但无法获取到结果数据,因为需要提前知道返回的数据的编码和数据大小(因不知道对方使用的协议格式,所以也不会知道返回的结果如何解析)。
还可以提供.gas()修饰器进行调用:
数据位置:memory
,storage
一般的定义会有默认的存储位置,但是我们可以通过memory和storage来改变默认的存储地址
memory
:
就是我们常说的内存,他不是永久存在的,比如函数的参数和函数的返回值都默认的存在内存中,memory是一个临时的空间,在函数调用的时候出现,在函数调用之后被释放;
storage
:
是永久存在的,比如一些复杂变量,状态变量就是存在区块链中,storage对gas的消耗是远远大于memory的;
pragma solidity ^0.4.4;
contract ArrayTest {
uint[] public u = [1,2,3];
string s = 'abcde';
function h() public returns (uint) {
u.push(6);
return bytes(s).length;
}
function f() public view returns (byte) {
return bytes(s)[1];
}
function newM(uint len) constant public returns (uint){
uint[] memory a = new uint[] (len);
return a.length;
}
function g(uint[3] _data) public constant{
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eXGVCyly-1572335194929)(.\img\4.png)]
pragma solidity ^0.4.4;
contract ArrayTest {
constructor() payable {
}
function testApi () public payable returns (address) {
// return msg.sender;(address) 当前用户地址
// return msg.value;(uint)
// return block.coinbase;(address) 当前矿工的地址
// return block.difficulty;(uint) 当前挖矿的难度
// return block.number;(uint)当前区块的号码
// return block.timestamp;(uint)
// return now;(uint)
// return tx.gasprice;(uint)
}
}
solidity的错误处理
如何处理:
assert
& require
在老版本中是使用throw
来实现抛出错误
if(msg.sender != owner) { throw; }
// 现可以完全等价于下面的代码
if(msg.sender != owner) { revert(); }
assert(msg.sender == owner);
require(msg.sender == owner);
注意在 assert()
和 require()
例子中的条件声明,是 if 例子中条件块取反,也就是用 == 代替了 != 。
assert()和require()之间的区别:
assert()
的情况:比如:下标越界,除以零这类情况是assert类型异常;
require是判断外部条件错误的;
检查 overflow/underflow,即:c = a+b; assert(c > b)
检查非变量(invariants),即:assert(this.balance >= totalSupply);
验证改变后的状态,不应该发生的条件
一般地,尽量少使用 assert 调用,如果要使用assert 应该在函数结尾处使用
基本上,require() 应该被用于函数中检查条件,assert() 用于预防不应该发生的情况,但不应该使条件错误。
require()
的情况:比如:gas不足,没有匹配到正常的函数的情况下的异常;
assert是判断程序内部逻辑的错误;
验证用户输入,即: require(input<20)
;
验证外部合约响应,即: require(external.send(amount))
;
执行合约前,验证状态条件,即: require(block.number > SOME_BLOCK_NUMBER)
或者 require(balance[msg.sender]>=amount)
一般地,尽量使用 require 函数
一般地,require 应该在函数最开始的地方使用
在我们的智能合约最佳实践中有很多使用 require() 的例子供参考。
pragma solidity ^0.4.20;
contract Test {
function simpleInput(uint a,uint b) public {
// uint a,uint b 就是输入参数
}
function simpleInput2(uint c,uint d) public returns (uint sum) {
// uint sum 就是输出参数
}
function testSimpleInput() public constant returns (uint sum) {
// 命名参数是和顺序没有关系的,所以可以调换b和a的顺序
sum = simpleInput2({d:1,c:2});
}
function test2(uint a,uint b)
public constant returns (uint sum,uint mul) {
// 参数的解构,同时返回两个值
sum = a + b;
mul = a * b;
}
function test33() public constant returns (uint sum,uint mul) {
(sum,mul) = test2({b:3,a:2});
}
function f() public constant returns (uint,bool,uint) {
return (7,true,2);
}
function g() public {
var (x,y,z) = f();
(x,z) = (z,x);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FyS58FtK-1572335194933)(.\img\5.png)]
控制语句有:if, else, while, do, for, break, continue, return, 三元表达式
没有:switch, goto
pragma solidity ^0.4.20;
contract Test {
function testWhile() public constant returns(uint) {
uint i = 0;
uint sumofOdd = 0;
while(true) {
i++;
if( i % 2 == 0) {
continue;
}
if( i > 10 ){
break;
}
sumofOdd += i;
}
return sumofOdd;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SEjxd3hr-1572335194937)(.\img\6.png)]
public 和 private
public:
公开函数是合约接口的一部分,可以通过内部或者消息来进行调用。对于public类型的状态变量,会自动创建一个访问器。 函数默认的可见性是public
private:
私有函数和状态变量仅在当前合约中可以访问,在继承的合约内,不可访问,外部也是无法访问的。
internal:
这种状态变量可供外部和子合约调用,这种类型的函数和private类型的函数一样,智能合约自己内部调用,不过和private不一样的是,他可以在继承的合约里面调用,它和其他语言中的protected不完全一样。
external:
外部函数是合约接口的一部分,只能使用消息调用。
pragma solidity ^0.4.20;
contract Test {
uint public data;
function f(uint a) private returns (uint b) {
return a + 1;
}
function setData(uint a) internal {
data = a;
}
function exSetData(uint a) external {
data = a;
}
function testsetData(uint a) public {
setData(1);
this.exSetData(1);
}
}
contract D {
function readData() {
Test test = new Test();
// test.setData(1); 这样写会报错
test.exSetData(1);
test.testsetData(1);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LST2DWTL-1572335194939)(.\img\7.png)]
pragma solidity ^0.4.20;
// 构造函数
contract Test {
uint internal data;
constructor (uint a) {
data = a;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vyedhSeu-1572335194941)(.\img\8.png)]
无名函数 也叫回退函数,一个合约里面只能有一个无名函数,这个函数无参数,也无返回值。如果调用合约时,没有匹配上任何一个函数(或者没有传哪怕一点数据),就会调用默认的回退函数。
下述提供给回退函数可执行的操作会比常规的花费得多一点。
ether
pragma solidity ^0.4.20;
contract Test {
uint internal data;
constructor (uint a) {
data = a;
}
event EVENTA(uint a);
function testview() public constant returns(uint) {
// data = 1;
// emit EVENTA(1);
return data;
}
function f() public pure returns (uint) {
// this.balance; 会报错
// msg.value; 会报错
// return 1 * 2 + data; 会报错
// 以上会报错是因为在声明f函数的时候,定义的是pure纯函数,所以不对storage进行读写
return 1 * 2 + 3;
}
// 无名函数,必须有payable属性
function () public payable {
// 调用无名函数的情况:
// 1、当我们对合约进行转账的时候会被触发
// 2、当调用这个合约中没有被定义的函数的时候会调用这个无名函数
}
}
pragma solidity ^0.4.20;
contract Mytoken {
// 定义一个变量来保存每个地址中的余额
mapping(address => uint256) public balanceOf;
// 合约中的构造函数
constructor(uint256 initSupply) public {
balanceOf[msg.sender] = initSupply;
}
function transfer(address _to,uint256 _value) public {
// 要求账户余额要大于转出的数额
require(balanceOf[msg.sender] >= _value);
// 目标地址的余额要远远大于转入的值,因为uint定义的是256,如果溢出会出现比原始金额还要小的状态
require(balanceOf[_to] + _value >= balanceOf[ _to] );
// 转账地址减去要转的值
balanceOf[msg.sender] -= _value;
// 目标地址加上转入的金额
balanceOf[ _to] += _value;
}
}
但是现实开发中是要求遵循EIPs标准的,EIPs中需要包含以下元素
pragma solidity ^0.4.20;
contract ERC20Interface {
string public name;
string public symbol;
uint8 public decimals;
uint public totalSupply;
function balanceOf(address _ower) view returns (uint256 balance);
// 输入地址就可以获取该地址的代币余额
function transfer(address _to,uint256 _value) returns (bool success);
// 调用transfer函数就可以将自己的token地址转给_to 地址,_value 是转账个数
function transferFrom(address _form,address _to,uint256 _value) returns (bool success);
// 与approve搭配使用,approve批准之后,调用transferFrom函数来转移token。
function approve(address _spender,uint _value) returns (bool success);
// 批准_spender账户从自己的账户转移_value个token。可以分多次转移
function allowance(address _owner,address _spender) view returns (uint256 remaining);
// 批准_spender账户从自己的账户转移_value个token。可以分多次转移
// 事件
event Transfer(address indexed _from,address indexed _to,uint256 _value);
// 当成功转移token时,一定要触发Transfer事件
event Approval(address indexed _owner,address indexed _spender,uint256 _value);
// 当调用approval函数成功时,一定要触发Approval事件
}
pragma solidity ^0.4.20;
contract ERC20Interface {
string public name;
string public symbol;
uint8 public decimals;
uint public totalSupply;
// function balanceOf(address _ower) view returns (uint256 balance);
function transfer(address _to,uint256 _value) returns (bool success);
function transferFrom(address _form,address _to,uint256 _value) returns (bool success);
function approve(address _spender,uint _value) returns (bool success);
function allowance(address _owner,address _spender) view returns (uint256 remaining);
event Transfer(address indexed _from,address indexed _to,uint256 _value);
event Approval(address indexed _owner,address indexed _spender,uint256 _value);
}
// approve、transferFrom及allowance解释:
// 账户A有1000个ETH,想允许B账户随意调用100个ETH。A账户按照以下形式调用approve函数approve(B,100)。
// 当B账户想用这100个ETH中的10个ETH给C账户时,则调用transferFrom(A, C, 10)。这时调用allowance(A, B)可以查看B账户还能够调用A账户多少个token。
// contract ERC20 is ERC20Interface 表示合约ERC20继承了合约ERC20Interface
contract ERC20 is ERC20Interface {
// 用mapping来储存每个地址和他对应的余额
mapping (address => uint256) public balanceOf;
mapping (address => mapping (address => uint256)) internal allowed;
// 表示allowed这个账号可以操控address里面的金额
constructor() public {
name = "BTC"; //代币名称叫EmmaToken
symbol = "IMOOC"; //域名
decimals = 0;//小数位为0
totalSupply = 10000;//发行了10000个币
balanceOf[msg.sender] = totalSupply; //,默认创建者拥有所有的代币
}
function balanceOf(address _owner) view returns (uint256 balance){
// 通过上面mapping的定义,此处可以用这种方法直接返回的传入地址的余额
return balanceOf[ _owner]
}
function transfer(address _to,uint256 _value) returns (bool success) {
// 在此之前要先做一些余额的检查
require(_to != address(0)); // _to 这个账号不能为零
require(balanceOf[msg.sender] >= _value); // 发送者的余额一定要大于转账值
require(balanceOf[_to] + _value >= balanceOf[_to]);// 表示接受者的余额加上转入值之后也要大于原来的余额,防止溢出的一个判断
balanceOf[msg.sender] -= _value;
balanceOf[ _to] += _value;
emit Transfer(msg.sender,_to,_value);
}
function transferFrom(address _form,address _to,uint256 _value) returns (bool success) {
require(_to != address(0)); // _to 这个账号不能为零
require(balanceOf[_form] >= _value); // 发送者的余额一定要大于转账值
require(allowed[_form][msg.sender] >= _value);
require(balanceOf[_to] + _value >= balanceOf[_to]);// 表示接受者的余额加上转入值之后也要大于原来的余额,防止溢出的一个判断
balanceOf[_form] -= _value;
balanceOf[ _to] += _value;
emit Transfer(_form,_to,_value);
success = true;
}
function approve(address _spender,uint _value) returns (bool success) {
// 表示允许 _spender 账户调用msg.sender的value 个币
allowed[msg.sender][_spender] = _value;
// 触发Approval事件
emit Approval(msg.sender, _spender, _value);
success = true;
}
function allowance(address _owner,address _spender) view returns (uint256 remaining) {
return allowed[_owner][_spender];
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZfIGmPnM-1572335194943)(.\img\9.png)]
// 安装truffle
npm i -g truffle
// 创建文件夹
mkdir pet-shop
cd pet-shop
// 初始化并下载框架
truffle init
// 可以用ls或者tree命令查看文件结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-65DC33md-1572335194945)(.\img\10.png)]
第二种创建方式:
mkdir pet-shop2
cd pet-shop2
// 用unbox来创建
truffle unbox pet-shop
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6usixyui-1572335194946)(.\img\11.png)]
可以看出每次的初始化都会为我们生成几个文件
contracts/
:智能合约的目录migrations/
:用来做部署的目录test/
:测试的目录truffle.js
: truffle的配置文件-----> ganache
用来模拟一个本地内存的节点的工具,可以理解为是本地的区块链,这个链只有一个节点,里面会有10个地址账户,每个账户有100的余额,为了方便测试转账
抽象合约和继承相关,类似于上面的例子
pragma solidity ^0.4.0;
contract Feline {
function utterance() public returns (bytes32);
}
这样的合约不能被编译(即使它们同时包含具体函数和抽象函数),但它们可以用作父合约:
contract Feline {
function utterance() public returns (bytes32);
}
contract Cat is Feline {
function utterance() public returns (bytes32) { return "miaow"; }
}
et-shop
[外链图片转存中...(img-6usixyui-1572335194946)]
可以看出每次的初始化都会为我们生成几个文件
+ `contracts/`:智能合约的目录
+ `migrations/`:用来做部署的目录
+ `test/`:测试的目录
+ `truffle.js`: truffle的配置文件
-----> ganache
用来模拟一个本地内存的节点的工具,可以理解为是本地的区块链,这个链只有一个节点,里面会有10个地址账户,每个账户有100的余额,为了方便测试转账
### 抽象合约
抽象合约和继承相关,类似于上面的例子
```javascript
pragma solidity ^0.4.0;
contract Feline {
function utterance() public returns (bytes32);
}
这样的合约不能被编译(即使它们同时包含具体函数和抽象函数),但它们可以用作父合约:
contract Feline {
function utterance() public returns (bytes32);
}
contract Cat is Feline {
function utterance() public returns (bytes32) { return "miaow"; }
}
如果一个合约是从抽象合约中继承的,但没实现所有的函数,则它也是抽象合约。