#46092 [SC-Insight] AgentVault::destroy mismatch between comment documentation and contract behavior
Submitted on May 24th 2025 at 18:37:18 UTC by @hunter0xweb3 for Audit Comp | Flare | FAssets
Report ID: #46092
Report Type: Smart Contract
Report severity: Insight
Target: https://github.com/flare-foundation/fassets/blob/main/contracts/assetManager/implementation/AgentVault.sol
Impacts:
Description
Brief/Intro
The documentation comments in AgentVault::destroy states that all funds (tokens and native balance) are transfer out to AgentVaultOwner However destroy functionality behaves different
Vulnerability Details
The documentation comments in AgentVault::destroy states that all funds (tokens and native balance) are transfered out to AgentVaultOwner https://github.com/flare-labs-ltd/fassets/blob/acb82a27b15c56ce9dfbb6dbbd76008da6753c26/contracts/assetManager/implementation/AgentVault.sol#L189-L190:
// Used by asset manager when destroying agent.
// Completely erases agent vault and transfers all funds to the owner.
However destroy functionality behaves different because it allows sending the funds to an arbitrary address (defined on call by assetManager). https://github.com/flare-labs-ltd/fassets/blob/acb82a27b15c56ce9dfbb6dbbd76008da6753c26/contracts/assetManager/implementation/AgentVault.sol#L189-L218
@> function destroy(address payable _recipient)
external override
onlyAssetManager
nonReentrant
{
uint256 length = usedTokens.length;
for (uint256 i = 0; i < length; i++) {
IERC20 token = usedTokens[i];
uint256 useFlags = tokenUseFlags[token];
//... snippet; undelegation logic...
if ((useFlags & TOKEN_DEPOSIT) != 0) {
uint256 balance = token.balanceOf(address(this));
if (balance > 0) {
@> token.safeTransfer(_recipient, balance);
}
}
}
// transfer native balance, if any (used to be done by selfdestruct)
@> Transfers.transferNAT(_recipient, address(this).balance);
}
This leads to a mismatch between documentation and contract behavior leading to potentially loss of funds and mismatch behavior between documentation and implementation described in detail in impact section
A better approach is use assetManager.getAgentVaultOwner(address(this)) to retrieve the owner address like used in AgentVault::transferExternalToken
function transferExternalToken(IERC20 _token, uint256 _amount)
external override onlyOwner nonReentrant {
//...
@> address ownerManagementAddress = assetManager.getAgentVaultOwner(address(this));
@> _token.safeTransfer(ownerManagementAddress, _amount);
}
Impact Details
Extra parameter defined in AgentVault::destroy leading to potentially loss of funds if non owner controlled address is provided by assetManager
Mismatch between documentation and contract behavior
References
https://github.com/flare-labs-ltd/fassets/blob/acb82a27b15c56ce9dfbb6dbbd76008da6753c26/contracts/assetManager/implementation/AgentVault.sol#L189-L190 https://github.com/flare-labs-ltd/fassets/blob/acb82a27b15c56ce9dfbb6dbbd76008da6753c26/contracts/assetManager/implementation/AgentVault.sol#L191-L218 https://github.com/flare-labs-ltd/fassets/blob/acb82a27b15c56ce9dfbb6dbbd76008da6753c26/contracts/assetManager/implementation/AgentVault.sol#L124-L132
Proof of Concept
Proof of Concept
AgentVault::destroy is called with _recipient parameter address non controlled by AgentVault's owner Loss of funds occurs because of mismatch between documentation and contract behavior
Was this helpful?