Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Full Code Review #24

Draft
wants to merge 141 commits into
base: review/full
Choose a base branch
from
Draft

[WIP] Full Code Review #24

wants to merge 141 commits into from

Conversation

iamnp
Copy link

@iamnp iamnp commented Oct 26, 2024

API-centered code review of the Depot project

Psirex and others added 30 commits November 29, 2023 13:45
Ivan-Feofanov and others added 25 commits October 2, 2024 16:19
feat: use cache only if it's enabled + refactor
Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) from 6.2.2 to 6.3.0.
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md)
- [Commits](pillarjs/path-to-regexp@v6.2.2...v6.3.0)

---
updated-dependencies:
- dependency-name: path-to-regexp
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
…to-regexp-6.3.0

build(deps): bump path-to-regexp from 6.2.2 to 6.3.0
function isValid(bytes: unknown): bytes is HexStr {
if (typeof bytes !== "string") return false;
const stripped = strip0x(bytes);
return stripped.length % 2 === 0 && /^[a-fA-f0-9]+$/.test(strip0x(bytes));

Check warning

Code scanning / CodeQL

Overly permissive regular expression range Medium

Suspicious character range that overlaps with a-f in the same character class, and is equivalent to [A-Z\[\]^_`a-f].

Copilot Autofix AI about 1 month ago

To fix the problem, we need to correct the regular expression to ensure it only matches valid hexadecimal characters. The best way to do this is to replace the overly permissive range A-f with the correct range A-F. This change will ensure that the regular expression only matches characters in the ranges 0-9, a-f, and A-F.

  • General fix: Replace the incorrect range A-f with the correct range A-F.
  • Detailed fix: Update the regular expression on line 68 in the file src/common/bytes.ts to use A-F instead of A-f.
  • Specific changes: Modify the regular expression in the isValid function to /^[a-fA-F0-9]+$/.
  • Requirements: No additional methods, imports, or definitions are needed to implement this change.
Suggested changeset 1
src/common/bytes.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/common/bytes.ts b/src/common/bytes.ts
--- a/src/common/bytes.ts
+++ b/src/common/bytes.ts
@@ -67,3 +67,3 @@
   const stripped = strip0x(bytes);
-  return stripped.length % 2 === 0 && /^[a-fA-f0-9]+$/.test(strip0x(bytes));
+  return stripped.length % 2 === 0 && /^[a-fA-F0-9]+$/.test(strip0x(bytes));
 }
EOF
@@ -67,3 +67,3 @@
const stripped = strip0x(bytes);
return stripped.length % 2 === 0 && /^[a-fA-f0-9]+$/.test(strip0x(bytes));
return stripped.length % 2 === 0 && /^[a-fA-F0-9]+$/.test(strip0x(bytes));
}
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
@iamnp iamnp self-assigned this Oct 26, 2024
Copy link
Author

@iamnp iamnp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TL;DR

  • Overall, the code is good, with several improvements of varying severity suggested, mostly aimed at improving the quality of API usage
  • Most of the decisions made by the project authors are reasonable and in line with the project's objectives
  • Two critical issues have been identified that require particular attention:
    • OmnibusPlan has fields with deferred completion that may be ambiguous to use: link
    • The omnibus test file structure is very spread out and contains many boilerplate code, giving a huge field for errors: link

Note

Severity ratings:
⚠️ - CRITICAL
🔴 - HIGH
🟡 - MEDIUM
👍 - GOOD IDEA

API Design

Requirements

APIs should be simple, concise, and easy to use. API should remain minimalist to reduce the amount of code the user must write. The framework should encapsulate complex logic to simplify API usage. Functions and objects should have descriptive names that clearly reflect their purpose and usage.

Review Points

Check that API functions and parameters are self-explanatory and require minimal documentation. Look for any extraneous methods or properties that could be removed or encapsulated within the framework. Look for areas where logic could be encapsulated, such as transaction handling or error management.

Findings

The project appears to be logically structured, with modular directories organised by functionality, including configurations, contracts, keystores, voting and trace handling, which helps with navigation and finding specific functionality. The naming conventions are generally well aligned with the expected functionality (e.g. providers, omnibuses, traces). Some directories, such as `omnibuses', contain broad functionality (e.g. tools, checks, blueprints) which might benefit from more granular sub-modules to clarify purpose at a glance. Moreover, enhancing function and parameter names, as suggested, will make the API more intuitive, leading to easier adoption and fewer errors during implementation. See code-related findings tagged with [API Design].

Error Prevention

Requirements

APIs should be bug-proof with no room for ambiguity, making it difficult for developers to misunderstand them. Design APIs to prevent misuse using intuitive naming conventions.

Review Points

Ensure that all API functions handle errors gracefully, with clear messages or error types for developers. Evaluate whether all exposed functions are logically grouped and that any complex functionality is encapsulated to simplify usage.

Findings

The codebase uses custom error classes, error handling and type-safe definitions. However, improvements could be made by extending the use of exhaustive checks for unsupported conditions. This would make the APIs more resilient to misuse and minimize potential edge cases that could lead to unhandled errors. See code-related findings tagged with [Error Prevention].

Code Quality

Requirements

Ensure robust TypeScript type definitions across the codebase to catch potential type errors at compile time. Each API function should include concise documentation outlining usage, parameters, and any edge cases.

Review Points

Check that all functions and variables are appropriately typed and that type inference is used wherever possible. Ensure function and module-level documentation is present and clear.

Findings

Overall, typing and type inference are used effectively within the project, with little room for improvement. The design seems to focus on encapsulating complex interactions. The presence of README files and examples of configuration files indicates some attention to usability. However, many files lack module and/or function level documentation, it may not be extremely useful to document everything, but focus on API documentation should be a priority. See code-related findings tagged with [Code Quality].

Testing

Requirements

The code should be rigorously tested, focusing on API behaviour under various edge cases. Where applicable, tests should mock Ethereum transactions to simulate and validate API behaviour.

Review Points

Ensure that tests cover a wide range of scenarios, including edge cases, expected behaviour and potential misuse. Verify that tests include mock transactions to ensure that the framework behaves predictably under simulated conditions.

Findings

It looks like mock data and utilities, including stubs and simulated transaction environments, are being used effectively. It is important to control test coverage and increase it from 60% to 80%+.

As a general suggestion, clear guidelines and conventions should be introduced for writing both omnibuses and, more importantly, tests.

Ability to run omnibus items one-by-one will decrease development time


export type ChainId = "1" | "5";
export type ChainName = "eth" | "arb" | "opt";
export type NetworkName = "mainnet" | "goerli";
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Code Quality] 🔴
Using the NetworkName type here for validation is good, but I suggest going further:
introduce the constants "mainnet" and "goerli" so that they can be referenced without having to type a string literal each time.

The same should be probably valid for ChainId and ChainName types.


const NETWORKS: Record<ChainName, Record<NetworkName, Network>> = {
eth: {
mainnet: new Network("mainnet", 1),
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Code Quality] 🔴
String literals should be replaced by constants here and everywhere else.

import { call, event } from "../src/votes";

export default omnibuses.create({
network: "mainnet",
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Code Quality] 🔴
The constant (to be introduced in src/networks.ts) should be used here instead of the string literal.


export default omnibuses.create({
network: "mainnet",
// launchedOn: 12345678, // Launch block number should be set only if omnibus was successfully launched.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[API Design] ⚠️
The following fields:
launchedOn, voteId, executedOn, quorumReached should be set separately from the original omnibus plan being written and tested.

These fields should be separated into a different entity, as it is currently misleading as to how and when these params should be set, as they modify the original object being tested. Ideally, it should be possible to call separate callback-style functions such as omnibusStarted(voteId) etc. to give a clear timeline and explicit control over the omnibus object. These functions will modify the omnibus object internally.

// launchedOn: 12345678, // Launch block number should be set only if omnibus was successfully launched.
// voteId: 000, // Vote ID should be set only if omnibus is already started.
// executedOn: 12345678, // Execution block number should be set only if vote is passed and omnibus was successfully executed.
quorumReached: false, // Should be set to true if quorum was reached during the vote.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[API Design] ⚠️
It should be considered to rethink the name and purpose of this field, as omnibuses are run until the quorum is reached, so there is no way that the final state could be quorumReached: false.

/**
* Contains the info about the omnibus launching - the number of the block where the omnibus was launched.
*/
launchedOn?: number | undefined;
Copy link
Author

@iamnp iamnp Oct 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[API Design] 🔴
launchedOn and executedOn should be renamed to include context, e.g., launchedOnBlock, executedOnBlock. Correct terminology should be used, launch and execute are words with a similar meaning, probably enact would work better here?

import { RpcProvider } from "./types";
import { isHardhatEthersProvider, isJsonRpcProvider, UnsupportedProviderError } from "./utils";

function provider(runner: ContractRunner): RpcProvider {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Code Quality] 🟡
A validateRpcProvdier should be more suitable here to clearly indicate that the function is validating and returning a specific type of provider.

const { easyTrack, callsScript, voting } = contracts;
return {
title: title,
evmCall: call(easyTrack.removeEVMScriptFactory, [factory]),
Copy link
Author

@iamnp iamnp Oct 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Error Prevention] 🟡
rename call?

contracts: Contracts<Lido>,
{ factory, title }: RemovePaymentEvmScriptFactoryInput,
): OmnibusItem {
const { easyTrack, callsScript, voting } = contracts;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Error Prevention] 🟡
Do we need to validate input here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check factory, title against common mistakes (0 address, etc.)

}
}

function checkEnvVars() {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Error Prevention] ⚠️
Is it enough to check only one var? I suggest checking vars when running tests too

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants