From 151bca84bc567523d188a86a03f239e625692377 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Thu, 10 Oct 2024 18:07:38 +0400 Subject: [PATCH 1/2] feat: more solidity tests (#8) * add solidity tests and fix links * update eof gas --- chapter1/bls-multisig/README.md | 2 +- chapter1/bls-multisig/python/README.md | 2 +- chapter1/bls-multisig/rust/README.md | 2 +- chapter1/bls-multisig/rust/src/main.rs | 2 +- .../contracts/src/SimpleDelegateContract.sol | 4 +- chapter1/contracts/test/BLS.t.sol | 94 +++++++++++++++++++ .../test/SimpleDelegateContract.t.sol | 23 +++++ chapter1/eof/README.md | 23 +++-- chapter1/simple-7702/README.md | 18 ++-- 9 files changed, 144 insertions(+), 26 deletions(-) create mode 100644 chapter1/contracts/test/BLS.t.sol create mode 100644 chapter1/contracts/test/SimpleDelegateContract.t.sol diff --git a/chapter1/bls-multisig/README.md b/chapter1/bls-multisig/README.md index 9912d18..8ea5949 100644 --- a/chapter1/bls-multisig/README.md +++ b/chapter1/bls-multisig/README.md @@ -8,7 +8,7 @@ EIP-2537 introduces a set of precompiled contracts enabling elliptic curve opera ## Implementation ### Contract -We demonstrate a simple multisignature contract [BLSMultisig](../contracts/BLSMultisig.sol) which keeps a list of signers public keys and allows executing arbitrary operations which are signed by a subset of signers. Both stored public keys and signatures can be aggregated, thus allowing for much better scalability for large numbers of signers vs ECDSA. Let's walk through the contract's code. +We demonstrate a simple multisignature contract [BLSMultisig](../contracts/src/BLSMultisig.sol) which keeps a list of signers public keys and allows executing arbitrary operations which are signed by a subset of signers. Both stored public keys and signatures can be aggregated, thus allowing for much better scalability for large numbers of signers vs ECDSA. Let's walk through the contract's code. BLS signing operates on two curves: G1 and G2. In our case we will store public keys on G1 while signatures and messages will be on G2. To sign or verify a message consisting of arbitrary bytes, we need to firstly map the message to a point on G2. There is a commonly used [algorithm](https://datatracker.ietf.org/doc/html/rfc9380#name-hashing-to-a-finite-field) for this mapping, we are using its implementation in Solidity: diff --git a/chapter1/bls-multisig/python/README.md b/chapter1/bls-multisig/python/README.md index c100c60..6ef4073 100644 --- a/chapter1/bls-multisig/python/README.md +++ b/chapter1/bls-multisig/python/README.md @@ -1,6 +1,6 @@ # Python BLS Multisig -This example demonstrates an integration of [BlsMultisig](../../contracts/BLSMultisig.sol) with Python. +This example demonstrates an integration of [BlsMultisig](../../contracts/src/BLSMultisig.sol) with Python. ## Running the example diff --git a/chapter1/bls-multisig/rust/README.md b/chapter1/bls-multisig/rust/README.md index b998388..c741e61 100644 --- a/chapter1/bls-multisig/rust/README.md +++ b/chapter1/bls-multisig/rust/README.md @@ -1,6 +1,6 @@ # Rust BLS Multisig -This example demonstrates an integration of [BlsMultisig](../../contracts/BLSMultisig.sol) with Rust. +This example demonstrates an integration of [BlsMultisig](../../contracts/src/BLSMultisig.sol) with Rust. ## Running the example diff --git a/chapter1/bls-multisig/rust/src/main.rs b/chapter1/bls-multisig/rust/src/main.rs index 25497e3..38f1778 100644 --- a/chapter1/bls-multisig/rust/src/main.rs +++ b/chapter1/bls-multisig/rust/src/main.rs @@ -12,7 +12,7 @@ alloy::sol! { #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord)] #[sol(rpc)] BLSMultisig, - "../../out/BLSMultisig.sol/BLSMultisig.json" + "../../contracts/out/BLSMultisig.sol/BLSMultisig.json" } /// Generates `num` BLS keys and returns them as a tuple of private and public keys diff --git a/chapter1/contracts/src/SimpleDelegateContract.sol b/chapter1/contracts/src/SimpleDelegateContract.sol index eb8f9c3..757bd66 100644 --- a/chapter1/contracts/src/SimpleDelegateContract.sol +++ b/chapter1/contracts/src/SimpleDelegateContract.sol @@ -13,11 +13,11 @@ contract SimpleDelegateContract { function execute(Call[] memory calls) external payable { for (uint256 i = 0; i < calls.length; i++) { Call memory call = calls[i]; - (bool success, ) = call.to.call{value: call.value}(call.data); + (bool success,) = call.to.call{value: call.value}(call.data); require(success, "Call failed"); emit Executed(call.to, call.value, call.data); } } receive() external payable {} -} \ No newline at end of file +} diff --git a/chapter1/contracts/test/BLS.t.sol b/chapter1/contracts/test/BLS.t.sol new file mode 100644 index 0000000..d66d1a8 --- /dev/null +++ b/chapter1/contracts/test/BLS.t.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {Test, console} from "forge-std/Test.sol"; +import {BLS} from "../src/libraries/BLS.sol"; + +/// @notice A simple test demonstrating BLS signature verification. +contract BLSTest is Test { + /// @notice The generator point in G1 (P1). + BLS.G1Point G1_GENERATOR = BLS.G1Point( + BLS.Fp( + 31827880280837800241567138048534752271, + 88385725958748408079899006800036250932223001591707578097800747617502997169851 + ), + BLS.Fp( + 11568204302792691131076548377920244452, + 114417265404584670498511149331300188430316142484413708742216858159411894806497 + ) + ); + + /// @notice The negated generator point in G1 (-P1). + BLS.G1Point NEGATED_G1_GENERATOR = BLS.G1Point( + BLS.Fp( + 31827880280837800241567138048534752271, + 88385725958748408079899006800036250932223001591707578097800747617502997169851 + ), + BLS.Fp( + 22997279242622214937712647648895181298, + 46816884707101390882112958134453447585552332943769894357249934112654335001290 + ) + ); + + /// @dev Demonstrates the signing and verification of a message. + function test() public { + // Obtain the private key as a random scalar. + uint256 privateKey = vm.randomUint(); + + // Public key is the generator point multiplied by the private key. + BLS.G1Point memory publicKey = BLS.G1Mul(G1_GENERATOR, privateKey); + + // Compute the message point by mapping message's keccak256 hash to a point in G2. + bytes memory message = "hello world"; + BLS.G2Point memory messagePoint = BLS.MapFp2ToG2(BLS.Fp2(BLS.Fp(0, 0), BLS.Fp(0, uint256(keccak256(message))))); + + // Obtain the signature by multiplying the message point by the private key. + BLS.G2Point memory signature = BLS.G2Mul(messagePoint, privateKey); + + // Invoke the pairing check to verify the signature. + BLS.G1Point[] memory g1Points = new BLS.G1Point[](2); + g1Points[0] = NEGATED_G1_GENERATOR; + g1Points[1] = publicKey; + + BLS.G2Point[] memory g2Points = new BLS.G2Point[](2); + g2Points[0] = signature; + g2Points[1] = messagePoint; + + assertTrue(BLS.Pairing(g1Points, g2Points)); + } + + /// @dev Demonstrates the aggregation and verification of two signatures. + function testAggregated() public { + // private keys + uint256 sk1 = vm.randomUint(); + uint256 sk2 = vm.randomUint(); + + // public keys + BLS.G1Point memory pk1 = BLS.G1Mul(G1_GENERATOR, sk1); + BLS.G1Point memory pk2 = BLS.G1Mul(G1_GENERATOR, sk2); + + // Compute the message point by mapping message's keccak256 hash to a point in G2. + bytes memory message = "hello world"; + BLS.G2Point memory messagePoint = BLS.MapFp2ToG2(BLS.Fp2(BLS.Fp(0, 0), BLS.Fp(0, uint256(keccak256(message))))); + + // signatures + BLS.G2Point memory sig1 = BLS.G2Mul(messagePoint, sk1); + BLS.G2Point memory sig2 = BLS.G2Mul(messagePoint, sk2); + + // aggregated signature + BLS.G2Point memory sig = BLS.G2Add(sig1, sig2); + + // Invoke the pairing check to verify the signature. + BLS.G1Point[] memory g1Points = new BLS.G1Point[](3); + g1Points[0] = NEGATED_G1_GENERATOR; + g1Points[1] = pk1; + g1Points[2] = pk2; + + BLS.G2Point[] memory g2Points = new BLS.G2Point[](3); + g2Points[0] = sig; + g2Points[1] = messagePoint; + g2Points[2] = messagePoint; + + assertTrue(BLS.Pairing(g1Points, g2Points)); + } +} diff --git a/chapter1/contracts/test/SimpleDelegateContract.t.sol b/chapter1/contracts/test/SimpleDelegateContract.t.sol new file mode 100644 index 0000000..fc400e1 --- /dev/null +++ b/chapter1/contracts/test/SimpleDelegateContract.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {Test, console} from "forge-std/Test.sol"; +import {SimpleDelegateContract} from "../src/SimpleDelegateContract.sol"; + +contract SimpleDelegateContractTest is Test { + function test() public { + address payable ALICE = payable(address(0xa11ce)); + SimpleDelegateContract delegation = new SimpleDelegateContract(); + + // let's inject EIP-7702 delegation designation into ALICE's code to + // make it forward all calls to the deployed contract + vm.etch(ALICE, bytes.concat(hex"ef0100", abi.encodePacked(delegation))); + + ALICE.call{value: 1e18}(""); + + SimpleDelegateContract.Call[] memory calls = new SimpleDelegateContract.Call[](1); + calls[0] = SimpleDelegateContract.Call({data: "", to: address(0), value: 1e18}); + + SimpleDelegateContract(ALICE).execute(calls); + } +} diff --git a/chapter1/eof/README.md b/chapter1/eof/README.md index 77ecd98..b85c28a 100644 --- a/chapter1/eof/README.md +++ b/chapter1/eof/README.md @@ -101,24 +101,31 @@ Contracts compiled for EOF tend to consume less gas. You can verify this yoursel ```bash $ forge test -Ran 1 test for test/P256.t.sol:BLSTest +Ran 1 test for contracts/test/SimpleDelegateContract.t.sol:SimpleDelegateContractTest +[PASS] test() (gas: 301928) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.87ms (2.15ms CPU time) + +Ran 1 test for contracts/test/P256.t.sol:BLSTest [PASS] test() (gas: 8951) -Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.74ms (3.89ms CPU time) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 8.86ms (4.14ms CPU time) -Ran 2 tests for test/BLS.t.sol:BLSTest +Ran 2 tests for contracts/test/BLS.t.sol:BLSTest [PASS] test() (gas: 321781) [PASS] testAggregated() (gas: 439327) -Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 9.27ms (9.88ms CPU time) $ forge test --eof -Ran 1 test for test/P256.t.sol:BLSTest +Ran 1 test for contracts/test/SimpleDelegateContract.t.sol:SimpleDelegateContractTest +[PASS] test() (gas: 261751) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 9.37ms (2.47ms CPU time) + +Ran 1 test for contracts/test/P256.t.sol:BLSTest [PASS] test() (gas: 8191) -Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 10.62ms (4.74ms CPU time) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 11.44ms (4.54ms CPU time) -Ran 2 tests for test/BLS.t.sol:BLSTest +Ran 2 tests for contracts/test/BLS.t.sol:BLSTest [PASS] test() (gas: 314754) [PASS] testAggregated() (gas: 425091) -Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 11.04ms (9.69ms CPU time) +Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 12.74ms (11.11ms CPU time) ``` As you can see, EOF-compiled contracts are 5-10% more gas efficient. diff --git a/chapter1/simple-7702/README.md b/chapter1/simple-7702/README.md index 2457246..d4c9a1a 100644 --- a/chapter1/simple-7702/README.md +++ b/chapter1/simple-7702/README.md @@ -67,17 +67,11 @@ Note that in this over-simplified example, you’ll already see some issues e.g. To test this delegation feature, you may use the `vm.etch` cheatcode in your tests as follows: -```bash -import {Test} from "forge-std/Test.sol"; -import {SimpleDelegateContract} from "../src/SimpleDelegateContract.sol"; - -contract DelegationTest is Test { - function test() public { - SimpleDelegateContract delegation = new SimpleDelegateContract(); - // this sets ALICE's EOA code to the deployed contract code - vm.etch(ALICE, address(delegation).code); - } -} +```solidity +// this will inject delegation designation into ALICE's code +vm.etch(ALICE, bytes.concat(hex"ef0100", abi.encodePacked(contractToDelegate))); ``` -This cheat code allows you to **simulate that ALICE's account is no longer a regular EOA but a contract**(like `P256Delegation`) and then test how delegations or transactions behave from that new "smart contract" EOA. \ No newline at end of file +This cheat code allows you to **simulate that ALICE's account is no longer a regular EOA but a contract**(like `P256Delegation`) and then test how delegations or transactions behave from that new "smart contract" EOA. + +You can check out complete example in [SimpleDelegateContract.t.sol](../contracts/test/SimpleDelegateContract.t.sol) \ No newline at end of file From 401de3f4d785a0f1a26ad32bbf65b68c9de1918f Mon Sep 17 00:00:00 2001 From: Jennifer Date: Thu, 10 Oct 2024 15:12:55 +0100 Subject: [PATCH 2/2] Update README.md (#9) Add context on what chapters are, formatting --- README.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 834be96..98ff2eb 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,23 @@ # Odyssey Examples -## Overview +This repository provides a step-by-step walk through for builders interested in the developer-preview features available on [Odyssey](https://www.ithaca.xyz/updates/introducing-ithaca), a L2 built for developers to innovate. We are rolling out each Chapter with new features for you to build on. -This repository provides a step-by-step walk through for builders interested in the developer-preview features available on [Odyssey](https://www.ithaca.xyz/updates/introducing-ithaca). Each chapter provides examples of new features added. - -## Chapter 1 -- [Simple Example for EIP-7702](./chapter1/simple-7702/): Basic example to showcase how EIP-7702 transactions work -- [Delegate Account to p256 key](./chapter1/delegate-p256/): Step-by-step walk-through of how EIP-7702+EIP-7212 provide the ability to sign a message with a P256 key -- [BLS Multisig](./chapter1/bls-multisig/): Examples in Python and Rust to showcase Multisig based on BLS signatures verified through precompiles from EIP-2537 +### Chapter 1 +- [Simple Example for EIP-7702](./chapter1/simple-7702/): Showcases how EIP-7702 transactions work +- [Delegate an account to a p256 key](./chapter1/delegate-p256/): Describes how EIP-7702+EIP-7212 provide the ability to sign a message with a P256 key +- [BLS Multisig](./chapter1/bls-multisig/): In-depth walk-through how to implement a Multisig based on BLS signatures verified through precompiles from EIP-2537 - [EOF](./chapter1/eof/): Instructions on how to deploy and inspect contracts in the new EOF format -## Build & Run +### Build & Test -Use foundry to build and run smart contracts in the repo +Use [foundry](https://github.com/foundry-rs/foundry) to build and run smart contracts in the repository: ```bash # Make sure foundry is up to date foundryup # Compile contracts and run tests in chapter 1 -cd chapter1 +cd chapter1/ forge build forge test -```` \ No newline at end of file +````