Last active
August 31, 2023 21:37
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
to run the test:
$ forge test -vvv