InsureDAO contest - WatchPug's results

Anyone can create an insurance pool like Uniswap.

General Information

Platform: Code4rena

Start Date: 07/01/2022

Pot Size: $80,000 USDC

Total HM: 21

Participants: 37

Period: 7 days

Judge: 0xean

Total Solo HM: 14

Id: 71

League: ETH

InsureDAO

Findings Distribution

Researcher Performance

Rank: 1/37

Findings: 24

Award: $25,579.68

🌟 Selected for report: 23

🚀 Solo Findings: 10

Findings Information

🌟 Selected for report: cmichel

Also found by: WatchPug

Labels

bug
duplicate
3 (High Risk)

Awards

1709.7813 INSURE - $598.42

1038.0815 USDC - $1,038.08

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/PoolTemplate.sol#L178-L221

function initialize(
    string calldata _metaData,
    uint256[] calldata _conditions,
    address[] calldata _references
) external override {
    require(
        initialized == false &&
            bytes(_metaData).length > 0 &&
            _references[0] != address(0) &&
            _references[1] != address(0) &&
            _references[2] != address(0) &&
            _references[3] != address(0) &&
            _references[4] != address(0) &&
            _conditions[0] <= _conditions[1],
        "ERROR: INITIALIZATION_BAD_CONDITIONS"
    );
    initialized = true;

    string memory _name = string(
        abi.encodePacked(
            "InsureDAO-",
            IERC20Metadata(_references[1]).name(),
            "-PoolInsurance"
        )
    );
    string memory _symbol = string(
        abi.encodePacked("i-", IERC20Metadata(_references[1]).symbol())
    );
    uint8 _decimals = IERC20Metadata(_references[0]).decimals();

    initializeToken(_name, _symbol, _decimals);

    registry = IRegistry(_references[2]);
    parameters = IParameters(_references[3]);
    vault = IVault(parameters.getVault(_references[1]));

    metadata = _metaData;

    marketStatus = MarketStatus.Trading;

    if (_conditions[1] > 0) {
        _depositFrom(_conditions[1], _references[4]);
    }
}

Anyone can create a pool from a whitelisted template and can specify certain parameters.

However, the parameters passed to PoolTemplate.sol#initialize() can be used to deposit from a specified address as long as they have approved the Vault contract before.

PoC

  • templates[address(_template)].approval == true
  • reflist[address(_template)][4][address(0)] = true
  1. Alice approved USDC for uint256.max to the Vault contract;
  2. Attacker call Factory#createMarket() and set _conditions[1] = 10,000 and _references[4] = Alice's address;

Impact

Users' allowances can be used by anyone.

Recommendation

Change to:

function initialize(
    string calldata _metaData,
    uint256[] calldata _conditions,
    address[] calldata _references
) external override {
    require(
        initialized == false &&
            bytes(_metaData).length > 0 &&
            _references[0] != address(0) &&
            _references[1] != address(0) &&
            _references[2] != address(0) &&
            _references[3] != address(0) &&
            _references[4] != address(0) &&
            _conditions[0] <= _conditions[1],
        "ERROR: INITIALIZATION_BAD_CONDITIONS"
    );
    initialized = true;

    string memory _name = string(
        abi.encodePacked(
            "InsureDAO-",
            IERC20Metadata(_references[1]).name(),
            "-PoolInsurance"
        )
    );
    string memory _symbol = string(
        abi.encodePacked("i-", IERC20Metadata(_references[1]).symbol())
    );
    uint8 _decimals = IERC20Metadata(_references[0]).decimals();

    initializeToken(_name, _symbol, _decimals);

    registry = IRegistry(_references[2]);
    parameters = IParameters(_references[3]);
    vault = IVault(parameters.getVault(_references[1]));

    metadata = _metaData;

    marketStatus = MarketStatus.Trading;

    if (_conditions[1] > 0) {
        _depositFrom(_conditions[1], tx.origin);
    }
}

#0 - oishun1112

2022-01-19T10:36:54Z

I don't want to use tx.origin

#2 - CloudEllie

2022-02-21T22:38:58Z

Noting here that post-awarding, the judge @0xean reviewed additional input from the sponsor and determined that this issue should be treated as a duplicate of #250 for the purposes of the report.

#3 - 0xean

2022-02-22T14:00:02Z

confirming dupe of #250

Findings Information

🌟 Selected for report: cmichel

Also found by: Ruhum, WatchPug, camden

Labels

bug
duplicate
3 (High Risk)

Awards

692.4614 INSURE - $242.36

420.423 USDC - $420.42

External Links

Handle

WatchPug

Vulnerability details

Based on the context, withdrawRedundant() intends to disallow the owner to withdraw more Vault tokens than the surplus amount.

However, the current implementation is wrong, which allows the Vault owner to drain all the funds.

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Vault.sol#L461-L479

function withdrawRedundant(address _token, address _to)
    external
    override
    onlyOwner
{
    if (
        _token == address(token) &&
        balance < IERC20(token).balanceOf(address(this))
    ) {
        uint256 _redundant = IERC20(token).balanceOf(address(this)) -
            balance;
        IERC20(token).safeTransfer(_to, _redundant);
    } else if (IERC20(_token).balanceOf(address(this)) > 0) {
        IERC20(_token).safeTransfer(
            _to,
            IERC20(_token).balanceOf(address(this))
        );
    }
}

PoC

If the owner's private key is compromised, the attacker can:

When token.balanceOf(address(this)) == balance:

  1. Call withdrawRedundant(), withdraw all the funds.

When token.balanceOf(address(this)) > balance:

  1. Call withdrawRedundant(), withdraw surplus amount;
  2. Call withdrawRedundant(), withdraw all the funds.

When token.balanceOf(address(this)) < balance:

  1. Transfer balance - token.balanceOf(address(this)) to the Vault contract;
  2. Call withdrawRedundant(), withdraw all the funds.

Recommendation

Change to:

function withdrawRedundant(address _token, address _to)
    external
    override
    onlyOwner
{
    if (
        _token == address(token) &&
        balance < IERC20(token).balanceOf(address(this))
    ) {
        uint256 _redundant = IERC20(token).balanceOf(address(this)) -
            balance;
        IERC20(token).safeTransfer(_to, _redundant);
    } else if (
        _token != address(token) &&
        IERC20(_token).balanceOf(address(this)) > 0
    ) {
        IERC20(_token).safeTransfer(
            _to,
            IERC20(_token).balanceOf(address(this))
        );
    }
}

Findings Information

🌟 Selected for report: WatchPug

Labels

bug
3 (High Risk)
disagree with severity
sponsor acknowledged

Awards

3799.5139 INSURE - $1,329.83

2306.8477 USDC - $2,306.85

External Links

Handle

WatchPug

Vulnerability details

The current design/implementation allows a market address (registered on registry) to call Vault#addValue() and transfer tokens from an arbitrary address to a specified _beneficiary up the approved amount at any time, and the _beneficiary can withdraw the funds by calling Vault#withdrawAllAttribution() immediately.

This poses a very dangerous risk to all the users that approved their tokens to the Vault contracts (each one holds all users' allowances for that token).

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Vault.sol#L52-L58

modifier onlyMarket() {
    require(
        IRegistry(registry).isListed(msg.sender),
        "ERROR_ONLY_MARKET"
    );
    _;
}

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Vault.sol#L124-L140

function addValue(
    uint256 _amount,
    address _from,
    address _beneficiary
) external override onlyMarket returns (uint256 _attributions) {

    if (totalAttributions == 0) {
        _attributions = _amount;
    } else {
        uint256 _pool = valueAll();
        _attributions = (_amount * totalAttributions) / _pool;
    }
    IERC20(token).safeTransferFrom(_from, address(this), _amount);
    balance += _amount;
    totalAttributions += _attributions;
    attributions[_beneficiary] += _attributions;
}

Registry owner can call Registry#supportMarket() and mark an arbitrary address as a market.

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Registry.sol#L49-L60

function supportMarket(address _market) external override {
    require(!markets[_market], "ERROR: ALREADY_REGISTERED");
    require(
        msg.sender == factory || msg.sender == ownership.owner(),
        "ERROR: UNAUTHORIZED_CALLER"
    );
    require(_market != address(0), "ERROR: ZERO_ADDRESS");

    allMarkets.push(_market);
    markets[_market] = true;
    emit NewMarketRegistered(_market);
}

Or, the owner of the Factory can call createMarket() to add a malicous market contract via a custom template contract to the markets list.

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Factory.sol#L214-L216

PoC

A malicious/compromised Registry owner can:

  1. Call Registry#supportMarket() and set markets[attackerAddress] to true;
  2. Call Vault#addValue(token.balanceOf(victimAddress), victimAddress, attackerAddress) and transferring all the balanceOf victim's wallet to the vault, owned by attackerAddress.
  3. Call Vault#withdrawAllAttribution(attackerAddress) and retrive the funds.

The malicious/compromised Registry owner can repeat the steps above for all the users who approved the Vault contract for all the Vault contracts.

As a result, the attacker can steal all the wallet balances of the tokens approved to the protocol.

Root Cause

Improper access control for using users' allowances.

Recommendation

Consider changing the design/implementation to make sure that the allowances approved by the users can only be used by themselves.

#0 - oishun1112

2022-01-20T07:36:36Z

this is an issue only when ownership control has fail. This architecture is necessary to achieve simplicity of the code. We assume ownership control works fine.

#1 - 0xean

2022-01-27T15:19:57Z

Agree with warden that the privilege addresses should not be able to use approvals in a way that rugs users funds.

Based on the fact that we have seen many rug pulls in the space based on compromised "owner" keys, this is a valid attack path.

3 — High: Assets can be stolen/lost/compromised directly (or indirectly if there is a valid attack path that does not have hand-wavy hypotheticals).

Findings Information

🌟 Selected for report: WatchPug

Labels

bug
3 (High Risk)
disagree with severity
resolved
sponsor confirmed

Awards

3799.5139 INSURE - $1,329.83

2306.8477 USDC - $2,306.85

External Links

Handle

WatchPug

Vulnerability details

Root Cause

Precision loss while converting between the amount of shares and the amount of underlying tokens back and forth is not handled properly.


https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/IndexTemplate.sol#L438-L447

uint256 _shortage;
if (totalLiquidity() < _amount) {
    //Insolvency case
    _shortage = _amount - _value;
    uint256 _cds = ICDSTemplate(registry.getCDS(address(this)))
        .compensate(_shortage);
    _compensated = _value + _cds;
}
vault.offsetDebt(_compensated, msg.sender);

In the current implementation, when someone tries to resume the market after a pending period ends by calling PoolTemplate.sol#resume(), IndexTemplate.sol#compensate() will be called internally to make a payout. If the index pool is unable to cover the compensation, the CDS pool will then be used to cover the shortage.

However, while CDSTemplate.sol#compensate() takes a parameter for the amount of underlying tokens, it uses vault.transferValue() to transfer corresponding _attributions (shares) instead of underlying tokens.

Due to precision loss, the _attributions transferred in the terms of underlying tokens will most certainly be less than the shortage.

At L444, the contract believes that it's been compensated for _value + _cds, which is lower than the actual value, due to precision loss.

At L446, when it calls vault.offsetDebt(_compensated, msg.sender), the tx will revert at require(underlyingValue(msg.sender) >= _amount).

As a result, resume() can not be done, and the debt can't be repaid.

PoC

Given:

  • vault.underlyingValue = 10,000
  • vault.valueAll = 30,000
  • totalAttributions = 2,000,000
  • _amount = 1,010,000
  1. _shortage = _amount - vault.underlyingValue = 1,000,000
  2. _attributions = (_amount * totalAttributions) / valueAll = 67,333,333
  3. actualValueTransfered = (valueAll * _attributions) / totalAttributions = 1009999

Expected results: actualValueTransfered = _shortage;

Actual results: actualValueTransfered < _shortage.

Impact

The precision loss isn't just happening on special numbers, but will most certainly always revert the txs.

This will malfunction the contract as the index pool can not compensate(), therefore the pool can not resume(). Causing the funds of the LPs of the pool and the index pool to be frozen, and other stakeholders of the same vault will suffer fund loss from an unfair share of the funds compensated before.

Recommendation

Change to:

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/IndexTemplate.sol#L439-L446

if (totalLiquidity() < _amount) {
    //Insolvency case
    _shortage = _amount - _value;
    uint256 _cds = ICDSTemplate(registry.getCDS(address(this)))
        .compensate(_shortage);
    _compensated = vault.underlyingValue(address(this));
}
vault.offsetDebt(_compensated, msg.sender);

#0 - takadr

2022-01-24T15:32:24Z

@oishun1112 I think the suggested code doesn't make sense to me because _cds is not being used and vault.underlyingValue(address(this)) is defined as _value before. I guess what he want to do is something like this to avoid precision loss?

if (totalLiquidity() < _amount) {
    //Insolvency case
    _shortage = _amount - _value;
    uint256 _cds = ICDSTemplate(registry.getCDS(address(this)))
        .compensate(_shortage);
    if(_cds < _shortage) {
      _compensated = _value + _cds;
    } else {
      // To avoid reversion due to precision loss on "underlyingValue(msg.sender) >= _amount" in "offsetDebt"
      _compensated = vault.underlyingValue(msg.sender);
    }
}
vault.offsetDebt(_compensated, msg.sender);

#1 - oishun1112

2022-01-27T05:34:29Z

he meant, _value + _cds ≒ vault.underlyingValue(address(this)) and this is true.

  1. IndexTemplate:compensate()
cds.compensate(_amount)
  1. CDSTemplate:"compensate() returns(_compensated)",
vault.transferValue(_compensated, msg.sender)
  1. Vault:"transferValue(_amount, )"
_attributions = (_amount * totalAttributions) / _valueAll

_attributions can be round down and become smaller than _amount's value. => _compensated can be higher than actual attribution transfer.

  1. IndexTemplate: compensate()
vault.offsetDebt(_compensated, msg.sender);

try to offsetDebt with _compensated.

  1. Vault: offsetDebt(_amount, )
require(underlyingValue(msg.sender, _valueAll) >= _amount)

_amount can be higher than actual attribution of index.

  1. Vault: underlyingValue()
return (_valueAll * attributions[_target]) / totalAttributions

=> attributions[index] can be less than intended, so return can be less than intended too. => trigger require() in step4

#2 - oishun1112

2022-01-27T05:38:45Z

In conclusion, this can be reverted due to the rounding down in the process of

value_1(_cds) -> attribution -> value_2 (underlyingValue(index) )

so,

value_1 >= attribution >= value_2

This can be prevent by doing

_compensated = vault.underlyingValue(index)

after step 2, using the actual value index has

#3 - oishun1112

2022-01-27T05:40:03Z

_cds won't be necessary anymore

#4 - takadr

2022-01-27T07:21:25Z

@oishun1112 Ah I got it now thank you for the detail. Yes I was understanding there was vulnerability as he said but I missed that the return value of vault.underlyingValue(index) is different from the one that is called before cds.compensate(shortage)

Findings Information

🌟 Selected for report: WatchPug

Labels

bug
3 (High Risk)
disagree with severity
sponsor acknowledged

Awards

3799.5139 INSURE - $1,329.83

2306.8477 USDC - $2,306.85

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Vault.sol#L485-L496

function setController(address _controller) public override onlyOwner {
    require(_controller != address(0), "ERROR_ZERO_ADDRESS");

    if (address(controller) != address(0)) {
        controller.migrate(address(_controller));
        controller = IController(_controller);
    } else {
        controller = IController(_controller);
    }

    emit ControllerSet(_controller);
}

The owner of the Vault contract can set an arbitrary address as the controller.

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Vault.sol#L342-L352

function utilize() external override returns (uint256 _amount) {
    if (keeper != address(0)) {
        require(msg.sender == keeper, "ERROR_NOT_KEEPER");
    }
    _amount = available(); //balance
    if (_amount > 0) {
        IERC20(token).safeTransfer(address(controller), _amount);
        balance -= _amount;
        controller.earn(address(token), _amount);
    }
}

A malicious controller contract can transfer funds from the Vault to the attacker.

PoC

A malicious/compromised can:

  1. Call Vault#setController() and set controller to a malicious contract;
    • L489 the old controller will transfer funds to the new, malicious controller.
  2. Call Vault#utilize() to deposit all the balance in the Vault contract into the malicious controller contract.
  3. Withdraw all the funds from the malicious controller contract.

Recommendation

Consider disallowing Vault#setController() to set a new address if a controller is existing, which terminates the possibility of migrating funds to a specified address provided by the owner. Or, putting a timelock to this function at least.

#0 - oishun1112

2022-01-20T07:51:43Z

we assume ownership control is driven safely

#1 - 0xean

2022-01-27T15:22:00Z

Agree with warden that the privilege addresses should not be able to use approvals in a way that rugs users funds.

Based on the fact that we have seen many rug pulls in the space based on compromised "owner" keys, this is a valid attack path.

3 — High: Assets can be stolen/lost/compromised directly (or indirectly if there is a valid attack path that does not have hand-wavy hypotheticals).

Findings Information

🌟 Selected for report: WatchPug

Labels

bug
3 (High Risk)
disagree with severity
sponsor acknowledged

Awards

3799.5139 INSURE - $1,329.83

2306.8477 USDC - $2,306.85

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Vault.sol#L52-L58

modifier onlyMarket() {
    require(
        IRegistry(registry).isListed(msg.sender),
        "ERROR_ONLY_MARKET"
    );
    _;
}

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Vault.sol#L201-L206

function borrowValue(uint256 _amount, address _to) external onlyMarket override {
    debts[msg.sender] += _amount;
    totalDebt += _amount;

    IERC20(token).safeTransfer(_to, _amount);
}

The current design/implementation allows a market address (registered on the registry) to call Vault#borrowValue() and transfer tokens to an arbitrary address.

PoC

See the PoC section on [WP-H24].

Recommendation

  1. Consider adding constrains (eg. timelock) to Registry#supportMarket().
  2. Consdier adding constrains (upper bound for each pool, and index pool for example) to Vault#borrowValue().

#0 - oishun1112

2022-01-20T07:27:32Z

Ownership has to be stolen to drain funds using this method and we assume ownership control driven safely, so we don't treat this as issue

#1 - 0xean

2022-01-27T15:22:31Z

Agree with warden that the privilege addresses should not be able to use approvals in a way that rugs users funds.

Based on the fact that we have seen many rug pulls in the space based on compromised "owner" keys, this is a valid attack path.

3 — High: Assets can be stolen/lost/compromised directly (or indirectly if there is a valid attack path that does not have hand-wavy hypotheticals).

Findings Information

🌟 Selected for report: WatchPug

Also found by: danb

Labels

bug
3 (High Risk)
resolved
sponsor confirmed

Awards

1709.7813 INSURE - $598.42

1038.0815 USDC - $1,038.08

External Links

Handle

WatchPug

Vulnerability details

Root Cause

Wrong arithmetic.


https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/PoolTemplate.sol#L700-L717

uint256 _deductionFromIndex = (_debt * _totalCredit * MAGIC_SCALE_1E6) /
            totalLiquidity();
    uint256 _actualDeduction;
    for (uint256 i = 0; i < indexList.length; i++) {
        address _index = indexList[i];
        uint256 _credit = indicies[_index].credit;
        if (_credit > 0) {
            uint256 _shareOfIndex = (_credit * MAGIC_SCALE_1E6) /
                _totalCredit;
            uint256 _redeemAmount = _divCeil(
                _deductionFromIndex,
                _shareOfIndex
            );
            _actualDeduction += IIndexTemplate(_index).compensate(
                _redeemAmount
            );
        }
    }

PoC

  • totalLiquidity = 200,000* 10**18;

  • totalCredit = 100,000 * 10**18;

  • debt = 10,000 * 10**18;

  • [Index Pool 1] Credit = 20,000 * 10**18;

  • [Index Pool 2] Credit = 30,000 * 10**18;

uint256 _deductionFromIndex = (_debt * _totalCredit * MAGIC_SCALE_1E6) / totalLiquidity(); // _deductionFromIndex = 10,000 * 10**6 * 10**18;

[Index Pool 1]:

uint256 _shareOfIndex = (_credit * MAGIC_SCALE_1E6) / _totalCredit; // _shareOfIndex = 200000 uint256 _redeemAmount = _divCeil( _deductionFromIndex, _shareOfIndex ); // _redeemAmount = 25,000 * 10**18;

[Index Pool 2]:

uint256 _shareOfIndex = (_credit * MAGIC_SCALE_1E6) / _totalCredit; // _shareOfIndex = 300000 uint256 _redeemAmount = _divCeil( _deductionFromIndex, _shareOfIndex ); // _redeemAmount = 16666666666666666666667 (~ 16,666 * 10**18)

In most cases, the transaction will revet on underflow at:

uint256 _shortage = _deductionFromIndex / MAGIC_SCALE_1E6 - _actualDeduction;

In some cases, specific pools will be liable for unfair compensation:

If the CSD is empty, Index Pool 1 only have 6,000 * 10**18 and Index Pool 2 only have 4,000 * 10**18, the _actualDeduction will be 10,000 * 10**18, _deductionFromPool will be 0.

Index Pool 1 should only pay 1,000 * 10**18, but actually paid 6,000 * 10**18, the LPs of Index Pool 1 now suffer funds loss.

Recommendation

Change to:

uint256 _deductionFromIndex = (_debt * _totalCredit * MAGIC_SCALE_1E6) / totalLiquidity();
uint256 _actualDeduction;
for (uint256 i = 0; i < indexList.length; i++) {
    address _index = indexList[i];
    uint256 _credit = indicies[_index].credit;
    if (_credit > 0) {
        uint256 _shareOfIndex = (_credit * MAGIC_SCALE_1E6) /
            _totalCredit;
        uint256 _redeemAmount = _divCeil(
            _deductionFromIndex * _shareOfIndex,
            MAGIC_SCALE_1E6 * MAGIC_SCALE_1E6
        );
        _actualDeduction += IIndexTemplate(_index).compensate(
            _redeemAmount
        );
    }
}

Findings Information

🌟 Selected for report: WatchPug

Also found by: leastwood

Labels

bug
3 (High Risk)

Awards

1709.7813 INSURE - $598.42

1038.0815 USDC - $1,038.08

External Links

Handle

WatchPug

Vulnerability details

Based on the context, the system intends to lock all the lps during PayingOut period.

However, the current implementation allows anyone, including LPs to call resume() and unlock the index pool.

It allows a malicious LP to escape the responsibility for the compensation, at the expense of other LPs paying more than expected.

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/IndexTemplate.sol#L459-L471

function resume() external override {
    uint256 _poolLength = poolList.length;

    for (uint256 i = 0; i < _poolLength; i++) {
        require(
            IPoolTemplate(poolList[i]).paused() == false,
            "ERROR: POOL_IS_PAUSED"
        );
    }

    locked = false;
    emit Resumed();
}

Recommendation

Change to:

function resume() external override {
   uint256 _poolLength = poolList.length;

   for (uint256 i = 0; i < _poolLength; i++) {
       require(
           IPoolTemplate(poolList[i]).marketStatus() == MarketStatus.Trading,
           "ERROR: POOL_IS_PAYINGOUT"
       );
   }

   locked = false;
   emit Resumed();
}

Findings Information

🌟 Selected for report: WatchPug

Labels

bug
3 (High Risk)
sponsor confirmed

Awards

3799.5139 INSURE - $1,329.83

2306.8477 USDC - $2,306.85

External Links

Handle

WatchPug

Vulnerability details

In the current implementation, when an incident is reported for a certain pool, the index pool can still withdrawCredit() from the pool, which in the best interest of an index pool, the admin of the index pool is preferred to do so.

This allows the index pool to escape from the responsibility for the risks of invested pools.

Making the LPs of the pool take an unfair share of the responsibility.

PoC

  • Pool A totalCredit = 10,000
  • Pool A rewardPerCredit = 1
  1. [Index Pool 1] allocates 1,000 credits to Pool A:
  • totalCredit = 11,000
  • indicies[Index Pool 1] = 1,000
  1. After a while, Pool A rewardPerCredit has grown to 1.1, and applyCover() has been called, [Index Pool 1] call withdrawCredit() get 100 premium
  • totalCredit = 10,000
  • indicies[Index Pool 1] = 0
  1. After pendingEnd, the pool resume(),[ Index Pool 1] will not be paying for the compensation since credit is 0.

In our case, [Index Pool 1] earned premium without paying for a part of the compensation.

Recommendation

Change to:

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/PoolTemplate.sol#L416-L421

    function withdrawCredit(uint256 _credit)
        external
        override
        returns (uint256 _pending)
    {
        require(
            marketStatus == MarketStatus.Trading,
            "ERROR: WITHDRAW_CREDIT_BAD_CONDITIONS"
        );
        IndexInfo storage _index = indicies[msg.sender];

#0 - oishun1112

2022-01-20T02:52:51Z

to call PoolTemplate: withdrawCredit(), someone has to call IndexTemplate: withdraw(), set(), and adjustAlloc().

set() is onlyOwner, so we assume it's fine() adjustAlloc() is public. this clean up and flatten the credit distribution. withdraw() is public. this reduce totalCredit to distribute. when exceed upperSlack, call adjustAlloc().

#1 - oishun1112

2022-01-20T02:59:12Z

We should lock the credit control when pool is in payout status. This implementation, still allows small amount of withdraw, for users who were requested Withdraw.

#2 - oishun1112

2022-01-20T02:59:25Z

@kohshiba what do you think?

#3 - oishun1112

2022-02-02T09:04:03Z

We have fixed with PVE02 (Peckshield audit) issue together. We will comment the PR link here

Findings Information

🌟 Selected for report: WatchPug

Also found by: Dravee, Ruhum, cmichel, pmerkleplant

Labels

bug
2 (Med Risk)

Awards

149.5717 INSURE - $52.35

90.8114 USDC - $90.81

External Links

Handle

WatchPug

Vulnerability details

There are ERC20 tokens that charge fee for every transfer() / transferFrom().

Vault.sol#addValue() assumes that the received amount is the same as the transfer amount, and uses it to calculate attributions, balance amounts, etc. While the actual transferred amount can be lower for those tokens.

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Vault.sol#L124-L140

function addValue(
    uint256 _amount,
    address _from,
    address _beneficiary
) external override onlyMarket returns (uint256 _attributions) {

    if (totalAttributions == 0) {
        _attributions = _amount;
    } else {
        uint256 _pool = valueAll();
        _attributions = (_amount * totalAttributions) / _pool;
    }
    IERC20(token).safeTransferFrom(_from, address(this), _amount);
    balance += _amount;
    totalAttributions += _attributions;
    attributions[_beneficiary] += _attributions;
}

Recommendation

Consider comparing before and after balance to get the actual transferred amount.

#0 - oishun1112

2022-01-18T08:53:24Z

only USDC can be underwriting asset. We are trying to make it multi currency. We won't implement this now due to the gas consumption, but we will when we develop new type of Vault

Findings Information

🌟 Selected for report: Dravee

Also found by: Fitraldys, Ruhum, WatchPug, danb, egjlmn1, robee

Labels

bug
duplicate
2 (Med Risk)

Awards

86.5379 INSURE - $30.29

52.5409 USDC - $52.54

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/PoolTemplate.sol#L655-L686

function applyCover(
    uint256 _pending,
    uint256 _payoutNumerator,
    uint256 _payoutDenominator,
    uint256 _incidentTimestamp,
    bytes32 _merkleRoot,
    string calldata _rawdata,
    string calldata _memo
) external override onlyOwner {
    require(paused == false, "ERROR: UNABLE_TO_APPLY");
    incident.payoutNumerator = _payoutNumerator;
    incident.payoutDenominator = _payoutDenominator;
    incident.incidentTimestamp = _incidentTimestamp;
    incident.merkleRoot = _merkleRoot;
    marketStatus = MarketStatus.Payingout;
    pendingEnd = block.timestamp + _pending;
    for (uint256 i = 0; i < indexList.length; i++) {
        if (indicies[indexList[i]].credit > 0) {
            IIndexTemplate(indexList[i]).lock();
        }
    }
    emit CoverApplied(
        _pending,
        _payoutNumerator,
        _payoutDenominator,
        _incidentTimestamp,
        _merkleRoot,
        _rawdata,
        _memo
    );
    emit MarketStatusChanged(marketStatus);
}

An attacker can add a lot of IndexPool to the individual pool, if the length of indexList is large enough, the gas cost of function applyCover() can exceed the block limit, making it impossible to applyCover.

Essentially, this allows an attacker (as LP) to get the premium without taking the risk of payout.

Recommendation

Consider allowing applyCover for a certain range of IndexPool.

Findings Information

🌟 Selected for report: cccz

Also found by: WatchPug

Labels

bug
duplicate
1 (Low Risk)

Awards

170.9781 INSURE - $59.84

103.8081 USDC - $103.81

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/CDSTemplate.sol#L156-L173

/**
    * @notice A liquidity provider supplies collatral to the pool and receives iTokens
    * @param _amount amount of token to deposit
*/
function fund(uint256 _amount) external {
    require(paused == false, "ERROR: PAUSED");

    //deposit and pay fees
    uint256 _attribution = vault.addValue(
        _amount,
        msg.sender,
        address(this)
    );

    surplusPool += _attribution;

    emit Fund(msg.sender, _amount, _attribution);
}

Per the comment above the fund() function, the liquidity provider should receive iTokens.

A user may get misled by this comment and call the fund() function and expected to get iTokens in return.

However, in the current implementation, the liquidity provider does not receive iTokens or any other entitlements.

Recommendation

Consider renaming the function to donate() and remove the misleading comment.

Findings Information

🌟 Selected for report: robee

Also found by: 0xngndev, Dravee, WatchPug, defsec, hyh

Labels

bug
duplicate
1 (Low Risk)
resolved
sponsor confirmed

Awards

37.3929 INSURE - $13.09

22.7028 USDC - $22.70

External Links

Handle

WatchPug

Vulnerability details

Contracts use assert() instead of require() in multiple places.

Per the Solidity's documentation:

Assert should only be used to test for internal errors, and to check invariants. Properly functioning code should never create a Panic, not even on invalid external input. If this happens, then there is a bug in your contract which you should fix. Language analysis tools can evaluate your contract to identify the conditions and function calls which will cause a Panic.

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Vault.sol#L163-L169

if (available() < _amount) {
    //when USDC in this contract isn't enough
    uint256 _shortage = _amount - available();
    _unutilize(_shortage);

    assert(available() >= _amount);
}

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/InsureDAOERC20.sol#L32-L36

assert(!tokenInitialized);
tokenInitialized = true;
_name = name_;
_symbol = symbol_;
_decimals = decimals_;

Recommendation

Change to require().

#0 - 0xean

2022-02-22T13:59:36Z

Moving these to dupe of #21

Findings Information

🌟 Selected for report: WatchPug

Labels

bug
1 (Low Risk)
resolved
sponsor confirmed

Awards

379.9514 INSURE - $132.98

230.6848 USDC - $230.68

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Vault.sol#L485-L496

function setController(address _controller) public override onlyOwner {
    require(_controller != address(0), "ERROR_ZERO_ADDRESS");

    if (address(controller) != address(0)) {
        controller.migrate(address(_controller));
        controller = IController(_controller);
    } else {
        controller = IController(_controller);
    }

    emit ControllerSet(_controller);
}

controller.migrate() is a critical operation, we recommend adding validation for the amount of migrated funds.

Recommendation

Can be changed to:

function setController(address _controller) public override onlyOwner {
    require(_controller != address(0), "ERROR_ZERO_ADDRESS");

    if (address(controller) != address(0)) {
        uint256 beforeUnderlying = controller.valueAll();
        controller.migrate(address(_controller));
        require(IController(_controller).valueAll() >= beforeUnderlying, "...");
        controller = IController(_controller);
    } else {
        controller = IController(_controller);
    }

    emit ControllerSet(_controller);
}

#0 - 0xHaku

2022-01-22T05:55:10Z

@oishun1112 what error message do we set at require(IController(_controller).valueAll() >= beforeUnderlying, "...");?

#1 - oishun1112

2022-01-22T06:22:30Z

@taka0409 let's have "ERROR_VALUE_ALL_DECREASED"

Findings Information

🌟 Selected for report: WatchPug

Labels

bug
1 (Low Risk)
resolved
sponsor confirmed

Awards

379.9514 INSURE - $132.98

230.6848 USDC - $230.68

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Vault.sol#L429-L434

function _unutilize(uint256 _amount) internal {
    require(address(controller) != address(0), "ERROR_CONTROLLER_NOT_SET");

    controller.withdraw(address(this), _amount);
    balance += _amount;
}

Recommendation

Can be changed to:

function _unutilize(uint256 _amount) internal {
    require(address(controller) != address(0), "ERROR_CONTROLLER_NOT_SET");

    uint256 beforeBalance = IERC20(token).balanceOf(address(this));
    controller.withdraw(address(this), _amount);
    uint256 received = IERC20(token).balanceOf(address(this)) - beforeBalance;
    require(received >= _amount, "...");
    balance += received;
}

#0 - 0xHaku

2022-01-22T05:35:24Z

@oishun1112 what error message do we set at require(received >= _amount, "...");?

#1 - oishun1112

2022-01-22T06:21:28Z

@taka0409 let's have "ERROR_INSUFFICIENT_RETURN_VALUE"

Findings Information

🌟 Selected for report: robee

Also found by: Jujic, Ruhum, WatchPug, defsec, saian

Labels

bug
duplicate
G (Gas Optimization)

Awards

6.1284 INSURE - $2.14

3.2174 USDC - $3.22

External Links

Findings Information

🌟 Selected for report: WatchPug

Labels

bug
G (Gas Optimization)
resolved
sponsor confirmed

Awards

62.2711 INSURE - $21.79

32.6923 USDC - $32.69

External Links

Handle

WatchPug

Vulnerability details

The Wrapped Ether (WETH) ERC-20 contract has a gas optimization that does not update the allowance if it is the max uint.

The latest version of OpenZeppelin's ERC20 token contract also adopted this optimization.

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/InsureDAOERC20.sol#L152-L168

function transferFrom(
    address sender,
    address recipient,
    uint256 amount
) public virtual override returns (bool) {
    _transfer(sender, recipient, amount);

    uint256 currentAllowance = _allowances[sender][_msgSender()];
    require(
        currentAllowance >= amount,
        "ERC20: transfer amount exceeds allowance"
    );

    _approve(sender, _msgSender(), currentAllowance - amount);

    return true;
}

See:

Recommendation

Change to:

function transferFrom(
    address sender,
    address recipient,
    uint256 amount
) public virtual override returns (bool) {
    _transfer(sender, recipient, amount);

    uint256 currentAllowance = _allowances[sender][_msgSender()];
    if (currentAllowance != type(uint256).max) {
        require(
            currentAllowance >= amount,
            "ERC20: transfer amount exceeds allowance"
        );

        _approve(sender, _msgSender(), currentAllowance - amount);
    }

    return true;
}

Findings Information

🌟 Selected for report: WatchPug

Labels

bug
G (Gas Optimization)
resolved
sponsor confirmed

Awards

62.2711 INSURE - $21.79

32.6923 USDC - $32.69

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/CDSTemplate.sol#L140-L148

if (_supply > 0 && _liquidity > 0) {
    _mintAmount = (_amount * _supply) / _liquidity;
} else if (_supply > 0 && _liquidity == 0) {
    //when vault lose all underwritten asset = 
    _mintAmount = _amount * _supply; //dilute LP token value af. Start CDS again.
} else {
    //when _supply == 0,
    _mintAmount = _amount;
}

Recommendation

Change to:

if (_supply == 0) {
    _mintAmount = _amount;
} else {
    _mintAmount = _liquidity == 0 ? _amount * _supply : (_amount * _supply) / _liquidity;
} 
  • Removed 2 checks;
  • Removed 1 branch;
  • Simpler branch (costs less gas) goes first.

#0 - oishun1112

2022-01-14T04:03:24Z

if (_supply != 0) { _mintAmount = _liquidity == 0 ? _amount * _supply : (_amount * _supply) / _liquidity; } else { _mintAmount = _amount; }

we'd like to flip, since _supply != 0 will be true for almost every time

#1 - 0xHaku

2022-01-25T16:33:29Z

@oishun1112 already fixed.

Findings Information

🌟 Selected for report: WatchPug

Labels

bug
G (Gas Optimization)
resolved
sponsor confirmed

Awards

62.2711 INSURE - $21.79

32.6923 USDC - $32.69

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/CDSTemplate.sol#L109-L113

string memory _name = "InsureDAO-CDS";
string memory _symbol = "iCDS";
uint8 _decimals = IERC20Metadata(_references[0]).decimals();

initializeToken(_name, _symbol, _decimals);

The local variable _name, _symbol, _decimals is used only once. Making the expression inline can save gas.

Recommendation

Change to:

initializeToken("InsureDAO-CDS", "iCDS", IERC20Metadata(_references[0]).decimals());

Other examples include:

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/CDSTemplate.sol#L189-L190

uint256 _balance = balanceOf(msg.sender);
require(_balance >= _amount, "ERROR: REQUEST_EXCEED_BALANCE");

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/CDSTemplate.sol#L257-L257

uint256 _surplusAttribution = surplusPool;

https://github.com/code-423n4/2022-01-timeswap/blob/bf50d2a8bb93a5571f35f96bd74af54d9c92a210/Timeswap/Timeswap-V1-Core/contracts/libraries/Callback.sol#L62-L63

uint256 _assetReserve = asset.safeBalance();
require(_assetReserve >= assetReserve + assetIn, 'E304');

https://github.com/code-423n4/2022-01-timeswap/blob/bf50d2a8bb93a5571f35f96bd74af54d9c92a210/Timeswap/Timeswap-V1-Core/contracts/libraries/Callback.sol#L51-L52

uint256 _collateralReserve = collateral.safeBalance();
require(_collateralReserve >= collateralReserve + collateralIn, 'E305');

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/IndexTemplate.sol#L456-L463

uint256 _shortage;
if (totalLiquidity() < _amount) {
    //Insolvency case
    _shortage = _amount - _value;
    uint256 _cds = ICDSTemplate(registry.getCDS(address(this)))
        .compensate(_shortage);
    _compensated = _value + _cds;
}

_shortage and _cds.

Findings Information

🌟 Selected for report: WatchPug

Labels

bug
G (Gas Optimization)
resolved
sponsor confirmed

Awards

62.2711 INSURE - $21.79

32.6923 USDC - $32.69

External Links

Handle

WatchPug

Vulnerability details

Removing return 0 can make the code simpler and save some gas.

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/CDSTemplate.sol#L295-L303

    function rate() external view returns (uint256) {
        if (totalSupply() > 0) {
            return
                (vault.attributionValue(crowdPool) * MAGIC_SCALE_1E6) /
                totalSupply();
        } else {
            return 0;
        }
    }

Recommendation

Can be changed to:

    function rate() external view returns (uint256) {
        if (totalSupply() > 0) {
            return
                (vault.attributionValue(crowdPool) * MAGIC_SCALE_1E6) /
                totalSupply();
        } 
    }

Other examples include:

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/CDSTemplate.sol#L312-L317

        if (_balance == 0) {
            return 0;
        } else {
            return
                _balance * vault.attributionValue(crowdPool) / totalSupply();
        }

Can be changed to:

if (_balance > 0) {
    return
        _balance * vault.attributionValue(crowdPool) / totalSupply();
} 

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Factory.sol#L176-L176

for (uint256 i = 0; i < _references.length; i++)

Can be changed to:

for (uint256 i; i < _references.length; i++)

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/IndexTemplate.sol#L493-L497

if (totalLiquidity() > 0) {
    return (totalAllocatedCredit * MAGIC_SCALE_1E6) / totalLiquidity();
} else {
    return 0;
}

Can be changed to:

if (totalLiquidity() > 0) {
    return (totalAllocatedCredit * MAGIC_SCALE_1E6) / totalLiquidity();
} 

Findings Information

🌟 Selected for report: WatchPug

Labels

bug
G (Gas Optimization)
resolved
sponsor confirmed

Awards

62.2711 INSURE - $21.79

32.6923 USDC - $32.69

External Links

Handle

WatchPug

Vulnerability details

Check of allowance can be done earlier (before _transfer()) to save some gas for failure transactions.

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/InsureDAOERC20.sol#L152-L168

function transferFrom(
    address sender,
    address recipient,
    uint256 amount
) public virtual override returns (bool) {
    _transfer(sender, recipient, amount);

    uint256 currentAllowance = _allowances[sender][_msgSender()];
    require(
        currentAllowance >= amount,
        "ERC20: transfer amount exceeds allowance"
    );

    _approve(sender, _msgSender(), currentAllowance - amount);

    return true;
}

See:

#0 - 0xkenta

2022-01-23T02:37:51Z

Findings Information

🌟 Selected for report: WatchPug

Labels

bug
G (Gas Optimization)
resolved
sponsor confirmed

Awards

62.2711 INSURE - $21.79

32.6923 USDC - $32.69

External Links

Handle

WatchPug

Vulnerability details

When there are multiple checks, adjusting the sequence to allow the tx to fail earlier can save some gas.

Checks using less gas should be executed earlier than those with higher gas costs, to avoid unnecessary storage read, arithmetic operations, etc when it reverts.

For example:

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/CDSTemplate.sol#L238-L256

require(paused == false, "ERROR: PAUSED");
require(
    request.timestamp +
        parameters.getLockup(msg.sender) <
        block.timestamp,
    "ERROR: WITHDRAWAL_QUEUE"
);
require(
    request.timestamp +
        parameters.getLockup(msg.sender) +
        parameters.getWithdrawable(msg.sender) >
        block.timestamp,
    "ERROR: WITHDRAWAL_NO_ACTIVE_REQUEST"
);
require(
    request.amount >= _amount,
    "ERROR: WITHDRAWAL_EXCEEDED_REQUEST"
);
require(_amount > 0, "ERROR: WITHDRAWAL_ZERO");

The check of _amount > 0 can be done earlier to avoid reading from storage when _amount = 0.

Recommendation

Change to:

        require(paused == false, "ERROR: PAUSED");
        require(_amount > 0, "ERROR: WITHDRAWAL_ZERO");
        require(
            request.amount >= _amount,
            "ERROR: WITHDRAWAL_EXCEEDED_REQUEST"
        );
        require(
            request.timestamp +
                parameters.getLockup(msg.sender) <
                block.timestamp,
            "ERROR: WITHDRAWAL_QUEUE"
        );
        require(
            request.timestamp +
                parameters.getLockup(msg.sender) +
                parameters.getWithdrawable(msg.sender) >
                block.timestamp,
            "ERROR: WITHDRAWAL_NO_ACTIVE_REQUEST"
        );

Other examples include:

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/CDSTemplate.sol#L189-L191

uint256 _balance = balanceOf(msg.sender);
require(_balance >= _amount, "ERROR: REQUEST_EXCEED_BALANCE");
require(_amount > 0, "ERROR: REQUEST_ZERO");

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/IndexTemplate.sol#L217-L236

require(locked == false, "ERROR: WITHDRAWAL_PENDING");
require(
    _requestTime + _lockup < block.timestamp,
    "ERROR: WITHDRAWAL_QUEUE"
);
require(
    _requestTime + _lockup + parameters.getWithdrawable(msg.sender) >
        block.timestamp,
    "ERROR: WITHDRAWAL_NO_ACTIVE_REQUEST"
);
require(
    withdrawalReq[msg.sender].amount >= _amount,
    "ERROR: WITHDRAWAL_EXCEEDED_REQUEST"
);
require(_amount > 0, "ERROR: WITHDRAWAL_ZERO");

require(
    _retVal <= withdrawable(),
    "ERROR: WITHDRAW_INSUFFICIENT_LIQUIDITY"
);

Findings Information

🌟 Selected for report: WatchPug

Also found by: p4st13r4

Labels

bug
G (Gas Optimization)
resolved
sponsor confirmed

Awards

28.022 INSURE - $9.81

14.7115 USDC - $14.71

External Links

Handle

WatchPug

Vulnerability details

Cache and reusing the function call results, instead of calling it again, can save gas from unnecessary code execution.

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Vault.sol#L163-L173

if (available() < _amount) { 
    //when USDC in this contract isn't enough
    uint256 _shortage = _amount - available();
    _unutilize(_shortage);

    assert(available() >= _amount);
}

balance -= _amount;
IERC20(token).safeTransfer(_to, _amount);

Recommendation

Change to:

uint256 availableAmount = available()
if ( availableAmount < _amount) { 
    //when USDC in this contract isn't enough
    uint256 _shortage = _amount - available();
    _unutilize(_shortage);

    assert(availableAmount >= _amount);
}

balance -= _amount;
IERC20(token).safeTransfer(_to, _amount);

Other examples include:

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/CDSTemplate.sol#L295-L304

function rate() external view returns (uint256) {
    if (totalSupply() > 0) {
        return
            (vault.attributionValue(crowdPool) * MAGIC_SCALE_1E6) /
            totalSupply();
    } else {
        return 0;
    }
}

totalSupply()

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Vault.sol#L309-L312

if (available() < _retVal) {
    uint256 _shortage = _retVal - available();
    _unutilize(_shortage);
}

available()

#0 - blue32captain

2022-01-20T05:21:58Z

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Vault.sol#L163-L173 available() in require could not be changed since its value changes in above function.

Findings Information

🌟 Selected for report: WatchPug

Labels

bug
G (Gas Optimization)
resolved
sponsor confirmed

Awards

62.2711 INSURE - $21.79

32.6923 USDC - $32.69

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/CDSTemplate.sol#L260-L270

if (_available >= _amount) {
    _compensated = _amount;
    _attributionLoss = vault.transferValue(_amount, msg.sender);
    emit Compensated(msg.sender, _amount);
} else {
    //when CDS cannot afford, pay as much as possible
    _compensated = _available;
    _attributionLoss = vault.transferValue(_available, msg.sender);
    emit Compensated(msg.sender, _available);
}

Recommendation

Change to:

_compensated = _available >= _amount? _amount: _available;

_attributionLoss = vault.transferValue(_compensated, msg.sender);
emit Compensated(msg.sender, _compensated);
  • Duplicated codes removed;
  • Shorter and simpler code.

#0 - 0xHaku

2022-01-25T16:18:50Z

@oishun1112 already fixed.

Findings Information

🌟 Selected for report: WatchPug

Labels

bug
G (Gas Optimization)
resolved
sponsor confirmed

Awards

62.2711 INSURE - $21.79

32.6923 USDC - $32.69

External Links

Handle

WatchPug

Vulnerability details

Check marketStatus before for loops can save gas from unnecessary repeated checks.

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/PoolTemplate.sol#L342-L365

function unlockBatch(uint256[] calldata _ids) external {
    for (uint256 i = 0; i < _ids.length; i++) {
        unlock(_ids[i]);
    }
}

function unlock(uint256 _id) public {
    require(
        insurances[_id].status == true &&
            marketStatus == MarketStatus.Trading &&
            insurances[_id].endTime + parameters.getGrace(msg.sender) <
            block.timestamp,
        "ERROR: UNLOCK_BAD_COINDITIONS"
    );
    insurances[_id].status == false;

    lockedAmount = lockedAmount - insurances[_id].amount;

    emit Unlocked(_id, insurances[_id].amount);
}

Recomandation

Change to:

function unlockBatch(uint256[] calldata _ids) external {
    require(marketStatus == MarketStatus.Trading, "ERROR: UNLOCK_BAD_COINDITIONS")
    for (uint256 i = 0; i < _ids.length; i++) {
        _unlock(_ids[i]);
    }
}

function unlock(uint256 _id) external {
    require(marketStatus == MarketStatus.Trading, "ERROR: UNLOCK_BAD_COINDITIONS");
    _unlock(_id);
}

function _unlock(uint256 _id) internal {
    require(
        insurances[_id].status == true &&
            insurances[_id].endTime + parameters.getGrace(msg.sender) <
            block.timestamp,
        "ERROR: UNLOCK_BAD_COINDITIONS"
    );
    insurances[_id].status == false;

    lockedAmount = lockedAmount - insurances[_id].amount;

    emit Unlocked(_id, insurances[_id].amount);
}

Findings Information

🌟 Selected for report: WatchPug

Labels

bug
G (Gas Optimization)
sponsor acknowledged

Awards

62.2711 INSURE - $21.79

32.6923 USDC - $32.69

External Links

Handle

WatchPug

Vulnerability details

Booleans are more expensive than uint256 or any type that takes up a full word because each write operation emits an extra SLOAD to first read the slot's contents, replace the bits taken up by the boolean, and then write back. This is the compiler's defense against contract upgrades and pointer aliasing, and it cannot be disabled.

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/CDSTemplate.sol#L46-L46

bool public paused;

Recommendation

Consider changing to:

uint256 private constant _NOT_PAUSED = 0;
uint256 private constant _PAUSED = 1;

uint256 public paused;

Other examples include:

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/CDSTemplate.sol#L45-L45

bool public initialized;

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/IndexTemplate.sol#L59-L61

bool public initialized;
bool public paused;
bool public locked;

#0 - oishun1112

2022-01-16T06:52:19Z

I did experiment and could reduce gas around 110. but keep the original code for the readability.

Findings Information

🌟 Selected for report: Jujic

Also found by: TomFrenchBlockchain, WatchPug

Labels

bug
duplicate
G (Gas Optimization)
resolved
sponsor confirmed

Awards

16.8132 INSURE - $5.88

8.8269 USDC - $8.83

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/PoolTemplate.sol#L549-L561

Insurance storage _insurance = insurances[_id];
require(_insurance.status == true, "ERROR: INSURANCE_NOT_ACTIVE");

uint256 _payoutNumerator = incident.payoutNumerator;
uint256 _payoutDenominator = incident.payoutDenominator;
uint256 _incidentTimestamp = incident.incidentTimestamp;
bytes32 _targets = incident.merkleRoot;

L549 Insurance storage insurance = insurances[_id]; can be changed to Insurance memory insurance = insurances[_id];.

And at L583 _insurance.status = false; should be changed to insurances[_id].status = false;.

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/PoolTemplate.sol#L125-L133

struct Insurance {
    uint256 id; //each insuance has their own id
    uint256 startTime; //timestamp of starttime
    uint256 endTime; //timestamp of endtime
    uint256 amount; //insured amount
    bytes32 target; //target id in bytes32
    address insured; //the address holds the right to get insured
    bool status; //true if insurance is not expired or redeemed
}

Because insured and status are packed in the same slot, copying to memory instead of reading from storage separately can save one SLOAD.

#0 - 0xean

2022-01-28T02:36:58Z

dupe of #253

Findings Information

🌟 Selected for report: WatchPug

Labels

bug
G (Gas Optimization)
resolved
sponsor confirmed

Awards

62.2711 INSURE - $21.79

32.6923 USDC - $32.69

External Links

Handle

WatchPug

Vulnerability details

Every call to an external contract costs a decent amount of gas. For optimization of gas usage, external call results should be cached if they are being used for more than one time.

For example:

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Vault.sol#L153-L158

require(
    attributions[msg.sender] > 0 &&
        underlyingValue(msg.sender) >= _amount,
    "ERROR_WITHDRAW-VALUE_BADCONDITOONS"
);
_attributions = (totalAttributions * _amount) / valueAll();

In Vault#withdrawValue(), controller.valueAll() is called twice:

  1. L155 underlyingValue(msg.sender) -> valueAll() -> controller.valueAll();
  2. L158 valueAll() -> controller.valueAll().

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Vault.sol#L400-L411

function underlyingValue(address _target)
    public
    view
    override
    returns (uint256)
{
    if (attributions[_target] > 0) {
        return (valueAll() * attributions[_target]) / totalAttributions;
    } else {
        return 0;
    }
}

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Vault.sol#L417-L423

function valueAll() public view returns (uint256) {
    if (address(controller) != address(0)) {
        return balance + controller.valueAll();
    } else {
        return balance;
    }
}

#0 - oishun1112

2022-01-26T08:43:46Z

created overload function of underlyingValue(). Some functions that can cache valueAll() pass the cached valueAll into the overloaded underlyingValue()

Findings Information

🌟 Selected for report: WatchPug

Labels

bug
G (Gas Optimization)
resolved
sponsor confirmed

Awards

62.2711 INSURE - $21.79

32.6923 USDC - $32.69

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Ownership.sol#L17-L20

constructor() {
    _owner = msg.sender;
    emit AcceptNewOwnership(_owner);
}

At L19, the parameter of AcceptNewOwnership can use msg.sender directly to avoid unnecessary storage read of _owner to save some gas.

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Ownership.sol#L65-L71

function acceptTransferOwnership() external override onlyFutureOwner {
    /***
        *@notice Accept a transfer of ownership
        */
    _owner = _futureOwner;
    emit AcceptNewOwnership(_owner);
}

At L69, _futureOwner can use msg.sender directly to avoid unnecessary storage read of _futureOwner to save some gas.

As onlyFutureOwner() ensures that require(_futureOwner == msg.sender, "...");.

Findings Information

🌟 Selected for report: WatchPug

Also found by: Jujic, robee

Labels

bug
G (Gas Optimization)
resolved
sponsor confirmed

Awards

16.8132 INSURE - $5.88

8.8269 USDC - $8.83

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/PoolTemplate.sol#L146-L146

uint256 public constant MAGIC_SCALE_1E6 = 1e6; //internal multiplication scale 1e6 to reduce decimal truncation

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/IndexTemplate.sol#L95-L95

uint256 public constant MAGIC_SCALE_1E6 = 1e6; //internal multiplication scale 1e6 to reduce decimal truncation

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/CDSTemplate.sol#L55-L55

uint256 public constant MAGIC_SCALE_1E6 = 1e6; //internal multiplication scale 1e6 to reduce decimal truncation

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Vault.sol#L38-L38

uint256 public constant MAGIC_SCALE_1E6 = 1e6; //internal multiplication scale 1e6 to reduce decimal truncation

For the constants that should not be public, changing them to private / internal can save some gas. To avoid unnecessary getter functions.

Findings Information

🌟 Selected for report: Dravee

Also found by: 0x0x0x, 0xngndev, Jujic, Ruhum, WatchPug, defsec, gzeon, solgryn

Labels

bug
duplicate
G (Gas Optimization)

Awards

2.9784 INSURE - $1.04

1.5637 USDC - $1.56

External Links

Handle

WatchPug

Vulnerability details

It is cheaper to use != 0 than > 0 for uint256.

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/CDSTemplate.sol#L132-L133

require(_amount > 0, "ERROR: DEPOSIT_ZERO");

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/CDSTemplate.sol#L295-L303

function rate() external view returns (uint256) {
    if (totalSupply() > 0) {
        return
            (vault.attributionValue(crowdPool) * MAGIC_SCALE_1E6) /
            totalSupply();
    } else {
        return 0;
    }
}

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/IndexTemplate.sol#L166-L166

require(_amount > 0, "ERROR: DEPOSIT_ZERO");

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/IndexTemplate.sol#L172-L172

if (_supply > 0 && _totalLiquidity > 0) {

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/PoolTemplate.sol#L237-L237

require(_amount > 0, "ERROR: DEPOSIT_ZERO");

#1 - 0xean

2022-01-28T00:08:12Z

dupe of #36

Findings Information

🌟 Selected for report: Dravee

Also found by: 0x0x0x, Jujic, WatchPug, defsec, egjlmn1, gzeon, robee

Labels

bug
duplicate
G (Gas Optimization)

Awards

2.4125 INSURE - $0.84

1.2666 USDC - $1.27

External Links

Handle

WatchPug

Vulnerability details

Reading array length at each iteration of the loop takes 6 gas (3 for mload and 3 to place memory_offset) in the stack.

Caching the array length in the stack saves around 3 gas per iteration.

Instances include:

Recommendation

Can be changed to:

uint256 _referencesLength = _references.length;
if (_referencesLength > 0) {
    for (uint256 i = 0; i < _referencesLength; i++) {
        require(
            reflist[address(_template)][i][_references[i]] == true ||
                reflist[address(_template)][i][address(0)] == true,
            "ERROR: UNAUTHORIZED_REFERENCE"
        );
    }
}

Findings Information

🌟 Selected for report: Dravee

Also found by: Jujic, WatchPug, defsec, hyh, sirhashalot

Labels

bug
duplicate
G (Gas Optimization)
resolved
sponsor confirmed

Awards

6.1284 INSURE - $2.14

3.2174 USDC - $3.22

External Links

Handle

WatchPug

Vulnerability details

For the arithmetic operations that will never over/underflow, using the unchecked directive (Solidity v0.8 has default overflow/underflow checks) can save some gas from the unnecessary internal over/underflow checks.

For example:

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Vault.sol#L163-L169

if (available() < _amount) {
    //when USDC in this contract isn't enough
    uint256 _shortage = _amount - available();
    _unutilize(_shortage);

    assert(available() >= _amount);
}

_amount - available() will never underflow.

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/IndexTemplate.sol#L289-L297

if (_allocated > _availableBalance) {
    uint256 _availableRate = (_availableBalance *
        MAGIC_SCALE_1E6) / _allocated;
    uint256 _lockedCredit = _allocated - _availableBalance;
    if (i == 0 || _availableRate < _lowestAvailableRate) {
        _lowestAvailableRate = _availableRate;
        _targetLockedCreditScore = _lockedCredit;
        _targetAllocPoint = _allocPoint;
    }

_allocated - _availableBalance will never underflow.

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Vault.sol#L300-L307

require(
    attributions[msg.sender] >= _attribution,
    "ERROR_WITHDRAW-ATTRIBUTION_BADCONDITOONS"
);
_retVal = (_attribution * valueAll()) / totalAttributions;

attributions[msg.sender] -= _attribution;
totalAttributions -= _attribution;

attributions[msg.sender] -= _attribution will never underflow.

#0 - oishun1112

2022-01-16T07:34:29Z

#2 - 0xean

2022-01-28T00:19:11Z

dupe of #66

Findings Information

🌟 Selected for report: egjlmn1

Also found by: 0x0x0x, Dravee, Jujic, OriDabush, WatchPug, defsec, robee, tqts

Labels

bug
duplicate
G (Gas Optimization)

Awards

2.9784 INSURE - $1.04

1.5637 USDC - $1.56

External Links

Handle

WatchPug

Vulnerability details

Using ++i is more gas efficient than i++, especially in a loop.

We suggest to use unchecked {++i} to even better gas performance. (for >= 0.8)

For example:

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/Factory.sol#L176-L176

for (uint256 i = 0; i < _references.length; i++)

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/IndexTemplate.sol#L280-L280

for (uint256 i = 0; i < _length; i++)

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/IndexTemplate.sol#L381-L381

for (uint256 i = 0; i < _length; i++)

https://github.com/code-423n4/2022-01-insure/blob/19d1a7819fe7ce795e6d4814e7ddf8b8e1323df3/contracts/IndexTemplate.sol#L462-L462

  for (uint256 i = 0; i < _poolLength; i++)
AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter