<!-- canonical: https://0xsimao.com/findings/spirit-protocol-vesting-overwrites-vestings-recipient -->

# SpiritVestingFactory::createSpiritVestingContract overwrites the spiritVestings mapping for the same recipient

Low/Info · 0xSimao · AI revenue sharing · 28th November 2025

Finding I-1 of the Spirit Protocol security review.

- Report: /reports/spirit-protocol
- Source: https://github.com/0xSimao/audits/blob/main/0xSimao/2025-11-28-spirit-protocol.pdf

---

**Description:**

[SpiritVestingFactory::createSpiritVestingContract()](https://github.com/0xPilou/spirit-contracts/blob/main/src/vesting/SpiritVestingFactory.sol#L87) is as follows:
```solidity
function createSpiritVestingContract(
    address recipient,
    uint256 amount,
    uint256 cliffAmount,
    uint32 cliffDate,
    uint32 endDate
) external onlyTreasury returns (address newSpiritVestingContract) {
    if (!(cliffAmount < amount)) revert FORBIDDEN();

    uint256 vestingDuration = endDate - cliffDate;

    uint256 vestingAmount = amount - cliffAmount;
    int96 flowRate = int256(vestingAmount / vestingDuration).toInt96();

    // Add the remainder to the cliff amount
    cliffAmount += vestingAmount - (uint96(flowRate) * vestingDuration);

    // Deploy the new SPIRIT Token Vesting contract
    newSpiritVestingContract =
        address(new SpiritVesting(VESTING_SCHEDULER, SPIRIT, recipient, cliffDate, flowRate, cliffAmount, endDate));

    // Maps the recipient address to the new SPIRIT Token Vesting contract
    spiritVestings[recipient] = newSpiritVestingContract; //@audit overwrites previous vesting contract if exists

    // Transfer the tokens from the treasury to the new vesting contract
    SPIRIT.transferFrom(msg.sender, newSpiritVestingContract, amount);

    // Emit the events
    emit Transfer(address(0), recipient, amount);
    emit SpiritVestingCreated(recipient, newSpiritVestingContract);
}
```
Note that creating more than 1 vesting contract for the same recipient overwrites the old now. The only impact is that the `SpiritVestingFactory::balanceOf()` function will point to the new vesting contract only.

**Impact:**

Informational, inconsistent behaviour.

**Recommended Mitigation:**

Consider replacing the mapping with an array.

**0xSimao:**

Fixed in PR [#2](https://github.com/0xPilou/spirit-contracts/pull/2).

---

Related findings:

- [BasicVaultFactory::createVault() inconsistent already existing vault check](https://0xsimao.com/findings/mitosis-create-inconsistent-already-existing): Mitosis
- [Double spending attack in the Vesting contract](https://0xsimao.com/findings/symmio-staking-and-vesting-double-spending-attack-vesting): Symmio, Staking and Vesting
- [In MapleLoanFactory, isLoan has the same functionality of isInstance and thus can be removed.](https://0xsimao.com/findings/maple-finance-iii-functionality-instance-thus-removed): Maple Finance
