Omniscia 0xPhase Audit

BalancerAccountingFacet Code Style Findings

BalancerAccountingFacet Code Style Findings

BAF-01C: Inefficient Evaluation of Transfer Fulfilment

TypeSeverityLocation
Gas OptimizationBalancerAccountingFacet.sol:L146-L149, L177-L180

Description:

The for loops within BalancerAccountingFacet::withdraw will evaluate whether the expected transfer is fulfillable at the start of each loop rather than after each withdrawal is performed.

Example:

balancer/diamond/BalancerAccountingFacet.sol
134if (asset.balanceOf(address(this)) >= amount) {
135 asset.safeTransfer(msg.sender, amount);
136 return amount;
137}
138
139(Offset[] memory arr, , ) = _calculations().offsets(asset);
140
141require(arr.length > 0, "BalancerInitializer: No yields on withdraw");
142
143uint256 acc = amount - asset.balanceOf(address(this));
144
145for (uint256 i = 0; i < arr.length; ) {
146 if (acc == 0) {
147 asset.safeTransfer(msg.sender, amount);
148 return amount;
149 }
150
151 Offset memory offset = arr[i];
152 Yield storage yield = s.yield[offset.yieldSrc];
153
154 if (!offset.isPositive || !yield.state) {
155 unchecked {
156 i++;
157 }
158
159 continue;
160 }
161
162 _updateAPR(offset.yieldSrc);
163
164 uint256 yieldAmount = MathLib.min(offset.offset, acc);
165
166 offset.yieldSrc.withdraw(yieldAmount);
167
168 yield.lastDeposit = offset.yieldSrc.totalBalance();
169
170 unchecked {
171 acc -= yieldAmount;
172 i++;
173 }
174}

Recommendation:

We advise the referenced code blocks to be relocated after the acc subtractions, ensuring that they are performed as efficiently as possible.

Alleviation:

The withdrawal flow of the balancer was updated as advised, evaluating whether the function should stop right after subtracting the accumulator variable rather than at the start of each loop.

BAF-02C: Optimization of Yield Status Evaluation

TypeSeverityLocation
Gas OptimizationBalancerAccountingFacet.sol:L53, L80, L154, L186

Description:

The BalancerAccountingFacet::deposit and BalancerAccountingFacet::withdraw functions will inefficiently evaluate the state of a yield source by accessing storage when the BalancerCalculationsFacet::offsets has already performed this check.

Example:

balancer/diamond/BalancerAccountingFacet.sol
145for (uint256 i = 0; i < arr.length; ) {
146 if (acc == 0) {
147 asset.safeTransfer(msg.sender, amount);
148 return amount;
149 }
150
151 Offset memory offset = arr[i];
152 Yield storage yield = s.yield[offset.yieldSrc];
153
154 if (!offset.isPositive || !yield.state) {
155 unchecked {
156 i++;
157 }
158
159 continue;
160 }
161
162 _updateAPR(offset.yieldSrc);
163
164 uint256 yieldAmount = MathLib.min(offset.offset, acc);
165
166 offset.yieldSrc.withdraw(yieldAmount);
167
168 yield.lastDeposit = offset.yieldSrc.totalBalance();
169
170 unchecked {
171 acc -= yieldAmount;
172 i++;
173 }
174}

Recommendation:

As the BalancerCalculationsFacet::offsets code will assign an offset of 0 if the yield's state is not active, we advise the code to evaluate offset.offset rather than yield.state, allowing the storage declaration to be relocated after the if clause and thus optimizing the code's gas cost whenever a particular yield source is meant to be skipped.

Alleviation:

The isPositive member of a particular offset has been replaced by an offset state that can contain the additional state of None, representing a yield source that should not be utilized. The code was updated to take advantage of this and thus has been made as optimal as possible, alleviating this exhibit.