Skip to content

Add Strategy composition & AutoBalancer management contracts#7

Merged
sisyphusSmiling merged 85 commits into
mainfrom
gio/add-strategy-factory
Jun 20, 2025
Merged

Add Strategy composition & AutoBalancer management contracts#7
sisyphusSmiling merged 85 commits into
mainfrom
gio/add-strategy-factory

Conversation

@sisyphusSmiling

@sisyphusSmiling sisyphusSmiling commented Jun 10, 2025

Copy link
Copy Markdown
Contributor

Description

  • Refactor the TidalYield (formerly Tidal) contract for use with generic strategies
  • Add Strategy interface along with patterns for enabling and constructing DeFi strategies
    • A StrategyComposerIssuer grants one the ability to build Strategy by way of a StrategyComposer
    • A StrategyComposer builds a supported Strategy Type
    • A Strategy acts as the top-level to DeFiBlocks stacks, allowing for the flow of funds in and out of the stack infrastructure
    • A StrategyFactory manages enabled Strategies, enabling the TidalYield contract to add & remove supported Strategies though inner StrategyComposers
  • Add a contract repository for TidalYield-defined Strategy implementations
  • Add the TidalYieldAutoBalancers contract to manage AutoBalancer storage, configuration, and cleanup once Tides are closed
  • Add initial happy path behavioral tests for the MVP tracer strategy

@sisyphusSmiling

Copy link
Copy Markdown
Contributor Author

@joshuahannan good call. I've reverted back to Tidal contract name.

@sisyphusSmiling

Copy link
Copy Markdown
Contributor Author

@nialexsan looks like CI is failing due to missing token in the repo secrets. I think you added one to TidalProtocol repo, could you add one to this repo as well please?

@joshuahannan joshuahannan left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Maybe all the strategy stuff and the Tide stuff should be in separate contracts? All the different layers of strategy composer, factory, issuer, etc is kind of confusing and they are still somewhat separate from the Tide stuff, right? Up to you

Comment thread cadence/contracts/Tidal.cdc
Comment thread cadence/contracts/Tidal.cdc
Comment thread cadence/contracts/Tidal.cdc
Comment thread cadence/contracts/Tidal.cdc
///
access(all) resource StrategyFactory {
/// A mapping of StrategyComposers indexed on the related Strategies they can compose
access(self) let composers: @{Type: {StrategyComposer}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

So is this implying that a given strategy can only be created by one type of Strategy Composer? What if someone wants to have multiple strategy composers that share a strategy type?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The short answer is that yes, it's implied that a Strategy is composed by a single Composer. The same Strategy could be created by another Composer, but it would require that the new Composer replace the old one. Ultimately, the composition of a Strategy is a Strategy conceptually and if there's any meaningful difference in how it's composed (in that funds flow differently), then the two should be different Strategy definitions.

This question points to something I think might be a bit of a code smell with this design - that two distinct Strategy instances don't differ much in fields or functionality and that their composition is identified by the Type.

The distinct Type is instead used to distinguish how those instances are composed as that's the real value of a conceptual strategy. That's largely the reason I restrict the initial TracerStrategy creation through the TracerStrategyComposer and further through the TracerStrategyComposerIssuer.

The generalized constraints here are that we need to build something (a Strategy) with a common interface such that two different instances can be captured and used from within a Tide. Two instances of that thing may largely look similar in fields and functions, but how they are constructed (i.e. the sequencing of DeFiBlocks connectors during the Strategy ultimately provides top-level access to) can vary greatly. So how do we:

  1. Convey meaning around how these are constructed as that's ultimately the differentiator
  2. Protect construction of these things since some may utilize resources (e.g. account storage)
  3. Maintain common interfaces such that they can be captured and used in a Tide
  4. Minimize the amount of configurations needed to initialize a Strategy given the design space between them

My thinking then is that we put that meaning in the Type definition. Then as more strategies are added to the platform their composition logic can be defined in a StrategyComposer and a request for the composition be identified by a new Strategy Type.

Do you have any feedback on that approach?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think we need to try to simplify this somehow. I think there are too many layers and it is getting way too hard to keep everything straight. I'm really worried that actually managing something like this is going to be prohibitively complicated for any given wallet or app. We have strategy, strategy composer, strategy composer issuer, strategy factory, and more and that is just this contract alone. it doesn't even include all the other defi blocks components and tide protocol stuff. I am really struggling to understand everything and see why all these are necessary.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Strategy composer, strategy composer issuer, and strategy factory all sound like they could be simplified to one construct. If I am having such a hard time with this, I can't imagine how any external developers or wallets are going to be willing to deal with this kind of complexity unless they are making a ton of money off it

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

While i do agree on the sentiment, i think that's likely more of a closed beta/productionizing task.

I've generated an Issue for it here: #9

@sisyphusSmiling

sisyphusSmiling commented Jun 16, 2025

Copy link
Copy Markdown
Contributor Author

Maybe all the strategy stuff and the Tide stuff should be in separate contracts?

I agree @joshuahannan, I think that makes things cleaner - maybe in a contract like TidalYieldStrategyInterfaces or similar.

I'll follow up with another PR to include those changes as I don't want to introduce minor changes that would for a redeployment while @nialexsan is setting up the initial MVP.

@sisyphusSmiling
sisyphusSmiling force-pushed the gio/add-strategy-factory branch from 549cf50 to 051302c Compare June 17, 2025 18:05

@joshuahannan joshuahannan left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'm still not satisfied with the structure of the code, but I'll approve this for now since you need it for the prototype and we can revisit my comments later

}

/// Returns an authorized reference on the AutoBalancer with the associated UniqueIdentifier.id. If none is found,
/// the operation reverts.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Should it just return nil instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It could, though it would revert where called since it's required for write operations

)
// get Sink & Source connectors relating to the new Position
let positionSink = position.createSinkWithOptions(type: collateralType, pushToDrawDownSink: true)
let positionSource = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true) // TODO: may need to be false

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why would it be false?

nialexsan and others added 4 commits June 19, 2025 13:26
…ral (#11)

* Make adjustments to allow closing the tide and retain the full collateral

* Add reserves to tests to allow closing tide after yield value increase
@sisyphusSmiling

Copy link
Copy Markdown
Contributor Author

Merging to main with the expectation we refactor after Tracer retrospective.

@sisyphusSmiling
sisyphusSmiling merged commit 9900beb into main Jun 20, 2025
1 check passed
@nialexsan
nialexsan deleted the gio/add-strategy-factory branch June 30, 2025 20:16
@nialexsan
nialexsan restored the gio/add-strategy-factory branch June 30, 2025 20:25
@nialexsan
nialexsan deleted the gio/add-strategy-factory branch October 10, 2025 18:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants