diff --git a/README.md b/README.md index f8d9b06..091ecf5 100644 --- a/README.md +++ b/README.md @@ -1,83 +1,103 @@ - - -# huff-project-template • [![ci](https://github.com/huff-language/huff-project-template/actions/workflows/ci.yaml/badge.svg)](https://github.com/huff-language/huff-project-template/actions/workflows/ci.yaml) ![license](https://img.shields.io/github/license/huff-language/huff-project-template.svg) ![solidity](https://img.shields.io/badge/solidity-^0.8.15-lightgrey) - -Versatile Huff Project Template using Foundry. - - ## Getting Started ### Requirements -The following will need to be installed in order to use this template. Please follow the links and instructions. +The following will need to be installed in order to use this repo. -- [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) - - You'll know you've done it right if you can run `git --version` - [Foundry / Foundryup](https://github.com/gakonst/foundry) - - This will install `forge`, `cast`, and `anvil` - - You can test you've installed them right by running `forge --version` and get an output like: `forge 0.2.0 (92f8951 2022-08-06T00:09:32.96582Z)` - - To get the latest of each, just run `foundryup` - [Huff Compiler](https://docs.huff.sh/get-started/installing/) - - You'll know you've done it right if you can run `huffc --version` and get an output like: `huffc 0.3.0` - -### Quickstart - -1. Clone this repo or use template - -Click "Use this template" on [GitHub](https://github.com/huff-language/huff-project-template) to create a new repository with this repo as the initial state. -Or run: +## Quickstart ``` -git clone https://github.com/huff-language/huff-project-template -cd huff-project-template -``` - -2. Install dependencies - -Once you've cloned and entered into your repository, you need to install the necessary dependencies. In order to do so, simply run: - -```shell forge install -``` - -3. Build & Test - -To build and test your contracts, you can run: - -```shell -forge build forge test ``` -For more information on how to use Foundry, check out the [Foundry Github Repository](https://github.com/foundry-rs/foundry/tree/master/forge) and the [foundry-huff library repository](https://github.com/huff-language/foundry-huff). +To run the benchmark script : +``` +FORK_URL=$ETH_RPC_URL make benchmark +``` +## Usage +Only the arguments encoding matter, the signature is not checked. +If you wish to deploy the contract, on a testnet you have to change the hard-coded deposit contract address in the contract. -## Blueprint +### HuffClassic interface : +``` +anySignature(bytes pubkeys, bytes withdrawal_creds, bytes signatures, bytes32[] deposit_data_roots) +``` +`pubkeys` : concatenation of the public keys of the validators +`withdrawal_creds` : concatenation of the withdrawal credentials of the validators +`signatures` : concatenation of the signatures of the validators +`deposit_data_roots` : concatenation of the deposit data roots of the validators -```ml -lib -├─ forge-std — https://github.com/foundry-rs/forge-std -├─ foundry-huff — https://github.com/huff-language/foundry-huff -scripts -├─ Deploy.s.sol — Deployment Script -src -├─ SimpleStore — A Simple Storage Contract in Huff -test -└─ SimpleStore.t — SimpleStoreTests +### HuffCompact interface : ``` +any_signature(bytes data) +``` +`data` : concatenation of the pubkeys, withdrawal_creds, signatures and deposit_data_roots of the validators +## Benchmark +Ran against a fork at block `18255674` +### Benchmark results HuffCompact : +``` +1 => 65605 +2 => 91302 +3 => 120344 +4 => 144104 +5 => 176922 +10 => 307011 +20 => 589729 +30 => 845282 +40 => 1118022 +50 => 1393065 +75 => 2050698 +100 => 2726240 +200 => 5411233 +``` -## License +### Benchmark results HuffClassic : +``` +1 => 66730 +2 => 92441 +3 => 121521 +4 => 145307 +5 => 178163 +10 => 308406 +20 => 591372 +30 => 847197 +40 => 1120173 +50 => 1395488 +75 => 2053783 +100 => 2729963 +200 => 5417544 +``` -[The Unlicense](https://github.com/huff-language/huff-project-template/blob/master/LICENSE) +### Benchmark results [Solidity](https//etherscan.io/address/0x9b8c989FF27e948F55B53Bb19B3cC1947852E394#code) : +``` +1 => 76074 +2 => 107877 +3 => 142933 +4 => 172835 +5 => 211715 +10 => 372342 +20 => 715792 +30 => 1032305 +40 => 1365669 +50 => 1701816 +75 => 2511743 +100 => 3338663 +200 => 6631612 +``` +HuffCompact is 18.4 % more efficient than the given solidity implementation. -## Acknowledgements +HuffClassic is 18.3 % more efficient than the given solidity implementation. -- [forge-template](https://github.com/foundry-rs/forge-template) -- [femplate](https://github.com/abigger87/femplate) +## License +TODO ## Disclaimer diff --git a/script/BenchmarkClassic.s.sol b/script/BenchmarkClassic.s.sol new file mode 100644 index 0000000..1c51123 --- /dev/null +++ b/script/BenchmarkClassic.s.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.15; + +import "forge-std/Script.sol"; +import "foundry-huff/HuffDeployer.sol"; + +interface IBatchDeposit { + function d( + bytes calldata pubkeys, + bytes calldata withdrawal_credentialss, + bytes calldata signatures, + bytes32[] calldata deposit_data_roots + ) external payable; +} + +contract BenchmarkClassic is Script { + address public huffCompact; + uint256 public runCount = 13; + function setUp() public { } + + function run() public { + + uint256[] memory validatorsCount = getValidatorsCount(); + + // Deploy BatchDeposit + address huff = HuffDeployer.broadcast("BatchDeposit"); + console.log("BatchDeposit: ", huff); + + { + for(uint256 i = 0; i < runCount; i++) { + uint256 count = validatorsCount[i]; + bytes memory publicKeys = publicKeys(count); + bytes memory withdrawalCredentials = withdrawalCredentials(count); + bytes memory signatures = signatures(count); + bytes32[] memory depositDataRoots = depositDataRoots(count); + + console.log("huff"); + vm.startBroadcast(); + IBatchDeposit(huff).d{value: 32 ether * count}(publicKeys, withdrawalCredentials, signatures, depositDataRoots); + vm.stopBroadcast(); + } + } + + console.log("Done!"); + } + + function getValidatorsCount() public view returns (uint256[] memory) { + uint256[] memory validatorsCount = new uint256[](runCount); + validatorsCount[0] = 1; + validatorsCount[1] = 2; + validatorsCount[2] = 3; + validatorsCount[3] = 4; + validatorsCount[4] = 5; + validatorsCount[5] = 10; + validatorsCount[6] = 20; + validatorsCount[7] = 30; + validatorsCount[8] = 40; + validatorsCount[9] = 50; + validatorsCount[10] = 75; + validatorsCount[11] = 100; + validatorsCount[12] = 200; + return validatorsCount; + } + + + function publicKeys(uint256 count) public pure returns (bytes memory) { + bytes memory publicKeys = new bytes(0); + for (uint256 i = 0; i < count; i++) { + publicKeys = bytes.concat(publicKeys, genPubkey()); + } + return publicKeys; + } + + function withdrawalCredentials(uint256 count) public pure returns (bytes memory) { + bytes memory withdrawalCredentials = new bytes(0); + for (uint256 i = 0; i < count; i++) { + withdrawalCredentials = bytes.concat(withdrawalCredentials, genWC()); + } + return withdrawalCredentials; + } + + function signatures(uint256 count) public pure returns (bytes memory) { + bytes memory signatures = new bytes(0); + for (uint256 i = 0; i < count; i++) { + signatures = bytes.concat(signatures, genSig()); + } + return signatures; + } + + function depositDataRoots(uint256 count) public pure returns (bytes32[] memory) { + bytes32[] memory depositDataRoots = new bytes32[](count); + for (uint256 i = 0; i < count; i++) { + depositDataRoots[i] = genBytes32(); + } + return depositDataRoots; + } + +} + +function genPubkey() pure returns (bytes memory) { + return hex'97eda78a0c1c3746d429451f194cce479b9aac7770eae790c638014087aceebcb4c68236e99e65f645fc1a436f059ab9'; +} + +function genWC() pure returns (bytes memory) { + return hex'01000000000000000000000034e9cb03516a70466d5c6d25c0f37cf622c43242'; +} + +function genSig() pure returns (bytes memory) { + return hex'b81b4423943820252a4a266a88eec5a87753191bd1838ffd3297dc44981368b186eae4f1fe0174b337c94064cf78f18f087785f60aa97db10e9e9d3826d1fc48baae3f9f94c7eb293354f56921a675db19f683ee12ec5508cdfb42805244d06f'; +} + +function genBytes32() pure returns (bytes32) { + return bytes32(hex'f1b0122e593763eb4fbe7b345f4fa9ec77f3096cbaec29bcec94b83b42d13579'); +} diff --git a/script/BenchmarkCompact.s.sol b/script/BenchmarkCompact.s.sol new file mode 100644 index 0000000..b50c6a4 --- /dev/null +++ b/script/BenchmarkCompact.s.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.15; + +import "forge-std/Script.sol"; +import "foundry-huff/HuffDeployer.sol"; + +interface IBatchDeposit { + function d( + bytes calldata pubkeys, + bytes calldata withdrawal_credentialss, + bytes calldata signatures, + bytes32[] calldata deposit_data_roots + ) external payable; + + function d(bytes calldata args) external payable; +} + +contract BenchmarkCompact is Script { + address public huffCompact; + uint256 public runCount = 13; + function setUp() public { } + + function run() public { + + uint256[] memory validatorsCount = getValidatorsCount(); + + // Deploy BatchDepositCompact + huffCompact = HuffDeployer.broadcast("BatchDepositCompact"); + console.log("BatchDepositCompact: ", huffCompact); + + { + for(uint256 i = 0; i < runCount; i++) { + uint256 count = validatorsCount[i]; + bytes memory args = makeArg(count); + + console.log("compact huff"); + vm.startBroadcast(); + IBatchDeposit(huffCompact).d{value: 32 ether * count}(args); + vm.stopBroadcast(); + } + } + + + console.log("Done!"); + } + + function getValidatorsCount() public view returns (uint256[] memory) { + uint256[] memory validatorsCount = new uint256[](runCount); + validatorsCount[0] = 1; + validatorsCount[1] = 2; + validatorsCount[2] = 3; + validatorsCount[3] = 4; + validatorsCount[4] = 5; + validatorsCount[5] = 10; + validatorsCount[6] = 20; + validatorsCount[7] = 30; + validatorsCount[8] = 40; + validatorsCount[9] = 50; + validatorsCount[10] = 75; + validatorsCount[11] = 100; + validatorsCount[12] = 200; + return validatorsCount; + } + + function makeArg(uint256 count) public pure returns (bytes memory args) { + args = new bytes(0); + for (uint256 i = 0; i < count; i++) { + args = bytes.concat(args, genPubkey()); + args = bytes.concat(args, genWC()); + args = bytes.concat(args, genSig()); + args = bytes.concat(args, genBytes32()); + } + } +} + +function genPubkey() pure returns (bytes memory) { + return hex'97eda78a0c1c3746d429451f194cce479b9aac7770eae790c638014087aceebcb4c68236e99e65f645fc1a436f059ab9'; +} + +function genWC() pure returns (bytes memory) { + return hex'01000000000000000000000034e9cb03516a70466d5c6d25c0f37cf622c43242'; +} + +function genSig() pure returns (bytes memory) { + return hex'b81b4423943820252a4a266a88eec5a87753191bd1838ffd3297dc44981368b186eae4f1fe0174b337c94064cf78f18f087785f60aa97db10e9e9d3826d1fc48baae3f9f94c7eb293354f56921a675db19f683ee12ec5508cdfb42805244d06f'; +} + +function genBytes32() pure returns (bytes32) { + return bytes32(hex'f1b0122e593763eb4fbe7b345f4fa9ec77f3096cbaec29bcec94b83b42d13579'); +} diff --git a/script/BenchmarkSolidity.s.sol b/script/BenchmarkSolidity.s.sol new file mode 100644 index 0000000..5028b1b --- /dev/null +++ b/script/BenchmarkSolidity.s.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.15; + +import "forge-std/Script.sol"; +import "foundry-huff/HuffDeployer.sol"; + +interface IBatchDeposit { + function batchDeposit( + bytes[] calldata pubkeys, + bytes[] calldata withdrawal_credentialss, + bytes[] calldata signatures, + bytes32[] calldata deposit_data_roots + ) external payable; +} + + +contract BenchmarkSolidity is Script { + address public huffCompact; + uint256 public runCount = 13; + function setUp() public { } + + function run() public { + + uint256[] memory validatorsCount = getValidatorsCount(); + + // Deploy BatchDeposit + address huff = HuffDeployer.broadcast("BatchDeposit"); + console.log("BatchDeposit: ", huff); + + { + for(uint256 i = 0; i < runCount; i++) { + uint256 count = validatorsCount[i]; + bytes[] memory publicKeys = publicKeys(count); + bytes[] memory withdrawalCredentials = withdrawalCredentials(count); + bytes[] memory signatures = signatures(count); + bytes32[] memory depositDataRoots = depositDataRoots(count); + + // Solidity Deposit + console.log("Solidity Deposit..."); + vm.startBroadcast(); + IBatchDeposit(0x9b8c989FF27e948F55B53Bb19B3cC1947852E394).batchDeposit{value: 32 ether * count}( + publicKeys, withdrawalCredentials, signatures, depositDataRoots + ); + vm.stopBroadcast(); + } + } + + console.log("Done!"); + } + + function getValidatorsCount() public view returns (uint256[] memory) { + uint256[] memory validatorsCount = new uint256[](runCount); + validatorsCount[0] = 1; + validatorsCount[1] = 2; + validatorsCount[2] = 3; + validatorsCount[3] = 4; + validatorsCount[4] = 5; + validatorsCount[5] = 10; + validatorsCount[6] = 20; + validatorsCount[7] = 30; + validatorsCount[8] = 40; + validatorsCount[9] = 50; + validatorsCount[10] = 75; + validatorsCount[11] = 100; + validatorsCount[12] = 200; + return validatorsCount; + } + + + function publicKeys(uint256 count) public pure returns (bytes[] memory) { + bytes[] memory publicKeys = new bytes[](count); + for (uint256 i = 0; i < count; i++) { + publicKeys[i] = genPubkey(); + } + return publicKeys; + } + + function withdrawalCredentials(uint256 count) public pure returns (bytes[] memory) { + bytes[] memory withdrawalCredentials = new bytes[](count); + for (uint256 i = 0; i < count; i++) { + withdrawalCredentials[i] = genWC(); + } + return withdrawalCredentials; + } + + function signatures(uint256 count) public pure returns (bytes[] memory) { + bytes[] memory signatures = new bytes[](count); + for (uint256 i = 0; i < count; i++) { + signatures[i] = genSig(); + } + return signatures; + } + + function depositDataRoots(uint256 count) public pure returns (bytes32[] memory) { + bytes32[] memory depositDataRoots = new bytes32[](count); + for (uint256 i = 0; i < count; i++) { + depositDataRoots[i] = genBytes32(); + } + return depositDataRoots; + } + +} + +function genPubkey() pure returns (bytes memory) { + return hex'97eda78a0c1c3746d429451f194cce479b9aac7770eae790c638014087aceebcb4c68236e99e65f645fc1a436f059ab9'; +} + +function genWC() pure returns (bytes memory) { + return hex'01000000000000000000000034e9cb03516a70466d5c6d25c0f37cf622c43242'; +} + +function genSig() pure returns (bytes memory) { + return hex'b81b4423943820252a4a266a88eec5a87753191bd1838ffd3297dc44981368b186eae4f1fe0174b337c94064cf78f18f087785f60aa97db10e9e9d3826d1fc48baae3f9f94c7eb293354f56921a675db19f683ee12ec5508cdfb42805244d06f'; +} + +function genBytes32() pure returns (bytes32) { + return bytes32(hex'f1b0122e593763eb4fbe7b345f4fa9ec77f3096cbaec29bcec94b83b42d13579'); +} diff --git a/script/benchmark.sh b/script/benchmark.sh new file mode 100644 index 0000000..ccd5115 --- /dev/null +++ b/script/benchmark.sh @@ -0,0 +1,239 @@ +#!/bin/bash + +# Set FORK_URL environment variable before running this script. + +# Run the sequence of commands: +# 1. Start Anvil server +# 2. Wait for Anvil server to start +# 3. Run forge benchmark script +# 4. Echo results +# 5. End Anvil server +run_benchmark() { + # BatchDepositCompact + # Run anvil + anvil -m 'test test test test test test test test test test test junk' --balance 100000 --fork-url ${FORK_URL} --fork-block-number 18255674 > anvil.log 2>&1 & + ANVIL_PID=$! + + # Wait for anvil to start + echo "Waiting for Anvil server to start" + while ! nc -z localhost 8545; do + sleep 1 + echo -n . + done + echo + + # Run benchmark + forge script BenchmarkCompact --rpc-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --broadcast --skip-simulation --slow + + printf "Last block-number: " + cast block-number --rpc-url http://localhost:8545 + + # Echo results + echo "" + echo "Benchmark results Compact :" + echo "==================" + echo "" + + printf "1 => " + cast bl 18255676 --field gasUsed --rpc-url http://localhost:8545 + + printf "2 => " + cast bl 18255677 --field gasUsed --rpc-url http://localhost:8545 + + printf "3 => " + cast bl 18255678 --field gasUsed --rpc-url http://localhost:8545 + + printf "4 => " + cast bl 18255679 --field gasUsed --rpc-url http://localhost:8545 + + printf "5 => " + cast bl 18255680 --field gasUsed --rpc-url http://localhost:8545 + + printf "10 => " + cast bl 18255681 --field gasUsed --rpc-url http://localhost:8545 + + printf "20 => " + cast bl 18255682 --field gasUsed --rpc-url http://localhost:8545 + + printf "30 => " + cast bl 18255683 --field gasUsed --rpc-url http://localhost:8545 + + printf "40 => " + cast bl 18255684 --field gasUsed --rpc-url http://localhost:8545 + + printf "50 => " + cast bl 18255685 --field gasUsed --rpc-url http://localhost:8545 + + printf "75 => " + cast bl 18255686 --field gasUsed --rpc-url http://localhost:8545 + + printf "100 => " + cast bl 18255687 --field gasUsed --rpc-url http://localhost:8545 + + printf "200 => " + cast bl 18255688 --field gasUsed --rpc-url http://localhost:8545 + + # End benchmark + if [ "${PRINT_LOGS}" = "true" ]; then + echo "Anvil output:" + cat anvil.log + fi + echo "Shutting down Anvil server..." + kill $ANVIL_PID + rm -f anvil.log + sleep 3 + + ##################### + # BatchDepositClassic + # Run anvil + anvil -m 'test test test test test test test test test test test junk' --balance 100000 --fork-url ${FORK_URL} --fork-block-number 18255674 > anvil.log 2>&1 & + ANVIL_PID=$! + + # Wait for anvil to start + echo "Waiting for Anvil server to start" + while ! nc -z localhost 8545; do + sleep 1 + echo -n . + done + echo + + # Run benchmark + forge script BenchmarkClassic --rpc-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --broadcast --skip-simulation --slow + + printf "Last block-number: " + cast block-number --rpc-url http://localhost:8545 + + # Echo results + echo "" + echo "Benchmark results Classic :" + echo "==================" + echo "" + + printf "1 => " + cast bl 18255676 --field gasUsed --rpc-url http://localhost:8545 + + printf "2 => " + cast bl 18255677 --field gasUsed --rpc-url http://localhost:8545 + + printf "3 => " + cast bl 18255678 --field gasUsed --rpc-url http://localhost:8545 + + printf "4 => " + cast bl 18255679 --field gasUsed --rpc-url http://localhost:8545 + + printf "5 => " + cast bl 18255680 --field gasUsed --rpc-url http://localhost:8545 + + printf "10 => " + cast bl 18255681 --field gasUsed --rpc-url http://localhost:8545 + + printf "20 => " + cast bl 18255682 --field gasUsed --rpc-url http://localhost:8545 + + printf "30 => " + cast bl 18255683 --field gasUsed --rpc-url http://localhost:8545 + + printf "40 => " + cast bl 18255684 --field gasUsed --rpc-url http://localhost:8545 + + printf "50 => " + cast bl 18255685 --field gasUsed --rpc-url http://localhost:8545 + + printf "75 => " + cast bl 18255686 --field gasUsed --rpc-url http://localhost:8545 + + printf "100 => " + cast bl 18255687 --field gasUsed --rpc-url http://localhost:8545 + + printf "200 => " + cast bl 18255688 --field gasUsed --rpc-url http://localhost:8545 + + # End benchmark + if [ "${PRINT_LOGS}" = "true" ]; then + echo "Anvil output:" + cat anvil.log + fi + echo "Shutting down Anvil server..." + kill $ANVIL_PID + rm -f anvil.log + sleep 3 + + + ##################### + # Solidity + # Run anvil + anvil -m 'test test test test test test test test test test test junk' --balance 100000 --fork-url ${FORK_URL} --fork-block-number 18255674 > anvil.log 2>&1 & + ANVIL_PID=$! + + # Wait for anvil to start + echo "Waiting for Anvil server to start" + while ! nc -z localhost 8545; do + sleep 1 + echo -n . + done + echo + + # Run benchmark + forge script BenchmarkSolidity --rpc-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --broadcast --skip-simulation --slow + + printf "Last block-number: " + cast block-number --rpc-url http://localhost:8545 + + # Echo results + echo "" + echo "Benchmark results solidity :" + echo "==================" + echo "" + + printf "1 => " + cast bl 18255676 --field gasUsed --rpc-url http://localhost:8545 + + printf "2 => " + cast bl 18255677 --field gasUsed --rpc-url http://localhost:8545 + + printf "3 => " + cast bl 18255678 --field gasUsed --rpc-url http://localhost:8545 + + printf "4 => " + cast bl 18255679 --field gasUsed --rpc-url http://localhost:8545 + + printf "5 => " + cast bl 18255680 --field gasUsed --rpc-url http://localhost:8545 + + printf "10 => " + cast bl 18255681 --field gasUsed --rpc-url http://localhost:8545 + + printf "20 => " + cast bl 18255682 --field gasUsed --rpc-url http://localhost:8545 + + printf "30 => " + cast bl 18255683 --field gasUsed --rpc-url http://localhost:8545 + + printf "40 => " + cast bl 18255684 --field gasUsed --rpc-url http://localhost:8545 + + printf "50 => " + cast bl 18255685 --field gasUsed --rpc-url http://localhost:8545 + + printf "75 => " + cast bl 18255686 --field gasUsed --rpc-url http://localhost:8545 + + printf "100 => " + cast bl 18255687 --field gasUsed --rpc-url http://localhost:8545 + + printf "200 => " + cast bl 18255688 --field gasUsed --rpc-url http://localhost:8545 + + # End benchmark + if [ "${PRINT_LOGS}" = "true" ]; then + echo "Anvil output:" + cat anvil.log + fi + echo "Shutting down Anvil server..." + kill $ANVIL_PID + rm -f anvil.log + sleep 3 + +} + +run_benchmark \ No newline at end of file