Skip to content

Add Solhint linting + pre-commit hook #69

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

Merged
merged 19 commits into from
Jul 18, 2025

Conversation

gonzaotc
Copy link
Collaborator

@gonzaotc gonzaotc commented Jun 6, 2025

  • Adding linting: solhint, solhint-plugin-openzeppelin

  • Adding two solhint configurations: solhint.src.config.js and solhint.test.config.js. One for src/ and other for /test, respectively. The rationale is that there are some rules such as foundry-test-functions that we may only want to run on tests, and func-name-mixedcase that we may want to run only on contracts.

  • Added pre-commit linting via husky

Copy link

netlify bot commented Jun 6, 2025

Deploy Preview for uniswap-hooks-docs failed. Why did it fail? →

Name Link
🔨 Latest commit 67438da
🔍 Latest deploy log https://app.netlify.com/projects/uniswap-hooks-docs/deploys/68755c99575aaa0008624950

Copy link

netlify bot commented Jun 6, 2025

Deploy Preview for uniswap-hooks failed. Why did it fail? →

Name Link
🔨 Latest commit 67438da
🔍 Latest deploy log https://app.netlify.com/projects/uniswap-hooks/deploys/68755c9a817eeb0008a2523c

@gonzaotc gonzaotc requested a review from luiz-lvj June 6, 2025 19:47
@gonzaotc gonzaotc changed the title Add similar linting to OpenZeppelin/contracts Add linting similar to OpenZeppelin/contracts Jun 6, 2025
@gonzaotc
Copy link
Collaborator Author

gonzaotc commented Jun 6, 2025

If this PR get's approved I will fix all the current warnings from prettier and solhint rules on the current contracts, which is an easy lift but I wanted to get this approved first.

Feel free to remove or add any rule of preference from https://protofire.github.io/solhint/docs/rules.html, I took the current configuration from OpenZeppelin/Contracts as a starting point.

@luiz-lvj thanks!

Copy link
Collaborator

@luiz-lvj luiz-lvj left a comment

Choose a reason for hiding this comment

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

LGTM @gonzaotc!
The configs seems nice, I don't have a strong opinion on the different linting mechanisms, so I think it makes sense to follow openzeppelin-contracts.
Afaik by merging this, the checks would fail, right? Let's wait on the releases to merge these changes. Thanks!

@gonzaotc
Copy link
Collaborator Author

gonzaotc commented Jun 24, 2025

LGTM @gonzaotc! The configs seems nice, I don't have a strong opinion on the different linting mechanisms, so I think it makes sense to follow openzeppelin-contracts. Afaik by merging this, the checks would fail, right? Let's wait on the releases to merge these changes. Thanks!

Hey @luiz-lvj yes, your intuition is correct, after we merge this PR all the code that violate rules will be flagged. However, take into account that the lint-staged pre-commit check only analyzes modified and added files (staged files), and therefore, the flagged code that the repository already has will not be a blocker for us to commit forward. Which is good!, since it allows us to apply the flagged fixes progressively if we want to.

Additionally, I already have other two PR's in line to get merged after this one: this fixing all prettier findings, and this fixing most solhint findings. I made them separate just to make them easier to review.

I agree with you on proceeding with this after the incoming release. Thanks!

@gonzaotc gonzaotc changed the title Add linting similar to OpenZeppelin/contracts Add Solhint linting + pre-commit hook Jul 14, 2025
@gonzaotc gonzaotc requested a review from luiz-lvj July 14, 2025 19:48
Comment on lines +25 to +33
class extends Base {
static ruleId = 'interface-only-external-functions';

FunctionDefinition(node) {
if (node.parent.kind === 'interface' && node.visibility !== 'external') {
this.error(node, 'Interface functions must be external');
}
}
},
Copy link
Collaborator

Choose a reason for hiding this comment

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

I see that instead of the rule interface-names coming from openzeppelin-contracts, we have interface-only-external-functions and leading-underscore is more strict. What's the reasoning?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good question, this update to the custom solhint rules is taken from a snapshot of a recent in-progress update for such that we did in openzeppelin-contracts with Hadrien: OpenZeppelin/openzeppelin-contracts#5794, those updates does better enforcement of our solidity guidelines. It wasn't applied yet since those fixes in the contracts library requires breaking changes that we may consider for the 6.0 release. On the other side, given the earliness of this library, we may be able to move faster and patch our code to our preferred practices.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Particularly, we are looking to enforce:

  • Only external functions in libraries
  • Preventing leading underscore in constants and immutables, which was previously only prevented in private constants
  • Preventing leading underscore in public and external functions.
  • Preventing external virtual functions.

Comment on lines +86 to +95
class extends Base {
static ruleId = 'no-external-virtual';

FunctionDefinition(node) {
if (node.visibility == 'external' && node.isVirtual) {
this.error(node, 'Functions should not be external and virtual');
}
}
},
];
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this rule needed? It's not enabled in openzeppelin-contracts. Also, note that this function is an example that would fail on this rule.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Preventing external virtual functions was in WIP for openzeppelin-contracts and part of our guidelines, the rationale is that if a function is virtual, it means that it is expected to be overwritten. However, an external virtual function cannot call .super, which effectively breaks inheritance. Therefore, we enforce public virtual over external virtual

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Additionally, all the required fixes for the solhint findings are done in this other PR! gonzaotc#8, which is dependent on this one

Copy link
Collaborator

Choose a reason for hiding this comment

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

That makes sense, thanks @gonzaotc!

Copy link
Collaborator

@luiz-lvj luiz-lvj left a comment

Choose a reason for hiding this comment

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

Thanks for the answers @gonzaotc, LGTM!

@gonzaotc gonzaotc merged commit c37db67 into OpenZeppelin:master Jul 18, 2025
4 of 13 checks passed
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.

2 participants