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

Simple ImmutableBeaconProxy implementation #19

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
36 changes: 36 additions & 0 deletions src/contracts/immutable-beacon-proxy/ImmutableBeaconProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IBeacon} from './interfaces/IBeacon.sol';
import {Proxy} from '../oz-common/Proxy.sol';
import {Address} from '../oz-common/Address.sol';

/**
* @dev This contract implements a proxy that gets the implementation address for each call from an {UpgradeableBeacon}.
* The beacon address is immutable. The purpose of it, is to be able to access this proxy via delegatecall

* !!! IMPORTANT CONSIDERATION !!!
* We expect that the implementation will not have any storage associated,
* because it when accessed via delegatecall, will not work as expected creating dangerous side-effects. Preferable, the implementation should be declared as a library
*/
contract ImmutableBeaconProxy is Proxy {
address internal immutable _beacon;

event ImmutableBeaconSet(address indexed beacon);

constructor(address beacon) {
require(Address.isContract(beacon), 'INVALID_BEACON');
require(Address.isContract(IBeacon(beacon).implementation()), 'INVALID_IMPLEMENTATION');

// there is no initialization call, because we expect that implementation should have no storage
_beacon = beacon;
emit ImmutableBeaconSet(beacon);
}

/**
* @dev Returns the current implementation address of the associated beacon.
*/
function _implementation() internal view virtual override returns (address) {
return IBeacon(_beacon).implementation();
}
}
68 changes: 68 additions & 0 deletions src/contracts/immutable-beacon-proxy/UpgradeableBeacon.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/beacon/UpgradeableBeacon.sol)

pragma solidity ^0.8.0;

import {IBeacon} from './interfaces/IBeacon.sol';
import {Ownable} from '../oz-common/Ownable.sol';
import {Address} from '../oz-common/Address.sol';

/**
* @dev This contract is used in conjunction with one or more instances of {BeaconProxy} to determine their
* implementation contract, which is where they will delegate all function calls.
*
* An owner is able to change the implementation the beacon points to, thus upgrading the proxies that use this beacon.
*/
contract UpgradeableBeacon is IBeacon, Ownable {
address private _implementation;

/**
* @dev Emitted when the implementation returned by the beacon is changed.
*/
event Upgraded(address indexed implementation);

/**
* @dev Sets the address of the initial implementation, and the deployer account as the owner who can upgrade the
* beacon.
*/
constructor(address implementation_) {
_setImplementation(implementation_);
}

/**
* @dev Returns the current implementation address.
*/
function implementation() public view virtual override returns (address) {
return _implementation;
}

/**
* @dev Upgrades the beacon to a new implementation.
*
* Emits an {Upgraded} event.
*
* Requirements:
*
* - msg.sender must be the owner of the contract.
* - `newImplementation` must be a contract.
*/
function upgradeTo(address newImplementation) public virtual onlyOwner {
_setImplementation(newImplementation);
emit Upgraded(newImplementation);
}

/**
* @dev Sets the implementation contract address for this beacon
*
* Requirements:
*
* - `newImplementation` must be a contract.
*/
function _setImplementation(address newImplementation) private {
require(
Address.isContract(newImplementation),
'UpgradeableBeacon: implementation is not a contract'
);
_implementation = newImplementation;
}
}
16 changes: 16 additions & 0 deletions src/contracts/immutable-beacon-proxy/interfaces/IBeacon.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)

pragma solidity ^0.8.0;

/**
* @dev This is the interface that {BeaconProxy} expects of its beacon.
*/
interface IBeacon {
/**
* @dev Must return an address that can be used as a delegate call target.
*
* {BeaconProxy} will check that this address is a contract.
*/
function implementation() external view returns (address);
}
2 changes: 1 addition & 1 deletion src/contracts/transparent-proxy/ERC1967Proxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

pragma solidity ^0.8.0;

import './Proxy.sol';
import '../oz-common/Proxy.sol';
import './ERC1967Upgrade.sol';

/**
Expand Down
47 changes: 47 additions & 0 deletions test/ImmutableBeaconProxy.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import 'forge-std/Test.sol';
import {ImmutableBeaconProxy, IBeacon} from '../src/contracts/immutable-beacon-proxy/ImmutableBeaconProxy.sol';

contract ImplementationMock {}

contract BeaconMock is IBeacon {
address public implementation;

constructor(address newImplementation) {
implementation = newImplementation;
}
}

contract ImmutableBeaconProxyMock is ImmutableBeaconProxy {
constructor(address beacon) ImmutableBeaconProxy(beacon) {}

function implementation() public view returns (address) {
return _implementation();
}
}

contract ImmutableBeaconProxyTest is Test {
event ImmutableBeaconSet(address indexed beacon);

function testResolvesImplementationCorrectly() public {
address implementation = address(new ImplementationMock());
address beacon = address(new BeaconMock(implementation));

vm.expectEmit(true, false, false, true);
emit ImmutableBeaconSet(beacon);
assertEq(implementation, (new ImmutableBeaconProxyMock(beacon)).implementation());
}

function testBeaconNotAContract() public {
vm.expectRevert(bytes('INVALID_BEACON'));
new ImmutableBeaconProxy(address(1));
}

function testImplementationNotAContract() public {
address beacon = address(new BeaconMock(address(1)));

vm.expectRevert(bytes('INVALID_IMPLEMENTATION'));
new ImmutableBeaconProxy(beacon);
}
}