Skip to content

Instantly share code, notes, and snippets.

@voith
Last active August 31, 2023 21:37
Show Gist options
  • Save voith/2b4f15ee19cb041a8521b300a176801b to your computer and use it in GitHub Desktop.
Save voith/2b4f15ee19cb041a8521b300a176801b to your computer and use it in GitHub Desktop.
A basic demo of the BeaconProxy pattern in solidity with usage demonstrated using foundry tests
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
import "forge-std/Test.sol";
import "forge-std/console.sol";
import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
interface IPerson {
function initialize(string memory, address) external;
function getName() external view returns (string memory);
}
contract Person is IPerson, Initializable, Ownable {
string public name;
function initialize(string memory _name, address _owner) external initializer {
name = _name;
_transferOwnership(_owner);
}
function getName() external view returns (string memory _name) {
_name = string.concat("v1:", name);
}
}
contract PersonProxy is BeaconProxy {
constructor(address _implementation, bytes memory data) BeaconProxy(_implementation, data) {}
}
contract PersonFactory is UpgradeableBeacon {
mapping(address => address) private persons;
constructor(address _implementation) UpgradeableBeacon(_implementation) {}
function newPerson(string memory _name) public {
require(persons[msg.sender] == address(0), "Ship already created for address");
PersonProxy person = new PersonProxy(
address(this),
abi.encodeWithSelector(Person.initialize.selector, _name, msg.sender)
);
persons[msg.sender] = address(person);
}
function getPersonAddress(address _account) external view returns (address) {
return persons[_account];
}
}
contract PersonV2 is IPerson, Initializable, Ownable {
string public name;
function initialize(string memory _name, address _owner) external initializer {
name = _name;
_transferOwnership(_owner);
}
function getName() external view returns (string memory _name) {
_name = string.concat("v2:", name);
}
}
contract BeaconProxyTest is Test {
Person v1Implementation;
PersonFactory personFactory;
address deployer = address(0x51);
address userA = address(0x52);
address userB = address(0x53);
function setUp() external {
vm.startPrank(deployer);
v1Implementation = new Person();
personFactory = new PersonFactory(address(v1Implementation));
vm.stopPrank();
}
function testUpdateBeaconProxy() external {
vm.prank(userA);
personFactory.newPerson("userA");
vm.prank(userB);
personFactory.newPerson("userB");
IPerson personA = IPerson(personFactory.getPersonAddress(userA));
IPerson personB = IPerson(personFactory.getPersonAddress(userB));
assertEq(personA.getName(), "v1:userA");
assertEq(personB.getName(), "v1:userB");
PersonV2 v2Implementation = new PersonV2();
vm.prank(deployer);
personFactory.upgradeTo(address(v2Implementation));
assertEq(personA.getName(), "v2:userA");
assertEq(personB.getName(), "v2:userB");
}
}
@voith
Copy link
Author

voith commented Aug 31, 2023

to run the test:

$ forge test -vvv

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment