Skip to content

ARM integration to Lending Markets #79

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 62 commits into
base: main
Choose a base branch
from

Conversation

naddison36
Copy link
Collaborator

@naddison36 naddison36 commented May 5, 2025

Design Specification

The following spec is copied from https://www.notion.so/originprotocol/ARM-Lending-Markets-Design-1d684d46f53c805984b3f9b4c1cbadc8

The design decisions can be exported on request https://www.notion.so/originprotocol/ARM-Lending-Markets-Design-1d684d46f53c805984b3f9b4c1cbadc8?p=1cf84d46f53c80b8ad3ef07f6a45dd12&pm=s

Features

  • An ARM maintains a liquidity buffer in the ARM to buy LSTs at a discount. Excess liquidity is deposited into the active lending market to earn yield.
  • There can be many support lending markets but only one can be used at any point in time.
  • The ARM will integrate to the lending markets using ERC-4626.

ARM Lending Market Use Cases

  • Any can allocate liquidity in the ARM. This tries to adjust the ARM’s liquidity to match the ARM’s liquidity buffer.
    • This requires an active lending market to be set. A lending market with a zero address will disable the lending market feature.
    • If not enough liquidity in the ARM
      • and there is enough liquidity in the active lending market
        • liquidity is withdrawn from the active lending market.
      • and there is not enough enough liquidity in the active lending market
        • all liquidity is redeemed from the active lending market.
    • If too much liquidity in the ARM, liquidity is deposited to the active lending market.
  • The Owner (Timelock) can add a supported lending market
    • The ARM’s liquidity asset must match the asset of the lending market’s vault
  • The Owner (Timelock) can remove a supported lending market
    • Must not be the active lending market
  • The Operator (Defender Relayer) can switch the active lending market
    • The lending market has to be one of the ARM’s supported lending markets
    • All liquidity is withdrawn from the previous active lending market.
      • If not all the liquidity can be removed as the market’s utilization is high, the transaction will fail
    • Liquidity is deposited to the new active lending market via the allocate process
  • The Owner (Timelock) can set the ARM liquidity buffer

Harvester Use Cases

The Harvesters on Sonic will use the Magpie aggregator to get best execution of swapping Silo for wS.

Some key design decisions

  • Anyone can collect token rewards from the supported lending market strategies. For the Silo lending markets, that will be the Silo token. Rewards from multiple lending market strategies can be collected before being swapped to the ARM’s associated liquidity asset.
  • Only the Owner or Operator can swap token rewards for the associated ARM’s liquidity asset. For the Silo lending markets, that’s swapping Silo for wrapped S (wS). The initial implementation will only support the Magpie DEX aggregator. The Owner or Operator is responsible for calling the Magpie APIs and passing the the Magpie specific swap data.
  • The Harvester will do the following validators of swaps
    • Swaps are to the associated ARM’s liquidity asset. ie wS
    • The swap price is within an allowed slippage from the Oracle price
    • The receiver of the swap is the Harvester’s rewards recipient. Initially that will be the ARM contract. That could be changed to a Dripper in the future.
  • There will be a separate Harvester contract for each ARM. This is a lot simplifier than having a single Harvester that has to account for the incoming rewards tokens and sending the appropriate liquidity assets to each ARM.

Contracts

  • Change license from MIT to BUSL-1.1
  • Upgraded AbstractARM to park idle liquidity in a lending market
  • Added OriginARM that implements AbstractARM by integrating to an Origin Vault
  • Added SiloMarket contract that can harvest Silo rewards from a Silo lending market
  • Added Harvester that can collect and swap Silo rewards to wS using the Magpie DEX aggregator
  • Added ZapperARM that integrates to multiple ARMs by swapping S to wS.

sonicContracts

Testing

To run all the tests

make test

To run the OriginARM fork tests

make test-c-Fork_Concrete_OriginARM

Deployment

The deployment scripts are
*script/deploy/sonic/001_DeployOriginARMProxy.sol
*script/deploy/sonic/002_DeployOriginARM.sol

In the .env file, set the DEPLOYER_PRIVATE_KEY and SONIC_URL variables

make deploy-sonic

naddison36 and others added 22 commits April 9, 2025 22:08
Added OriginARM deploy script for Sonic
Updated the Sonic deploy script
* Sonic ARM contract dependencies

* update Code settings

* Silo market contract

* fix: handle zero shares case before redeeming in AbstractARM contract

* Fet liquidity from the active lending market for claimRedeem if not enough liquidity in the ARM

* Added transfer from ARM to SiloMarket contract for deposit
* feat: setup unit test for Origin ARM

* fix: update requestWithdrawal function to return additional value and ensure MockERC20 burn call

* feat: add unit tests for OriginARM deposit functionality and update DEFAULT_FEE constant

* feat: add setArmBuffer function to manage liquidity asset buffer and emit event on update

* feat: add setDefaultStrategy and deposit modifiers for unit tests in OriginARM

* feat: replace MockStrategy with MockMarket and update related modifiers in tests

* feat: add unit tests for OriginARM market management and enhance modifier functionality

* feat: introduce MockERC4626Market and replace MockMarket in shared tests

* feat: enhance market management by adding support for multiple markets and updating modifiers

* feat: add new modifiers for withdrawal and token swap functionality in Modifiers contract

* feat: add unit tests for total assets functionality in OriginARM contract

* feat: add swapAllOETHForWETH modifier to facilitate token swapping in Modifiers contract

* feat: add unit tests for fee collector and fee management in OriginARM contract

* fix: prevent redeeming zero shares in AbstractARM contract

* feat: add additional tests for fee management and address validation in OriginARM contract

* fix: update maxWithdraw call to use contract address in AbstractARM

* feat: enhance simulateMarketLoss modifier to include maxRedeem calculations

* feat: add unit tests for allocate functionality in OriginARM contract

* feat: add marketLoss modifier to simulate market loss in tests

* feat: add unit tests for claim and deposit functionalities in OriginARM contract

* fix: rename contract to reflect correct functionality for claim and redeem tests

* feat: add unit tests for request redeem functionality in OriginARM contract

* feat: add Sonic configuration and update AddressResolver for new market references

* feat: add shared test setup and modifiers for OriginARM contract

* feat: add Allocated event to track asset allocation in AbstractARM contract

* fix: prevent redeem call with zero shares in AbstractARM contract

* fix: handle zero shares case before redeeming in AbstractARM contract

* feat: enhance testing framework with new modifiers and helper functions for OriginARM contract

* fix: prevent redeem call with insufficient shares in AbstractARM contract

* feat: add MIN_BALANCE constant and update allocation tests for edge cases in Fork_Concrete_OriginARM_Allocate_Test_

* feat: add new test cases for liquidity delta scenarios and update assertions in Allocate.sol

* fix: update SiloMarket to use market address for deposit, withdraw, and redeem functions

* refactor: simplify maxWithdraw and maxRedeem functions to return 0 for non-ARM owners

* feat: add tests for allocation scenarios with SiloMarket integration and update shared setup

* feat: add unit tests for SiloMarket with revert scenarios and setup adjustments

* fmt

* refactor: remove unused imports from unit test files

* refactor: simplify maxWithdraw and maxRedeem functions for clarity
* Fix maxRedeem

* FIx import breaking build

* Renamed collectRewardTokens to collectRewards

* First cut off the Harvester

* Cleaned up Harvester

* Added Harvester to deploy script

* Updated Sonic contract diagram

* Added magpieQuote Hardhat task

* Added magpieTx Hardhat task

* Remove function sig from Magpie swap data

* only console log the swap data

* wip: add first API test interaction

* rename test function to reflect Magpie swap functionality

* Fix parsing of Magpie data

* Enable ffi

* Got Magpie swap working

* Clean up assembly validation

* Update assembly comments

---------

Co-authored-by: Clément <[email protected]>
* fix: assign maxShares in maxRedeem function in SiloMarket contract

* fix: update label for siloMarket to Silo Market Adapter in Base_Test_ contract

* test: add VaultInteractions tests for OriginARM contract

* fix: update clean-all target to remove yarn.lock and lcov.info files

* feat: migrate invariant for LidoARM in a separated folder

* feat: add invariant tests and setup for OriginARM with helper contracts

* feat: add ClaimRedeem fork tests for Fork_Concrete_OriginARM contract

* feat: add requestRedeem and timejump modifiers to Modifiers contract

* feat: implement handler_claimRedeem and update request management in TargetFunction and Helpers contracts

* feat: add handler_setARMBuffer function and update ClaimRedeem and Allocate status in TargetFunction

* feat: update SetARMBuffer status to implemented and adjust pct calculation in handler_setARMBuffer function

* feat: update Allocate status to not implemented in TargetFunction contract

* feat: add handler_setActiveMarket function and update market management in TargetFunction and Helpers contracts

* feat: update Allocate status to implemented and add handler_allocate function in TargetFunction contract

* feat: add handler_setPrices function to manage buy and sell prices in TargetFunction contract

* feat: update price handling in TargetFunction contract to improve precision and mimic market behavior

* feat: update price handling and add setCrossPrice function in TargetFunction contract

* feat: add handler_swapExactTokensForTokens function and update liquidity calculations in TargetFunction contract

* feat: add handler for `swapTokensForExactTokens()`.

* feat: add handlers for `collectFees` and `setFee`.

* feat: add logic and handler for `requestOriginWithdrawal`.

* feat: add claimOriginWithdrawals handler and update related logic in TargetFunction and Helpers contracts

* feat: add handler for `donateToARM` and update related logic in TargetFunction and FuzzerFoundry contracts

* WIP

* feat: enhance claimRedeem logic and implement fee collection in TargetFunction contract

* feat: add funding logic for markets in FuzzerFoundry contract

* feat: add console logging toggle in Setup and TargetFunction contracts

* feat: update MockVault and related tests to support multiple tokens

* feat: enhance FuzzerFoundry and Properties contracts with new swap properties and invariants

* feat: add new LP invariants and update deposit/redeem logic in TargetFunction contract

* feat: update funding logic and add assertions for LP balances in FuzzerFoundry and TargetFunction contracts

* feat: update assertLpsAreUpOnly function to include tolerance parameter for balance checks

* feat: enable fail_on_revert in fuzz configuration for improved error handling

* feat: refactor getRandomMarket and getRandomLPsWithRequest functions for improved clarity and efficiency

* feat: refactor random selection functions to use Fisher-Yates shuffle for improved randomness and efficiency
* feat: enhance Harvester tests with additional swap scenarios and helper functions

* fix: correct variable name in allowed slippage validation

* test: add comprehensive tests for Harvester setters and swaps

* feat: enhance collectRewards function to return claimed reward tokens and amounts
* feat: enhance Harvester tests with additional swap scenarios and helper functions

* fix: correct variable name in allowed slippage validation

* test: add comprehensive tests for Harvester setters and swaps

* feat: enhance collectRewards function to return claimed reward tokens and amounts

* feat: integrate distribution manager and incentives controller in SiloMarket

* fix: correct import statement for Math utility in Harvester contract

* feat: add Fork_Concrete_Harvester_Collect_Test contract and enhance shared test setup
* Changed setActiveMarket to use balanceOf instead of maxRedeem

* Change _availableAssets to use previewRedeem instead of maxWithdraw
* Add WOS token and update related tests for market utilization

* fix: update assertions in allocation tests to use approximate equality

* fix: update minimum shares to redeem in AbstractARM contract

* fix: update minimum shares to redeem in AbstractARM contract

* feat: add balanceOf and previewRedeem functions to SiloMarket contract

* fix: update afterInvariant function to use dynamic minimum shares and improve property_swap_A logic

* fix: update minimum shares to redeem in AbstractARM contract
@naddison36 naddison36 self-assigned this May 5, 2025
@naddison36
Copy link
Collaborator Author

naddison36 commented May 5, 2025

Requirements

See the PR description or Notion https://www.notion.so/originprotocol/ARM-Lending-Markets-Design-1d684d46f53c805984b3f9b4c1cbadc8

Easy Checks

Authentication

  • Never use tx.origin
  • Every external/public function is supposed to be externally accessible
  • Every external/public function has the correct authentication

Ethereum

  • Contract does not send or receive Ethereum.
    ZapperARM receives native S but that's ok.
  • Contract has no payable methods.
    deposit on ZapperARM is payable but that's ok.
  • Contract is not vulnerable to being sent self destruct ETH

Cryptographic code

  • This contract code does not roll it's own crypto.
  • No signature checks without reverting on a 0x00 result.
  • No signed data could be used in a replay attack, on our contract or others.

Gas problems

  • Contracts with for loops must have either:
    • A way to remove items
    • Can be upgraded to get unstuck
    • Size can only controlled by admins
  • Contracts with for loops must not allow end users to add unlimited items to a loop that is used by others or admins.

Black magic

  • Does not contain selfdestruct
  • Does not use delegatecall outside of proxying. If an implementation contract were to call delegatecall under attacker control, it could call selfdestruct the implementation contract, leading to calls through the proxy silently succeeding, even though they were failing.
    Only the existing Proxy contract uses delegatecall
  • Address.isContract should be treated as if could return anything at any time, because that's reality.

Overflow

  • Code is solidity version >= 0.8.0
  • All for loops use uint256

License

  • The contract uses the appropriate limited BUSL-1.1 (Business) or the open MIT license
  • If the contract license changes from MIT to BUSL-1.1 any contracts importing it need to also have their license set to BUSL-1.1

Proxy

  • No storage variable initialized at definition when contract used as a proxy implementation.

Events

  • All state changing functions emit events

Medium Checks

Rounding and casts

  • Contract rounds in the protocols favor
  • Contract does not have bugs from loosing rounding precision
  • Code correctly multiplies before division
  • Contract does not have bugs from zero or near zero amounts
  • Safecast is aways used when casting

Dependencies

  • Review any new contract dependencies thoroughly (e.g. OpenZeppelin imports) when new dependencies are added or version of dependencies changes.
  • If OpenZeppelin ACL roles are use review & enumerate all of them.
  • Check OpenZeppelin security vulnerabilities and see if any apply to current PR considering the version of OpenZeppelin contract used.

External calls

  • Contract addresses passed in are validated
  • No unsafe external calls
  • Reentrancy guards on all state changing functions
    • Still doesn't protect against external contracts changing the state of the world if they are called.
  • No malicious behaviors
  • Low level call() must require success.
  • No slippage attacks (we need to validate expected tokens received)
    The Harvester will not have slippage protection until there is a Silo Oracle on Sonic
  • Oracles, one of:
    • No oracles
    • Oracles can't be bent
    • If oracle can be bent, it won't hurt us.
  • Do not call balanceOf for external contracts to determine what they will do when they use internal accounting
    AbstractARM uses balanceOf in the new allocate and setActiveMarket functions.

Tests

  • Each publicly callable method has a test
  • Each logical branch has a test
  • Each require() has a test
  • Edge conditions are tested
  • If tests interact with AMM make sure enough edge cases (pool tilts) are tested. Ideally with fuzzing.

Deploy

  • Deployer permissions are removed after deploy

Downstream

  • We have monitoring on all backend protocol's governances
  • We have monitoring on a pauses in all downstream systems

Thinking

Logic

  • Correct usage of global & local variables. -> they might differentiate only by an underscore that can be overlooked (e.g. address vs _address).

Deployment Considerations

  • The final set of Silo Markets is still to be decided on
  • The currently is no Oracle for Silo on Sonic.

Internal State

  • What can be always said about relationships between stored state
  • What must hold true about state before a function can run correctly (preconditions)
  • What must hold true about the return or any changes to state after a function has run.

Does this code do that?

Attack

For the initial deployment, the Harvester will not have an Oracle for the Silo rewards. This means the Operator can route Silo to wS swaps to less liquid markets.

Flavor

The lending market integration was made a lot simpler by only having one active lending market. If there is a large amount of idle liquidity deposited into that active market, then that can diminish the returns as the utilization of the market will drop. It could be more profitable to split the idle liquidity across multiple lending markets but that adds a lot of complexity that isn't needed for now.

naddison36 and others added 2 commits May 5, 2025 19:16
* fix: adjust invariant test parameters and refactor setup logic in FuzzerFoundry and Setup contracts

* fix: increase invariant test runs and depth for improved coverage

* fix: update SiloMarket constructor to accept gauge address directly

* fix: update minimum shares to redeem from 10,000 to 10,000,000

* feat: deploy new SiloMarket for Varlamore with gauge integration

* fix: correct balance assertion in harvester test and update MIN_BALANCE constant in AllocateWithAdapter test

* fix: update deposit and swap handler functions to use uint88 for amounts (#82)

* Fix: Change MIN_SHARES_TO_REDEEM from constant to immutable. (#83)

* fix: refactor minSharesToRedeem to be an immutable variable in AbstractARM contract

* fix: add minSharesToRedeem parameter to OriginARM constructor and update related deployments

* fix: update test target to use Fuzzer for improved testing coverage

* fix: format

* feat: add MathComparisons library and integrate it into Properties and TargetFunction contracts
clement-ux and others added 3 commits May 23, 2025 04:05
* Refactor test workflow to include non-invariant and invariant tests for LidoARM

* Refactor Makefile to use $(MAKE) for consistency in test commands

* Comment out requirement for SONIC_URL in _createAndSelectFork function

* Add SONIC_URL to environment variables in foundry-tests job

* Uncomment requirement for SONIC_URL in _createAndSelectFork function

* Increase tolerance in totalAssets assertions for allocation tests

* Update totalAssets assertion to use assertApproxEqAbs for accuracy in LidoARM deposit test

* Refactor totalAssets assertions to use assertApproxEqAbs for improved accuracy in deposit tests

* Add debugging output to non-invariant tests step in CI workflow

* Enhance non-invariant tests step with environment checks for better debugging

* Fix installation command in CI workflow to use 'make install' instead of 'forge soldeer install'

* Fix installation command in CI workflow to use 'make install' instead of 'forge soldeer install'

* Simplify non-invariant tests step by removing unnecessary commands before 'make test'

* Fix formatting in CI workflow for Install Dependencies step

* Fix job name for foundry tests in CI workflow
* Deploy Origin ARM on Sonic

* Changed depositLido to depositARM so it works with OriginARM on Sonic

* Made requestRedeem and claimRedeem work with the origin ARM on Sonic

* swap HH task now works with Origin ARM on Sonic

* hh tasks to request and claim Vault withdrawals now work with Origin ARM on Sonic

* HH snap task now works with Sonic ARM

* Removed unused interfaces in SiloMarket

* Allocate excess liquidity to the lending market after claiming OS withdrawal
@@ -550,6 +574,14 @@ abstract contract AbstractARM is OwnableOperable, ERC20Upgradeable {
// Store the updated claimed amount
withdrawsClaimed += SafeCast.toUint128(assets);

// If there is not enough liquidity assets in the ARM, get from the active market

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block can be skipped if activeMarket is not set.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in cc88ff5

(, amountClaimed) = IOriginVault(vault).claimWithdrawals(requestIds);

// Store the reduced amount outstanding withdrawals from the Origin Vault
vaultWithdrawalAmount -= amountClaimed;
Copy link

@pandadefi pandadefi May 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case a withdrawal request is transferred to the OriginARM contract, you won't be able to claim it because this line will underflow.

This issue also exists in the lido contract.

lidoWithdrawalQueueAmount -= totalAmountRequested;

A simple fix could be

if (vaultWithdrawalAmount <  amountClaimed) {
vaultWithdrawalAmount = 0;
}

Copy link
Collaborator Author

@naddison36 naddison36 May 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good spot for claiming the Lido withdrawals. I've fixed the LidoARM with bb0b2cd

It won't be an issue for the Origin ARM as withdrawals from the Origin Vault are not transferrable. I'll put in a comment.

/// less the liquidity assets reserved for the ARM's withdrawal queue and accrued fees.
/// @return The total amount of assets in the ARM
function totalAssets() public view virtual returns (uint256) {
Copy link
Contributor

@DanielVF DanielVF May 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 The intention of the conditional in this function is that totalAssets() never returns a number lower than MIN_TOTAL_SUPPLY.

However, this function can return a number lower than MIN_TOTAL_SUPPLY, including returning a zero. The current code results in a discontinuity as the fee approaches and then passes newAvailableAssets. The returned assets goes down to zero and then jumps back up to the minimum total supply.

What we actually want here is a minimum function, taking the larger of two values, while also avoiding any subtraction before we know that the subtraction is safe. I think the correct if statement here might be this:

if (fees + MIN_TOTAL_SUPPLY >= newAvailableAssets) return MIN_TOTAL_SUPPLY;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent spot. Fixed in 5b1a9cf

Copy link
Contributor

@DanielVF DanielVF left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OETH ARM

Requirements

This PR is adding the ability to auto-allocate liquidity to a 4626 lending market to earn extra yield on idle capital.

It does this via an intermediate wrapper 4626 contract that provides the ability to harvest rewards.

One open question is if anyone should be able to move liquidity back into the contract, or if it needs to be operator only so that the prices can be set before liquidity is re-added.

A portion of our volume is very large transactions. It would be helpful to know how many of these transactions are actually users wanting to transaction large amounts which is good or are bots looking the transaction large amounts which we don't want we would prefer to change our prices and raise them. Knowing this can help us determine how big of an allocation buffer we should set.

Having this lending platform option puts a threshold on how far we should change the prices. If we're going to be making 2% on a lending platform then we shouldn't set our sell price margins thin enough to the point where we will make less than the 2% going through a redeem cycle.

Authentication

  • Never use tx.origin
  • Every external/public function is supposed to be externally accessible
  • Every external/public function has the correct authentication

Ethereum

🟣 Ethereum is only accepted for payable receives to receive WETH and for Zap contracts.

Cryptographic code

_ No crypto._

Gas problems

  • Contracts with for loops must have either:
    • A way to remove items
    • Can be upgraded to get unstuck
    • Size can only controlled by admins
  • Contracts with for loops must not allow end users to add unlimited items to a loop that is used by others or admins.

Black magic

  • Does not contain selfdestruct
  • Does not use delegatecall outside of proxying. If an implementation contract were to call delegatecall under attacker control, it could call selfdestruct the implementation contract, leading to calls through the proxy silently succeeding, even though they were failing.
  • Address.isContract should be treated as if could return anything at any time, because that's reality.

Overflow

  • Code is solidity version >= 0.8.0
  • All for loops use uint256

License

  • The contract uses the appropriate limited BUSL-1.1 (Business) or the open MIT license
  • If the contract license changes from MIT to BUSL-1.1 any contracts importing it need to also have their license set to BUSL-1.1

Proxy

  • No storage variable initialized at definition when contract used as a proxy implementation.

Events

  • All state changing functions emit events
    • AMM swaps do not, and we are okay with that.

Rounding and casts

  • Contract rounds in the protocols favor
  • Contract does not have bugs from loosing rounding precision
  • Code correctly multiplies before division
  • Contract does not have bugs from zero or near zero amounts
  • Safecast is aways used when casting

Dependencies

No dependency changes

External calls

  • Contract addresses passed in are validated
  • No unsafe external calls
  • 🟠✅ No slippage attacks (we need to validate expected tokens received) Fixed

🟣 Harvesting requires correct minimum amounts to be passed in in order to avoid sandwich attacks. This is okay since this is not user funds.

Deploy

  • Deployer permissions are removed after deploy

Strategy Specific

Strategy checks

  • Check balance cannot be manipulated up AND down by an attacker
    • As long as the 4626 lending market is well behaved.
    • The operator could reduce the active assets by the size of the minimum redeem amount. See discussion under attacks
  • No read only reentrancy on downstream protocols during checkBalance
    • A read-only reentrancy in a downstream 4626 could result in a catastrophic loss of user funds. We must ensure that 4626 contracts used as markets are not subject to read-only reentry.
  • All reward tokens are collected
  • The harvester can sell all reward tokens
  • No funds are left in the contract that should not be as a result of depositing or withdrawing
  • All funds can be recovered from the strategy by some combination of depositAll, withdraw, or withdrawAll()
  • WithdrawAll() cannot be MEV'd

Logic

Appears correct, outside of panda’s finding on extra withdraws

Attack

This new code holds our user’s funds. It also tracks how much funds we think we have. Bugs in this new could result in a total loss of user funds.

The harvester code could only result in a loss of some of our yield. Because these these stakes are much, much, much smaller. I'm going to spend less time examining them.

We want to ensure that our balances stay within a rounding error when moving funds into or out of the lending markets.

We need to make sure that any funds that go into a lending market can only come back to us.

The asset balance accounting should be correct and not subject to manipulation.

The intersection between withdrawals and balance checking should be correct.

What could the impacts of code failure in this code be.

What conditions could cause this code to fail if they were not true.

Does this code successfully block all attacks.

🟠✅ An operator can artificially reduce the supply of the token by making a small small amount of tokens fall under the minimum mint required when switching active strategies.

This minimum value is currently set to 1e7. Assuming a total supply of 1e22, this would give an attack a 1/1e15 privileged mint. Assuming an attacker was using a 100 million dollars, this would give the attacker a free $0.0000001 per loop. Current sonic gas prices are more than $0.01 for something basic. This is currently attack economically unfeasible.

This has been fixed.

Flavor

See comment on read only reentrancy check on silo.

naddison36 and others added 3 commits May 28, 2025 14:12
* Added allocateThreshold to AbstractARM

* Fixed allocate unit tests

* Prettier

* Fixed allocate fork tests

* Fixed allocate fork tests

---------

Co-authored-by: Daniel Von Fange <[email protected]>
* SonicHarvester changed so the Magpie swap is sent to the harvester before the recipient

* Fix setActiveMarket unit test

* Script to upgrade the SonicHarvester
* Renamed the upgrade script

* Changed claimRedeem to check if the activeMarket has been configured

* Added view functions convertToShares and convertToAssets to SiloMarket

* Added upgrade of SiloMarket to deploy script

* Fix handling of claiming Lido withdrawals if a withdrawal request is transferred to the LidoARM

* Ensure totalAssets returns at least MIN_TOTAL_SUPPLY
@naddison36
Copy link
Collaborator Author

@DanielVF looking at example deposit and allocate txs for read-only reentrancy, the calls are contained within the Silo contracts.
There are a lot of calls from the Varlamore S Vault as it loops over the different markets and calls the various Silo lending market contracts. But none of them call out to third party contracts. eg token contracts.
There are also no native S transfers in the Silo calls.

Deposit
49f8b659

Allocate
55574c2a

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants