Skip to content

Commit bd95dbd

Browse files
add challenges
1 parent 647a056 commit bd95dbd

20 files changed

+766
-94
lines changed

.env.example

-6
This file was deleted.

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@ hardhat-dependency-compiler/
2525
coverage
2626
coverage.json
2727

28-
hints.md
28+
hints.md
29+
answer/

README.md

+28-30
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,43 @@
1-
# Smart Contracts Vulns Hacking
1+
# Sol Challenge
22

3-
## Getting Started
3+
CTF-Like Challenges of smart contracts
44

5-
### Setup
5+
## Installing
66

7-
`yarn`
7+
### Prerequisites
88

9-
To run testing on the forking main net, set the APIKEY and mnemonic.
10-
you need access to an archive node like the free ones from [Alchemy](https://alchemyapi.io/).
11-
```
12-
ALCHEMY_API_KEY=
13-
MNEMONIC=
14-
BLOCK_NUMBER=
15-
FORK=false
16-
```
9+
- node
10+
- yarn
1711

18-
### Compiling
12+
## Setup
1913

20-
`yarn compile`
14+
Type on a terminal:
15+
16+
```bash
17+
yarn
18+
```
2119

22-
### Replaying hack
20+
### Compiling
2321

24-
The hacks are implemented as hardhat tests and can therefore be run as:
22+
Type:
2523

26-
`yarn hardhat test test/<name>.ts`
24+
```bash
25+
yarn compile
26+
```
2727

28-
### Debugging transactions with tenderly
28+
### Testing & Running Challenges
2929

30-
Set up `tenderly.yaml` in the repo root and follow [this article](http://blog.tenderly.co/level-up-your-smart-contract-productivity-using-hardhat-and-tenderly/).
30+
1. Copy this [template](./test/test.test.ts) to `./test/<*Challenge>.test.ts` file.
31+
2. Each `<*Challenge.sol>` file contains a description of the challenge.
32+
3. you solve challenges. write tests to prove the solution works correctly.
3133

32-
TLDR:
34+
Type:
3335

3436
```bash
35-
# run this in second terminal
36-
npx hardhat node
3737
# run test against local network
38-
npx hardhat test test/foo.js --network local
39-
# you want an actual tx id which means the tx may not fail in eth_estimateGas already
40-
# therefore hardcode some gas values
41-
# {value: ethers.utils.parseEther(`100`), gasLimit: `15000000`, gasPrice: ethers.utils.parseUnits(`200`, 9) }
42-
# the (failed) tx hash appears on the CLI after "eth_sendTransaction"
43-
# --force is required to skip gas estimation check in tenderly
44-
tenderly export <txHash> --force
45-
```
38+
yarn test test/<*Challenge>.test.ts
39+
```
40+
41+
### About this challenges
42+
43+
[Slide [ja]](https://docs.google.com/presentation/d/17FKtVC1S29WFnQjq92_SiqGIS6EGuslHsUfuNv-NlXU/edit?usp=sharing)

contracts/accounts/BankChallenge.sol

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
pragma solidity 0.8.10;
3+
4+
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5+
import "@openzeppelin/contracts/utils/Address.sol";
6+
import "./Batchable.sol";
7+
8+
/// @notice Challenge Description
9+
/// BankCoin is permission-less token. Drain the balance of deposited ETH.
10+
contract Bank is ERC20("BankCoin", "BANK"), Batchable {
11+
function deposit() external payable {
12+
_mint(msg.sender, msg.value);
13+
}
14+
15+
function depositTo(address account) external payable {
16+
_mint(account, msg.value);
17+
}
18+
19+
function withdraw(uint256 amount) external {
20+
_burn(msg.sender, amount);
21+
Address.sendValue(payable(msg.sender), amount);
22+
}
23+
24+
function withdrawFrom(address account, uint256 amount) external {
25+
uint256 currentAllowance = allowance(account, msg.sender);
26+
require(
27+
currentAllowance >= amount,
28+
"ERC20: withdraw amount exceeds allowance"
29+
);
30+
unchecked {
31+
_approve(account, msg.sender, currentAllowance - amount);
32+
}
33+
_burn(account, amount);
34+
Address.sendValue(payable(msg.sender), amount);
35+
}
36+
}
37+
38+
contract BankChallenge {
39+
Bank public bank;
40+
41+
constructor() payable {
42+
require(msg.value >= 1 ether, "rule: send some ether");
43+
44+
bank = new Bank();
45+
bank.deposit{ value: msg.value }();
46+
}
47+
48+
function isSolved() public view returns (bool) {
49+
return address(bank).balance == 0;
50+
}
51+
}

contracts/accounts/Batchable.sol

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.10;
3+
4+
/// @dev The batch function works by making multiple DELEGATECALLs to itself.
5+
/// @notice reference: sushiswap Miso https://github.com/sushiswap/miso/blob/44b51769b614826c50d8f5c55cb60094fa1c7bad/contracts/Utils/BoringBatchable.sol
6+
abstract contract Batchable {
7+
/// @dev Helper function to extract a useful revert message from a failed call.
8+
/// If the returned data is malformed or not correctly abi encoded then this call can fail itself.
9+
function _getRevertMsg(bytes memory _returnData)
10+
internal
11+
pure
12+
returns (string memory)
13+
{
14+
// If the _res length is less than 68, then the transaction failed silently (without a revert message)
15+
if (_returnData.length < 68) return "Transaction reverted silently";
16+
17+
assembly {
18+
// Slice the sighash.
19+
_returnData := add(_returnData, 0x04)
20+
}
21+
return abi.decode(_returnData, (string)); // All that remains is the revert string
22+
}
23+
24+
/// @notice Allows batched call to self (this contract).
25+
/// @param calls An array of inputs for each call.
26+
/// @param revertOnFail If True then reverts after a failed call and stops doing further calls.
27+
/// @return successes An array indicating the success of a call, mapped one-to-one to `calls`.
28+
/// @return results An array with the returned data of each function call, mapped one-to-one to `calls`.
29+
function batch(bytes[] calldata calls, bool revertOnFail)
30+
external
31+
payable
32+
returns (bool[] memory successes, bytes[] memory results)
33+
{
34+
successes = new bool[](calls.length);
35+
results = new bytes[](calls.length);
36+
for (uint256 i = 0; i < calls.length; i++) {
37+
(bool success, bytes memory result) = address(this).delegatecall(
38+
calls[i]
39+
);
40+
require(success || !revertOnFail, _getRevertMsg(result));
41+
successes[i] = success;
42+
results[i] = result;
43+
}
44+
}
45+
}
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// SPDX-License-Identifier:GPL-3.0-or-later
2+
pragma solidity 0.8.10;
3+
4+
/// @notice Challenge Description
5+
/// This vault allows anyone to withdraw deposited ether freely.
6+
/// You can get money for free
7+
contract OpenVaultChallenge {
8+
bool public isSolved;
9+
10+
constructor() payable {
11+
require(msg.value >= 1, "rule: send at least one wei");
12+
}
13+
14+
function withdraw() external {
15+
require(
16+
tx.origin != msg.sender,
17+
"tx.origin is not equal to msg.sender"
18+
);
19+
require(!isContract(msg.sender), "invalid sender");
20+
21+
payable(msg.sender).transfer(address(this).balance);
22+
23+
isSolved = true;
24+
}
25+
26+
function isContract(address _account) public view returns (bool) {
27+
return _account.code.length > 0;
28+
}
29+
}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// SPDX-License-Identifier:GPL-3.0-or-later
2+
pragma solidity 0.7.6;
3+
4+
/// @notice Challenge Description
5+
/// Make `isSolved` true
6+
contract WeirdVaultChallenge {
7+
bool public isSolved;
8+
9+
function complete() external {
10+
require(address(this).balance != 0, "balance zero");
11+
payable(msg.sender).transfer(address(this).balance);
12+
13+
isSolved = true;
14+
}
15+
}
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// SPDX-License-Identifier:GPL-3.0-or-later
2+
pragma solidity 0.7.6;
3+
4+
/// @notice Challenge Description
5+
/// Guess two numbers which satisfy conditions.
6+
contract GuessTheNumberChallenge {
7+
bool public isSolved;
8+
uint256 public MAX_UINT256 = uint256(-1);
9+
10+
function input(uint256 a, uint256 b) external {
11+
if (a == (b + 1000) && b > a) {
12+
isSolved = true;
13+
}
14+
}
15+
16+
function hint() external view returns (uint256) {
17+
return MAX_UINT256 + 1;
18+
}
19+
}

contracts/mocks/ERC20Mock.sol

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.0;
4+
5+
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
6+
7+
contract ERC20Mock is ERC20 {
8+
// solhint-disable-next-line no-empty-blocks
9+
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}
10+
11+
function mint(address account, uint256 amount) external {
12+
_mint(account, amount);
13+
}
14+
15+
function burn(address account, uint256 amount) external {
16+
_burn(account, amount);
17+
}
18+
}

contracts/mocks/WETHMock.sol

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.10;
3+
4+
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5+
6+
/// @title WETH9 Mock: https://etherscan.io/address/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
7+
/// WETH9 does not emit `Transfer` events when deposit/withdraw methods are called. but this mock does
8+
contract WETHMock is ERC20("Wrapped ETH", "WETH") {
9+
event Deposit(address indexed dst, uint256 wad);
10+
event Withdrawal(address indexed src, uint256 wad);
11+
12+
/// @dev Original WETH9 implements `fallback` function instead of `receive` function due to a earlier solidity version
13+
fallback() external payable {
14+
deposit();
15+
}
16+
17+
function deposit() public payable {
18+
_mint(msg.sender, msg.value);
19+
emit Deposit(msg.sender, msg.value);
20+
}
21+
22+
function withdraw(uint256 wad) public {
23+
require(balanceOf(msg.sender) >= wad, "weth: insufficient balance");
24+
25+
_burn(msg.sender, wad);
26+
(bool success, ) = msg.sender.call{ value: wad }("");
27+
require(success, "weth: failed");
28+
29+
emit Withdrawal(msg.sender, wad);
30+
}
31+
}

contracts/tokens/NftSaleChallenge.sol

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
pragma solidity 0.8.10;
3+
4+
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
5+
6+
/// @notice Challenge Description
7+
/// You can mint awesome nfts. but the nft contract limits the number of nft you can buy at a time. Mint at least 100 nfts within one transaction.
8+
interface INft {
9+
function mint(uint256 numberOfNfts) external payable;
10+
11+
function getNFTPrice() external returns (uint256 price);
12+
}
13+
14+
contract Nft is INft, ERC721("Awesome NFT", "AWESOMENFT") {
15+
uint256 private constant MAX_NFT_SUPPLY = 1_000;
16+
uint256 private constant ONE_ETHER = 1e18;
17+
18+
uint256 public totalSupply;
19+
20+
address public owner;
21+
22+
address public pendingOwner;
23+
24+
function mint(uint256 numberOfNfts) public payable override {
25+
uint256 _totalSupply = totalSupply;
26+
27+
require(numberOfNfts > 0, "numberOfNfts cannot be 0");
28+
require(
29+
numberOfNfts <= 30,
30+
"You may not buy more than 30 NFTs at once"
31+
);
32+
require(
33+
_totalSupply + numberOfNfts <= MAX_NFT_SUPPLY,
34+
"Exceeds MAX_NFT_SUPPLY"
35+
);
36+
require(
37+
getNFTPrice() * numberOfNfts == msg.value,
38+
"Ether value sent is not correct"
39+
);
40+
41+
for (uint256 i; i < numberOfNfts; i++) {
42+
uint256 tokenId = totalSupply;
43+
_safeMint(msg.sender, tokenId);
44+
}
45+
}
46+
47+
function getNFTPrice() public view override returns (uint256 price) {
48+
price = ONE_ETHER / MAX_NFT_SUPPLY;
49+
}
50+
51+
// transfer ownership to a new address
52+
function transferOwnership(address newOwner) public {
53+
require(msg.sender == owner);
54+
55+
pendingOwner = newOwner;
56+
}
57+
58+
// accept the ownership transfer
59+
function acceptOwnership() public {
60+
require(msg.sender == pendingOwner);
61+
62+
owner = pendingOwner;
63+
pendingOwner = address(0);
64+
}
65+
66+
function _beforeTokenTransfer(
67+
address from,
68+
address to,
69+
uint256
70+
) internal override {
71+
if (from == address(0)) {
72+
totalSupply += 1;
73+
}
74+
if (to == address(0)) {
75+
totalSupply -= 1;
76+
}
77+
}
78+
}
79+
80+
contract NftSaleChallenge {
81+
Nft public immutable token;
82+
83+
constructor() {
84+
token = new Nft();
85+
}
86+
}

0 commit comments

Comments
 (0)