Protocol Insolvency Risk Due to Unchecked Asset Increases in Atom and Triple Creation #81
Description
Github username: --
Twitter username: --
Submission hash (on-chain): 0x45af594e37907786efc19eea5046d4b897518936ba6dc1ee0e011e4b1bfa26dd
Severity: high
Description:
Description
In the EthMultiVault contract, there are two critical functions, _createTriple
and _createAtom
, that increase the total assets of vaults without ensuring that the protocol has sufficient ETH to back these increases. This can lead to a situation where the protocol becomes insolvent, unable to fulfill withdrawal requests if users attempt to redeem their shares.
-
In
_createTriple
:- The function increases the total assets of each underlying atom vault by
tripleConfig.atomDepositFractionOnTripleCreation / 3
without increasing the total shares or ensuring the protocol has these additional assets.
- The function increases the total assets of each underlying atom vault by
-
In
_createAtom
:- The function deposits
atomConfig.atomWalletInitialDepositAmount
into the new atom vault for the atom wallet, without verifying if the protocol actually has these funds available.
- The function deposits
These operations create a discrepancy between the recorded total assets and the actual ETH held by the protocol, potentially leading to insolvency.
Attack Scenario\
- An attacker observes that
tripleConfig.atomDepositFractionOnTripleCreation
is set to a non-zero value. - The attacker creates multiple triples, each time increasing the total assets of the underlying atom vaults without actually providing these additional assets.
- Over time, this inflates the recorded total assets of the atom vaults beyond the actual ETH held by the protocol.
- When users try to withdraw their funds, the protocol may not have sufficient ETH to fulfill all withdrawal requests, leading to insolvency.
Similarly, for atom creation:
- An attacker creates multiple atoms, each time the protocol "deposits"
atomConfig.atomWalletInitialDepositAmount
into the new atom vault. - If the protocol doesn't actually hold these funds, it creates a deficit that grows with each atom creation.
- This could be exploited to drain the protocol of its actual ETH holdings.
Attachments
Intuition-0x538dbadc50cc87b281cd655f1edbc6ebda02a66a/src/EthMultiVault.sol
Lines 483 to 487 in b2e422f
Intuition-0x538dbadc50cc87b281cd655f1edbc6ebda02a66a/src/EthMultiVault.sol
Lines 646 to 655 in b2e422f
- Proof of Concept (PoC) File
function testProtocolInsolvencyRisk() external {
vm.startPrank(alice, alice);
// Create atoms
uint256 testAtomCost = getAtomCost();
uint256 subjectId = ethMultiVault.createAtom{value: testAtomCost}("subject");
uint256 predicateId = ethMultiVault.createAtom{value: testAtomCost}("predicate");
uint256 objectId = ethMultiVault.createAtom{value: testAtomCost}("object");
// Create a triple
uint256 tripleCost = getTripleCost();
uint256 tripleId = ethMultiVault.createTriple{value: tripleCost}(subjectId, predicateId, objectId);
vm.stopPrank();
// Record initial balances
uint256 initialProtocolBalance = address(ethMultiVault).balance;
uint256 initialTotalAssets = vaultTotalAssets(subjectId) + vaultTotalAssets(predicateId) + vaultTotalAssets(objectId);
// Set a non-zero atomDepositFractionOnTripleCreation
vm.prank(address(ethMultiVault.generalConfig().admin));
ethMultiVault.setAtomDepositFractionOnTripleCreation(1 ether);
vm.startPrank(bob, bob);
// Create another triple, which should increase atom vault balances
ethMultiVault.createTriple{value: tripleCost}(subjectId, predicateId, objectId);
vm.stopPrank();
// Check final balances
uint256 finalProtocolBalance = address(ethMultiVault).balance;
uint256 finalTotalAssets = vaultTotalAssets(subjectId) + vaultTotalAssets(predicateId) + vaultTotalAssets(objectId);
// Verify that recorded assets have increased more than actual ETH balance
assertGt(finalTotalAssets - initialTotalAssets, finalProtocolBalance - initialProtocolBalance, "Protocol should show higher asset increase than actual ETH received");
// Attempt to withdraw all assets from an atom vault
vm.prank(alice);
uint256 aliceShares = ethMultiVault.vaults(subjectId).balanceOf[alice];
vm.expectRevert(); // Expect this to revert due to insufficient funds
ethMultiVault.redeemAtom(aliceShares, alice, subjectId);
}
- Revised Code File (Optional)
contract EthMultiVault {
uint256 public protocolOwnedAssets;
function _createTriple(uint256 subjectId, uint256 predicateId, uint256 objectId, uint256 value) internal returns (uint256, uint256) {
// ... existing code ...
if (tripleConfig.atomDepositFractionOnTripleCreation > 0) {
uint256 totalIncrease = tripleConfig.atomDepositFractionOnTripleCreation;
require(protocolOwnedAssets >= totalIncrease, "Insufficient protocol assets");
protocolOwnedAssets -= totalIncrease;
for (uint256 i = 0; i < 3; i++) {
uint256 atomId = tripleAtomIds[i];
uint256 increase = totalIncrease / 3;
_setVaultTotals(
atomId,
vaults[atomId].totalAssets + increase,
vaults[atomId].totalShares
);
}
}
// ... rest of the function ...
}
function _createAtom(bytes calldata atomUri, uint256 value) internal returns (uint256, uint256) {
// ... existing code ...
uint256 initialDeposit = atomConfig.atomWalletInitialDepositAmount;
require(protocolOwnedAssets >= initialDeposit, "Insufficient protocol assets");
protocolOwnedAssets -= initialDeposit;
_depositOnVaultCreation(
id,
atomWallet,
initialDeposit
);
// ... rest of the function ...
}
// New function to add protocol-owned assets
function addProtocolAssets() external payable {
protocolOwnedAssets += msg.value;
}
// ... other necessary changes ...
}
// Comments:
// 1. We've introduced a `protocolOwnedAssets` variable to track the protocol's actual ETH holdings.
// 2. Before increasing vault assets or making deposits, we check if the protocol has sufficient assets.
// 3. We deduct from `protocolOwnedAssets` when increasing vault balances or making initial atom wallet deposits.
// 4. A new function `addProtocolAssets` allows the protocol to add to its asset holdings.
// 5. This approach ensures that the protocol cannot become insolvent due to these operations.
// 6. Additional changes may be needed throughout the contract to maintain this balance accurately.