-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
feat(cheatcode): impl getDeployedCode to handle immutable variables #5718
base: master
Are you sure you want to change the base?
Conversation
Noticed there is a problem now with the |
This is neat, can you expand more on the UX here? For example, given this contract: contract Counter {
uint256 public number;
uint256 public immutable immutable1;
uint256 public immutable immutable2;
constructor(uint256 x) {
immutable1 = x;
immutable2 = x * 2;
}
function foo(uint256 newNumber) public {
number = newNumber + immutable1;
}
function bar() public {
number += immutable2;
}
} The compiler outputs: "immutableReferences": {
"5": [
{
"start": 184,
"length": 32
},
{
"start": 231,
"length": 32
}
],
"7": [
{
"start": 97,
"length": 32
},
{
"start": 276,
"length": 32
}
]
} As a user, the UX is:
Is that correct? |
Your comment made me realize I did not handle the case where an immutable variable was referenced several times in the contract byte code. Commit Regarding the UX:
This is I guess the best way to make sure we're setting the right value for the right immutable variable but as far as I've seen, immutable var declaration is in order so the user might also use the declaration order to set the values (special care must be taken in case of inheritance).
Yes in the given example as there are 2 immutable variable in the contract the array passed to the
Right, if I have NOTE: As mentioned here, I expect that iterating over the REMANING ISSUE: I'm not 100% sure to understand how the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some nits
still need to catch up with the discussion
@@ -113,12 +113,47 @@ impl ArtifactBytecode { | |||
} | |||
} | |||
|
|||
fn into_deployed_bytecode(self) -> Option<Bytes> { | |||
fn into_deployed_bytecode(self, immutables: &[[u8; 32]]) -> Option<Bytes> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know what this does.
this needs docs, I'd be totally lost when I have to revisit this again
@@ -113,12 +113,47 @@ impl ArtifactBytecode { | |||
} | |||
} | |||
|
|||
fn into_deployed_bytecode(self) -> Option<Bytes> { | |||
fn into_deployed_bytecode(self, immutables: &[[u8; 32]]) -> Option<Bytes> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really want to modify these functions,
but I'm open to additional functions _with_immutables
or something like this
what does this mean? |
Not well explained indeed. I meant that I'm struggling to have serde figure out what variant of the |
could you add the json artifacts, how do they differ? |
The json artifacts are those I added in the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just catching up—wondering:
- Is expecting the variables to be 32 bytes long a reasonable invariant to hold? When does this break down? I'm feeling this could be a huge footgun.
- What would be the recommended way to deal with inheritance? I feel this could get messy
Currently solidity does not allow immutables for dynamic length types, and all immutables (even smaller types like uint8) are padded to the full 32 bytes, so this is ok for now. But solidity will likely lift this restrictions—if you click the link in this comment it seems it's planned for Q1 2024
Agreed, I think it's the workflow I explained here but @xenoliss can confirm. If so, let's make sure that workflow gets documented in the book: #5718 (comment) |
Yeah this was my worry—this approach seems good for now but not super future proof. We can probably go with this for now and iterate later, but we def should keep track of this so it doesn't bite people later.
gotcha, cool! |
Yes it is, I also explained it here. Regarding inheritance the user must indeed be aware of all the immutable variables brought by inherited contracts. In case the user is not providing all the immutable values the current implementation will return Regarding the way immutable values are currently padded to 32 bytes it's indeed not future proofed. If you have other ideas I have no problem to change the interface (also I feel like it's better to have it, knowing this restriction, rather than not having it at all). The main technical problem remaining on my side is the deserialization of the json payload into the |
Hey @xenoliss, we recently refactored how cheatcodes are implemented in #6131. You can read more about it in the dev docs. Please let me know if you have any problems rebasing! |
Hi @xenoliss, thanks for your PR! Would be great to get this merged - would you be interested in picking this back up? The new cheatcode system is really neat. If not I can also take it from here. |
Motivation
While writing forge unit tests it's often that I need to mock some contracts at specific addresses. For this I'm using 2 cheatcodes as shown below:
This works well when the contract bytecode does not include immutable variables.
Using the code above with a contract that defines immutable variables will let the immutable references uninitialized in the contract deployed bytecode. Most of the time this makes the contract unusable if the immutable variables are used by the methods we plan to call.
Solution
This PR extends the current
getDeployedCode
cheatcode by adding another prototype which accepts a list ofbytes32
values. Those bytes32 values are given to theget_deployed_code
method and passed to theinto_deployed_bytecode
trait method. Inside theinto_deployed_bytecode
method, theArtifactBytecode::Forge
handles the case where there are some immutable variables defined and if so, ensures their values are specified and manually patches the byte code to set their values.NOTE 1: The implementation assumes that the bytes length of an immutable variable is 32.
NOTE 2: I had to move
ArtifactBytecode::Forge
first in theArtifactBytecode
enum to give it the priority when deserializing.NOTE 3: Manually patching (with a nested loop) the bytecode is inefficient and I'm open to suggestions to improve this.
NOTE 4:
into_deployed_bytecode
was returning anOption
so I kept it this way but it may be preferable now to have it return aResult<Option<Bytes>, Error>
withError
used to describe an problem related to immutable variables initialization.Here is an example of the new
getDeployedCode
prototype usage:Which outputs: