Skip to content

Commit

Permalink
scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
panditdhamdhere committed Sep 9, 2024
1 parent 7c8e48d commit 324d987
Show file tree
Hide file tree
Showing 9 changed files with 273 additions and 63 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
[submodule "lib/murky"]
path = lib/murky
url = https://github.com/dmfxyz/murky
69 changes: 6 additions & 63 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,66 +1,9 @@
## Foundry
1. GenerateInput: Create the input JSON file to create Merkle Tree with,

**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**
- the eligible addresses
- corresponding amount they can receive

Foundry consists of:
2. MakeMerkle: This will Create Merkle Tree using the input file generated by generateInput.

- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.

## Documentation

https://book.getfoundry.sh/

## Usage

### Build

```shell
$ forge build
```

### Test

```shell
$ forge test
```

### Format

```shell
$ forge fmt
```

### Gas Snapshots

```shell
$ forge snapshot
```

### Anvil

```shell
$ anvil
```

### Deploy

```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
```

### Cast

```shell
$ cast <subcommand>
```

### Help

```shell
$ forge --help
$ anvil --help
$ cast --help
```
- create the hashes, the intermediate hashes and the root hash,
- create the output files that contains all of leaf hashes, the proofs for each leaf node and the root hash.
1 change: 1 addition & 0 deletions lib/murky
Submodule murky added at 5feccd
78 changes: 78 additions & 0 deletions script/GenerateInput.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Script} from "forge-std/Script.sol";
import {stdJson} from "forge-std/StdJson.sol";
import {console} from "forge-std/console.sol";

// Merkle tree input file generator script
contract GenerateInput is Script {
uint256 private constant AMOUNT = 25 * 1e18;
string[] types = new string[](2);
uint256 count;
string[] whitelist = new string[](4);
string private constant INPUT_PATH = "/script/target/input.json";

function run() public {
types[0] = "address";
types[1] = "uint";
whitelist[0] = "0x6CA6d1e2D5347Bfab1d91e883F1915560e09129D";
whitelist[1] = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266";
whitelist[2] = "0x2ea3970Ed82D5b30be821FAAD4a731D35964F7dd";
whitelist[3] = "0xf6dBa02C01AF48Cf926579F77C9f874Ca640D91D";
count = whitelist.length;
string memory input = _createJSON();
// write to the output file the stringified output json tree dumpus
vm.writeFile(string.concat(vm.projectRoot(), INPUT_PATH), input);

console.log("DONE: The output is found at %s", INPUT_PATH);
}

function _createJSON() internal view returns (string memory) {
string memory countString = vm.toString(count); // convert count to string
string memory amountString = vm.toString(AMOUNT); // convert amount to string
string memory json = string.concat(
'{ "types": ["address", "uint"], "count":',
countString,
',"values": {'
);
for (uint256 i = 0; i < whitelist.length; i++) {
if (i == whitelist.length - 1) {
json = string.concat(
json,
'"',
vm.toString(i),
'"',
': { "0":',
'"',
whitelist[i],
'"',
', "1":',
'"',
amountString,
'"',
" }"
);
} else {
json = string.concat(
json,
'"',
vm.toString(i),
'"',
': { "0":',
'"',
whitelist[i],
'"',
', "1":',
'"',
amountString,
'"',
" },"
);
}
}
json = string.concat(json, "} }");

return json;
}
}
144 changes: 144 additions & 0 deletions script/MakeMerkle.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Script} from "forge-std/Script.sol";
import {stdJson} from "forge-std/StdJson.sol";
import {console} from "forge-std/console.sol";
import {Merkle} from "murky/src/Merkle.sol";
import {ScriptHelper} from "murky/script/common/ScriptHelper.sol";

// Merkle proof generator script
// To use:
// 1. Run `forge script script/GenerateInput.s.sol` to generate the input file
// 2. Run `forge script script/Merkle.s.sol`
// 3. The output file will be generated in /script/target/output.json

/**
* @title MakeMerkle
* @author Ciara Nightingale
* @author Cyfrin
*
* Original Work by:
* @author kootsZhin
* @notice https://github.com/dmfxyz/murky
*/

contract MakeMerkle is Script, ScriptHelper {
using stdJson for string; // enables us to use the json cheatcodes for strings

Merkle private m = new Merkle(); // instance of the merkle contract from Murky to do shit

string private inputPath = "/script/target/input.json";
string private outputPath = "/script/target/output.json";

string private elements =
vm.readFile(string.concat(vm.projectRoot(), inputPath)); // get the absolute path
string[] private types = elements.readStringArray(".types"); // gets the merkle tree leaf types from json using forge standard lib cheatcode
uint256 private count = elements.readUint(".count"); // get the number of leaf nodes

// make three arrays the same size as the number of leaf nodes
bytes32[] private leafs = new bytes32[](count);

string[] private inputs = new string[](count);
string[] private outputs = new string[](count);

string private output;

/// @dev Returns the JSON path of the input file
// output file output ".values.some-address.some-amount"
function getValuesByIndex(
uint256 i,
uint256 j
) internal pure returns (string memory) {
return string.concat(".values.", vm.toString(i), ".", vm.toString(j));
}

/// @dev Generate the JSON entries for the output file
function generateJsonEntries(
string memory _inputs,
string memory _proof,
string memory _root,
string memory _leaf
) internal pure returns (string memory) {
string memory result = string.concat(
"{",
'"inputs":',
_inputs,
",",
'"proof":',
_proof,
",",
'"root":"',
_root,
'",',
'"leaf":"',
_leaf,
'"',
"}"
);

return result;
}

/// @dev Read the input file and generate the Merkle proof, then write the output file
function run() public {
console.log("Generating Merkle Proof for %s", inputPath);

for (uint256 i = 0; i < count; ++i) {
string[] memory input = new string[](types.length); // stringified data (address and string both as strings)
bytes32[] memory data = new bytes32[](types.length); // actual data as a bytes32

for (uint256 j = 0; j < types.length; ++j) {
if (compareStrings(types[j], "address")) {
address value = elements.readAddress(
getValuesByIndex(i, j)
);
// you can't immediately cast straight to 32 bytes as an address is 20 bytes so first cast to uint160 (20 bytes) cast up to uint256 which is 32 bytes and finally to bytes32
data[j] = bytes32(uint256(uint160(value)));
input[j] = vm.toString(value);
} else if (compareStrings(types[j], "uint")) {
uint256 value = vm.parseUint(
elements.readString(getValuesByIndex(i, j))
);
data[j] = bytes32(value);
input[j] = vm.toString(value);
}
}
// Create the hash for the merkle tree leaf node
// abi encode the data array (each element is a bytes32 representation for the address and the amount)
// Helper from Murky (ltrim64) Returns the bytes with the first 64 bytes removed
// ltrim64 removes the offset and length from the encoded bytes. There is an offset because the array
// is declared in memory
// hash the encoded address and amount
// bytes.concat turns from bytes32 to bytes
// hash again because preimage attack
leafs[i] = keccak256(
bytes.concat(keccak256(ltrim64(abi.encode(data))))
);
// Converts a string array into a JSON array string.
// store the corresponding values/inputs for each leaf node
inputs[i] = stringArrayToString(input);
}

for (uint256 i = 0; i < count; ++i) {
// get proof gets the nodes needed for the proof & strigify (from helper lib)
string memory proof = bytes32ArrayToString(m.getProof(leafs, i));
// get the root hash and stringify
string memory root = vm.toString(m.getRoot(leafs));
// get the specific leaf working on
string memory leaf = vm.toString(leafs[i]);
// get the singified input (address, amount)
string memory input = inputs[i];

// generate the Json output file (tree dump)
outputs[i] = generateJsonEntries(input, proof, root, leaf);
}

// stringify the array of strings to a single string
output = stringArrayToArrayString(outputs);
// write to the output file the stringified output json tree dumpus
vm.writeFile(string.concat(vm.projectRoot(), outputPath), output);

console.log("DONE: The output is found at %s", outputPath);
}
}
25 changes: 25 additions & 0 deletions script/target/input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"types": [
"address",
"uint"
],
"count": 4,
"values": {
"0": {
"0": "0x6CA6d1e2D5347Bfab1d91e883F1915560e09129D",
"1": "25000000000000000000"
},
"1": {
"0": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"1": "25000000000000000000"
},
"2": {
"0": "0x2ea3970Ed82D5b30be821FAAD4a731D35964F7dd",
"1": "25000000000000000000"
},
"3": {
"0": "0xf6dBa02C01AF48Cf926579F77C9f874Ca640D91D",
"1": "25000000000000000000"
}
}
}
Empty file added script/target/output.json
Empty file.
8 changes: 8 additions & 0 deletions src/MerkleAirdrop.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,12 @@ contract MerkleAirdrop {
emit Claim(account, amount);
i_airdropToken.safeTransfer(account, amount);
}

function getMerkleRoot() external view returns (bytes32) {
return i_merkleRoot;
}

function getAirdropToken() external view returns (IERC20) {
return i_airdropToken;
}
}
8 changes: 8 additions & 0 deletions test/MerkleAirdropTest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Test} from "forge-std/Test.sol"

contract MerkleAirdropTest is Test {

}

0 comments on commit 324d987

Please sign in to comment.