Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions src/mutability/Implementation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ abstract contract Implementation is IImplementation, Contract {
/// @custom:storage-location erc7201:equilibria.root.Implementation
struct ImplementationStorage {
bool constructing;
bool constructed;
}

/// @dev The erc7201 storage location of the mix-in
Expand All @@ -37,6 +38,7 @@ abstract contract Implementation is IImplementation, Contract {
constructor(string memory version_, string memory predecessor_) {
_version = ShortStrings.toShortString(version_);
_predecessor = ShortStrings.toShortString(predecessor_);
_disableInitializers();
}

/// @dev The name of the implementation.
Expand All @@ -54,6 +56,7 @@ abstract contract Implementation is IImplementation, Contract {

/// @dev Called at upgrade time to initialize the contract with `data`.
function construct(bytes memory data) external {
if (Implementation$().constructed) revert ImplementationAlreadyConstructedError();
Implementation$().constructing = true;

string memory constructorVersion = __constructor(data);
Expand All @@ -72,6 +75,14 @@ abstract contract Implementation is IImplementation, Contract {
return IMutator(msg.sender).owner();
}

/// @dev Locks the contract, preventing any future reinitialization. Called in the constructor to
/// prevent the implementation contract from being used directly.
/// @custom:oz-ref https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol#L192C14-L192C34
function _disableInitializers() internal virtual {
if (Implementation$().constructing) revert ImplementationAlreadyConstructedError();
Implementation$().constructed = true;
}

/// @dev Hook for inheriting contracts to construct the contract.
function __constructor(bytes memory data) internal virtual returns (string memory);
}
4 changes: 4 additions & 0 deletions src/mutability/interfaces/IImplementation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ interface IImplementation {
/// @dev Thrown when the constructor version of the implementation does not match the version of the implementation.
error ImplementationConstructorVersionMismatch();

// sig: 0x17e9e41b
/// @dev Thrown when construct() is called directly on the implementation contract.
error ImplementationAlreadyConstructedError();

function name() external view returns (string memory);
function version() external view returns (string memory);
function predecessor() external view returns (string memory);
Expand Down
2 changes: 2 additions & 0 deletions test/attribute/Attribute.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ contract AttributeTest is Test {
attribute = new MockAttribute();
mockMutable = new MockMutable(address(this));

// clear `constructed` flag set by the implementation's constructor for direct unit testing
vm.store(address(attribute), 0x3c57b102c533ff058ebe9a7c745178ce4174563553bb3edde7874874c532c200, bytes32(0));
vm.prank(address(mockMutable));
attribute.construct(abi.encode("foo"));
}
Expand Down
2 changes: 2 additions & 0 deletions test/attribute/Delegatable.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ contract DelegatableTest is Test {
mockToken.mint(owner, 1000 ether);
vm.stopPrank();

// clear `constructed` flag set by the implementation's constructor for direct unit testing
vm.store(address(delegatable), 0x3c57b102c533ff058ebe9a7c745178ce4174563553bb3edde7874874c532c200, bytes32(0));
vm.prank(address(mockMutable));
delegatable.construct("");
}
Expand Down
2 changes: 2 additions & 0 deletions test/attribute/Executable.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ contract ExecutableTest is Test {
mockMutable = new MockMutable(owner);
vm.stopPrank();

// clear `constructed` flag set by the implementation's constructor for direct unit testing
vm.store(address(executable), 0x3c57b102c533ff058ebe9a7c745178ce4174563553bb3edde7874874c532c200, bytes32(0));
vm.prank(address(mockMutable));
executable.construct("");
}
Expand Down
3 changes: 3 additions & 0 deletions test/attribute/Ownable.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ contract OwnableTest is Test {
vm.prank(owner);
ownable = new MockOwnable();
mockMutable = new MockMutable(owner);

// clear `constructed` flag set by the implementation's constructor for direct unit testing
vm.store(address(ownable), 0x3c57b102c533ff058ebe9a7c745178ce4174563553bb3edde7874874c532c200, bytes32(0));
}

function test_initializeInitializesOwner() public {
Expand Down
3 changes: 3 additions & 0 deletions test/attribute/Pausable.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ contract PausableTest is Test {
vm.prank(owner);
pausable = new MockPausable();
mockMutable = new MockMutable(owner);

// clear `constructed` flag set by the implementation's constructor for direct unit testing
vm.store(address(pausable), 0x3c57b102c533ff058ebe9a7c745178ce4174563553bb3edde7874874c532c200, bytes32(0));
}

function test_constructor() public {
Expand Down
2 changes: 2 additions & 0 deletions test/attribute/Withdrawable.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ contract OwnerWithdrawableTest is Test {
erc20.mint(owner, 1000);
vm.stopPrank();

// clear `constructed` flag set by the implementation's constructor for direct unit testing
vm.store(address(withdrawable), 0x3c57b102c533ff058ebe9a7c745178ce4174563553bb3edde7874874c532c200, bytes32(0));
vm.prank(address(mockMutable));
withdrawable.construct("");
}
Expand Down
5 changes: 5 additions & 0 deletions test/mutability/Mutable.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ contract MutableTestV1 is MutableTestV1Deploy {
instance1.construct("");
}

function test_noDirectConstructorAccessOnImplementation() public {
vm.expectRevert(IImplementation.ImplementationAlreadyConstructedError.selector);
impl1.construct("");
}

function test_canPause() public {
vm.prank(owner);
vm.expectEmit();
Expand Down
2 changes: 2 additions & 0 deletions test/utils/OwnableStub.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ contract OwnableStubTest is Test {
mockMutable = new MockMutable(owner);
vm.stopPrank();

// clear `constructed` flag set by the implementation's constructor for direct unit testing
vm.store(address(ownableContract), 0x3c57b102c533ff058ebe9a7c745178ce4174563553bb3edde7874874c532c200, bytes32(0));
vm.prank(address(mockMutable));
ownableContract.construct("");
}
Expand Down
Loading