diff --git a/package/version b/package/version index 59e55c1f1..660b5ae0c 100644 --- a/package/version +++ b/package/version @@ -1 +1 @@ -0.1.82 +0.1.83 diff --git a/pyproject.toml b/pyproject.toml index b2f785a6a..03f5fb218 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "kontrol" -version = "0.1.82" +version = "0.1.83" description = "Foundry integration for KEVM" authors = [ "Runtime Verification, Inc. ", diff --git a/src/kontrol/__init__.py b/src/kontrol/__init__.py index d8193f906..5c3289b28 100644 --- a/src/kontrol/__init__.py +++ b/src/kontrol/__init__.py @@ -5,4 +5,5 @@ if TYPE_CHECKING: from typing import Final -VERSION: Final = '0.1.82' + +VERSION: Final = '0.1.83' diff --git a/src/tests/integration/test-data/foundry-prove-all b/src/tests/integration/test-data/foundry-prove-all index ffae5dd35..38d9606bf 100644 --- a/src/tests/integration/test-data/foundry-prove-all +++ b/src/tests/integration/test-data/foundry-prove-all @@ -139,6 +139,7 @@ LoopsTest.test_sum_100() LoopsTest.test_sum_1000() LoopsTest.testSumToN(uint256) LoopsTest.testSumToNBroken(uint256) +MerkleProofTest.testValidateMerkleProof(bytes32,uint256,bytes32,bytes32,bytes32,bytes32) MethodDisambiguateTest.test_method_call() MockCallTest.testMockCall() MockCallTest.testMockCalls() diff --git a/src/tests/integration/test-data/foundry-prove-skip b/src/tests/integration/test-data/foundry-prove-skip index b7246b03d..e79d057de 100644 --- a/src/tests/integration/test-data/foundry-prove-skip +++ b/src/tests/integration/test-data/foundry-prove-skip @@ -85,6 +85,7 @@ LoopsTest.test_sum_100() LoopsTest.test_sum_1000() LoopsTest.testSumToN(uint256) LoopsTest.testSumToNBroken(uint256) +MerkleProofTest.testValidateMerkleProof(bytes32,uint256,bytes32,bytes32,bytes32,bytes32) MockCallTest.testMockCall() MockCallTest.testMockCalls() MockCallTest.testMockCallValue() diff --git a/src/tests/integration/test-data/foundry/test/MerkleProofTest.t.sol b/src/tests/integration/test-data/foundry/test/MerkleProofTest.t.sol new file mode 100644 index 000000000..39c39d4d7 --- /dev/null +++ b/src/tests/integration/test-data/foundry/test/MerkleProofTest.t.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.13; + +import "forge-std/Test.sol"; + +contract MerkleProofTest is Test { + + /** + * The purpose of this test is to evaluate how well we handle branching. + * When we assume that _validateMerkleProof holds, the execution splits + * into 2 ** proof.length (in this case, 8) branches. We want to be able + * to handle this amount of branching without losing information, so we + * can still prove that it holds in the final assertTrue. + * + * Increase the length of the proof to evaluate scalability. + */ + function testValidateMerkleProof( + bytes32 leaf, + uint256 index, + bytes32 root, + bytes32 proofElement0, + bytes32 proofElement1, + bytes32 proofElement2 + ) external { + uint256 proofLength = 3; + + bytes32[] memory proof = new bytes32[](proofLength); + proof[0] = proofElement0; + proof[1] = proofElement1; + proof[2] = proofElement2; + + vm.assume(index < 2 ** proof.length); + + vm.assume(_validateMerkleProof(leaf, index, root, proof)); + + assertTrue(_validateMerkleProof(leaf, index, root, proof)); + } + + /** + * Checks that the proof is valid for a Merkle tree with the given root + * where the given leaf is at the given index. + */ + function _validateMerkleProof( + bytes32 leaf, + uint256 index, + bytes32 root, + bytes32[] memory proof + ) internal pure returns (bool) { + // Number of leaves is exponential on the tree depth + require(index < 2 ** proof.length); + + bytes32 hash = leaf; + + for (uint256 i; i < proof.length; i++) { + if (index % 2 == 0) { + // If index is even, proof element is to the right + hash = keccak256(abi.encodePacked(hash, proof[i])); + } else { + // If index is odd, proof element is to the left + hash = keccak256(abi.encodePacked(proof[i], hash)); + } + + // Go up one level in the tree + index = index / 2; + } + + return hash == root; + } +}