Skip to content

Commit 1ad1244

Browse files
committed
first commit
0 parents  commit 1ad1244

File tree

10 files changed

+249
-0
lines changed

10 files changed

+249
-0
lines changed

.github/workflows/test.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
pull_request:
6+
workflow_dispatch:
7+
8+
env:
9+
FOUNDRY_PROFILE: ci
10+
11+
jobs:
12+
check:
13+
name: Foundry project
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
with:
18+
submodules: recursive
19+
20+
- name: Install Foundry
21+
uses: foundry-rs/foundry-toolchain@v1
22+
23+
- name: Show Forge version
24+
run: |
25+
forge --version
26+
27+
- name: Run Forge fmt
28+
run: |
29+
forge fmt --check
30+
id: fmt
31+
32+
- name: Run Forge build
33+
run: |
34+
forge build --sizes
35+
id: build
36+
37+
- name: Run Forge tests
38+
run: |
39+
forge test -vvv
40+
id: test

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Compiler files
2+
cache/
3+
out/
4+
5+
# Ignores development broadcast logs
6+
!/broadcast
7+
/broadcast/*/31337/
8+
/broadcast/**/dry-run/
9+
10+
# Docs
11+
docs/
12+
13+
# Dotenv file
14+
.env

.gitmodules

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[submodule "lib/forge-std"]
2+
path = lib/forge-std
3+
url = https://github.com/foundry-rs/forge-std
4+
[submodule "lib/openzeppelin-contracts"]
5+
path = lib/openzeppelin-contracts
6+
url = https://github.com/openzeppelin/openzeppelin-contracts

README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
## Foundry
2+
3+
**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**
4+
5+
Foundry consists of:
6+
7+
- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
8+
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
9+
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
10+
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.
11+
12+
## Documentation
13+
14+
https://book.getfoundry.sh/
15+
16+
## Usage
17+
18+
### Build
19+
20+
```shell
21+
$ forge build
22+
```
23+
24+
### Test
25+
26+
```shell
27+
$ forge test
28+
```
29+
30+
### Format
31+
32+
```shell
33+
$ forge fmt
34+
```
35+
36+
### Gas Snapshots
37+
38+
```shell
39+
$ forge snapshot
40+
```
41+
42+
### Anvil
43+
44+
```shell
45+
$ anvil
46+
```
47+
48+
### Deploy
49+
50+
```shell
51+
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
52+
```
53+
54+
### Cast
55+
56+
```shell
57+
$ cast <subcommand>
58+
```
59+
60+
### Help
61+
62+
```shell
63+
$ forge --help
64+
$ anvil --help
65+
$ cast --help
66+
```

foundry.lock

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"lib\\openzeppelin-contracts": {
3+
"tag": {
4+
"name": "v5.4.0",
5+
"rev": "c64a1edb67b6e3f4a15cca8909c9482ad33a02b0"
6+
}
7+
},
8+
"lib\\forge-std": {
9+
"tag": {
10+
"name": "v1.10.0",
11+
"rev": "8bbcf6e3f8f62f419e5429a0bd89331c85c37824"
12+
}
13+
}
14+
}

foundry.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[profile.default]
2+
src = "src"
3+
out = "out"
4+
libs = ["lib"]
5+
remappings=[
6+
'@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/'
7+
]
8+
9+
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options

lib/forge-std

Submodule forge-std added at c7be2a3

lib/openzeppelin-contracts

Submodule openzeppelin-contracts added at c64a1ed

src/BagelTokensol

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//SPDX-License-identifier:MIT
2+
3+
pragma solidity ^0.8.24;
4+
5+
import {ERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6+
import {OWnable} from "@openzeppelin/contracts/access/Ownable.sol";
7+
8+
contract BagelToken is IERC20, Ownable {
9+
constructor()ERC20("BBagelToken","BT")Ownable(msg.sender){}
10+
function mint(addrress account,uint256 amount)external onlyOwner {
11+
_mint(account, amount);
12+
}
13+
}

src/MerkleAirdrop.sol

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.24;
4+
5+
import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
6+
import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
7+
import {ERC712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
8+
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
9+
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
10+
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
11+
12+
contract MerkleAirdrop is EIP712 {
13+
using ECDSA for bytes32;
14+
using SafeERC20 for IERC20;
15+
16+
error MerkleAirdrop__InvalidProof();
17+
error MerkleAirdrop__InvalidSignature();
18+
error MerkleAirdrop__AlreadyClaimed();
19+
20+
IERC20 public immutable i_airdropToken;
21+
bytes32 private immutable i_merkleRoot;
22+
mapping(address => bool) private s_hasClaimed;
23+
bytes32 private immutable MESSAGE_TYPEHASH = keccak256("Airdrop(address account,uint256 amount)");
24+
25+
struct AirdropClaim {
26+
address account;
27+
uint256 amount;
28+
}
29+
30+
event Claimed(address account, uint256 amount);
31+
event MerkleRootUpdated(bytes32 newMerkleRoot);
32+
33+
constructor(bytes32 merkleRoot, IERC20 airdropToken) EIP712("MerkleAirdrop", "1.0.0") {
34+
i_merkleRoot = merkleRoot;
35+
i_airdropToken = airdropToken;
36+
}
37+
38+
function Claim(address account, uint256 amount, bytes32[] calldata merkleProof, uint8 v, bytes32 r, bytes32 s)
39+
external
40+
{
41+
if (s_hasClaimed[account]) {
42+
revert MerkleAirdrop__AlreadyClaimed();
43+
}
44+
if (!_isValidSignature(account, getMessageHash(account, amount), v, r, s)) {
45+
revert MerkleAirdrop__InvalidSignature();
46+
}
47+
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(MESSAGE_TYPEHASH, account, amount))));
48+
if (!MerkleProof.verify(merkleProof, i_merkleRoot, leaf)) {
49+
revert MerkleAirdrop__InvalidProof();
50+
}
51+
s_hasClaimed[account] = true;
52+
emit Claimed(account, amount);
53+
}
54+
55+
function getMessageHash(address account, uint256 amount) internal view returns (bytes32) {
56+
return
57+
_hashTypedDataV4(keccak256(abi.encode(MESSAGE_TYPEHASH, AirdropClaim({account: account, amount: amount}))));
58+
}
59+
60+
function getMerkleRoot() external view returns (bytes32) {
61+
return i_merkleRoot;
62+
}
63+
64+
function getAirdropToken() external view returns (IERC20) {
65+
return i_airdropToken;
66+
}
67+
68+
function hasClaimed(address account) external view returns (bool) {
69+
return s_hasClaimed[account];
70+
}
71+
72+
function _isValidSignature(address signer, bytes32 digest, uint8 _v, bytes32 _r, bytes32 _s)
73+
internal
74+
pure
75+
returns (bool)
76+
{
77+
(
78+
address actualSigner,
79+
/*ECDSA.RecoverError recoverError*/
80+
,
81+
/*bytes32 SignatureLength*/
82+
) = ECDSA.tryRecover(digest, _v, _r, _s);
83+
return actualSigner == signer;
84+
}
85+
}

0 commit comments

Comments
 (0)