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

Optimize Create2.computeAddress #3600

Merged
merged 15 commits into from Aug 23, 2022
Merged

Conversation

ToweringTopaz
Copy link
Contributor

@ToweringTopaz ToweringTopaz commented Aug 5, 2022

This is a more efficient method of computing a create2 address, utilizing the scratch space and (temporarily) the free memory pointer slot. See this discussion with solc devs regarding the memory-safety of this method.

For consistency and simplicity, also refactored the deploy function to use a named return variable.

PR Checklist

  • Tests
  • Documentation
  • Changelog entry

@Amxx
Copy link
Collaborator

Amxx commented Aug 10, 2022

Hello @ToweringTopaz and thank you for your PR.

IMO, this kind of optimization negatively affect readability and auditability of the code. I'm not sure how significant the gas savings are. We would need number on that.

@Amxx
Copy link
Collaborator

Amxx commented Aug 10, 2022

I ran both versions of the code with similar input.

Going from solidity to assembly saves 122 gas.

I tried setting the free memory ptr to a large value before running the code, to see if memory depth would affect the cost of the solidity version and would cause a more significant different. That was not the case.

@@ -31,16 +31,14 @@ library Create2 {
uint256 amount,
bytes32 salt,
bytes memory bytecode
) internal returns (address) {
address addr;
) internal returns (address addr) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

It that change optimizing anything? If not, I would revert it and focus on the computeAddress function

contracts/utils/Create2.sol Outdated Show resolved Hide resolved
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
@ToweringTopaz
Copy link
Contributor Author

Hello @ToweringTopaz and thank you for your PR.

IMO, this kind of optimization negatively affect readability and auditability of the code. I'm not sure how significant the gas savings are. We would need number on that.

In addition to the savings on execution, this reduces deployment size by 360 bytes for a simple contract. That's a lot of bytecode, saving 77,000 gas on deployment.

@Amxx
Copy link
Collaborator

Amxx commented Aug 11, 2022

In addition to the savings on execution, this reduces deployment size by 360 bytes for a simple contract. That's a lot of bytecode, saving 77,000 gas on deployment.

Were the optimizations turned on when you tested that ? I'm getting "just" 13k gas difference. That might depend on the number of use if the function is inlined.

@ToweringTopaz
Copy link
Contributor Author

In addition to the savings on execution, this reduces deployment size by 360 bytes for a simple contract. That's a lot of bytecode, saving 77,000 gas on deployment.

Were the optimizations turned on when you tested that ? I'm getting "just" 13k gas difference. That might depend on the number of use if the function is inlined.

Optimization was off. Another factor that might apply is if abi.encodePacked is used elsewhere in the same contract.

Would it be helpful to add comments to the assembly code, or is it self-explanatory enough?

@Amxx
Copy link
Collaborator

Amxx commented Aug 14, 2022

I think it's self explanatory.

How would abi.encodePacked been used elsewhere affect the produced code. Can you measure that?

@ToweringTopaz
Copy link
Contributor Author

ToweringTopaz commented Aug 16, 2022

I think it's self explanatory.

How would abi.encodePacked been used elsewhere affect the produced code. Can you measure that?

If abi.encodePacked(bytes1,address,bytes32,bytes32) is used elsewhere, the new method only saves 27 bytes of bytecode with optimizations turned off and 60 with optimizations on, via-IR, 200 runs. This is the extreme "minimum benefit" case, so in all cases the new method provides a cost savings.

contract Create2Test1 {

    function matchingEncodePacked(bytes1 a, address b, bytes32 c, bytes32 d) external pure returns (bytes memory) {
        return abi.encodePacked(a,b,c,d);
    }

    function test1() external view returns (address addr) {
        return Create2.computeAddressOld(bytes32(block.timestamp), bytes32(block.timestamp), msg.sender);
    }
}
contract Create2Test2 {
    function matchingEncodePacked(bytes1 a, address b, bytes32 c, bytes32 d) external pure returns (bytes memory) {
        return abi.encodePacked(a,b,c,d);
    }

    function test2() external view returns (address addr) {
        return Create2.computeAddressNew(bytes32(block.timestamp), bytes32(block.timestamp), msg.sender);
    }
}

@Amxx
Copy link
Collaborator

Amxx commented Aug 16, 2022

If abi.encodePacked(bytes1,address,bytes32,bytes32) is used elsewhere

that is deployment cost saving, right ?

I guess its because the corresponding logic would be included anyway (like a private function).

@ToweringTopaz
Copy link
Contributor Author

If abi.encodePacked(bytes1,address,bytes32,bytes32) is used elsewhere

that is deployment cost saving, right ?

Right. Of course this is the most extreme example offering the least savings. Not many other things pack (bytes1,address,bytes32,bytes32) in this manner, in practice.

@frangio frangio changed the title optimize Create2.computeAddress Optimize Create2.computeAddress Aug 19, 2022
@frangio
Copy link
Contributor

frangio commented Aug 19, 2022

I am not comfortable with overwriting the free memory pointer in this way. The Gitter discussion is not a strong enough guarantee. The reality is that this use of memory is outside of what is assumed by Solidity in memory-safe blocks, according to the documentation (source).

In this PR I haven't seen how much gas is saved on execution. Is most of the gas saved due to avoiding memory expansion or is it due to an optimized code path? It would be okay and memory-safe if instead of doing this we simply write on free memory without updating the free memory pointer. We would be expanding memory slightly but it may not be that bad in context.

If we move forward with my suggestion, the assembly block also needs a comment explaining what it does.

@frangio frangio added this to the 4.8 milestone Aug 19, 2022
@frangio frangio mentioned this pull request Aug 19, 2022
3 tasks
@ToweringTopaz
Copy link
Contributor Author

I am not comfortable with overwriting the free memory pointer in this way. The Gitter discussion is not a strong enough guarantee. The reality is that this use of memory is outside of what is assumed by Solidity in memory-safe blocks, according to the documentation (source).

In this PR I haven't seen how much gas is saved on execution. Is most of the gas saved due to avoiding memory expansion or is it due to an optimized code path? It would be okay and memory-safe if instead of doing this we simply write on free memory without updating the free memory pointer. We would be expanding memory slightly but it may not be that bad in context.

@Amxx calculated gas savings of 122 using the unmodified method. Using free memory reduces this a bit due to ADD opcodes and 3 words of memory expansion in some cases, but it's still an optimized code path.

@Amxx
Copy link
Collaborator

Amxx commented Aug 23, 2022

I've added a "visual explanation" of how the assembly write to memory

Copy link
Contributor

@frangio frangio left a comment

Choose a reason for hiding this comment

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

LGTM.

I want to add some detail. I consider this optimization to be almost too small to matter and something that the compiler should be taking care of. However, the assembly required is simple enough, and evidently the compiler is not taking care of it, so I agree with merging.

@frangio frangio merged commit 6d8017d into OpenZeppelin:master Aug 23, 2022
ronhuafeng added a commit to ronhuafeng/openzeppelin-contracts that referenced this pull request Sep 9, 2022
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
Co-authored-by: Francisco <frangio.1@gmail.com>
JulissaDantes pushed a commit to JulissaDantes/openzeppelin-contracts that referenced this pull request Nov 3, 2022
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
Co-authored-by: Francisco <frangio.1@gmail.com>
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.

None yet

3 participants