Platform: Code4rena
Start Date: 16/09/2021
Pot Size: $50,000 USDC
Total HM: 26
Participants: 30
Period: 7 days
Judge: GalloDaSballo
Total Solo HM: 17
Id: 36
League: ETH
Rank: 6/30
Findings: 6
Award: $3,819.27
π Selected for report: 7
π Solo Findings: 3
450.8693 USDC - $450.87
goatbug
Given a bondDiv of say 400, it requires 0.25% of a portfolios capital to perform a Ddos attack.
Portfolio rebalancing is often a time sensitive task. New weights need to be put into place to take advantage of shifts in capital market expectations etc.
Users can block rebalances by 48hrs, a sizeable window, through a relatively cheap DDoS of 0.25% of the portfolio, a relatively small size.
48hrs since, a 24hr timelock is required for a new proposal, and a 24hr bond arbitration period is required. A user simply watches the mempool and submits a bond proposal immediately to lock the contract in an auction phase.
A user could therefore block a market from any change for more than 196 hours (4*24) for the cost of 1%. 8 Days is often too slow for a portfolio to react and balance in crypto market conditions. This is annoying and another reason one needs to consider the bondDiv more carefully and scale it according to portfolio size.
One could consider simply allowing anyone to settle the auction. This removes the DDoS attack.
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
#0 - frank-beard
2021-10-19T16:43:22Z
this is possible however the ability for anyone to rebalance is important. we will look into possible solutions to mitigate this
#1 - GalloDaSballo
2021-12-18T14:08:34Z
Duplicate of #66
π Selected for report: GalloDaSballo
Also found by: goatbug
1001.9319 USDC - $1,001.93
goatbug
function setMinLicenseFee(uint256 newMinLicenseFee) public override onlyOwner { minLicenseFee = newMinLicenseFee; } function setAuctionDecrement(uint256 newAuctionDecrement) public override onlyOwner { auctionDecrement = newAuctionDecrement; }
Both min license fee and auction decrement have no restrictions on the values that can be set.
This can be critical and block the contract if a too high value or zero value is set.
I.e. the min license fee could be set higher than the total amount, causing revert.
Similar applies with the decrement.
#0 - frank-beard
2021-09-30T18:37:45Z
it is assumed the owner is trustworthy in this version of the protocol, however we will add mitigations and further decentralization in future updates
#1 - GalloDaSballo
2021-12-19T22:05:46Z
Duplicate of #119
π Selected for report: GalloDaSballo
Also found by: goatbug
1001.9319 USDC - $1,001.93
goatbug
bondPercentDiv can be set to zero by the owner. This would give a div by zero error in line 61 bondAmount = basketToken.totalSupply() / factory.bondPercentDiv(); and brick any portfolio balancing ever.
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
#0 - frank-beard
2021-09-30T18:37:13Z
it is assumed the owner is trustworthy in this version of the protocol, however we will add mitigations and further decentralization in future updates
#1 - GalloDaSballo
2021-12-19T22:07:15Z
Duplicate of #121
π Selected for report: goatbug
1001.9319 USDC - $1,001.93
goatbug
function setAuctionMultiplier(uint256 newAuctionMultiplier) public override onlyOwner { auctionMultiplier = newAuctionMultiplier; }
auction multiplier can be set to zero by factory owner. This would stop the auction settling, function would always revert.
uint256 a = factory.auctionMultiplier() * basket.ibRatio(); uint256 b = (bondTimestamp - auctionStart) * BASE / factory.auctionDecrement(); uint256 newRatio = a - b;
causing a safe math error and newRatio to revert.
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
#0 - frank-beard
2021-09-30T18:37:31Z
it is assumed the owner is trustworthy in this version of the protocol, however we will add mitigations and further decentralization in future updates
#1 - GalloDaSballo
2021-12-19T22:06:45Z
Agree with the finding, because the warden showed a specific "admin privilege" that DOSses the protocol, the finding is valid and of medium severity
43.8245 USDC - $43.82
goatbug
Anyone can add any bounty and pass in any smart contract instead of an erc20 token. When calling settleAuction, this triggers a tranfer function to be called from the smart contract that can be used to re-enter the auction or basket code.
Currently this cannot do anything malicious, but after the code is heavily refactored and improved based on the audit, it is possible this reentrancy hook could maliciously be used.
The contract should rather have a whitelist of token addresses that could be used for the bounty to avoid reentrancy.
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
#0 - frank-beard
2021-10-19T16:46:37Z
dduplicate of #31
#1 - GalloDaSballo
2021-12-19T15:48:28Z
Duplicate of #136
#2 - GalloDaSballo
2021-12-27T00:48:57Z
After re-review this is a duplicate of #270 Mentions re-entrancy without a poc
4.3799 USDC - $4.38
goatbug
Long list of functions should be set from public to external since they are not called anywhere by the contract itself.
There are too many to list them all from all the contracts.
Just some examples in the Factory contract.
There are lots in every contract that should rather be external.
function setMinLicenseFee(uint256 newMinLicenseFee) public override onlyOwner { minLicenseFee = newMinLicenseFee; } function setAuctionDecrement(uint256 newAuctionDecrement) public override onlyOwner { auctionDecrement = newAuctionDecrement; } function setAuctionMultiplier(uint256 newAuctionMultiplier) public override onlyOwner { auctionMultiplier = newAuctionMultiplier; } function setBondPercentDiv(uint256 newBondPercentDiv) public override onlyOwner { bondPercentDiv = newBondPercentDiv; } function setOwnerSplit(uint256 newOwnerSplit) public override onlyOwner { require(newOwnerSplit <= 2e17); // 20% ownerSplit = newOwnerSplit; }
#0 - GalloDaSballo
2021-11-26T01:26:15Z
Agree with the finding, notice that external vs public provides gas savings exclusively when using arrays as calldata, so the cases above would not actually save gas
The finding is valid as it's best practice to set functions that are never internally used to external
25.9609 USDC - $25.96
goatbug
uint256 private constant BLOCK_DECREMENT = 10000; BLOCK_DECREMENT declared but never used in Auction contract
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
#0 - GalloDaSballo
2021-11-28T17:25:36Z
Duplicate of #198
π Selected for report: goatbug
57.691 USDC - $57.69
goatbug
Even if the same token is added as a bounty, they will be treated as seperate bounties and require 2 transfers of the same token to claim.
function addBounty(IERC20 token, uint256 amount) public override returns (uint256) { // add bounty to basket token.safeTransferFrom(msg.sender, address(this), amount); _bounties.push(Bounty({ token: address(token), amount: amount, active: true })); uint256 id = _bounties.length - 1; emit BountyAdded(token, amount, id); return id; }
Another optimization: Bounties further keep being added to the list and never deleted. Gas could be saved by removing bounties instead of setting their active flag to false.
#0 - GalloDaSballo
2021-11-28T17:27:10Z
Agree with the finding, it may be best to check for uniqueness (for example by using an EnumerableMap) As per the second finding, that's correct as setting storage to 0 will trigger gas refunds (up to 1/5 of the cost of the tx)
π Selected for report: goatbug
57.691 USDC - $57.69
goatbug
All structs can be packed to use less storage slots.
struct PendingPublisher { address publisher; uint256 block; } struct PendingLicenseFee { uint256 licenseFee; uint256 block; } struct PendingWeights { address[] tokens; uint256[] weights; uint256 block; bool pending; }
struct PendingPublisher { address publisher; uint256 block; }
Block can likely be smaller and fit in to a uint88 etc packing into a single slot
struct PendingLicenseFee { uint256 licenseFee; uint256 block; }
Each could be uint128 here.
struct PendingWeights { address[] tokens; uint256[] weights; uint256 block; bool pending; }
block and pending packed together.
#0 - GalloDaSballo
2021-11-28T17:37:43Z
Agree with the finding, although there's seems to be no clear advice
π Selected for report: goatbug
57.691 USDC - $57.69
goatbug
In createBasket (Factory), tokens are first transsferred from the creator, to the factory, before being approved and then transfered to the Basket.
The function is atomic and the tokens could simply be written to be transferred straight to Basket to save a lot of gas, especially on large numbers of tokens in a basket.
for (uint256 i = 0; i < bProposal.weights.length; i++) { IERC20 token = IERC20(bProposal.tokens[i]); token.safeTransferFrom(msg.sender, address(this), bProposal.weights[i]); token.safeApprove(address(newBasket), bProposal.weights[i]); } newBasket.mintTo(BASE, msg.sender);
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
#0 - GalloDaSballo
2021-11-28T17:38:30Z
The finding is valid, the implementation will require a fair tech lift
π Selected for report: goatbug
goatbug
2 slots instead of 3
struct Bounty { address token; uint256 amount; bool active; }
having token and active lines consecutive can pack those two values into single 256 bit slot. Result is packing this struct into 2 256 slots as opposed to 3.
#0 - GalloDaSballo
2021-11-28T17:40:04Z
Finding is valid, address and bool can pack together
π Selected for report: goatbug
57.691 USDC - $57.69
goatbug
Use less storage slots
struct Proposal { uint256 licenseFee; string tokenName; string tokenSymbol; address proposer; address[] tokens; uint256[] weights; address basket; }
License fee is a smaller number does not need to be uint256.
Could use an 8 bit value and pack it comfortable with one of the addresses to save a full storage slot.
#0 - GalloDaSballo
2021-11-28T17:39:30Z
Finding is valid, it would save an extra storage slot