Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/format-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Format Check

on:
pull_request:
paths:
- '**.sol'

jobs:
check:
name: Check Code Formatting
runs-on: ubuntu-latest

steps:
- name: 📥 Checkout code
uses: actions/checkout@v4

- name: 🔧 Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: 🔍 Check formatting
run: |
if ! forge fmt --check; then
echo ""
echo "❌ Code is not properly formatted!"
echo "💡 Run 'forge fmt' locally and commit the changes"
echo ""
exit 1
fi
echo "✅ All code is properly formatted!"
19 changes: 19 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

echo ""
echo "🔨 Formatting Solidity code..."

# Run forge fmt
forge fmt

# Check for changes and stage them
if [ -n "$(git diff --name-only *.sol **/*.sol 2>/dev/null)" ]; then
echo "✨ Code formatted! Adding to commit..."
git add *.sol **/*.sol 2>/dev/null || true
echo "✅ Formatted files staged"
else
echo "✅ No formatting needed"
fi

echo ""
101 changes: 101 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,104 @@ goerli = { key = "${ETHERSCAN_API_KEY}" }

# Used for verifying contracts on Tenderly Virtual Testnet
unknown_chain = { key = "${TENDERLY_ACCESS_KEY}", chain =1, url = "${TENDERLY_VIRTUAL_TESTNET_RPC_URL}/verify/etherscan" }
# ==============================================================================
# FORMATTER CONFIGURATION (forge fmt)
# ==============================================================================
# Comprehensive Solidity code formatting settings for consistent style across
# the entire codebase. These settings control how `forge fmt` formats your code.
[fmt]
# Maximum line length before wrapping (set very high to prevent line splitting)
# Setting to 999 essentially disables line wrapping, keeping code on single lines
# where possible. This prevents the formatter from splitting long statements.
line_length = 999

# Number of spaces per indentation level
# Controls the width of each indentation level. 4 spaces is the Solidity
# community standard and provides good readability while keeping code compact.
tab_width = 4

# Whether to add spaces inside array/function call brackets
# true: function( arg1, arg2 ) or array[ index ]
# false: function(arg1, arg2) or array[index]
# true improves readability, especially with complex expressions
bracket_spacing = false

# String literal quote style: "double" or "single"
# "double" uses "string" (standard for most languages)
# "single" uses 'string' (some prefer this for Solidity)
# Double quotes are the Solidity convention and default compiler behavior
quote_style = "double"

# Number formatting: where to place underscores in numeric literals
# "thousands": 1_000_000 (groups by thousands - most readable for large numbers)
# "none": 1000000 (no underscores - compact but harder to read)
# "decimal": 1000.000 (groups only around decimal point)
# Thousands separator improves readability for large numbers like wei amounts
number_underscore = "thousands"

# Whether function bodies can be on a single line
# false: Always use braces and newlines even for single-statement functions
# true: Allows single-line bodies like "function f() external { doSomething(); }"
# false is safer and more consistent, enforcing explicit structure
multiline_func_body = false

# Opening brace placement for code blocks
# false: Opening brace on same line (K&R style)
# if (condition) {
# code
# }
# true: Opening brace on new line (Allman style)
# if (condition)
# {
# code
# }
# Same-line braces are more compact and standard in Solidity community
multiline_block_brace = false

# Whether single-line statement blocks are allowed
# "preserve": Keeps existing formatting (no change)
# "single": Allows single-line blocks without braces: if (condition) doSomething();
# "multi": Always requires braces and multi-line formatting (safer, prevents bugs)
# if (condition) {
# doSomething();
# }
# "preserve" maintains your existing formatting style without forcing changes
single_line_statement_blocks = "preserve"

# Whether to add blank lines between contract/interface/library definitions
# false: No extra lines between definitions (compact)
# true: Adds blank lines between contracts for visual separation
# false keeps the file more compact, which helps with navigation
contract_new_lines = false

# Whether to format inline Yul assembly code
# false: Leave Yul code as-is (assembly formatting is tricky)
# true: Attempt to format Yul code (may break some assembly)
# false is recommended as assembly often requires precise formatting
yul = false

# Whether to sort imports alphabetically, preventing duplicate imports
sort_imports = true

# true: import {Foo} -> import { Foo } (Solidity standard)
# false: import { Foo } -> import {Foo} (Solidity standard)
# false is recommended as it is the Solidity standard
import_spacing = false

# Integer type preference: "long" (uint256) or "short" (uint)
# "long" uses explicit types like uint256, int256, bytes32
# "short" uses aliases like uint, int, bytes32 (bytes32 stays same)
# Long is preferred for clarity and avoids confusion (uint is uint256, not uint8)
# int_types = "long"

# Multiline function header formatting style
# "params_first": Parameters start on new line, type comes first
# function transfer(
# address to,
# uint256 amount
# ) external
# "all_one_line": Everything on one line if possible
# "types_first": Type comes before parameter name
# Since line_length is high, this mainly affects explicitly multiline functions
# multiline_func_header = "all_one_line"

28 changes: 28 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"devDependencies": {
"husky": "^9.1.7"
}
}
29 changes: 8 additions & 21 deletions script/ContractCodeChecker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,8 @@ pragma solidity ^0.8.13;
import {console} from "forge-std/console.sol";
import {console2} from "forge-std/console2.sol";


contract ContractCodeChecker {

event ByteMismatchSegment(
uint256 startIndex,
uint256 endIndex,
bytes aSegment,
bytes bSegment
);
event ByteMismatchSegment(uint256 startIndex, uint256 endIndex, bytes aSegment, bytes bSegment);

function compareBytes(bytes memory a, bytes memory b) internal returns (bool) {
if (a.length != b.length) {
Expand Down Expand Up @@ -50,12 +43,7 @@ contract ContractCodeChecker {
return !anyMismatch;
}

function emitMismatchSegment(
bytes memory a,
bytes memory b,
uint256 start,
uint256 end
) internal {
function emitMismatchSegment(bytes memory a, bytes memory b, uint256 start, uint256 end) internal {
// endIndex is inclusive
uint256 segmentLength = end - start + 1;

Expand All @@ -82,8 +70,8 @@ contract ContractCodeChecker {

// Every byte corresponds to two hex characters
bytes memory str = new bytes(2 + data.length * 2);
str[0] = '0';
str[1] = 'x';
str[0] = "0";
str[1] = "x";
for (uint256 i = 0; i < data.length; i++) {
str[2 + i * 2] = alphabet[uint8(data[i] >> 4)];
str[3 + i * 2] = alphabet[uint8(data[i] & 0x0f)];
Expand All @@ -110,7 +98,7 @@ contract ContractCodeChecker {
// Fetch runtime bytecode from on-chain addresses
bytes memory localBytecode = localDeployed.code;
bytes memory onchainRuntimeBytecode = deployedImpl.code;

// Optionally check length first (not strictly necessary if doing a partial match)
if (localBytecode.length == 0 || onchainRuntimeBytecode.length == 0) {
revert("One of the bytecode arrays is empty, cannot verify.");
Expand Down Expand Up @@ -155,11 +143,11 @@ contract ContractCodeChecker {
// This is a heuristic based on known patterns in the metadata.
function trimMetadata(bytes memory code) internal pure returns (bytes memory) {
// Metadata usually starts with 0xa2 or a similar tag near the end.
// We can scan backward for a known marker.
// We can scan backward for a known marker.
// In Solidity 0.8.x, metadata often starts near the end with 0xa2 0x64 ... pattern.
// This is a simplified approach and may need refinement.
// For a more robust approach, you'd analyze the last bytes.

// For a more robust approach, you'd analyze the last bytes.
// Typically, the CBOR metadata is at the very end of the bytecode.
uint256 length = code.length;
if (length < 4) {
Expand Down Expand Up @@ -190,5 +178,4 @@ contract ContractCodeChecker {
// If no metadata marker found, return as is.
return code;
}

}
25 changes: 9 additions & 16 deletions script/DeployHoodiContracts.s.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import "forge-std/Script.sol";
import "../src/EtherFiNodesManager.sol";
import "../src/EtherFiNode.sol";
import "../src/EtherFiNodesManager.sol";
import "../src/EtherFiRateLimiter.sol";
import "forge-std/Script.sol";

interface IUpgradable {
function upgradeTo(address newImplementation) external;
Expand All @@ -18,20 +18,20 @@ contract DeployHoodiContracts is Script {

// Known addresses on Hoodi testnet
address stakingManagerProxy = 0xEcf3C0Dc644DBC7d0fbf7f69651D90f2177D0dFf;

address roleRegistryProxy = 0x62247D29B4B9BECf4BB73E0c722cf6445cfC7cE9;
address liquidityPoolProxy = 0x308861A430be4cce5502d0A12724771Fc6DaF216;
address eigenPodManagerProxy = 0x91E677b07F7AF907ec9a428aafA9fc14a0d3A338;
address delegationManagerProxy = 0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A;
address etherFiNodesManagerProxy = 0x8B71140AD2e5d1E7018d2a7f8a288BD3CD38916F;
address etherFiNodeProxy = 0xfD4Ff2942e183161a5920749CD5A8B0cFD4164AC;
address etherFiRateLimiterProxy = address(0x0);
address etherFiRateLimiterProxy = address(0x0);

// Deploy EtherFiRateLimiter first
console.log("Deploying EtherFiRateLimiter implementation...");
EtherFiRateLimiter rateLimiterImpl = new EtherFiRateLimiter(roleRegistryProxy);
console.log("EtherFiRateLimiter implementation deployed at:", address(rateLimiterImpl));

// Deploy new EtherFiNodesManager implementation
console.log("Deploying new EtherFiNodesManager implementation...");
EtherFiNodesManager newNodesManagerImpl = new EtherFiNodesManager(stakingManagerProxy, roleRegistryProxy, address(etherFiRateLimiterProxy));
Expand All @@ -41,16 +41,9 @@ contract DeployHoodiContracts is Script {

// Deploy new EtherFiNode implementation
console.log("Deploying new EtherFiNode implementation...");
EtherFiNode newNode = new EtherFiNode(
liquidityPoolProxy,
etherFiNodesManagerProxy,
eigenPodManagerProxy,
delegationManagerProxy,
roleRegistryProxy
);
EtherFiNode newNode = new EtherFiNode(liquidityPoolProxy, etherFiNodesManagerProxy, eigenPodManagerProxy, delegationManagerProxy, roleRegistryProxy);
console.log("EtherFiNode deployed at:", address(newNode));


console.log("Upgrading NodesManager proxy...");
IUpgradable(etherFiNodesManagerProxy).upgradeTo(address(newNodesManagerImpl));
console.log(" -> NodesManager proxy upgraded");
Expand All @@ -61,10 +54,10 @@ contract DeployHoodiContracts is Script {
console.log(" -> EtherFiNode beacon upgraded");

vm.stopBroadcast();

// Log the addresses for upgrade transactions
console.log("\n=== Deployment Summary ===");
console.log("New EtherFiNodesManager implementation:", address(newNodesManagerImpl));
console.log("New EtherFiNode implementation:", address(newNode));
}
}
}
Loading
Loading