Skip to content

Commit c9f0f0e

Browse files
authored
feat: add the ability to run benchmarks on the QGB contract (#218)
<!-- Please read and fill out this form before submitting your PR. Please make sure you have reviewed our contributors guide before submitting your first PR. --> ## Overview <!-- Please provide an explanation of the PR, including the appropriate context, background, goal, and rationale. If there is an issue with this information, please provide a tl;dr and link the issue. --> ## Checklist <!-- Please complete the checklist to ensure that the PR is ready to be reviewed. IMPORTANT: PRs should be left in Draft until the below checklist is completed. --> - [ ] New and updated code has appropriate documentation - [ ] New and updated code has new and/or updated testing - [ ] Required CI checks are passing - [ ] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords
1 parent e71c325 commit c9f0f0e

File tree

2 files changed

+126
-0
lines changed

2 files changed

+126
-0
lines changed

Diff for: foundry.toml

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
[profile.default]
22
solc_version = "0.8.20"
33
via_ir = true
4+
gas_reports = ["*"]

Diff for: src/test/BlobstreamBenchmark.t.sol

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.19;
3+
4+
import "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
5+
6+
import "../Constants.sol";
7+
import "../DataRootTuple.sol";
8+
import "../Blobstream.sol";
9+
import "../lib/tree/binary/BinaryMerkleProof.sol";
10+
11+
import "ds-test/test.sol";
12+
13+
interface CheatCodes {
14+
function addr(uint256 privateKey) external returns (address);
15+
function sign(uint256 privateKey, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s);
16+
function deriveKey(string calldata, string calldata, uint32) external returns (uint256);
17+
}
18+
19+
/// @notice Example command to run the benchmark:
20+
/// `forge test --match-test testBenchmarkSubmitDataRootTupleRoot -vvvvvv --gas-report`.
21+
/// To change the validator set size, change the `numberOfValidators` constant.
22+
/// To make custom calculations of the gas, you can use the `gasleft()` solidity
23+
/// built-in function.
24+
/// The following answer has some insights on using that:
25+
/// https://ethereum.stackexchange.com/a/132325/65649
26+
/// The gas estimations might not be accurate to the real cost in a real network,
27+
/// and that's because foundry doesn't track calldata cost. source:
28+
/// https://github.com/foundry-rs/foundry/issues/3475#issuecomment-1469940917
29+
/// To have accurate results, make sure to add the following costs:
30+
/// A byte of calldata costs either 4 gas (if it is zero) or 16 gas (if it is any other value).
31+
contract Benchmark is DSTest {
32+
uint256 private constant numberOfValidators = 100;
33+
uint256 private constant numberOfSigners = 30;
34+
35+
// Private keys used for test signatures.
36+
uint256[] private privateKeys;
37+
38+
Blobstream private bridge;
39+
40+
Validator[] private validators;
41+
uint256 private totalValidatorPower = 1000000;
42+
uint256 private dataTupleRootNonce = 0;
43+
44+
// Set up Foundry cheatcodes.
45+
CheatCodes cheats = CheatCodes(HEVM_ADDRESS);
46+
47+
function setUp() public {
48+
uint256 initialVelsetNonce = 0;
49+
privateKeys = derivePrivateKeys(numberOfValidators);
50+
validators = initializeValidators(privateKeys);
51+
52+
bytes32 hash = computeValidatorSetHash(validators);
53+
bridge = new Blobstream();
54+
bridge.initialize(initialVelsetNonce, (2 * totalValidatorPower) / 3, hash);
55+
}
56+
57+
function testBenchmarkSubmitDataRootTupleRoot() public {
58+
uint256 initialVelsetNonce = 0;
59+
uint256 nonce = 1;
60+
61+
// 32 bytes, chosen at random.
62+
bytes32 newTupleRoot = 0x0de92bac0b356560d821f8e7b6f5c9fe4f3f88f6c822283efd7ab51ad56a640e;
63+
bytes32 newDataRootTupleRoot = domainSeparateDataRootTupleRoot(nonce, newTupleRoot);
64+
65+
// Signature for the update.
66+
Signature[] memory sigs = new Signature[](numberOfValidators);
67+
bytes32 digest_eip191 = ECDSA.toEthSignedMessageHash(newDataRootTupleRoot);
68+
uint256 threshold = 2 * totalValidatorPower / 3;
69+
uint256 cumulatedPower = 0;
70+
for (uint256 i = 0; i < numberOfValidators; i++) {
71+
if (cumulatedPower > threshold) {
72+
break;
73+
}
74+
(uint8 v, bytes32 r, bytes32 s) = cheats.sign(privateKeys[i], digest_eip191);
75+
sigs[i] = Signature(v, r, s);
76+
cumulatedPower += validators[i].power;
77+
}
78+
79+
// these are called here so that they're part of the gas report.
80+
// uint256 currentPowerThreshold = (2 * votingPower * numberOfValidators) / 3;
81+
// bytes32 currentValidatorSetHash = bridge.computeValidatorSetHash(validators);
82+
// bridge.domainSeparateValidatorSetHash(nonce, currentPowerThreshold, currentValidatorSetHash);
83+
// bridge.checkValidatorSignatures(validators, sigs, newDataRootTupleRoot, currentPowerThreshold);
84+
85+
bridge.submitDataRootTupleRoot(nonce, initialVelsetNonce, newTupleRoot, validators, sigs);
86+
}
87+
88+
function computeValidatorSetHash(Validator[] memory _validators) private pure returns (bytes32) {
89+
return keccak256(abi.encode(_validators));
90+
}
91+
92+
function domainSeparateDataRootTupleRoot(uint256 _nonce, bytes32 _dataRootTupleRoot)
93+
private
94+
pure
95+
returns (bytes32)
96+
{
97+
bytes32 c = keccak256(abi.encode(DATA_ROOT_TUPLE_ROOT_DOMAIN_SEPARATOR, _nonce, _dataRootTupleRoot));
98+
99+
return c;
100+
}
101+
102+
function derivePrivateKeys(uint256 count) private returns (uint256[] memory) {
103+
string memory mnemonic = "test test test test test test test test test test test junk";
104+
uint256[] memory keys = new uint256[](count);
105+
for (uint32 i = 0; i < count; i++) {
106+
keys[i] = cheats.deriveKey(mnemonic, "m/44'/60'/0'/0", i);
107+
}
108+
return keys;
109+
}
110+
111+
function initializeValidators(uint256[] memory keys) private returns (Validator[] memory) {
112+
Validator[] memory vs = new Validator[](keys.length);
113+
uint256 threshold = 2 * totalValidatorPower / 3;
114+
uint256 primaryPower = threshold / (numberOfSigners - 1);
115+
uint256 secondaryPower = (totalValidatorPower - threshold) / (numberOfValidators - numberOfSigners + 1);
116+
for (uint256 i = 0; i < keys.length; i++) {
117+
if (i < numberOfSigners) {
118+
vs[i] = Validator(cheats.addr(keys[i]), primaryPower);
119+
} else {
120+
vs[i] = Validator(cheats.addr(keys[i]), secondaryPower);
121+
}
122+
}
123+
return vs;
124+
}
125+
}

0 commit comments

Comments
 (0)