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

Add support for EIP-7201 namespaces #127

Merged
merged 35 commits into from
Aug 28, 2023
Merged

Add support for EIP-7201 namespaces #127

merged 35 commits into from
Aug 28, 2023

Conversation

frangio
Copy link
Contributor

@frangio frangio commented Aug 19, 2023

No description provided.

@socket-security
Copy link

socket-security bot commented Aug 19, 2023

New and removed dependencies detected. Learn more about Socket for GitHub ↗︎

Packages Version New capabilities Transitives Size Publisher
hardhat-ignore-warnings 0.2.9 None +14 3.76 MB frangio
ethereum-cryptography 2.0.0 None +4 2.3 MB paulmillr

🚮 Removed packages: solidity-ast@0.4.49

Copy link
Contributor

@Amxx Amxx left a comment

Choose a reason for hiding this comment

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

Starting to review. Here is a first round of comments.

hardhat.config.js Outdated Show resolved Hide resolved
Comment on lines +37 to +40
const node = this.resolveNode('*', id);
if (isNodeType(nodeType, node)) {
return node;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd love to understand this change.

My understanding so far is that if node id is not of type nodeType, we don't go in the if, and we implicitely return nothing (undefined) ?

Before the change, I guess resolveNode would fail and that would be catched by ASTDereferencerError ?

Can you explain why this change does / why its needed ?

Copy link
Contributor Author

@frangio frangio Aug 24, 2023

Choose a reason for hiding this comment

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

To be honest this change ended up not being necessary, so we could revert it, but there is an explanation behind it.

What happened was that when I first introduced the namespace logic the transpiler slowed down significantly. To transpile OZ Contracts took perhaps 1 minute. So in debugging I traced it down to resolveContract/tryResolveNode and experimented with approaches to improve this.

The issue was that deref: ASTDereferencer, used by tryResolveNode, had an optimized path that worked only when the requested node type was correct. But tryResolveNode was being used through resolveContract to check if a node id corresponded to a ContractDefinition. If the id was instead something like a StructDefinition, deref would fall back to brute force enumeration of all nodes in the source code to try to find one with this id (!).

The reason I introduced the '*' feature in ASTDerefencer was to avoid falling back to the slow path, instead quickly returning whatever node was found even if it was not the wanted node type.

This helped somewhat but I ended up finding a much better optimization that causes this to not really matter. (That should also make a lot of other things faster, including hardhat-upgrades and hardhat-exposed.)

@@ -11,7 +11,7 @@ import { findAlreadyInitializable } from './find-already-initializable';

async function getPaths() {
const hardhat = require.resolve('hardhat', { paths: [process.cwd()] });
const hre: HardhatRuntimeEnvironment = await import(hardhat);
const hre: HardhatRuntimeEnvironment = (await import(hardhat)).default;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm curious if there is a way to make the type explicit in something like

Suggested change
const hre: HardhatRuntimeEnvironment = (await import(hardhat)).default;
const { default: hre } = await import(hardhat);

Copy link
Contributor Author

@frangio frangio Aug 24, 2023

Choose a reason for hiding this comment

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

Yes but it's more verbose. It would be like this:

  const { default: hre }: { default: HardhatRuntimeEnvironment } = await import(hardhat);

src/transformations/utils/new-function-position.ts Outdated Show resolved Hide resolved
src/utils/erc7201.ts Outdated Show resolved Hide resolved
Comment on lines 23 to 30
function __C2_init() internal onlyInitializing {␊
__C2_init_unchained();␊
}␊
function __C2_init_unchained() internal onlyInitializing {␊
C2Storage storage $ = _getC2Storage();␊
$.s1 = "";␊
}␊
Copy link
Contributor

Choose a reason for hiding this comment

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

do we want the __XXX_init and __XXX_init_unchained to be at the top like this ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They are added at the top because there is no constructor in this case, but I suppose it would be nicer if this was added right before the first function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in the latest commit.

Comment on lines +41 to +47
struct C2Storage {␊
// a␊
uint x;␊
uint z;␊
string s1;␊
}␊
Copy link
Contributor

Choose a reason for hiding this comment

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

spacing is a bit strange.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Spacing here is inherited from the original code, where variables look like this:

    uint constant C = 3;
    // a
    uint x;
    // b
    /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
    uint immutable y = 2;
    uint private z;

    string private s1 = "";

So I think it's good!

Copy link
Contributor

Choose a reason for hiding this comment

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

It really comes down to what // a and // b means and refer to. I find it strange that // a is attached to x, and // b is attached to y.

Copy link
Contributor

@Amxx Amxx Aug 25, 2023

Choose a reason for hiding this comment

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

that may not be an issue if variables are sorted (first constants, then immutable, then private)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My reasoning was that if a comment is immediately before a variable, it should be attached to that variable. But I didn't consider the case where there is a newline between comment and variable, in that case I would say they should be considered separate and the comment should possibly be left outside of the struct...

// atached comment
uint y;

// separate comment

uint x;

Copy link
Contributor Author

@frangio frangio Aug 25, 2023

Choose a reason for hiding this comment

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

We're very limited in how we can handle comments because they're not present in the AST so I would leave the current behavior, unless it's producing results that we don't like for the Contracts codebase.

src/transform-namespaces.test.ts.md Outdated Show resolved Hide resolved
package.json Show resolved Hide resolved
src/transform.ts Outdated Show resolved Hide resolved
src/transform.ts Outdated Show resolved Hide resolved
src/utils/erc7201.ts Outdated Show resolved Hide resolved
@frangio
Copy link
Contributor Author

frangio commented Aug 28, 2023

I realized none of the existing tests are passing the output through the compiler. We rely on OpenZeppelin Contracts tests for that. It makes sense to add compilation tests in this repo but we should do this in a follow up PR.

@frangio frangio marked this pull request as ready for review August 28, 2023 19:04
@frangio frangio merged commit 6c50c71 into master Aug 28, 2023
@frangio frangio deleted the namespaces branch August 28, 2023 19:04
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.C3")) - 1)) & ~bytes32(uint256(0xff))␊
bytes32 private constant C3StorageLocation = 0xaa7e0685867d809c517036b8f21e99d58bd04b1da2b202f167c355fdf82b4a00;␊
function _getC3Storage() private pure returns (C3Storage storage $) {␊

Choose a reason for hiding this comment

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

@frangio could we make the slot getters internal virtual (e.g. function _getC3Storage() internal pure virtual returns (C3Storage storage $))? It may be possible for contracts already deployed to specify their existing storage location this way, allowing us to upgrade to the latest OZ. It would be risky of course, in case something else about the storage had changed, and would require a lot of testing - but may give us a path to stay on the latest OZ libraries.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It may be possible for contracts already deployed to specify their existing storage location this way

It's not possible, we've changed many things about the storage variables. We don't want to encourage this because it's guaranteed to break. For now, if you want to take this risk, feel free to fork the code and change the storage locations.

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.

3 participants