Test suite
When building a complex contract ecosystem with multiple moving parts and dependencies, comprehensive testing is essential. This includes testing for both positive and negative scenarios. Positive tests should verify that each function's side effect is as expected, while negative tests should cover every revert, preferably in every logical branch.
The test coverage for this project should include all contracts, not just surface-level functions. It is important to test the invariants required for ensuring security and also verify mathematical properties.
Therefore, we recommend building a rigorous test suite that includes all contracts to ensure that the system operates securely and as intended.
Good test coverage has multiple effects.
It finds bugs and design flaws early (preaudit or prerelease).
It gives insight into areas for optimization (e.g., gas cost).
It displays code maturity.
It bolsters customer trust in your product.
It improves understanding of how the code functions, integrates, and operates — for developers and auditors alike.
It increases development velocity long-term.
The last point may seem contradictory given the time investment to create and maintain tests. To expand upon it, tests help developers trust their own changes. It is difficult to know if a code refactor — or even just a small one-line fix — breaks something if there are no tests. This is especially true for new developers or those returning to the code after a prolonged absence. Tests have your back here. They are an indicator that the existing functionality most likely was not broken by your change to the code.
In our opinion, the tests for the assessed code are inadequate for the following reasons:
Several functions are not tested at all. While many of the functions are simple or copy significant amounts of logic from other functions, any change in logic is inherently risky and should be properly tested.
Looping.loopPUSDLooping.loopNRWAWrappedTokenGatewayV3.loopEntryPLUMESingleSwapWrappedTokenGatewayV3.loopEntryPLUMEMultiSwapWrappedTokenGatewayV3.loopEntryPLUMESingleAssetWrappedTokenGatewayV3.loopExitPLUMESingleSwapWrappedTokenGatewayV3.loopExitPLUMEMultiSwapWrappedTokenGatewayV3.loopExitPLUMESingleAssetWrappedTokenGatewayV3.loopPLUMESingleAsset
Looping tests do not test against many positive scenarios — for example, using varying borrow amounts (prices, supply, etc.), testing with tokens of varying decimals, and so on.
Few looping tests assert that the state was properly changed after executing functions.
There are few tests for edge-case scenarios — for example, extreme low-liquidity situations in swap paths, extreme token decimals, and outdated price oracles (all with a range of target health factors).
Solera added the following note regarding this point:
The primary / sole path for users to enter loops is through direct minting (nest, pETH, solera staking) and swap routes are not a priority / primary route.