区块链100讲:Solidity语法难点解析及故障排查

Posted by

区块链100讲:Solidity语法难点解析及故障排查

本文作者:HiBlock区块链技术布道群-辉哥

原文发布于简书

原文链接:https://www.jianshu.com/p/275ed3d7aeb7

加微信baobaotalk_com,加入技术布道群

区块链100讲:Solidity语法难点解析及故障排查

1

编辑器说明

1、推荐编辑器

目前尝试 Solidity 编程的最好的方式是使用 Remix(https://remix.ethereum.org/) (需要时间加载,请耐心等待)。Remix 是一个基于 Web 的 IDE,它可以让你编写 Solidity 智能合约,然后部署并运行该智能合约。

如果外网不能访问,可以访问欧阳哥哥搭建的REMIX编辑器(http://remix2.ju3ban.net/#optimize=false&version=soljson-v0.4.22+commit.4cb486ee.js)

2、Visual Studio Extension

Microsoft Visual Studio 的 Solidity 插件(https://marketplace.visualstudio.com/items?itemName=ConsenSys.Solidity),包含 Solidity 编译器。

3、Visual Studio Code extension

Microsoft Visual Studio Code 插件(http://juan.blanco.ws/solidity-contracts-in-visual-studio-code/),包含语法高亮和 Solidity 编译器。

2

REMIN的函数引用

function mint(address receiver, uint amount)

1、在REMIX输入时,地址一定要有””表示,否则amount就取不到值。

例如是mint(“0xca35b7d915458ef540ade6068dfe2f44e8fa733c”,101)

2、程序中,如果注释包含中文,单步调试的位置不准确。

3

address相关全局函数

<address>.balance (uint256):该地址有多少以太坊余额(wei为单位)

<address>.transfer(uint256 amount):发送特定数量(wei为单位)的以太坊到对应地址,当出现错误时会扔出异常,但不会因异常而停止。固定需要耗费2300个gas。

<address>.send(uint256 amount) returns (bool):发送特定数量(wei为单位)的以太坊到对应地址,当出现错误时会返回flase。固定需要耗费2300个gas。

【警告】send() 执行有一些风险:如果调用栈的深度超过1024或gas耗光,交易都会失败。因此,为了保证安全,必须检查send的返回值,如果交易失败,会回退以太币。如果用transfer会更好。

<address>.call(…) returns (bool):CALL的低级调用函数,当失败时返回false。执行需要消耗不固定的gas。

【说明】不鼓励使用call函数,后期将会被移除。调用该函数可能造成安全攻击,详见后期安全相关文章。

<address>.callcode(…) returns (bool):CALLCODE的低级调用函数,当失败时返回false。执行需要消耗不固定的gas。

不建议使用,后续版本会删除。

<address>.delegatecall(…) returns (bool):DELEGATECALL的低级调用函数,当失败时返回false。执行需要消耗不固定的gas。

【说明】为了和非ABI协议的合约进行交互,可以使用call() 函数, 它用来向另一个合约发送原始数据,支持任何类型任意数量的参数,每个参数会按规则(ABI协议)打包成32字节并一一拼接到一起。一个例外是:如果第一个参数恰好4个字节,在这种情况下,会被认为根据ABI协议定义的函数器指定的函数签名而直接使用。如果仅想发送消息体,需要避免第一个参数是4个字节。如下面的例子:

function callfunc(address addr) returns (bool){bytes4 methodId = bytes4(keccak256(“setScore(uint256)”));return addr.call(methodId, 100);}

测试地址和地址调用代码举例

pragma solidity ^0.4.18;

contract AddrTest{

/*event函数知识把参数信息打印在REMIX等编译器的LOG位置区,不需要函数定义。*/

event logdata(bytes data);

event LogContractAddress(address exAccount, address contractAddress);

uint score = 0;

/*回调函数,没有函数名。任何调用不存在的函数,这时被调用的合约的fallback函数会执行。

payable:如果一个函数需要进行货币操作,必须要带上payable关键字*/

function() payable {

logdata(msg.data);

}

/*智能合约构建函数*/

function AddrTest(){

LogContractAddress(msg.sender,this);

}

function getBalance() returns (uint) {

return this.balance;

}

function setScore(uint s) public {

score = s;

}

function getScore() returns ( uint){

return score;

}

}

contract CallTest{

/*该函数有函数申明没有实际函数内容,在remix的value区域设置以太坊的个数,调用该函数会把外部账户(ACCOUNT)中的 以太坊转移到智能合约账户中*/

function deposit() payable {

}

event logSendEvent(address to, uint value);

event LogContractAddress(address exAccount, address contractAddress);

/*转以太坊给目标地址*/

function transferEther(address towho) payable {

towho.transfer(10);/*单位为wei*/

logSendEvent(towho, 10);

}

/*不指定调用函数,则调用无函数名的回调函数*/

function callNoFunc(address addr) returns (bool){

return addr.call(“tinyxiong”, 1234);

}

/*制定调用函数的方法*/

function callfunc(address addr) returns (bool){

bytes4 methodId = bytes4(keccak256(“setScore(uint256)”));

return addr.call(methodId, 100);

}

/*返回当前合约的以太坊余额*/

function getBalance() returns (uint) {

return this.balance;//0

}

/*销毁智能合约,把以太坊余额返回给当前外部账户*/

function ContractSuide() {

LogContractAddress(this,msg.sender);

suicide(msg.sender);

}

}

4

Contract Related

this (current contract’s type):表示当前合约,可以显式的转换为Address

selfdestruct(address recipient):destroy the current contract, sending its funds to the given Address

销毁当前合约,发送当前以太坊余额到给定的地址suicide(address recipient):

selfdestruct的别名函数

5

区块和交易属性

block.blockhash(uint blockNumber) returns (bytes32):

给定区块的哈希—仅对最近的 256 个区块有效而不包括当前区块

block.coinbase (address):

挖出当前区块的矿工地址

block.difficulty (uint):

当前区块难度

block.gaslimit (uint):

当前区块 gas 限额

block.number (uint):

当前区块号

block.timestamp (uint):

自 unix epoch 起始当前区块以秒计的时间戳

msg.data (bytes):

完整的 calldata

msg.gas (uint):

剩余 gas

msg.sender (address):

消息发送者(当前调用)

msg.sig (bytes4):

calldata 的前 4 字节(也就是函数标识符)

msg.value (uint):

随消息发送的 wei 的数量

now (uint):

目前区块时间戳(block.timestamp)

tx.gasprice (uint):

交易的 gas 价格

tx.origin (address):

交易发起者(完全的调用链)

注解对于每一个外部函数调用,包括 msg.sender 和 msg.value 在内所有 msg 成员的值都会变化。这里包括对库函数的调用。

注解不要依赖 block.timestamp、 now 和 block.blockhash 产生随机数,除非你知道自己在做什么。

时间戳和区块哈希在一定程度上都可能受到挖矿矿工影响。例如,挖矿社区中的恶意矿工可以用某个给定的哈希来运行赌场合约的 payout 函数,而如果他们没收到钱,还可以用一个不同的哈希重新尝试。当前区块的时间戳必须严格大于最后一个区块的时间戳,但这里唯一能确保的只是它会是在权威链上的两个连续区块的时间戳之间的数值。

注解基于可扩展因素,区块哈希不是对所有区块都有效。你仅仅可以访问最近 256 个区块的哈希,其余的哈希均为零。

6

错误处理

assert(bool condition):如果条件不满足就抛出—用于内部错误。

require(bool condition):如果条件不满足就抛掉—用于输入或者外部组件引起的错误。

revert():终止运行并恢复状态变动。

7

数学和密码学函数

addmod(uint x, uint y, uint k) returns (uint):计算 (x + y) % k,加法会在任意精度下执行,并且加法的结果即使超过 2**256 也不会被截取。从 0.5.0 版本的编译器开始会加入对 k != 0 的校验(assert)。

mulmod(uint x, uint y, uint k) returns (uint):计算 (x * y) % k,乘法会在任意精度下执行,并且乘法的结果即使超过 2**256 也不会被截取。从 0.5.0 版本的编译器开始会加入对 k != 0 的校验(assert)。

keccak256(…) returns (bytes32):计算 (tightly packed) arguments 的 Ethereum-SHA-3 (Keccak-256)哈希。

sha256(…) returns (bytes32):计算 (tightly packed) arguments 的 SHA-256 哈希。

sha3(…) returns (bytes32):等价于 keccak256。

ripemd160(…) returns (bytes20):计算 (tightly packed) arguments 的 RIPEMD-160 哈希。

ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address) :利用椭圆曲线签名恢复与公钥相关的地址,错误返回零值。(example usage:https://ethereum.stackexchange.com/q/1777/222)

上文中的“tightly packed”是指不会对参数值进行 padding 处理(就是说所有参数值的字节码是连续存放的,译者注),这意味着下边这些调用都是等价的:

keccak256(“ab”, “c”) keccak256(“abc”) keccak256(0x616263) keccak256(6382179) keccak256(97, 98, 99)

如果需要 padding,可以使用显式类型转换:keccak256(“x00x12”) 和 keccak256(uint16(0x12)) 是一样的。

请注意,常量值会使用存储它们所需要的最少字节数进行打包。例如:keccak256(0) == keccak256(uint8(0)),keccak256(0x12345678) == keccak256(uint32(0x12345678))。

在一个私链上,你很有可能碰到由于 sha256、ripemd160 或者 ecrecover 引起的 Out-of-Gas。这个原因就是他们被当做所谓的预编译合约而执行,并且在第一次收到消息后这些合约才真正存在(尽管合约代码是硬代码)。发送到不存在的合约的消息非常昂贵,所以实际的执行会导致 Out-of-Gas 错误。在你的合约中实际使用它们之前,给每个合约发送一点儿以太币,比如 1 Wei。这在官方网络或测试网络上不是问题。

8

solidity常见错误提示及原因分析

1、智能合约执行失败

告警描述: ” Warning! Error encountered during contract execution [Out of gas] “

发生场景:执行官网众筹智能合约代码,在给智能合约打代币前,往智能合约地址账户打ETH,交易失败,提示如下:

点击查看信息链接:https://ropsten.etherscan.io/tx/0x8b4da573b36dbf7361c95a0156dfe060553874fbb4d401f989c7a4a6d539ebfa

代码及原因分析:下面是执行的回调函数,其中“tokenReward.transfer(msg.sender, amount / price);”的意思就是把智能合约的代币打给发送者账号。这个账号还没有代币,所以肯定会执行失败。

function () payable {

require(!crowdsaleClosed);

uint amount = msg.value;

balanceOf[msg.sender] += amount;

amountRaised += amount;

tokenReward.transfer(msg.sender, amount / price);

FundTransfer(msg.sender, amount, true);

}

解决方法:先往智能合约账号打代币,然后打ETH,就不会执行失败了。

2、REMIX+MetaMASK的场景下,调用智能合约函数,提示不可知地址

告警描述:

REMIX输出框输出以下告警内容:transact to Crowdsale.checkGoalReached errored: Unknown address – unable to sign transaction for this address: “0x3d7dfb80e71096f2c4ee63c42c4d849f2cbbe363”

发生场景:更换了Meta账号后,调用之前账号创建的智能合约的函数

原因分析:Remix输出框提示未知地址:

区块链100讲:Solidity语法难点解析及故障排查

告警描述

查看MetaMask的当前账号,发现是Account 1的账号,所以无法执行合约。

区块链100讲:Solidity语法难点解析及故障排查MetaMask的当前账号

解决方法:

更换MetaMask为Account8(地址为0x3D7DfB80E71096F2c4Ee63C42C4D849F2CBBE363)的账号即可正常运行。

3、GETH安装时出现异常

告警描述:GETH安装时出现以下告警:

E: Failed to fetch http://101.110.118.22/ppa.launchpad.net/ethereum/ethereum/ubuntu/pool/main/e/ethereum/ethereum_1.8.10+build13740+artful_i386.deb Could not connect to 101.110.118.22:80 (101.110.118.22), connection timed out

E: Unable to fetch some archives, maybe run apt-get update or try with –fix-missing?

发生场景:

GETH安装,执行以下命令出现。

sudo apt-get install ethereum

原因分析:

解决方法:

<1> 参考网上解决方案sudo vim /etc/resolv.conf ,添加:

nameserver 8.8.8.8

然后执行:

sudo /etc/init.d/networking restart

还是不行。

<2> 从绿地金融中心搬到家里,做相同的操作,就可以安装成功了。应该是绿地金融中心的路由器做了某些配置,导致下载不成功。

4、问题描述模板

告警描述:发生场景:原因分析:解决方法:

9

常见问题及解答

1).modifer函数是干什么的?

2).如何打币回支付账号?

3).智能合约的定时器和系统函数是什么?

4).当创建一个智能合约时,msg.sender和this的区别?

答复:msg.sender是指外部账户的地址,this是指当前创建的智能合约的地址。

contract AddrTest{

event LogContractAddress(address exAccount, address contractAddress);

function AddrTest(){

LogContractAddress(msg.sender,this);

}

}

在remix运行,可以证明这个推测

区块链100讲:Solidity语法难点解析及故障排查

LOG说明

10

参考资料

1,官方中文网站 http://solidity-cn.readthedocs.io/zh/develop/

2, tiny熊翻译系列(https://blog.csdn.net/xilibi2003)

1] Solidity教程序列1 – 类型介绍2] 智能合约语言Solidity教程系列2 – 地址类型介绍3] 智能合约语言 Solidity 教程系列3 – 函数类型4] 智能合约语言 Solidity 教程系列4 – 数据存储位置分析5] 智能合约语言 Solidity 教程系列5 – 数组介绍6] 智能合约语言 Solidity 教程系列6 – 结构体与映射7] 智能合约语言 Solidity 教程系列7 – 以太单位及时间单位8] 智能合约语言 Solidity 教程系列8 – Solidity API9] 智能合约语言 Solidity 教程系列9 – 错误处理10] 智能合约语言 Solidity 教程系列10 – 完全理解函数修改器

— 线下活动推荐–

技术工坊|理性思考区块链,数字货币与区块链的辩证对视(上海)

技术工坊|如何利用ERC875协议开发世界杯区块链门票?(北京)

— 线上课程推荐–

区块链课堂|猜想区块链数字货币交易所的未来进化(线上免费)

始发于微信公众号: 区块链社区HiBlock