From f4c7993967161ef4b6e708a5f640c00aecb50830 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 22:58:33 +0000 Subject: [PATCH 01/19] feat: add ERC20 transfer benchmark for Porto with passkey MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add testERC20TransferViaPortoOrchestratorWithPasskey() benchmark to isolate passkey authentication costs from spend limit enforcement costs. - Uses secp256k1 passkey for transaction signing - Sets execution permissions for ERC20 transfers - Requires spend limits (set to max) for passkey operations - Gas cost: 116,094 (vs 97,030 without passkey, 117,083 with restrictive limits) - Provides clean measurement of passkey overhead (~19k gas) Resolves #272 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Tanishk Goyal --- test/Benchmark.t.sol | 46 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test/Benchmark.t.sol b/test/Benchmark.t.sol index c169de27..dd430a5d 100644 --- a/test/Benchmark.t.sol +++ b/test/Benchmark.t.sol @@ -315,6 +315,52 @@ contract BenchmarkTest is BaseTest { vm.resumeGasMetering(); } + function testERC20TransferViaPortoOrchestratorWithPasskey() public { + vm.pauseGasMetering(); + + PassKey memory k = _randomSecp256k1PassKey(); + + DelegatedEOA memory d = _randomEIP7702DelegatedEOA(); + vm.deal(d.eoa, type(uint128).max); + _mint(address(paymentToken), d.eoa, type(uint128).max); + + vm.startPrank(d.eoa); + d.d.authorize(k.k); + d.d.setCanExecute( + k.keyHash, address(paymentToken), bytes4(keccak256("transfer(address,uint256)")), true + ); + d.d.setSpendLimit( + k.keyHash, address(paymentToken), GuardedExecutor.SpendPeriod.Hour, type(uint256).max + ); + d.d.setSpendLimit(k.keyHash, address(0), GuardedExecutor.SpendPeriod.Hour, type(uint256).max); + vm.stopPrank(); + + Orchestrator.Intent memory u; + u.eoa = d.eoa; + u.nonce = 0; + u.combinedGas = 1000000; + u.prePaymentAmount = 0 ether; + u.prePaymentMaxAmount = 0 ether; + u.totalPaymentAmount = 0.01 ether; + u.totalPaymentMaxAmount = 0.1 ether; + u.paymentToken = address(0); + // To maintain parity with the old benchmarks. + u.paymentRecipient = address(oc); + u.executionData = _transferExecutionData(address(paymentToken), address(0xbabe), 1 ether); + u.signature = _sig(k, u); + + bytes[] memory encodedIntents = new bytes[](1); + encodedIntents[0] = abi.encode(u); + + vm.resumeGasMetering(); + + oc.execute(encodedIntents); + + vm.pauseGasMetering(); + assertEq(paymentToken.balanceOf(address(0xbabe)), 1 ether); + vm.resumeGasMetering(); + } + function testERC20TransferDirect() public { vm.pauseGasMetering(); From ab8f1a4dd24aa2de4fe5c434c9a4b356aecdd189 Mon Sep 17 00:00:00 2001 From: o-az Date: Tue, 16 Sep 2025 09:58:16 -0700 Subject: [PATCH 02/19] chore: use `auto-assign-pr.yml` org action --- .github/workflows/auto-assign.yaml | 37 +----------------------------- 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/.github/workflows/auto-assign.yaml b/.github/workflows/auto-assign.yaml index f256bdc4..0ecd2e5e 100644 --- a/.github/workflows/auto-assign.yaml +++ b/.github/workflows/auto-assign.yaml @@ -9,39 +9,4 @@ jobs: permissions: issues: write pull-requests: write - runs-on: ubuntu-latest - steps: - - name: Auto assign PR to author - if: > - github.event.pull_request.user.type != 'Bot' && - ( - github.event.pull_request.author_association == 'MEMBER' || - github.event.pull_request.author_association == 'OWNER' || - github.event.pull_request.author_association == 'COLLABORATOR' - ) - uses: actions/github-script@v7 - with: - script: | - const pr = context.payload.pull_request - const prAuthor = pr.user.login - - console.log(`PR #${pr.number} author: ${prAuthor}`) - console.log(`Current assignees: ${pr.assignees.map(a => a.login).join(', ') || 'none'}`) - - if (pr.assignees && pr.assignees.length > 0) { - console.log('PR already has assignees, skipping') - return - } - - try { - await github.rest.issues.addAssignees({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - assignees: [prAuthor] - }) - console.log(`Successfully assigned PR #${pr.number} to ${prAuthor}`) - } catch (error) { - console.error(`Failed to assign PR: ${error.message}`) - throw error - } + uses: ithacaxyz/ci/.github/workflows/auto-assign-pr.yml@main From 2ecce316b8bad62c40f31a5ceeb500726d926d1c Mon Sep 17 00:00:00 2001 From: Tanishk Goyal Date: Wed, 24 Sep 2025 22:22:07 +0530 Subject: [PATCH 03/19] feat: add merkle sigs natively into the account --- snapshots/BenchmarkTest.json | 30 +++++++++--------- src/IthacaAccount.sol | 59 ++++++++++++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 17 deletions(-) diff --git a/snapshots/BenchmarkTest.json b/snapshots/BenchmarkTest.json index 2d5a899d..b32e01a9 100644 --- a/snapshots/BenchmarkTest.json +++ b/snapshots/BenchmarkTest.json @@ -16,11 +16,11 @@ "testERC20Transfer_ERC4337MinimalAccount": "171509", "testERC20Transfer_ERC4337MinimalAccount_AppSponsor": "168500", "testERC20Transfer_ERC4337MinimalAccount_ERC20SelfPay": "199831", - "testERC20Transfer_IthacaAccount": "128059", - "testERC20Transfer_IthacaAccountWithSpendLimits": "193602", - "testERC20Transfer_IthacaAccount_AppSponsor": "138595", - "testERC20Transfer_IthacaAccount_AppSponsor_ERC20": "143884", - "testERC20Transfer_IthacaAccount_ERC20SelfPay": "128548", + "testERC20Transfer_IthacaAccount": "128100", + "testERC20Transfer_IthacaAccountWithSpendLimits": "193589", + "testERC20Transfer_IthacaAccount_AppSponsor": "138636", + "testERC20Transfer_IthacaAccount_AppSponsor_ERC20": "143937", + "testERC20Transfer_IthacaAccount_ERC20SelfPay": "128589", "testERC20Transfer_Safe4337": "197561", "testERC20Transfer_Safe4337_AppSponsor": "191725", "testERC20Transfer_Safe4337_ERC20SelfPay": "221464", @@ -29,25 +29,25 @@ "testERC20Transfer_ZerodevKernel_ERC20SelfPay": "235489", "testERC20Transfer_batch100_AlchemyModularAccount": "10109066", "testERC20Transfer_batch100_AlchemyModularAccount_ERC20SelfPay": "11609298", - "testERC20Transfer_batch100_IthacaAccount": "7531912", - "testERC20Transfer_batch100_IthacaAccount_AppSponsor": "8140216", - "testERC20Transfer_batch100_IthacaAccount_AppSponsor_ERC20": "7966120", - "testERC20Transfer_batch100_IthacaAccount_ERC20SelfPay": "7353064", + "testERC20Transfer_batch100_IthacaAccount": "7535892", + "testERC20Transfer_batch100_IthacaAccount_AppSponsor": "8144196", + "testERC20Transfer_batch100_IthacaAccount_AppSponsor_ERC20": "7970208", + "testERC20Transfer_batch100_IthacaAccount_ERC20SelfPay": "7357152", "testERC20Transfer_batch100_ZerodevKernel": "12631318", "testERC20Transfer_batch100_ZerodevKernel_ERC20SelfPay": "14149937", "testNativeTransfer_AlchemyModularAccount": "180829", "testNativeTransfer_CoinbaseSmartWallet": "178916", - "testNativeTransfer_IthacaAccount": "129415", - "testNativeTransfer_IthacaAccount_AppSponsor": "139982", - "testNativeTransfer_IthacaAccount_ERC20SelfPay": "137204", + "testNativeTransfer_IthacaAccount": "129456", + "testNativeTransfer_IthacaAccount_AppSponsor": "140023", + "testNativeTransfer_IthacaAccount_ERC20SelfPay": "137245", "testNativeTransfer_Safe4337": "198595", "testNativeTransfer_ZerodevKernel": "208635", "testUniswapV2Swap_AlchemyModularAccount": "238647", "testUniswapV2Swap_CoinbaseSmartWallet": "237451", "testUniswapV2Swap_ERC4337MinimalAccount": "230691", - "testUniswapV2Swap_IthacaAccount": "187179", - "testUniswapV2Swap_IthacaAccount_AppSponsor": "197691", - "testUniswapV2Swap_IthacaAccount_ERC20SelfPay": "192480", + "testUniswapV2Swap_IthacaAccount": "187244", + "testUniswapV2Swap_IthacaAccount_AppSponsor": "197744", + "testUniswapV2Swap_IthacaAccount_ERC20SelfPay": "192533", "testUniswapV2Swap_Safe4337": "257333", "testUniswapV2Swap_ZerodevKernel": "266367" } \ No newline at end of file diff --git a/src/IthacaAccount.sol b/src/IthacaAccount.sol index 45b2d6c8..cbe96ff3 100644 --- a/src/IthacaAccount.sol +++ b/src/IthacaAccount.sol @@ -23,6 +23,7 @@ import {LibNonce} from "./libraries/LibNonce.sol"; import {TokenTransferLib} from "./libraries/TokenTransferLib.sol"; import {LibTStack} from "./libraries/LibTStack.sol"; import {IIthacaAccount} from "./interfaces/IIthacaAccount.sol"; +import {MerkleProofLib} from "solady/utils/MerkleProofLib.sol"; /// @title Account /// @notice A account contract for EOAs with EIP7702. @@ -490,9 +491,52 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor { return isMultichain ? _hashTypedDataSansChainId(structHash) : _hashTypedData(structHash); } + /// @dev Verifies the merkle sig for the multi chain intents. + /// - Note: Each leaf of the merkle tree should be a standard intent digest, computed with chainId. + /// - Leaf intents do NOT need to have the multichain nonce prefix. + /// - The signature for multi chain intents using merkle verification is encoded as: + /// - bytes signature = abi.encode(bytes32[] proof, bytes32 root, bytes rootSig) + function _verifyMerkleSig(bytes32 digest, bytes calldata signature) + internal + view + returns (bool isValid, bytes32 keyHash) + { + bytes32[] calldata proof; + bytes32 root; + bytes calldata rootSig; + + assembly ("memory-safe") { + let proofOffset := add(signature.offset, calldataload(signature.offset)) + proof.length := calldataload(proofOffset) + proof.offset := add(proofOffset, 0x20) + + root := calldataload(add(signature.offset, 0x20)) + + let rootSigOffset := add(signature.offset, calldataload(add(signature.offset, 0x40))) + rootSig.length := calldataload(rootSigOffset) + rootSig.offset := add(rootSigOffset, 0x20) + } + + console.logBytes32(root); + + console.log("Proof length: ", proof.length); + console.logBytes32(proof[0]); + + if (MerkleProofLib.verifyCalldata(proof, root, digest)) { + console.log("Entered here"); + (isValid, keyHash) = unwrapAndValidateSignature(root, rootSig); + + console.logBytes32(keyHash); + console.log(isValid); + return (isValid, keyHash); + } + + return (false, bytes32(0)); + } + /// @dev Returns if the signature is valid, along with its `keyHash`. /// The `signature` is a wrapped signature, given by - /// `abi.encodePacked(bytes(innerSignature), bytes32(keyHash), bool(prehash))`. + /// `abi.encode(bytes(innerSignature), bytes32(keyHash), bool(prehash), bool(merkle))`. function unwrapAndValidateSignature(bytes32 digest, bytes calldata signature) public view @@ -507,14 +551,25 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor { return (ECDSA.recoverCalldata(digest, signature) == address(this), 0); } + bool merkle; unchecked { - uint256 n = signature.length - 0x21; + uint256 n = signature.length - 0x22; keyHash = LibBytes.loadCalldata(signature, n); signature = LibBytes.truncatedCalldata(signature, n); // Do the prehash if last byte is non-zero. if (uint256(LibBytes.loadCalldata(signature, n + 1)) & 0xff != 0) { + console.log("prehash"); digest = EfficientHashLib.sha2(digest); // `sha256(abi.encode(digest))`. } + merkle = uint256(LibBytes.loadCalldata(signature, n + 2)) & 0xff != 0; + } + + console.logBytes(signature); + + console.log("Reached here", merkle); + if (merkle) { + console.log("merkle"); + return _verifyMerkleSig(digest, signature); } Key memory key = getKey(keyHash); From 557774798309a3b79c070634f20d96b2a465c52b Mon Sep 17 00:00:00 2001 From: Tanishk Goyal Date: Wed, 24 Sep 2025 22:23:47 +0530 Subject: [PATCH 04/19] fix: tests --- src/IthacaAccount.sol | 20 +++---------------- test/Account.t.sol | 40 ++++++++++++++++++++++++++++++++++++-- test/Base.t.sol | 11 ++++++----- test/Orchestrator.t.sol | 5 +++-- test/SimulateExecute.t.sol | 3 ++- 5 files changed, 52 insertions(+), 27 deletions(-) diff --git a/src/IthacaAccount.sol b/src/IthacaAccount.sol index cbe96ff3..1dc7f18b 100644 --- a/src/IthacaAccount.sol +++ b/src/IthacaAccount.sol @@ -491,10 +491,9 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor { return isMultichain ? _hashTypedDataSansChainId(structHash) : _hashTypedData(structHash); } - /// @dev Verifies the merkle sig for the multi chain intents. - /// - Note: Each leaf of the merkle tree should be a standard intent digest, computed with chainId. - /// - Leaf intents do NOT need to have the multichain nonce prefix. - /// - The signature for multi chain intents using merkle verification is encoded as: + /// @dev Verifies the merkle sig + /// - Note: Each leaf of the merkle tree should be a standard digest. + /// - The signature for using merkle verification is encoded as: /// - bytes signature = abi.encode(bytes32[] proof, bytes32 root, bytes rootSig) function _verifyMerkleSig(bytes32 digest, bytes calldata signature) internal @@ -517,17 +516,9 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor { rootSig.offset := add(rootSigOffset, 0x20) } - console.logBytes32(root); - - console.log("Proof length: ", proof.length); - console.logBytes32(proof[0]); - if (MerkleProofLib.verifyCalldata(proof, root, digest)) { - console.log("Entered here"); (isValid, keyHash) = unwrapAndValidateSignature(root, rootSig); - console.logBytes32(keyHash); - console.log(isValid); return (isValid, keyHash); } @@ -558,17 +549,12 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor { signature = LibBytes.truncatedCalldata(signature, n); // Do the prehash if last byte is non-zero. if (uint256(LibBytes.loadCalldata(signature, n + 1)) & 0xff != 0) { - console.log("prehash"); digest = EfficientHashLib.sha2(digest); // `sha256(abi.encode(digest))`. } merkle = uint256(LibBytes.loadCalldata(signature, n + 2)) & 0xff != 0; } - console.logBytes(signature); - - console.log("Reached here", merkle); if (merkle) { - console.log("merkle"); return _verifyMerkleSig(digest, signature); } diff --git a/test/Account.t.sol b/test/Account.t.sol index e4b8310b..67e3fe50 100644 --- a/test/Account.t.sol +++ b/test/Account.t.sol @@ -6,6 +6,8 @@ import "./Base.t.sol"; import {MockSampleDelegateCallTarget} from "./utils/mocks/MockSampleDelegateCallTarget.sol"; import {LibEIP7702} from "solady/accounts/LibEIP7702.sol"; +import {Merkle} from "murky/Merkle.sol"; + contract AccountTest is BaseTest { struct _TestExecuteWithSignatureTemps { TargetFunctionPayload[] targetFunctionPayloads; @@ -68,6 +70,41 @@ contract AccountTest is BaseTest { } } + function testMerkleSignature(bytes32 randomDigest) public { + DelegatedEOA memory d = _randomEIP7702DelegatedEOA(); + PassKey memory k = _randomSecp256k1PassKey(); + + k.k.isSuperAdmin = true; + + vm.prank(d.eoa); + d.d.authorize(k.k); + + bytes32 actualDigest = keccak256("actualDigest"); + + Merkle merkle = new Merkle(); + + bytes32[] memory leaves = new bytes32[](2); + leaves[0] = actualDigest; + leaves[1] = randomDigest; + bytes32 root = merkle.getRoot(leaves); + + bytes32[] memory proof = merkle.getProof(leaves, 0); + + bytes memory rootSig = abi.encode(proof, root, _sig(k, root)); + bytes memory sig = abi.encodePacked(rootSig, bytes32(0), uint8(0), uint8(1)); + + (bool isValid, bytes32 keyHash) = d.d.unwrapAndValidateSignature(actualDigest, sig); + + assertEq(isValid, true); + assertEq(keyHash, k.keyHash); + + // Test some random digest + bytes32 randomDigest2 = keccak256("randomDigest2"); + (isValid, keyHash) = d.d.unwrapAndValidateSignature(randomDigest2, sig); + assertEq(isValid, false); + assertEq(keyHash, bytes32(0)); + } + function testSignatureCheckerApproval(bytes32) public { DelegatedEOA memory d = _randomEIP7702DelegatedEOA(); PassKey memory k = _randomSecp256k1PassKey(); @@ -93,8 +130,7 @@ contract AccountTest is BaseTest { bytes32 replaySafeDigest = keccak256(abi.encode(d.d.SIGN_TYPEHASH(), digest)); - (, string memory name, string memory version,, address verifyingContract,,) = - d.d.eip712Domain(); + (,,,, address verifyingContract,,) = d.d.eip712Domain(); bytes32 domain = keccak256( abi.encode( 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749, // DOMAIN_TYPEHASH with only verifyingContract diff --git a/test/Base.t.sol b/test/Base.t.sol index 1765a651..f9f91ac4 100644 --- a/test/Base.t.sol +++ b/test/Base.t.sol @@ -215,7 +215,7 @@ contract BaseTest is SoladyTest { { (bytes32 r, bytes32 s) = vm.signP256(privateKey, digest); s = P256.normalized(s); - return abi.encodePacked(abi.encode(r, s), keyHash, uint8(prehash ? 1 : 0)); + return abi.encodePacked(abi.encode(r, s), keyHash, uint8(prehash ? 1 : 0), uint8(0)); } function _secp256k1Sig(uint256 privateKey, bytes32 keyHash, bytes32 digest) @@ -232,7 +232,8 @@ contract BaseTest is SoladyTest { returns (bytes memory) { (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); - return abi.encodePacked(abi.encodePacked(r, s, v), keyHash, uint8(prehash ? 1 : 0)); + return + abi.encodePacked(abi.encodePacked(r, s, v), keyHash, uint8(prehash ? 1 : 0), uint8(0)); } function _multiSig(MultiSigKey memory k, bytes32 keyHash, bool preHash, bytes32 digest) @@ -245,7 +246,7 @@ contract BaseTest is SoladyTest { signatures[i] = _sig(k.owners[i], digest); } - return abi.encodePacked(abi.encode(signatures), keyHash, uint8(preHash ? 1 : 0)); + return abi.encodePacked(abi.encode(signatures), keyHash, uint8(preHash ? 1 : 0), uint8(0)); } function _estimateGasForEOAKey(Orchestrator.Intent memory i) @@ -277,7 +278,7 @@ contract BaseTest is SoladyTest { { (uint8 v, bytes32 r, bytes32 s) = vm.sign(uint128(_randomUniform()), bytes32(_randomUniform())); - i.signature = abi.encodePacked(abi.encodePacked(r, s, v), keyHash, uint8(0)); + i.signature = abi.encodePacked(abi.encodePacked(r, s, v), keyHash, uint8(0), uint8(0)); return _estimateGas(i); } @@ -285,7 +286,7 @@ contract BaseTest is SoladyTest { internal returns (uint256 gExecute, uint256 gCombined, uint256 gUsed) { - i.signature = abi.encodePacked(keccak256("a"), keccak256("b"), keyHash, uint8(0)); + i.signature = abi.encodePacked(keccak256("a"), keccak256("b"), keyHash, uint8(0), uint8(0)); return _estimateGas(i); } diff --git a/test/Orchestrator.t.sol b/test/Orchestrator.t.sol index c36a3248..c5cc3cc1 100644 --- a/test/Orchestrator.t.sol +++ b/test/Orchestrator.t.sol @@ -924,8 +924,9 @@ contract OrchestratorTest is BaseTest { if (_randomChance(16)) { u.combinedGas += 10_000; // Fill with some junk signature, but with the session `keyHash`. - u.signature = - abi.encodePacked(keccak256("a"), keccak256("b"), kSession.keyHash, uint8(0)); + u.signature = abi.encodePacked( + keccak256("a"), keccak256("b"), kSession.keyHash, uint8(0), uint8(0) + ); (t.gExecute, t.gCombined, t.gUsed) = _estimateGas(u); diff --git a/test/SimulateExecute.t.sol b/test/SimulateExecute.t.sol index 681266f7..aa3261cd 100644 --- a/test/SimulateExecute.t.sol +++ b/test/SimulateExecute.t.sol @@ -300,7 +300,8 @@ contract SimulateExecuteTest is BaseTest { // it needs to add the variance for non-precompile P256 verification. // We need the `keyHash` in the signature so that the simulation is able // to hit all the gas for the GuardedExecutor stuff for the `keyHash`. - i.signature = abi.encodePacked(keccak256("a"), keccak256("b"), k.keyHash, uint8(0)); + i.signature = + abi.encodePacked(keccak256("a"), keccak256("b"), k.keyHash, uint8(0), uint8(0)); uint256 snapshot = vm.snapshotState(); vm.deal(_ORIGIN_ADDRESS, type(uint192).max); From 995a3e17a01c2d9e12bcdbc9384cfdd4f00b1d4c Mon Sep 17 00:00:00 2001 From: Tanishk Goyal Date: Wed, 24 Sep 2025 22:29:40 +0530 Subject: [PATCH 05/19] test: add more sophisticated fuzz test --- test/Account.t.sol | 69 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/test/Account.t.sol b/test/Account.t.sol index 67e3fe50..21c7c36e 100644 --- a/test/Account.t.sol +++ b/test/Account.t.sol @@ -70,39 +70,68 @@ contract AccountTest is BaseTest { } } - function testMerkleSignature(bytes32 randomDigest) public { + function testMerkleSignature(uint256 seed) public { DelegatedEOA memory d = _randomEIP7702DelegatedEOA(); PassKey memory k = _randomSecp256k1PassKey(); - k.k.isSuperAdmin = true; vm.prank(d.eoa); d.d.authorize(k.k); - bytes32 actualDigest = keccak256("actualDigest"); - - Merkle merkle = new Merkle(); + // Fuzz number of leaves (2 to 256) + uint256 numLeaves = bound(seed, 2, 256); + bytes32[] memory leaves = new bytes32[](numLeaves); - bytes32[] memory leaves = new bytes32[](2); - leaves[0] = actualDigest; - leaves[1] = randomDigest; - bytes32 root = merkle.getRoot(leaves); + // Generate random leaves + for (uint256 i = 0; i < numLeaves; i++) { + leaves[i] = keccak256(abi.encodePacked("leaf", i, seed)); + } - bytes32[] memory proof = merkle.getProof(leaves, 0); + // Pick a random valid index + uint256 validIndex = seed % numLeaves; + bytes32 validDigest = leaves[validIndex]; - bytes memory rootSig = abi.encode(proof, root, _sig(k, root)); - bytes memory sig = abi.encodePacked(rootSig, bytes32(0), uint8(0), uint8(1)); + Merkle merkle = new Merkle(); + bytes32 root = merkle.getRoot(leaves); + bytes32[] memory proof = merkle.getProof(leaves, validIndex); - (bool isValid, bytes32 keyHash) = d.d.unwrapAndValidateSignature(actualDigest, sig); + // Test valid merkle proof + { + bytes memory rootSig = abi.encode(proof, root, _sig(k, root)); + bytes memory sig = abi.encodePacked(rootSig, bytes32(0), uint8(0), uint8(1)); + + (bool isValid, bytes32 keyHash) = d.d.unwrapAndValidateSignature(validDigest, sig); + assertEq(isValid, true); + assertEq(keyHash, k.keyHash); + + // Test invalid digest not in tree + bytes32 invalidDigest = keccak256(abi.encodePacked("not_in_tree", seed)); + (isValid, keyHash) = d.d.unwrapAndValidateSignature(invalidDigest, sig); + assertEq(isValid, false); + assertEq(keyHash, bytes32(0)); + } - assertEq(isValid, true); - assertEq(keyHash, k.keyHash); + // Test tampered proof (only if proof has elements to tamper) + if (proof.length > 0) { + bytes32[] memory tamperedProof = new bytes32[](proof.length); + for (uint256 i = 0; i < proof.length; i++) { + tamperedProof[i] = i == 0 ? bytes32(uint256(proof[i]) ^ 1) : proof[i]; + } + bytes memory tamperedRootSig = abi.encode(tamperedProof, root, _sig(k, root)); + bytes memory tamperedSig = + abi.encodePacked(tamperedRootSig, bytes32(0), uint8(0), uint8(1)); + (bool isValid,) = d.d.unwrapAndValidateSignature(validDigest, tamperedSig); + assertEq(isValid, false); + } - // Test some random digest - bytes32 randomDigest2 = keccak256("randomDigest2"); - (isValid, keyHash) = d.d.unwrapAndValidateSignature(randomDigest2, sig); - assertEq(isValid, false); - assertEq(keyHash, bytes32(0)); + // Test wrong root (tampered root should fail verification) + { + bytes32 wrongRoot = bytes32(uint256(root) ^ 1); + bytes memory wrongRootSig = abi.encode(proof, wrongRoot, _sig(k, wrongRoot)); + bytes memory wrongSig = abi.encodePacked(wrongRootSig, bytes32(0), uint8(0), uint8(1)); + (bool isValid,) = d.d.unwrapAndValidateSignature(validDigest, wrongSig); + assertEq(isValid, false); + } } function testSignatureCheckerApproval(bytes32) public { From 4918a12d97acfad5294fbbc151c7fa12c8c13b26 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 24 Sep 2025 17:05:28 +0000 Subject: [PATCH 06/19] chore: bump contract versions due to bytecode changes - Contracts updated: IthacaAccount --- src/IthacaAccount.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IthacaAccount.sol b/src/IthacaAccount.sol index 1dc7f18b..a8ffdbca 100644 --- a/src/IthacaAccount.sol +++ b/src/IthacaAccount.sol @@ -796,6 +796,6 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor { returns (string memory name, string memory version) { name = "IthacaAccount"; - version = "0.5.10"; + version = "0.5.11"; } } From 05f08430b34f8852dd7e4e04b6fe98ea59dbfb37 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:23:00 +0700 Subject: [PATCH 07/19] # Default ignored files --- .husky/pre-commit | 0 .idea/.gitignore | 3 + .idea/account.iml | 13 + .idea/caches/deviceStreaming.xml | 1698 ++++++++++++++++++++ .idea/codeStyles/Project.xml | 117 ++ .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/copilot.data.migration.ask2agent.xml | 6 + .idea/markdown.xml | 8 + .idea/modules.xml | 8 + .idea/vcs.xml | 22 + deploy/execute_config.sh | 0 deploy/verify_config.sh | 0 lib/forge-std | 2 +- prep/check-bytecode-changes.js | 0 14 files changed, 1881 insertions(+), 1 deletion(-) mode change 100755 => 100644 .husky/pre-commit create mode 100644 .idea/.gitignore create mode 100644 .idea/account.iml create mode 100644 .idea/caches/deviceStreaming.xml create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/copilot.data.migration.ask2agent.xml create mode 100644 .idea/markdown.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml mode change 100755 => 100644 deploy/execute_config.sh mode change 100755 => 100644 deploy/verify_config.sh mode change 100755 => 100644 prep/check-bytecode-changes.js diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100755 new mode 100644 diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/account.iml b/.idea/account.iml new file mode 100644 index 00000000..1ba0d660 --- /dev/null +++ b/.idea/account.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml new file mode 100644 index 00000000..cc4b430a --- /dev/null +++ b/.idea/caches/deviceStreaming.xml @@ -0,0 +1,1698 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..4bec4ea8 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,117 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..a55e7a17 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.ask2agent.xml b/.idea/copilot.data.migration.ask2agent.xml new file mode 100644 index 00000000..1f2ea11e --- /dev/null +++ b/.idea/copilot.data.migration.ask2agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/markdown.xml b/.idea/markdown.xml new file mode 100644 index 00000000..c61ea334 --- /dev/null +++ b/.idea/markdown.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..4ae0bf91 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..1995e14d --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/deploy/execute_config.sh b/deploy/execute_config.sh old mode 100755 new mode 100644 diff --git a/deploy/verify_config.sh b/deploy/verify_config.sh old mode 100755 new mode 100644 diff --git a/lib/forge-std b/lib/forge-std index c2cf7017..aeb45e9f 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit c2cf7017d27c1d20e74ace4dacb6c5ce4bbbe899 +Subproject commit aeb45e9f32ef8ca78f0aeda17596e9c46374da41 diff --git a/prep/check-bytecode-changes.js b/prep/check-bytecode-changes.js old mode 100755 new mode 100644 From 9745888cae52f0c5a4369f3a666f5a3dd6aff3c1 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sun, 15 Feb 2026 22:16:31 +0700 Subject: [PATCH 08/19] .snapshot_worktree --- .husky/pre-commit | 0 deploy/execute_config.sh | 0 deploy/verify_config.sh | 0 lib/LayerZero-v2 | 2 +- lib/forge-std | 2 +- prep/check-bytecode-changes.js | 0 6 files changed, 2 insertions(+), 2 deletions(-) mode change 100755 => 100644 .husky/pre-commit mode change 100755 => 100644 deploy/execute_config.sh mode change 100755 => 100644 deploy/verify_config.sh mode change 100755 => 100644 prep/check-bytecode-changes.js diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100755 new mode 100644 diff --git a/deploy/execute_config.sh b/deploy/execute_config.sh old mode 100755 new mode 100644 diff --git a/deploy/verify_config.sh b/deploy/verify_config.sh old mode 100755 new mode 100644 diff --git a/lib/LayerZero-v2 b/lib/LayerZero-v2 index 88428755..ab9b0834 160000 --- a/lib/LayerZero-v2 +++ b/lib/LayerZero-v2 @@ -1 +1 @@ -Subproject commit 88428755be6caa71cb1d2926141d73c8989296b5 +Subproject commit ab9b083410b9359285a5756807e1b6145d4711a7 diff --git a/lib/forge-std b/lib/forge-std index c2cf7017..aeb45e9f 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit c2cf7017d27c1d20e74ace4dacb6c5ce4bbbe899 +Subproject commit aeb45e9f32ef8ca78f0aeda17596e9c46374da41 diff --git a/prep/check-bytecode-changes.js b/prep/check-bytecode-changes.js old mode 100755 new mode 100644 From 512c204d2ce396d77dcf91b37ee514585ff337b3 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 19 Feb 2026 21:24:38 +0700 Subject: [PATCH 09/19] Delete .idea directory (#35) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- .idea/.gitignore | 3 - .idea/account.iml | 13 - .idea/caches/deviceStreaming.xml | 1698 -------------------- .idea/codeStyles/Project.xml | 117 -- .idea/codeStyles/codeStyleConfig.xml | 5 - .idea/copilot.data.migration.ask2agent.xml | 6 - .idea/markdown.xml | 8 - .idea/modules.xml | 8 - .idea/vcs.xml | 22 - 9 files changed, 1880 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/account.iml delete mode 100644 .idea/caches/deviceStreaming.xml delete mode 100644 .idea/codeStyles/Project.xml delete mode 100644 .idea/codeStyles/codeStyleConfig.xml delete mode 100644 .idea/copilot.data.migration.ask2agent.xml delete mode 100644 .idea/markdown.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d33521..00000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/account.iml b/.idea/account.iml deleted file mode 100644 index 1ba0d660..00000000 --- a/.idea/account.iml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml deleted file mode 100644 index cc4b430a..00000000 --- a/.idea/caches/deviceStreaming.xml +++ /dev/null @@ -1,1698 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 4bec4ea8..00000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index a55e7a17..00000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/copilot.data.migration.ask2agent.xml b/.idea/copilot.data.migration.ask2agent.xml deleted file mode 100644 index 1f2ea11e..00000000 --- a/.idea/copilot.data.migration.ask2agent.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/markdown.xml b/.idea/markdown.xml deleted file mode 100644 index c61ea334..00000000 --- a/.idea/markdown.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 4ae0bf91..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 1995e14d..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From ab9e8839428e1a9f047cb65d708ae02e270b23e9 Mon Sep 17 00:00:00 2001 From: googleworkspace-bot Date: Fri, 10 Apr 2026 07:28:05 +0700 Subject: [PATCH 10/19] Normalize file modes and update forge-std submodule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove executable bit from deploy/execute_config.sh, deploy/verify_config.sh, and prep/check-bytecode-changes.js (mode 100755 → 100644). Update lib/forge-std submodule from commit c2cf7017d27c1d20e74ace4dacb6c5ce4bbbe899 to 07853315f998f94dc724e464b1bab1270888ee64. No other content changes. --- deploy/execute_config.sh | 0 deploy/verify_config.sh | 0 lib/forge-std | 2 +- prep/check-bytecode-changes.js | 0 4 files changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 deploy/execute_config.sh mode change 100755 => 100644 deploy/verify_config.sh mode change 100755 => 100644 prep/check-bytecode-changes.js diff --git a/deploy/execute_config.sh b/deploy/execute_config.sh old mode 100755 new mode 100644 diff --git a/deploy/verify_config.sh b/deploy/verify_config.sh old mode 100755 new mode 100644 diff --git a/lib/forge-std b/lib/forge-std index c2cf7017..07853315 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit c2cf7017d27c1d20e74ace4dacb6c5ce4bbbe899 +Subproject commit 07853315f998f94dc724e464b1bab1270888ee64 diff --git a/prep/check-bytecode-changes.js b/prep/check-bytecode-changes.js old mode 100755 new mode 100644 From 5dfb43aab8fbdd3c38ff900e931ef201520e70ed Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 11 Apr 2026 17:23:41 +0700 Subject: [PATCH 11/19] Revert "Delete .idea directory (#35)" (#55) This reverts commit 512c204d2ce396d77dcf91b37ee514585ff337b3. --- .idea/.gitignore | 3 + .idea/account.iml | 13 + .idea/caches/deviceStreaming.xml | 1698 ++++++++++++++++++++ .idea/codeStyles/Project.xml | 117 ++ .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/copilot.data.migration.ask2agent.xml | 6 + .idea/markdown.xml | 8 + .idea/modules.xml | 8 + .idea/vcs.xml | 22 + 9 files changed, 1880 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/account.iml create mode 100644 .idea/caches/deviceStreaming.xml create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/copilot.data.migration.ask2agent.xml create mode 100644 .idea/markdown.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/account.iml b/.idea/account.iml new file mode 100644 index 00000000..1ba0d660 --- /dev/null +++ b/.idea/account.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml new file mode 100644 index 00000000..cc4b430a --- /dev/null +++ b/.idea/caches/deviceStreaming.xml @@ -0,0 +1,1698 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..4bec4ea8 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,117 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..a55e7a17 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.ask2agent.xml b/.idea/copilot.data.migration.ask2agent.xml new file mode 100644 index 00000000..1f2ea11e --- /dev/null +++ b/.idea/copilot.data.migration.ask2agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/markdown.xml b/.idea/markdown.xml new file mode 100644 index 00000000..c61ea334 --- /dev/null +++ b/.idea/markdown.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..4ae0bf91 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..1995e14d --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 379fa7b0467720dd935259b8a2f210eeeecda1d3 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 11 Apr 2026 19:49:57 +0700 Subject: [PATCH 12/19] Dargon789 legion rouge (#61) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add merkle sigs natively into the account * fix: tests * test: add more sophisticated fuzz test * chore: bump contract versions due to bytecode changes - Contracts updated: IthacaAccount * Add .circleci/config.yml (#1) Add CircleCI configuration file to set up a basic pipeline with a say-hello job and workflow CI: Add .circleci/config.yml to define a say-hello job that checks out the code and prints a greeting using the cimg/base Docker image Add a workflow to orchestrate the say-hello job under the CircleCI 2.1 engine * Update ci.yaml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml (#2) CI: Update Forge test command to use --rerun and increase verbosity to -vvvvv Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml (#4) Enhance the CI test step to automatically rerun failed tests and increase verbosity in Forge. CI: Enable the --rerun flag for Forge tests to retry failures automatically Increase Forge test verbosity from -vvv to -vvvvv Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml (#5) Change Forge test invocation from "forge test --rerun -vvvvv" to "forge test -vvv" Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create CNAME * Revert "Merge branch 'master'" This reverts commit 6c02fbff6486bb206c08af83d948830ace042353, reversing changes made to a317ddb944a1f5ed7294092790902da6b255f73f. * Create CNAME (#9) * Update ci.yaml (#2) CI: Update Forge test command to use --rerun and increase verbosity to -vvvvv Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml (#4) Enhance the CI test step to automatically rerun failed tests and increase verbosity in Forge. CI: Enable the --rerun flag for Forge tests to retry failures automatically Increase Forge test verbosity from -vvv to -vvvvv Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml (#5) Change Forge test invocation from "forge test --rerun -vvvvv" to "forge test -vvv" Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create CNAME * Revert "Merge branch 'master'" This reverts commit 6c02fbff6486bb206c08af83d948830ace042353, reversing changes made to a317ddb944a1f5ed7294092790902da6b255f73f. --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory (#27) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * pre-commit * deploy execute_config.sh * Update forge-std * test: add tests for getContextKeyHash in Account.t.sol (#412) * Normalize file modes and update forge-std submodule Remove executable bit from deploy/execute_config.sh, deploy/verify_config.sh, and prep/check-bytecode-changes.js (mode 100755 → 100644). Update lib/forge-std submodule from commit c2cf7017d27c1d20e74ace4dacb6c5ce4bbbe899 to 07853315f998f94dc724e464b1bab1270888ee64. No other content changes. * Update LayerZero-v2 * Update LayerZero-v2 --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Tanishk Goyal Co-authored-by: GitHub Action Co-authored-by: googleworkspace-bot --- .github/workflows/ci.yaml | 2 +- CNAME | 1 + deploy/execute_config.sh | 0 deploy/verify_config.sh | 0 lib/LayerZero-v2 | 2 +- lib/forge-std | 2 +- prep/check-bytecode-changes.js | 0 src/IthacaAccount.sol | 47 +++++++++++++++++++++-- test/Account.t.sol | 69 +++++++++++++++++++++++++++++++++- test/Base.t.sol | 11 +++--- test/Orchestrator.t.sol | 5 ++- test/SimulateExecute.t.sol | 3 +- 12 files changed, 126 insertions(+), 16 deletions(-) create mode 100644 CNAME mode change 100755 => 100644 deploy/execute_config.sh mode change 100755 => 100644 deploy/verify_config.sh mode change 100755 => 100644 prep/check-bytecode-changes.js diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6915c959..6de4b9e5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -33,7 +33,7 @@ jobs: UPGRADE_TEST_OLD_PROXY: ${{ secrets.UPGRADE_TEST_OLD_PROXY }} UPGRADE_TEST_OLD_VERSION: ${{ secrets.UPGRADE_TEST_OLD_VERSION }} run: | - forge test -vvv + forge test --no-match-contract UpgradeTests - name: Format contracts and generate snapshots if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository diff --git a/CNAME b/CNAME new file mode 100644 index 00000000..a37c284a --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +legion-rouge.vercel.app \ No newline at end of file diff --git a/deploy/execute_config.sh b/deploy/execute_config.sh old mode 100755 new mode 100644 diff --git a/deploy/verify_config.sh b/deploy/verify_config.sh old mode 100755 new mode 100644 diff --git a/lib/LayerZero-v2 b/lib/LayerZero-v2 index 88428755..ab9b0834 160000 --- a/lib/LayerZero-v2 +++ b/lib/LayerZero-v2 @@ -1 +1 @@ -Subproject commit 88428755be6caa71cb1d2926141d73c8989296b5 +Subproject commit ab9b083410b9359285a5756807e1b6145d4711a7 diff --git a/lib/forge-std b/lib/forge-std index c2cf7017..07853315 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit c2cf7017d27c1d20e74ace4dacb6c5ce4bbbe899 +Subproject commit 07853315f998f94dc724e464b1bab1270888ee64 diff --git a/prep/check-bytecode-changes.js b/prep/check-bytecode-changes.js old mode 100755 new mode 100644 diff --git a/src/IthacaAccount.sol b/src/IthacaAccount.sol index 0f0068ac..f770cabc 100644 --- a/src/IthacaAccount.sol +++ b/src/IthacaAccount.sol @@ -23,6 +23,7 @@ import {LibNonce} from "./libraries/LibNonce.sol"; import {TokenTransferLib} from "./libraries/TokenTransferLib.sol"; import {LibTStack} from "./libraries/LibTStack.sol"; import {IIthacaAccount} from "./interfaces/IIthacaAccount.sol"; +import {MerkleProofLib} from "solady/utils/MerkleProofLib.sol"; /// @title Account /// @notice A account contract for EOAs with EIP7702. @@ -485,9 +486,43 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor { return isMultichain ? _hashTypedDataSansChainId(structHash) : _hashTypedData(structHash); } + /// @dev Verifies the merkle sig + /// - Note: Each leaf of the merkle tree should be a standard digest. + /// - The signature for using merkle verification is encoded as: + /// - bytes signature = abi.encode(bytes32[] proof, bytes32 root, bytes rootSig) + function _verifyMerkleSig(bytes32 digest, bytes calldata signature) + internal + view + returns (bool isValid, bytes32 keyHash) + { + bytes32[] calldata proof; + bytes32 root; + bytes calldata rootSig; + + assembly ("memory-safe") { + let proofOffset := add(signature.offset, calldataload(signature.offset)) + proof.length := calldataload(proofOffset) + proof.offset := add(proofOffset, 0x20) + + root := calldataload(add(signature.offset, 0x20)) + + let rootSigOffset := add(signature.offset, calldataload(add(signature.offset, 0x40))) + rootSig.length := calldataload(rootSigOffset) + rootSig.offset := add(rootSigOffset, 0x20) + } + + if (MerkleProofLib.verifyCalldata(proof, root, digest)) { + (isValid, keyHash) = unwrapAndValidateSignature(root, rootSig); + + return (isValid, keyHash); + } + + return (false, bytes32(0)); + } + /// @dev Returns if the signature is valid, along with its `keyHash`. /// The `signature` is a wrapped signature, given by - /// `abi.encodePacked(bytes(innerSignature), bytes32(keyHash), bool(prehash))`. + /// `abi.encode(bytes(innerSignature), bytes32(keyHash), bool(prehash), bool(merkle))`. function unwrapAndValidateSignature(bytes32 digest, bytes calldata signature) public view @@ -502,14 +537,20 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor { return (ECDSA.recoverCalldata(digest, signature) == address(this), 0); } + bool merkle; unchecked { - uint256 n = signature.length - 0x21; + uint256 n = signature.length - 0x22; keyHash = LibBytes.loadCalldata(signature, n); signature = LibBytes.truncatedCalldata(signature, n); // Do the prehash if last byte is non-zero. if (uint256(LibBytes.loadCalldata(signature, n + 1)) & 0xff != 0) { digest = EfficientHashLib.sha2(digest); // `sha256(abi.encode(digest))`. } + merkle = uint256(LibBytes.loadCalldata(signature, n + 2)) & 0xff != 0; + } + + if (merkle) { + return _verifyMerkleSig(digest, signature); } Key memory key = getKey(keyHash); @@ -747,6 +788,6 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor { returns (string memory name, string memory version) { name = "IthacaAccount"; - version = "0.5.10"; + version = "0.5.11"; } } diff --git a/test/Account.t.sol b/test/Account.t.sol index d612ef5a..87c007d9 100644 --- a/test/Account.t.sol +++ b/test/Account.t.sol @@ -6,6 +6,8 @@ import "./Base.t.sol"; import {MockSampleDelegateCallTarget} from "./utils/mocks/MockSampleDelegateCallTarget.sol"; import {LibEIP7702} from "solady/accounts/LibEIP7702.sol"; +import {Merkle} from "murky/Merkle.sol"; + contract AccountTest is BaseTest { struct _TestExecuteWithSignatureTemps { TargetFunctionPayload[] targetFunctionPayloads; @@ -68,6 +70,70 @@ contract AccountTest is BaseTest { } } + function testMerkleSignature(uint256 seed) public { + DelegatedEOA memory d = _randomEIP7702DelegatedEOA(); + PassKey memory k = _randomSecp256k1PassKey(); + k.k.isSuperAdmin = true; + + vm.prank(d.eoa); + d.d.authorize(k.k); + + // Fuzz number of leaves (2 to 256) + uint256 numLeaves = bound(seed, 2, 256); + bytes32[] memory leaves = new bytes32[](numLeaves); + + // Generate random leaves + for (uint256 i = 0; i < numLeaves; i++) { + leaves[i] = keccak256(abi.encodePacked("leaf", i, seed)); + } + + // Pick a random valid index + uint256 validIndex = seed % numLeaves; + bytes32 validDigest = leaves[validIndex]; + + Merkle merkle = new Merkle(); + bytes32 root = merkle.getRoot(leaves); + bytes32[] memory proof = merkle.getProof(leaves, validIndex); + + // Test valid merkle proof + { + bytes memory rootSig = abi.encode(proof, root, _sig(k, root)); + bytes memory sig = abi.encodePacked(rootSig, bytes32(0), uint8(0), uint8(1)); + + (bool isValid, bytes32 keyHash) = d.d.unwrapAndValidateSignature(validDigest, sig); + assertEq(isValid, true); + assertEq(keyHash, k.keyHash); + + // Test invalid digest not in tree + bytes32 invalidDigest = keccak256(abi.encodePacked("not_in_tree", seed)); + (isValid, keyHash) = d.d.unwrapAndValidateSignature(invalidDigest, sig); + assertEq(isValid, false); + assertEq(keyHash, bytes32(0)); + } + + // Test tampered proof (only if proof has elements to tamper) + if (proof.length > 0) { + bytes32[] memory tamperedProof = new bytes32[](proof.length); + for (uint256 i = 0; i < proof.length; i++) { + tamperedProof[i] = i == 0 ? bytes32(uint256(proof[i]) ^ 1) : proof[i]; + } + bytes memory tamperedRootSig = abi.encode(tamperedProof, root, _sig(k, root)); + bytes memory tamperedSig = + abi.encodePacked(tamperedRootSig, bytes32(0), uint8(0), uint8(1)); + (bool isValid,) = d.d.unwrapAndValidateSignature(validDigest, tamperedSig); + assertEq(isValid, false); + } + + // Test wrong root (tampered root should fail verification) + { + bytes32 wrongRoot = bytes32(uint256(root) ^ 1); + bytes memory wrongRootSig = abi.encode(proof, wrongRoot, _sig(k, wrongRoot)); + bytes memory wrongSig = abi.encodePacked(wrongRootSig, bytes32(0), uint8(0), uint8(1)); + (bool isValid,) = d.d.unwrapAndValidateSignature(validDigest, wrongSig); + assertEq(isValid, false); + } + } + function testSignatureCheckerApproval(bytes32) public { DelegatedEOA memory d = _randomEIP7702DelegatedEOA(); PassKey memory k = _randomSecp256k1PassKey(); @@ -93,8 +159,7 @@ contract AccountTest is BaseTest { bytes32 replaySafeDigest = keccak256(abi.encode(d.d.SIGN_TYPEHASH(), digest)); - (, string memory name, string memory version,, address verifyingContract,,) = - d.d.eip712Domain(); + (,,,, address verifyingContract,,) = d.d.eip712Domain(); bytes32 domain = keccak256( abi.encode( 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749, // DOMAIN_TYPEHASH with only verifyingContract diff --git a/test/Base.t.sol b/test/Base.t.sol index 94749645..87e82660 100644 --- a/test/Base.t.sol +++ b/test/Base.t.sol @@ -220,7 +220,7 @@ contract BaseTest is SoladyTest { { (bytes32 r, bytes32 s) = vm.signP256(privateKey, digest); s = P256.normalized(s); - return abi.encodePacked(abi.encode(r, s), keyHash, uint8(prehash ? 1 : 0)); + return abi.encodePacked(abi.encode(r, s), keyHash, uint8(prehash ? 1 : 0), uint8(0)); } function _secp256k1Sig(uint256 privateKey, bytes32 keyHash, bytes32 digest) @@ -237,7 +237,8 @@ contract BaseTest is SoladyTest { returns (bytes memory) { (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); - return abi.encodePacked(abi.encodePacked(r, s, v), keyHash, uint8(prehash ? 1 : 0)); + return + abi.encodePacked(abi.encodePacked(r, s, v), keyHash, uint8(prehash ? 1 : 0), uint8(0)); } function _multiSig(MultiSigKey memory k, bytes32 keyHash, bool preHash, bytes32 digest) @@ -250,7 +251,7 @@ contract BaseTest is SoladyTest { signatures[i] = _sig(k.owners[i], digest); } - return abi.encodePacked(abi.encode(signatures), keyHash, uint8(preHash ? 1 : 0)); + return abi.encodePacked(abi.encode(signatures), keyHash, uint8(preHash ? 1 : 0), uint8(0)); } function _estimateGasForEOAKey(Orchestrator.Intent memory i) @@ -282,7 +283,7 @@ contract BaseTest is SoladyTest { { (uint8 v, bytes32 r, bytes32 s) = vm.sign(uint128(_randomUniform()), bytes32(_randomUniform())); - i.signature = abi.encodePacked(abi.encodePacked(r, s, v), keyHash, uint8(0)); + i.signature = abi.encodePacked(abi.encodePacked(r, s, v), keyHash, uint8(0), uint8(0)); return _estimateGas(i); } @@ -290,7 +291,7 @@ contract BaseTest is SoladyTest { internal returns (uint256 gExecute, uint256 gCombined, uint256 gUsed) { - i.signature = abi.encodePacked(keccak256("a"), keccak256("b"), keyHash, uint8(0)); + i.signature = abi.encodePacked(keccak256("a"), keccak256("b"), keyHash, uint8(0), uint8(0)); return _estimateGas(i); } diff --git a/test/Orchestrator.t.sol b/test/Orchestrator.t.sol index d79c4cab..9f2ada0c 100644 --- a/test/Orchestrator.t.sol +++ b/test/Orchestrator.t.sol @@ -924,8 +924,9 @@ contract OrchestratorTest is BaseTest { if (_randomChance(16)) { u.combinedGas += 10_000; // Fill with some junk signature, but with the session `keyHash`. - u.signature = - abi.encodePacked(keccak256("a"), keccak256("b"), kSession.keyHash, uint8(0)); + u.signature = abi.encodePacked( + keccak256("a"), keccak256("b"), kSession.keyHash, uint8(0), uint8(0) + ); (t.gExecute, t.gCombined, t.gUsed) = _estimateGas(u); diff --git a/test/SimulateExecute.t.sol b/test/SimulateExecute.t.sol index 099d6acc..1e4a89a4 100644 --- a/test/SimulateExecute.t.sol +++ b/test/SimulateExecute.t.sol @@ -305,7 +305,8 @@ contract SimulateExecuteTest is BaseTest { // it needs to add the variance for non-precompile P256 verification. // We need the `keyHash` in the signature so that the simulation is able // to hit all the gas for the GuardedExecutor stuff for the `keyHash`. - i.signature = abi.encodePacked(keccak256("a"), keccak256("b"), k.keyHash, uint8(0)); + i.signature = + abi.encodePacked(keccak256("a"), keccak256("b"), k.keyHash, uint8(0), uint8(0)); uint256 snapshot = vm.snapshotState(); vm.deal(_ORIGIN_ADDRESS, type(uint192).max); From 0cb7a770acad764e0bd217f27a99bc2d0c5538b8 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 11 Apr 2026 20:29:55 +0700 Subject: [PATCH 13/19] Ithaca (#58) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add ERC20 transfer benchmark for Porto with passkey Add testERC20TransferViaPortoOrchestratorWithPasskey() benchmark to isolate passkey authentication costs from spend limit enforcement costs. - Uses secp256k1 passkey for transaction signing - Sets execution permissions for ERC20 transfers - Requires spend limits (set to max) for passkey operations - Gas cost: 116,094 (vs 97,030 without passkey, 117,083 with restrictive limits) - Provides clean measurement of passkey overhead (~19k gas) Resolves #272 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Tanishk Goyal * chore: use `auto-assign-pr.yml` org action * feat: add merkle sigs natively into the account * fix: tests * test: add more sophisticated fuzz test * chore: bump contract versions due to bytecode changes - Contracts updated: IthacaAccount * # Default ignored files * .snapshot_worktree * Delete .idea directory (#35) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Normalize file modes and update forge-std submodule Remove executable bit from deploy/execute_config.sh, deploy/verify_config.sh, and prep/check-bytecode-changes.js (mode 100755 → 100644). Update lib/forge-std submodule from commit c2cf7017d27c1d20e74ace4dacb6c5ce4bbbe899 to 07853315f998f94dc724e464b1bab1270888ee64. No other content changes. * Revert "Delete .idea directory (#35)" (#55) This reverts commit 512c204d2ce396d77dcf91b37ee514585ff337b3. --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Tanishk Goyal Co-authored-by: o-az Co-authored-by: Tanishk Goyal Co-authored-by: GitHub Action Co-authored-by: googleworkspace-bot --- .idea/.gitignore | 3 + .idea/account.iml | 13 + .idea/caches/deviceStreaming.xml | 1698 ++++++++++++++++++++ .idea/codeStyles/Project.xml | 117 ++ .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/copilot.data.migration.ask2agent.xml | 6 + .idea/markdown.xml | 8 + .idea/modules.xml | 8 + .idea/vcs.xml | 22 + test/Benchmark.t.sol | 46 + 10 files changed, 1926 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/account.iml create mode 100644 .idea/caches/deviceStreaming.xml create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/copilot.data.migration.ask2agent.xml create mode 100644 .idea/markdown.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/account.iml b/.idea/account.iml new file mode 100644 index 00000000..1ba0d660 --- /dev/null +++ b/.idea/account.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml new file mode 100644 index 00000000..cc4b430a --- /dev/null +++ b/.idea/caches/deviceStreaming.xml @@ -0,0 +1,1698 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..4bec4ea8 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,117 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..a55e7a17 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.ask2agent.xml b/.idea/copilot.data.migration.ask2agent.xml new file mode 100644 index 00000000..1f2ea11e --- /dev/null +++ b/.idea/copilot.data.migration.ask2agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/markdown.xml b/.idea/markdown.xml new file mode 100644 index 00000000..c61ea334 --- /dev/null +++ b/.idea/markdown.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..4ae0bf91 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..1995e14d --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Benchmark.t.sol b/test/Benchmark.t.sol index 2ce04524..13cbfcc1 100644 --- a/test/Benchmark.t.sol +++ b/test/Benchmark.t.sol @@ -1805,6 +1805,52 @@ contract BenchmarkTest is BaseTest { assertEq(paymentToken.balanceOf(address(0xbabe)), 1 ether); } + function testERC20TransferViaPortoOrchestratorWithPasskey() public { + vm.pauseGasMetering(); + + PassKey memory k = _randomSecp256k1PassKey(); + + DelegatedEOA memory d = _randomEIP7702DelegatedEOA(); + vm.deal(d.eoa, type(uint128).max); + _mint(address(paymentToken), d.eoa, type(uint128).max); + + vm.startPrank(d.eoa); + d.d.authorize(k.k); + d.d.setCanExecute( + k.keyHash, address(paymentToken), bytes4(keccak256("transfer(address,uint256)")), true + ); + d.d.setSpendLimit( + k.keyHash, address(paymentToken), GuardedExecutor.SpendPeriod.Hour, type(uint256).max + ); + d.d.setSpendLimit(k.keyHash, address(0), GuardedExecutor.SpendPeriod.Hour, type(uint256).max); + vm.stopPrank(); + + Orchestrator.Intent memory u; + u.eoa = d.eoa; + u.nonce = 0; + u.combinedGas = 1000000; + u.prePaymentAmount = 0 ether; + u.prePaymentMaxAmount = 0 ether; + u.totalPaymentAmount = 0.01 ether; + u.totalPaymentMaxAmount = 0.1 ether; + u.paymentToken = address(0); + // To maintain parity with the old benchmarks. + u.paymentRecipient = address(oc); + u.executionData = _transferExecutionData(address(paymentToken), address(0xbabe), 1 ether); + u.signature = _sig(k, u); + + bytes[] memory encodedIntents = new bytes[](1); + encodedIntents[0] = abi.encode(u); + + vm.resumeGasMetering(); + + oc.execute(encodedIntents); + + vm.pauseGasMetering(); + assertEq(paymentToken.balanceOf(address(0xbabe)), 1 ether); + vm.resumeGasMetering(); + } + function testERC20TransferDirect() public { DelegatedEOA memory d = _randomEIP7702DelegatedEOA(); _giveAccountSomeTokens(d.eoa); From 430926dc5aa41d8cfdf9e81fabc003fcb82fe2d7 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 11 Apr 2026 21:03:06 +0700 Subject: [PATCH 14/19] Chore bump contract versions due to bytecode changes (#60) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add merkle sigs natively into the account * fix: tests * test: add more sophisticated fuzz test * chore: bump contract versions due to bytecode changes - Contracts updated: IthacaAccount * pre-commit * deploy execute_config.sh * Update forge-std * Normalize file modes and update forge-std submodule Remove executable bit from deploy/execute_config.sh, deploy/verify_config.sh, and prep/check-bytecode-changes.js (mode 100755 → 100644). Update lib/forge-std submodule from commit c2cf7017d27c1d20e74ace4dacb6c5ce4bbbe899 to 07853315f998f94dc724e464b1bab1270888ee64. No other content changes. * Update LayerZero-v2 * Update LayerZero-v2 --------- Co-authored-by: Tanishk Goyal Co-authored-by: GitHub Action Co-authored-by: googleworkspace-bot From de3b18806de911b846dd1daab9d0880df28fd022 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Sat, 11 Apr 2026 21:12:20 +0700 Subject: [PATCH 15/19] Legion rouge (#63) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add merkle sigs natively into the account * fix: tests * test: add more sophisticated fuzz test * chore: bump contract versions due to bytecode changes - Contracts updated: IthacaAccount * Add .circleci/config.yml (#1) Add CircleCI configuration file to set up a basic pipeline with a say-hello job and workflow CI: Add .circleci/config.yml to define a say-hello job that checks out the code and prints a greeting using the cimg/base Docker image Add a workflow to orchestrate the say-hello job under the CircleCI 2.1 engine * Update ci.yaml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml (#2) CI: Update Forge test command to use --rerun and increase verbosity to -vvvvv Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml (#4) Enhance the CI test step to automatically rerun failed tests and increase verbosity in Forge. CI: Enable the --rerun flag for Forge tests to retry failures automatically Increase Forge test verbosity from -vvv to -vvvvv Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml (#5) Change Forge test invocation from "forge test --rerun -vvvvv" to "forge test -vvv" Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create CNAME * Revert "Merge branch 'master'" This reverts commit 6c02fbff6486bb206c08af83d948830ace042353, reversing changes made to a317ddb944a1f5ed7294092790902da6b255f73f. * Create CNAME (#9) * Update ci.yaml (#2) CI: Update Forge test command to use --rerun and increase verbosity to -vvvvv Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml (#4) Enhance the CI test step to automatically rerun failed tests and increase verbosity in Forge. CI: Enable the --rerun flag for Forge tests to retry failures automatically Increase Forge test verbosity from -vvv to -vvvvv Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml (#5) Change Forge test invocation from "forge test --rerun -vvvvv" to "forge test -vvv" Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create CNAME * Revert "Merge branch 'master'" This reverts commit 6c02fbff6486bb206c08af83d948830ace042353, reversing changes made to a317ddb944a1f5ed7294092790902da6b255f73f. --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory (#27) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * test: add tests for getContextKeyHash in Account.t.sol (#412) * Normalize file modes and update forge-std submodule Remove executable bit from deploy/execute_config.sh, deploy/verify_config.sh, and prep/check-bytecode-changes.js (mode 100755 → 100644). Update lib/forge-std submodule from commit c2cf7017d27c1d20e74ace4dacb6c5ce4bbbe899 to 07853315f998f94dc724e464b1bab1270888ee64. No other content changes. * Update LayerZero-v2 --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Tanishk Goyal Co-authored-by: GitHub Action Co-authored-by: googleworkspace-bot From 7194a3182114111d2a5bcaa79823d06f9c058714 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 16 Apr 2026 01:43:43 +0000 Subject: [PATCH 16/19] feat: add merkle sigs natively into the account legion rouge (#70) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add merkle sigs natively into the account * fix: tests * test: add more sophisticated fuzz test * chore: bump contract versions due to bytecode changes - Contracts updated: IthacaAccount * Add .circleci/config.yml (#1) Add CircleCI configuration file to set up a basic pipeline with a say-hello job and workflow CI: Add .circleci/config.yml to define a say-hello job that checks out the code and prints a greeting using the cimg/base Docker image Add a workflow to orchestrate the say-hello job under the CircleCI 2.1 engine * Update ci.yaml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml (#2) CI: Update Forge test command to use --rerun and increase verbosity to -vvvvv Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml (#4) Enhance the CI test step to automatically rerun failed tests and increase verbosity in Forge. CI: Enable the --rerun flag for Forge tests to retry failures automatically Increase Forge test verbosity from -vvv to -vvvvv Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml (#5) Change Forge test invocation from "forge test --rerun -vvvvv" to "forge test -vvv" Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create CNAME * Revert "Merge branch 'master'" This reverts commit 6c02fbff6486bb206c08af83d948830ace042353, reversing changes made to a317ddb944a1f5ed7294092790902da6b255f73f. * Create CNAME (#9) * Update ci.yaml (#2) CI: Update Forge test command to use --rerun and increase verbosity to -vvvvv Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml (#4) Enhance the CI test step to automatically rerun failed tests and increase verbosity in Forge. CI: Enable the --rerun flag for Forge tests to retry failures automatically Increase Forge test verbosity from -vvv to -vvvvv Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml (#5) Change Forge test invocation from "forge test --rerun -vvvvv" to "forge test -vvv" Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Update ci.yaml Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Create CNAME * Revert "Merge branch 'master'" This reverts commit 6c02fbff6486bb206c08af83d948830ace042353, reversing changes made to a317ddb944a1f5ed7294092790902da6b255f73f. --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * Delete .circleci directory (#27) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> * pre-commit * deploy execute_config.sh * Update forge-std * test: add tests for getContextKeyHash in Account.t.sol (#412) * Normalize file modes and update forge-std submodule Remove executable bit from deploy/execute_config.sh, deploy/verify_config.sh, and prep/check-bytecode-changes.js (mode 100755 → 100644). Update lib/forge-std submodule from commit c2cf7017d27c1d20e74ace4dacb6c5ce4bbbe899 to 07853315f998f94dc724e464b1bab1270888ee64. No other content changes. * Update LayerZero-v2 * Update LayerZero-v2 --------- Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Tanishk Goyal Co-authored-by: GitHub Action Co-authored-by: googleworkspace-bot From d21b185a03b578daf994f04456b3c9c7e9e4a1ac Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Thu, 16 Apr 2026 23:01:30 +0000 Subject: [PATCH 17/19] Potential fix for code scanning alert no. 3: Workflow does not contain permissions (#66) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/test-infra.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test-infra.yml b/.github/workflows/test-infra.yml index 6ce9859b..8b69956d 100644 --- a/.github/workflows/test-infra.yml +++ b/.github/workflows/test-infra.yml @@ -1,4 +1,6 @@ name: Manual Deployment Execution +permissions: + contents: read on: workflow_dispatch: From bf50ece54e69eef4ec4c3cfc0a788ee6cab4ff08 Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 17 Apr 2026 01:13:29 +0000 Subject: [PATCH 18/19] Potential fix for code scanning alert no. 2: Workflow does not contain permissions (#67) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/manual-deployment.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/manual-deployment.yml b/.github/workflows/manual-deployment.yml index 6ce9859b..7a3a3f27 100644 --- a/.github/workflows/manual-deployment.yml +++ b/.github/workflows/manual-deployment.yml @@ -1,5 +1,8 @@ name: Manual Deployment Execution +permissions: + contents: read + on: workflow_dispatch: inputs: From a001b1ed3da4b22c8ea4aebf308a39929fa1b5ab Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Fri, 17 Apr 2026 01:40:31 +0000 Subject: [PATCH 19/19] Potential fix for code scanning alert no. 1: Workflow does not contain permissions (#68) Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/claude-code.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/claude-code.yml b/.github/workflows/claude-code.yml index ac2e8dbb..e069019a 100644 --- a/.github/workflows/claude-code.yml +++ b/.github/workflows/claude-code.yml @@ -17,6 +17,8 @@ jobs: check-permissions: name: Check permissions runs-on: ubuntu-latest + permissions: + contents: read outputs: has-permission: ${{ steps.check.outputs.has-permission }} steps: