Summary
The MembershipERC1155 TransparentUpgradeableProxy
is initialized with the wrong initialOwner
address in the createNewDAOMembership
function. This error occurs due to the assignment of proxyAdmin
(an address of a contract) as the initialOwner
, instead of an EOA (Externally Owned Account) that could perform future upgrades. Consequently, if upgrades are needed for the MembershipERC1155
, they would be inaccessible since the proxyAdmin
contract cannot invoke upgradeAndCall
.
Vulnerability Details
The TransparentUpgradeableProxy
contract requires three parameters: _logic
, initialOwner
, and _data
. In createNewDAOMembership
, the initialOwner
is set as the proxyAdmin
, this is the address of the proxyAdmin contract created in the MembershipFactory constructor. The TransparentUpgradeableProxy
contract creates a new proxyAdmin
instance and sets the originally created proxyAdmin
(from MembershipFactory
) as its owner.
The onlyOwner
modifier on upgradeAndCall
restricts future upgrades, as the proxyAdmin
contract (incorrectly set as the initialOwner
) cannot be used to invoke upgradeAndCall
.
constructor(
address _currencyManager,
address _owpWallet,
string memory _baseURI,
address _membershipImplementation
) {
currencyManager = ICurrencyManager(_currencyManager);
baseURI = _baseURI;
owpWallet = _owpWallet;
membershipImplementation = _membershipImplementation;
@> proxyAdmin = new ProxyAdmin(msg.sender);
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(EXTERNAL_CALLER, msg.sender);
}
function createNewDAOMembership(DAOInputConfig calldata daoConfig, TierConfig[] calldata tierConfigs)
external
returns (address)
{
require(currencyManager.isCurrencyWhitelisted(daoConfig.currency), "Currency not accepted.");
require(daoConfig.noOfTiers == tierConfigs.length, "Invalid tier input.");
require(daoConfig.noOfTiers > 0 && daoConfig.noOfTiers <= TIER_MAX, "Invalid tier count.");
require(getENSAddress[daoConfig.ensname] == address(0), "DAO already exist.");
if (daoConfig.daoType == DAOType.SPONSORED) {
require(daoConfig.noOfTiers == TIER_MAX, "Invalid tier count for sponsored.");
}
uint256 totalMembers = 0;
for (uint256 i = 0; i < tierConfigs.length; i++) {
totalMembers += tierConfigs[i].amount;
}
require(totalMembers <= daoConfig.maxMembers, "Sum of tier amounts exceeds maxMembers.");
console.log("admin1", address(proxyAdmin));
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
membershipImplementation,
@> address(proxyAdmin),
abi.encodeWithSignature(
"initialize(string,string,string,address,address)",
daoConfig.ensname,
"OWP",
baseURI,
_msgSender(),
daoConfig.currency
)
);
DAOConfig storage dao = daos[address(proxy)];
dao.ensname = daoConfig.ensname;
dao.daoType = daoConfig.daoType;
dao.currency = daoConfig.currency;
dao.maxMembers = daoConfig.maxMembers;
dao.noOfTiers = daoConfig.noOfTiers;
for (uint256 i = 0; i < tierConfigs.length; i++) {
require(tierConfigs[i].minted == 0, "Invalid tier config");
dao.tiers.push(tierConfigs[i]);
}
getENSAddress[daoConfig.ensname] = address(proxy);
userCreatedDAOs[_msgSender()][daoConfig.ensname] = address(proxy);
emit MembershipDAONFTCreated(daoConfig.ensname, address(proxy), dao);
return address(proxy);
}
constructor(address _logic, address initialOwner, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
@> _admin = address(new ProxyAdmin(initialOwner));
ERC1967Utils.changeAdmin(_proxyAdmin());
}
Impact
There is no way to upgrade the MembershipERC1155
proxy contract if necessary. This could result in functional and security issues remaining unresolved.
Tools Used
Manual Review
Recommendations
Update the createNewDAOMembership
function to set initialOwner
to an address capable of performing upgrades, such as an EOA that is the actual owner or DAO multisig address for more secure access management.