Farseen0/scar-pairs huggingface dataset:
HIGH severity: Incorrect comment regarding the configuration of `DebtToken::FLASH_LOAN_FEE` parameter. **Description:** `DebtToken::_flashFee` divides by `10000`, hence if `FLASH_LOAN_FEE = 1` then:

So the comment indicating that 1 = 0.0001% is incorrect:

**Bima:**
Fixed in commit [4ae0be3](https://github.com/Bima-Labs/bima-v1-core/commit/4ae0be305e1c87b2c9df89b4676f352d87ded3cf#diff-d399aa6726575b...

Vulnerable code:
File: DebtToken.sol
- uint256 public constant FLASH_LOAN_FEE = 9; // 1 = 0.0001%
+ uint256 public constant FLASH_LOAN_FEE = 9; // 1 = 0.01%
HIGH severity: `BorrowingFeePaid` event should include token that was repaid. **Description:** `BorrowingFeePaid` event should include collateral token that was repaid:

Then emit the event with the collateral being repaid to easily track via events which collateral tokens were used in repayments.

**Bima:**
Fixed in commit...

Vulnerable code:
- event BorrowingFeePaid(address indexed borrower, uint256 amount);
+ event BorrowingFeePaid(address indexed borrower, IERC20 collateralToken, uint256 amount);
LOW severity: Enforce 18 decimal collateral tokens in `Factory::deployNewInstance`. **Description:** The protocol assumes collateral tokens decimals are 18.

**Proof of Concept:** While calculating a `TCR` in [BorrowerOperations::_getTCRData](https://github.com/Bima-Labs/bima-v1-core/blob/main/contracts/core/BorrowerOperations.sol#L583), it uses a raw collateral amount.

It gets...

Vulnerable code:
function _getTCRData(
        SystemBalances memory balances
    ) internal pure returns (uint256 amount, uint256 totalPricedCollateral, uint256 totalDebt) {
        uint256 loopEnd = balances.collaterals.length;
        for (uint256 i; i < loopEnd; ) {
            totalPricedCollateral += (balances.collaterals[i] * balances.prices[i]);
            totalDebt += balances.debts[i];
            unchecked {
                ++i;
            }
        }
        amount = BabelMath._computeCR(totalPricedCollateral, totalDebt);

        return (amount, totalPricedCollateral, totalDebt);
    }

    function _computeCR(uint256 _coll, uint256 _debt) internal pure returns (uint256) {
        if (_debt > 0) {
            uint256 newCollRatio = (_coll) / _debt;

            return newCollRatio;
        }
        // Return the maximal value for uint256 if the Trove has a debt of 0. Represents "infinite" CR.
        else {
            // if (_debt == 0)
            return 2 ** 256 - 1;
        }
    }
HIGH severity: Remove duplicate calculation in `TokenLocker::withdrawWithPenalty`. **Description:** Remove duplicate [calculation](https://github.com/Bima-Labs/bima-v1-core/blob/09461f0d22556e810295b12a6d7bc5c0efec4627/contracts/dao/TokenLocker.sol#L878-L879) in `TokenLocker::withdrawWithPenalty`:

To get around "stack too deep" remove this...

Vulnerable code:
- accountData.locked -= uint32((amountToWithdraw + penaltyTotal - unlocked) / lockToTokenRatio);
- totalDecayRate -= uint32((amountToWithdraw + penaltyTotal - unlocked) / lockToTokenRatio);
+ // calculate & cache total amount of locked tokens withdraw inc penalties,
+ // scaled down by lockToTokenRatio
+ uint32 lockedPlusPenalties = SafeCast.toUint32((amountToWithdraw + penaltyTotal - unlocked) / lockToTokenRatio);

+ // update account locked and global totalDecayRate subtracting
+ // locked tokens withdrawn including penalties paid
+ accountData.locked -= lockedPlusPenalties;
+ totalDecayRate -= lockedPlusPenalties;

- uint32[65535] storage unlocks = accountWeeklyUnlocks[msg.sender];

- uint256 lockAmount = unlocks[systemWeek] * lockToTokenRatio;
+ uint256 lockAmount = accountWeeklyUnlocks[msg.sender][systemWeek] * lockToTokenRatio;
MEDIUM severity: Allow message value to be more than the quote cost. **Description:** The `SecuritizeBridge` contract's `bridgeDSTokens()` function requires users to provide an exact value that matches the quote obtained from `quoteBridge()`. This strict matching requirement creates issues because the actual cost can change between when a user checks the quote and...

Vulnerable code:
function bridgeDSTokens(uint16 targetChain, uint256 value) public override payable whenNotPaused {
        uint256 cost = quoteBridge(targetChain);
        require(msg.value == cost, "Transaction value should be equal to quoteBridge response");
...
    }

function quoteEvmDeliveryPrice(
        uint16 targetChain,
        Gas gasLimit,
        TargetNative receiverValue
    )
        public
        view
        returns (LocalNative nativePriceQuote, GasPrice targetChainRefundPerUnitGasUnused)
    {
        // Calculates the amount to refund user on the target chain, for each unit of target chain gas unused
        // by multiplying the price of that amount of gas (in target chain currency)
        // by a target-chain-specific constant 'denominator'/('denominator' + 'buffer'), which will be close to 1

        (uint16 buffer, uint16 denominator) = assetConversionBuffer(targetChain);
        targetChainRefundPerUnitGasUnused = GasPrice.wrap(gasPrice(targetChain).unwrap() * (denominator) / (uint256(denominator) + buffer));

        // Calculates the cost of performing a delivery with 'gasLimit' units of gas and 'receiverValue' wei delivered to the target contract

        LocalNative gasLimitCostInSourceCurrency = quoteGasCost(targetChain, gasLimit);
        LocalNative receiverValueCostInSourceCurrency = quoteAssetCost(targetChain, receiverValue);
        nativePriceQuote = quoteDeliveryOverhead(targetChain) + gasLimitCostInSourceCurrency + receiverValueCostInSourceCurrency;

        // Checks that the amount of wei that needs to be sent into the target chain is <= the 'maximum budget' for the target chain

        TargetNative gasLimitCost = gasLimit.toWei(gasPrice(targetChain)).asTargetNative();
        if(receiverValue.asNative() + gasLimitCost.asNative() > maximumBudget(targetChain).asNative()) {
            revert ExceedsMaximumBudget(targetChain, receiverValue.unwrap() + gasLimitCost.unwrap(), maximumBudget(targetChain).unwrap());
        }
    }
LOW severity: Make the gas limit configurable. **Description:** The `SecuritizeBridge` contract currently uses a fixed (hardcoded) gas limit of 2,500,000 for all cross-chain message transactions through the Wormhole protocol. This value represents the maximum computational units (gas) allowed for the execution of the transaction on the target...

Vulnerable code:
uint256 public constant GAS_LIMIT = 2500_000;

uint256 public gasLimit;

function setGasLimit(uint256 _gasLimit) external onlyOwner {
    gasLimit = _gasLimit;
}
HIGH severity: DAO creator can inflate their privileges to mint/burn membership tokens, steal profits, and abuse approvals to `MembershipER`. **Description:** During the [creation of a new DAO](https://github.com/OneWpOrg/audit-2024-10-oneworld/blob/416630e46ea6f0e9bd9bdd0aea6a48119d0b515a/contracts/dao/MembershipFactory.sol#L66-L70), the `MembershipFactory` contract is...

Vulnerable code:
if (_totalSupply > 0) {
    totalProfit[currency] += (amount * ACCURACY) / _totalSupply;
    IERC20(currency).safeTransferFrom(msg.sender, address(this), amount);
    emit Profit(amount);
} else {
    IERC20(currency).safeTransferFrom(msg.sender, creator, amount); // Redirect profit to creator if no supply
}

it("can give OWP_FACTORY_ROLE to an address and abuse priviliges", async function () {
    const [factory, creator, user] = await ethers.getSigners();
    const membership = await MembershipERC1155.connect(factory).deploy();
    await membership.deployed();
    await membership.initialize("TestToken", "TST", tokenURI, creator.address);

    await membership.connect(creator).grantRole(await membership.OWP_FACTORY_ROLE(), creator.address);
    expect(await membership.hasRole(await membership.OWP_FACTORY_ROLE(), creator.address)).to.be.true;

    // creator can mint and burn at will
    await membership.connect(creator).mint(user.address, 1, 100);
    await membership.connect(creator).burn(user.address, 1, 50);

    await testERC20.mint(user.address, ethers.utils.parseEther("1"));
    await testERC20.connect(user).approve(membership.address, ethers.utils.parseEther("1"));

    const creatorBalanceBefore = await testERC20.balanceOf(creator.address);

    // creator can abuse approvals
    const data = testERC20.interface.encodeFunctionData("transferFrom", [user.address, creator.address, ethers.utils.parseEther("1")]);
    await membership.connect(creator).callExternalContract(testERC20.address, data);

    const creatorBalanceAfter = await testERC20.balanceOf(creator.address);
    expect(creatorBalanceAfter.sub(creatorBalanceBefore)).to.equal(ethers.utils.parseEther("1"));
});
HIGH severity: `MembershipER:sendProfit` can be front-run by calls to `MembershipFactory::joinDAO` to steal profit from existing DAO members. **Description:** Profit is distributed to DAO members following a call to [`MembershipERC1155::sendProfit`](https://github.com/OneWpOrg/audit-2024-10-oneworld/blob/416630e46ea6f0e9bd9bdd0aea6a48119d0b515a/contracts/dao/tokens/MembershipERC1155.sol#L189-L201) which increases the profit per share...

Vulnerable code:
it("lets users front-run profit distribution", async function () {
  const tierIndex = 0;
  await testERC20.mint(addr1.address, ethers.utils.parseEther("1"));
  await testERC20.connect(addr1).approve(membershipFactory.address, TierConfig[tierIndex].price);
  await testERC20.mint(addr2.address, ethers.utils.parseEther("1"));
  await testERC20.connect(addr2).approve(membershipFactory.address, ethers.utils.parseEther("1"));
  await testERC20.mint(owner.address, ethers.utils.parseEther("1"));
  await testERC20.connect(owner).approve(membershipERC1155.address, ethers.utils.parseEther("1"));
  // user1 joins
  await membershipFactory.connect(addr1).joinDAO(membershipERC1155.address, tierIndex);

  // time passes

  // user2 sees a pending sendProfit tx and front-runs it by buying a lot of membership tokens
  // this can be done with a deployed contract
  for(let i = 0; i < 9; i++) {
    await membershipFactory.connect(addr2).joinDAO(membershipERC1155.address, tierIndex);
  }

  // send profit tx is executed
  await membershipERC1155.sendProfit(testERC20.address, ethers.utils.parseEther("1"));

  const addr1Profit = await membershipERC1155.profitOf(addr1.address, testERC20.address);
  const addr2Profit = await membershipERC1155.profitOf(addr2.address, testERC20.address);

  // user2 has gotten 9x the profit of user1
  expect(addr1Profit).to.equal(ethers.utils.parseEther("0.1"));
  expect(addr2Profit).to.equal(ethers.utils.parseEther("0.9"));
});
HIGH severity: One World Project has unilateral control over all DAOs, allowing the owner to update tier configurations, mint/burn membership tokens, steal profits, and abuse token approvals to `MembershipFactory` and `MembershipER` proxy contracts. **Description:** When the `MembershipFactory` contract is deployed, the `EXTERNAL_CALLER` role is granted to the caller. This allows the One World Project to update the tiers configurations for a specific DAO via...

Vulnerable code:
it("allows admin to have unilateral power", async function() {
  await testERC20.mint(addr1.address, ethers.utils.parseEther("2"));
  await testERC20.connect(addr1).approve(membershipFactory.address, ethers.utils.parseEther("1"));

  await currencyManager.addCurrency(testERC20.address);  // Assume addCurrency function exists in CurrencyManager
  const tx = await membershipFactory.createNewDAOMembership(DAOConfig, TierConfig);
  const receipt = await tx.wait();
  const event = receipt.events.find((event:any) => event.event === "MembershipDAONFTCreated");
  const nftAddress = event.args[1];
  const membershipERC1155 = await MembershipERC1155.attach(nftAddress);

  let ownerBalanceBefore = await testERC20.balanceOf(owner.address);

  // admin can steal approvals made to factory
  const transferData = testERC20.interface.encodeFunctionData("transferFrom", [addr1.address, owner.address, ethers.utils.parseEther("1")]);
  await membershipFactory.callExternalContract(testERC20.address, transferData);

  let ownerBalanceAfter = await testERC20.balanceOf(owner.address);
  expect(ownerBalanceAfter.sub(ownerBalanceBefore)).to.equal(ethers.utils.parseEther("1"));

  // admin can mint/burn any DAO membership tokens
  const mintData = membershipERC1155.interface.encodeFunctionData("mint", [owner.address, 1, 100]);
  await membershipFactory.callExternalContract(nftAddress, mintData);

  let ownerBalanceERC1155 = await membershipERC1155.balanceOf(owner.address, 1);
  expect(ownerBalanceERC1155).to.equal(100);

  const burnData = membershipERC1155.interface.encodeFunctionData("burn", [owner.address, 1, 50]);
  await membershipFactory.callExternalContract(nftAddress, burnData);

  ownerBalanceERC1155 = await membershipERC1155.balanceOf(owner.address, 1);
  expect(ownerBalanceERC1155).to.equal(50);

  // admin can abuse approvals to any membership tokens as well
  await testERC20.connect(addr1).approve(membershipERC1155.address, ethers.utils.parseEther("1"));

  ownerBalanceBefore = await testERC20.balanceOf(owner.address);

  const data = membershipERC1155.interface.encodeFunctionData("callExternalContract", [testERC20.address, transferData]);
  await membershipFactory.callExternalContract(membershipERC1155.address, data);

  ownerBalanceAfter = await testERC20.balanceOf(owner.address);
  expect(ownerBalanceAfter.sub(ownerBalanceBefore)).to.equal(ethers.utils.parseEther("1"));
});
HIGH severity: DAO name can be stolen by front-running calls to `MembershipFactory::createNewDAOMembership`. **Description:** When `MembershipFactory::createNewDAOMembership` is called, the newly created `MembershipERC1155` instance it is [associated](https://github.com/OneWpOrg/audit-2024-10-oneworld/blob/416630e46ea6f0e9bd9bdd0aea6a48119d0b515a/contracts/dao/MembershipFactory.sol#L61) with a name,...

Vulnerable code:
require(getENSAddress[daoConfig.ensname] == address(0), "DAO already exist.");
HIGH severity: DAO membership fees cannot be retrieved by the creator. **Description:** The DAO membership fee taken from users who invoke [`MembershipFactory::joinDAO`](https://github.com/OneWpOrg/audit-2024-10-oneworld/blob/416630e46ea6f0e9bd9bdd0aea6a48119d0b515a/contracts/dao/MembershipFactory.sol#L120-L133) is split between the One World Project and the DAO...

Vulnerable code:
uint256 tierPrice = daos[daoMembershipAddress].tiers[tierIndex].price;
uint256 platformFees = (20 * tierPrice) / 100;
daos[daoMembershipAddress].tiers[tierIndex].minted += 1;
IERC20(daos[daoMembershipAddress].currency).transferFrom(msg.sender, owpWallet, platformFees);
IERC20(daos[daoMembershipAddress].currency).transferFrom(msg.sender, daoMembershipAddress, tierPrice - platformFees);

it("only allows owner to recover dao membership fees", async function () {
  await currencyManager.addCurrency(testERC20.address);
  const creator = addr1;

  await membershipFactory.connect(creator).createNewDAOMembership(DAOConfig, TierConfig);

  const ensAddress = await membershipFactory.getENSAddress("testdao.eth");
  const membershipERC1155 = await MembershipERC1155.attach(ensAddress);

  await testERC20.mint(addr2.address, ethers.utils.parseEther("20"));
  await testERC20.connect(addr2).approve(membershipFactory.address, ethers.utils.parseEther("20"));
  await expect(membershipFactory.connect(addr2).joinDAO(membershipERC1155.address, 1)).to.not.be.reverted;

  // fees are in the membership token but cannot be retrieved by the creator
  const daoMembershipBalance = await testERC20.balanceOf(membershipERC1155.address);
  expect(daoMembershipBalance).to.equal(160); // minus protocol fee

  const creatorBalanceBefore = await testERC20.balanceOf(creator.address);

  // only admin can recover them
  const transferData = testERC20.interface.encodeFunctionData("transfer", [creator.address, 160]);
  const data = membershipERC1155.interface.encodeFunctionData("callExternalContract", [testERC20.address, transferData]);
  await membershipFactory.callExternalContract(membershipERC1155.address, data);

  const creatorBalanceAfter = await testERC20.balanceOf(creator.address);
  expect(creatorBalanceAfter.sub(creatorBalanceBefore)).to.equal(160);
});
HIGH severity: Meta transactions do not work with most of the calls in `MembershipFactory`. **Description:** `MembershipFactory` uses a custom meta transactions implementation by inheriting `NativeMetaTransaction` which allow a relayer to pay the transaction fees on behalf of a user. This is achieved by following the same standard as ERC2771, where the user signs a transaction that is...

Vulnerable code:
describe("Native meta transaction", function () {
  it("Meta transactions causes creation to use the wrong owner", async function () {
    await currencyManager.addCurrency(testERC20.address);

    const { chainId } = await ethers.provider.getNetwork();
    const salt = ethers.utils.hexZeroPad(ethers.utils.hexlify(chainId), 32)

    const domain = {
      name: 'OWP',
      version: '1',
      salt: salt,
      verifyingContract: membershipFactory.address,
    };
    const types = {
      MetaTransaction: [
        { name: 'nonce', type: 'uint256' },
        { name: 'from', type: 'address' },
        { name: 'functionSignature', type: 'bytes' },
      ],
    };
    const nonce = await membershipFactory.getNonce(addr1.address);
    const metaTransaction = {
      nonce,
      from: addr1.address,
      functionSignature: membershipFactory.interface.encodeFunctionData('createNewDAOMembership', [DAOConfig, TierConfig]),
    };
    const signature = await addr1._signTypedData(domain, types, metaTransaction);
    const {v,r,s} = ethers.utils.splitSignature(signature);

    const tx = await membershipFactory.executeMetaTransaction(metaTransaction.from, metaTransaction.functionSignature, r, s, v);
    const receipt = await tx.wait();
    const event = receipt.events.find((event:any) => event.event === "MembershipDAONFTCreated");
    const nftAddress = event.args[1];
    const creator = await MembershipERC1155.attach(nftAddress).creator();

    // creator becomes the membership factory not addr1
    expect(creator).to.equal(membershipFactory.address);
  });
});
LOW severity: Tier restrictions for `SPONSORED` DAOs can be bypassed by calling `MembershipFactory::upgradeTier`. **Description:** If the DAO specified by the `daoMembershipAddress` parameter in a call to `MembershipFactory::upgradeTier` is registered as [`SPONSORED`](https://github.com/OneWpOrg/audit-2024-10-oneworld/blob/416630e46ea6f0e9bd9bdd0aea6a48119d0b515a/contracts/dao/MembershipFactory.sol#L139),...

Vulnerable code:
it("can upgrade above max amount and minted not updated", async function () {
  const lowTier = 5;
  const highTier = 4;
  await testERC20.mint(addr1.address, ethers.utils.parseEther("1000000"));
  await testERC20.connect(addr1).approve(membershipFactory.address, ethers.utils.parseEther("1000000"));
  for(let i = 0; i < 40; i++) {
    await membershipFactory.connect(addr1).joinDAO(membershipERC1155.address, highTier);
  }
  // cannot join anymore
  await expect(membershipFactory.connect(addr1).joinDAO(membershipERC1155.address, highTier)).to.be.revertedWith("Tier full.");

  await membershipFactory.connect(addr1).joinDAO(membershipERC1155.address, lowTier);
  await membershipFactory.connect(addr1).joinDAO(membershipERC1155.address, lowTier);

  const tiersBefore = await membershipFactory.daoTiers(membershipERC1155.address);
  expect(tiersBefore[lowTier].minted).to.equal(2);
  expect(tiersBefore[highTier].minted).to.equal(40);

  // but can upgrade tier
  await membershipFactory.connect(addr1).upgradeTier(membershipERC1155.address, lowTier);

  // a total of 41 tokens for tier 4, max amount is 40
  const numberOfTokens = await membershipERC1155.balanceOf(addr1.address, highTier);
  expect(numberOfTokens).to.equal(41);

  // and minted hasn't changed
  const tiersAfter = await membershipFactory.daoTiers(membershipERC1155.address);
  expect(tiersAfter[lowTier].minted).to.equal(tiersBefore[lowTier].minted);
  expect(tiersAfter[highTier].minted).to.equal(tiersBefore[highTier].minted);
});
LOW severity: No membership restrictions placed on `PRIVATE` DAOs allows anyone to join. **Description:** [`MembershipDAOStructs::DAOType`](https://github.com/OneWpOrg/audit-2024-10-oneworld/blob/416630e46ea6f0e9bd9bdd0aea6a48119d0b515a/contracts/dao/libraries/MembershipDAOStructs.sol#L6-L10) exposes the different types a DAO can have, namely `PRIVATE`, `SPONSORED`, and the default...

Vulnerable code:
it("lets anyone join PRIVATE DAOs", async function () {
  await currencyManager.addCurrency(testERC20.address);

  // DAO membership is private
  DAOConfig.daoType = DAOType.PRIVATE;
  await membershipFactory.createNewDAOMembership(DAOConfig, TierConfig);

  const ensAddress = await membershipFactory.getENSAddress("testdao.eth");
  const membershipERC1155 = await MembershipERC1155.attach(ensAddress);

  await testERC20.mint(addr1.address, ethers.utils.parseEther("20"));
  await testERC20.connect(addr1).approve(membershipFactory.address, ethers.utils.parseEther("20"));

  // but anyone can join
  await expect(membershipFactory.connect(addr1).joinDAO(membershipERC1155.address, 1)).to.not.be.reverted;
});
LOW severity: DAO membership can exceed `MembershipDAOStructs::DAOConfig.maxMembers`. **Description:** The [`MembershipDAOStructs::DAOConfig.maxMembers`](https://github.com/OneWpOrg/audit-2024-10-oneworld/blob/416630e46ea6f0e9bd9bdd0aea6a48119d0b515a/contracts/dao/libraries/MembershipDAOStructs.sol#L18) field is intended as a cap to DAO membership, beyond which should not be...

Vulnerable code:
it("can exceed maxMembers", async function () {
  // max members is 1
  DAOConfig.maxMembers = 1;
  await currencyManager.addCurrency(testERC20.address);
  await membershipFactory.createNewDAOMembership(DAOConfig, TierConfig);

  const ensAddress = await membershipFactory.getENSAddress("testdao.eth");
  const membershipERC1155 = await MembershipERC1155.attach(ensAddress);

  await testERC20.mint(addr1.address, ethers.utils.parseEther("20"));
  await testERC20.connect(addr1).approve(membershipFactory.address, ethers.utils.parseEther("20"));
  await testERC20.mint(addr2.address, ethers.utils.parseEther("20"));
  await testERC20.connect(addr2).approve(membershipFactory.address, ethers.utils.parseEther("20"));

  // two members can join
  await expect(membershipFactory.connect(addr1).joinDAO(membershipERC1155.address, 1)).to.not.be.reverted;
  await expect(membershipFactory.connect(addr2).joinDAO(membershipERC1155.address, 1)).to.not.be.reverted;
});
LOW severity: Lowest tier (highest index) membership cannot be upgraded. **Description:** For `SPONSORED` DAOs, members are permitted to upgrade from a lower tier membership to a higher tier by burning two tokens within a call to [`MembershipFactory::upgradeTier`](https://github.com/OneWpOrg/smart-contracts-blockchain-1wp/blob/416630e46ea6f0e9bd9bdd0aea6a48119d0b515a/con...

Vulnerable code:
require(daos[daoMembershipAddress].noOfTiers > fromTierIndex + 1, "No higher tier available.");

it("cannot upgrade from lowest tier, highest index", async function () {
  const fromTierIndex = 6;
  await testERC20.mint(addr1.address, ethers.utils.parseEther("1000000"));
  await testERC20.connect(addr1).approve(membershipFactory.address, ethers.utils.parseEther("1000000"));

  await membershipFactory.connect(addr1).joinDAO(membershipERC1155.address, fromTierIndex);
  await membershipFactory.connect(addr1).joinDAO(membershipERC1155.address, fromTierIndex);

  // cannot upgrade from highest index, lowest tier, because of off-by-one
  await expect(membershipFactory.connect(addr1).upgradeTier(membershipERC1155.address, fromTierIndex)).to.be.revertedWith("No higher tier available.");
});

-    require(daos[daoMembershipAddress].noOfTiers > fromTierIndex + 1, "No higher tier available.");
+    require(daos[daoMembershipAddress].noOfTiers > fromTierIndex, "No higher tier available.");