Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fluffy-steaks-exist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': patch
---

`Create2`, `Clones`: Mask `computeAddress` and `cloneDeterministic` outputs to produce a clean value for an `address` type (i.e. only use 20 bytes)
37 changes: 37 additions & 0 deletions contracts/mocks/ComputedAddressRemainderMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {Clones} from "../proxy/Clones.sol";
import {Create2} from "../utils/Create2.sol";

contract ComputedAddressRemainderMock {
uint256 private constant _REMAINDER_MASK = ~(uint256(type(uint160).max));

function getCreate2ComputedRemainder(
bytes32 salt,
bytes32 bytecodeHash,
address deployer
) external pure returns (uint256 remainder) {
address predicted = Create2.computeAddress(salt, bytecodeHash, deployer);
remainder = _getRemainder(predicted);
}

function getClonesPredictedRemainder(
address implementation,
bytes32 salt,
address deployer
) external pure returns (uint256 remainder) {
address predicted = Clones.predictDeterministicAddress(implementation, salt, deployer);
remainder = _getRemainder(predicted);
}

function _getRemainder(address addr) internal pure returns (uint256 remainder) {
uint256 remainderMask = _REMAINDER_MASK;

/// @solidity memory-safe-assembly
assembly {
remainder := and(addr, remainderMask)
}
}
}
2 changes: 1 addition & 1 deletion contracts/proxy/Clones.sol
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ library Clones {
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
mstore(add(ptr, 0x58), salt)
mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
predicted := keccak256(add(ptr, 0x43), 0x55)
predicted := and(keccak256(add(ptr, 0x43), 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
}
}

Expand Down
2 changes: 1 addition & 1 deletion contracts/utils/Create2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ library Create2 {
mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
mstore8(start, 0xff)
addr := keccak256(start, 85)
addr := and(keccak256(start, 85), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
}
13 changes: 12 additions & 1 deletion test/proxy/Clones.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ async function fixture() {

const factory = await ethers.deployContract('$Clones');
const implementation = await ethers.deployContract('DummyImplementation');
const computedAddressMock = await ethers.deployContract('ComputedAddressRemainderMock');

const newClone = async (initData, opts = {}) => {
const clone = await factory.$clone.staticCall(implementation).then(address => implementation.attach(address));
Expand All @@ -27,7 +28,7 @@ async function fixture() {
return clone;
};

return { deployer, factory, implementation, newClone, newCloneDeterministic };
return { deployer, factory, implementation, computedAddressMock, newClone, newCloneDeterministic };
}

describe('Clones', function () {
Expand Down Expand Up @@ -83,5 +84,15 @@ describe('Clones', function () {
.to.emit(this.factory, 'return$cloneDeterministic')
.withArgs(predicted);
});

it('clean address prediction', async function () {
const salt = ethers.randomBytes(32);
const predictedRemainder = await this.computedAddressMock.getClonesPredictedRemainder(
this.implementation,
salt,
ethers.Typed.address(this.deployer),
);
expect(predictedRemainder).to.equal(0);
});
});
});
12 changes: 11 additions & 1 deletion test/utils/Create2.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ async function fixture() {
const [deployer, other] = await ethers.getSigners();

const factory = await ethers.deployContract('$Create2');
const computedAddressMock = await ethers.deployContract('ComputedAddressRemainderMock');

// Bytecode for deploying a contract that includes a constructor.
// We use a vesting wallet, with 3 constructor arguments.
Expand All @@ -19,7 +20,7 @@ async function fixture() {
.getContractFactory('$Create2')
.then(({ bytecode, interface }) => ethers.concat([bytecode, interface.encodeDeploy([])]));

return { deployer, other, factory, constructorByteCode, constructorLessBytecode };
return { deployer, other, factory, computedAddressMock, constructorByteCode, constructorLessBytecode };
}

describe('Create2', function () {
Expand Down Expand Up @@ -54,6 +55,15 @@ describe('Create2', function () {
);
expect(onChainComputed).to.equal(offChainComputed);
});

it('computes the clean contract address', async function () {
const computedRemainder = await this.computedAddressMock.getCreate2ComputedRemainder(
saltHex,
ethers.keccak256(this.constructorByteCode),
ethers.Typed.address(this.deployer),
);
expect(computedRemainder).to.equal(0);
});
});

describe('deploy', function () {
Expand Down