The createNewDAOMembership() function is supposed to create a new DAO membership by deploying a transparent proxy.
OpenZeppelin’s TransparentUpgradeableProxy contract expects the address of an initialOwner and automatically deploys its own ProxyAdmin. However, in the membershipFactory.sol code, a separate ProxyAdmin instance is deployed manually (let’s call it PA1), and its address is passed to the TransparentUpgradeableProxy as the initialOwner.
As a result, when TransparentUpgradeableProxy deploys its own ProxyAdmin (let’s call this one PA2), it sets PA1 as the owner of PA2. But PA1 is simply a deployed contract and can't interact with PA2 to perform upgrades. This configuration blocks any upgrades for the proxy.
The flow looks like this:
msg.sender → proxyAdmin1 (PA1) → proxyAdmin2 (PA2) → TransparentProxy
InmembershipFactory.solthe constructor deploys a proxyAdmin (lets call it PA1 contract):
user calls createNewDAOMembership()function to create a new DAO membership
This function creates a TransparentUpgradeableProxy and passes PA1 address as initialOwner:
Inside the TransparentUpgradeableProxy constructor, another ProxyAdmin (let's call it PA2) is deployed, with PA1 set as its initialOwner. This code in OpenZeppelin’s library demonstrates that:
Consequently, PA1 (deployed contract) is the owner of PA2. And PA2 acts as the proxyAdmin of the proxy. PA1 can not call functions on PA2 to perform upgrades or change admin. As a result, the proxy cannot be upgraded
The membership DAO cannot be upgraded due to an incorrect configuration of the proxyAdmin ownership.
vscode, manual review
don't deploy a proxyAdmin and just set msg.sender as owner of proxyAdmin (which will be deployed by openZeplin library automatically):
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.