This repository contains the smart contracts discussed in the RareSkills article: The Beacon Proxy Pattern Explained.
A Beacon Proxy is a smart contract upgrade pattern where multiple proxies use the same implementation contract, and all the proxies can be upgraded in a single transaction. This repository demonstrates how to implement this pattern, specifically using the "FactoryBeacon" optimization used by Kwenta, where the factory and beacon are combined into a single contract.
The code here serves as a real-world example of the Beacon Proxy pattern. It was originally built for Kwenta's vesting packages, which at one point secured over $20M in TVL. Each user gets their own vesting contract (a proxy), but all share the same logic (the implementation). This setup allows for:
- Mass Upgradability: Upgrading the vesting logic for all users in one transaction.
- Siloed Storage: Each user has their own contract storage, preventing data intermingling.
- Gas Efficiency: The "FactoryBeacon" design simplifies deployment and reduces gas costs.
Below is the original README for the cc-vesting project, which details the specific implementation and usage of these contracts.
CC Vesting contracts are upgradable. This is achieved using a beaconproxy pattern. The VestingBase
contract is the implementation and VestingProxy is the proxy. The VestingProxy contract is the
contract deployed by the FactoryBeacon and what CC's interact with. The VestingProxy delegates all calls
to the current implemenation (VestingBase) defined by the FactoryBeacon. The VestingBase can be upgraded
by deploying a VestingBaseV3 and telling the FactoryBeacon to point to the new implementation. That will upgrade
all new and current VestingProxy contracts.
FactoryBeacon will only be accessible by the owner (AdminDAO) to ensure safety of VestingProxy contracts.
1 - Deploy the new VestingBase3 implementation using the script/upgrades/v3.0.1/Upgrade.s.sol script :
- (1.1) load the variables in the .env file via
source .env - (1.2) run
forge script script/upgrades/v3.0.1/Upgrade.s.sol:UpgradeVestingPackagesOptimism --rpc-url $OPTIMISM_RPC_URL --broadcast -vvvv
2 - Call the upgradeTo function on the VestingFactoryBeacon contract with the address of the new VestingBase implementation.
Note : Only the owner of the VestingFactoryBeacon (currently AdminDAO) can call the upgradeTo function
CC Vesting Smart Contracts
abi's for each contract can be found in the
abifolder
src
├── VestingBase.sol
├── VestingBase2.sol
├── VestingBase3.sol
├── VestingFactory.sol
└── VestingFactoryBeacon.sol
| File | % Lines | % Statements | % Branches | % Funcs |
| ---------------------------- | --------------- | --------------- | --------------- | --------------- |
| src/VestingBase3.sol | 100.00% (55/55) | 100.00% (64/64) | 100.00% (14/14) | 100.00% (21/21) |
| src/VestingFactoryBeacon.sol | 100.00% (16/16) | 100.00% (23/23) | 83.33% (5/6) | 100.00% (3/3) |
forge test --fork-url $(grep OPTIMISM_RPC_URL .env | cut -d '=' -f2) --gas-report -vvv
forge coverage --fork-url $(grep OPTIMISM_RPC_URL .env | cut -d '=' -f2)
VestingFactory (for VestingBase2)
0x769a0246afecba504389d0d295cae4bc951daa8e
VestingFactory (for VestingBase)
0x7D840210cB8FB67Ba6394C675dde3408BE3aeCff
VestingFactoryBeacon (for VestingBase3)
0x03C926329D432e2f7a2e99212183bfD055401551
VestingBase3 (for VestingBase3) v3.0.0
0xD6fc79c3De003494c4D26Aa1df41dE926C3e4C1F
VestingBase3 (for VestingBase3) v3.0.1
0xa1F54Fb12e1f548876D38B96D88D08dcE950caA8
VestingBase3 (for VestingBase3) v3.0.2
0xda38F9cBAa43b65cFd0bAE94f9Dbc7EF932e9dAA
VestingBase3 (for VestingBase3) v3.0.3
0x7381bB080D05f8d313a566f20B029D0E8DCDe095
VestingBase3 (for VestingBase3) v3.0.4
0x7FEfaa4Cdbb56708Aa6b2D9e1165F8bBF67667Bc
VestingBase3 (for VestingBase3) v3.0.5
0x5225dFdC934f9569d2612a082E1F66B16DD5d624
VestingBase3 (for VestingBase3) v3.0.6
0xBA467102390Ff4d2CDab1F66e634BD3f37C56849
VestingFactory
0xf37b7e3933d3b8b1eec60112ac073fc7d3b5cb9e
- The
ownerofVestingFactoryBeacon- currently0xF510a2Ff7e9DD7e18629137adA4eb56B9c13E885- the adminDAO multisig, must pay for the creation of the packages. Hence it needs sufficient $KWENTA before this can be executed, and it must approve theVestingFactoryBeaconto spend sufficient $KWENTA for all the packages created in a given transaction. - The
VestingBeaconFactoryaddress is0x03C926329D432e2f7a2e99212183bfD055401551.
The following arguments must be passed in for each package
_beneficiary- address of the CC_startDate- unix timestamp of the start date of the package_vestingAmount- number of $KWENTA in wei for the package, so 1 $KWENTA would be1e18-1000000000000000000. That is 18 zeros._duration- duration until complete maturity of the package. This is in seconds.
This is the function to call:
VestingFactoryBeacon.createVestingPackage(
address[] calldata _beneficiary,
uint256[] calldata _startDate,
uint256[] calldata _vestingAmount,
uint256[] calldata _duration
)
Imagine a CC with a 100 KWENTA vesting package, 4 years total vesting duration.
_beneficiary- lets say this is0x2d228a8ef35463cc39136c54a5e4f4078eeef5c1(a random address)_startDate- 1695232348 (Wed Sep 20 2023 17:52:28 GMT+0000) - the time of writing_vestingAmount- 100000000000000000000_duration- 60 * 60 * 24 * 365 * 4 = 126144000
This is the function to call:
VestingFactoryBeacon.createVestingPackage(
[0x2d228a8ef35463cc39136c54a5e4f4078eeef5c1],
[1695232348],
[100000000000000000000],
[126144000]
)
Then you can just add extra packages to the same transaction, for example, another package for a different beneficiary with 200 KWENTA, 4 years total vesting duration:
VestingFactoryBeacon.createVestingPackage(
[0x2d228a8ef35463cc39136c54a5e4f4078eeef5c1, 0xa82dc8ae1a5129362fdfcdae82619128ca92c033],
[1695232348, 1695232348],
[100000000000000000000, 200000000000000000000],
[126144000, 126144000]
)