Skip to content

Commit 16af405

Browse files
authored
Merge pull request #55 from onflow/nialexsan/non-public-pool
Nialexsan/non public pool
2 parents c8fc555 + d4b9bc4 commit 16af405

26 files changed

Lines changed: 471 additions & 39 deletions

.github/workflows/e2e_tests.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: E2E test
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
11+
jobs:
12+
e2e-tests:
13+
name: Tidal Yield E2E Test
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
with:
18+
token: ${{ secrets.GH_PAT }}
19+
submodules: recursive
20+
- name: Set up Go
21+
uses: actions/setup-go@v3
22+
with:
23+
go-version: "1.23.x"
24+
- uses: actions/cache@v4
25+
with:
26+
path: ~/go/pkg/mod
27+
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
28+
restore-keys: |
29+
${{ runner.os }}-go-
30+
- name: Install Flow CLI
31+
run: sh -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/master/install.sh)"
32+
- name: Flow CLI Version
33+
run: flow version
34+
- name: Update PATH
35+
run: echo "/root/.local/bin" >> $GITHUB_PATH
36+
- name: Install dependencies
37+
run: flow deps install --skip-alias --skip-deployments
38+
- name: Run Emulator
39+
run: ./local/run_emulator.sh
40+
- name: Setup Emulator
41+
run: ./local/setup_emulator.sh
42+
- name: Setup Wallets
43+
run: ./local/setup_wallets.sh
44+
- name: Run E2E test
45+
run: ./local/e2e_test.sh

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
1+
.DS_Store
22
# flow
33
*.pkey
44
!local/mock-incrementfi.pkey

cadence/contracts/TidalYield.cdc

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import "Burner"
44
import "ViewResolver"
55
// DeFiActions
66
import "DeFiActions"
7+
import "TidalYieldClosedBeta"
78

89
/// THIS CONTRACT IS A MOCK AND IS NOT INTENDED FOR USE IN PRODUCTION
910
/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@@ -318,7 +319,11 @@ access(all) contract TidalYield {
318319
return self.tides.length
319320
}
320321
/// Creates a new Tide executing the specified Strategy with the provided funds
321-
access(all) fun createTide(strategyType: Type, withVault: @{FungibleToken.Vault}) {
322+
access(all) fun createTide(betaRef: auth(TidalYieldClosedBeta.Beta) &TidalYieldClosedBeta.BetaBadge, strategyType: Type, withVault: @{FungibleToken.Vault}) {
323+
pre {
324+
TidalYieldClosedBeta.validateBeta(self.owner?.address!, betaRef):
325+
"Invalid Beta Ref"
326+
}
322327
let balance = withVault.balance
323328
let type = withVault.getType()
324329
let tide <-create Tide(strategyType: strategyType, withVault: <-withVault)
@@ -332,35 +337,51 @@ access(all) contract TidalYield {
332337
creator: self.owner?.address
333338
)
334339

335-
self.addTide(<-tide)
340+
self.addTide(betaRef: betaRef, <-tide)
336341
}
337342
/// Adds an open Tide to this TideManager resource. This effectively transfers ownership of the newly added
338343
/// Tide to the owner of this TideManager
339-
access(all) fun addTide(_ tide: @Tide) {
344+
access(all) fun addTide(betaRef: auth(TidalYieldClosedBeta.Beta) &TidalYieldClosedBeta.BetaBadge, _ tide: @Tide) {
340345
pre {
341346
self.tides[tide.uniqueID.id] == nil:
342347
"Collision with Tide ID \(tide.uniqueID.id) - a Tide with this ID already exists"
348+
349+
TidalYieldClosedBeta.validateBeta(self.owner?.address!, betaRef):
350+
"Invalid Beta Ref"
343351
}
344352
emit AddedToManager(id: tide.uniqueID.id, owner: self.owner?.address, managerUUID: self.uuid, tokenType: tide.getType().identifier)
345353
self.tides[tide.uniqueID.id] <-! tide
346354
}
347355
/// Deposits additional funds to the specified Tide, reverting if none exists with the provided ID
348-
access(all) fun depositToTide(_ id: UInt64, from: @{FungibleToken.Vault}) {
356+
access(all) fun depositToTide(betaRef: auth(TidalYieldClosedBeta.Beta) &TidalYieldClosedBeta.BetaBadge, _ id: UInt64, from: @{FungibleToken.Vault}) {
349357
pre {
350358
self.tides[id] != nil:
351359
"No Tide with ID \(id) found"
360+
361+
TidalYieldClosedBeta.validateBeta(self.owner?.address!, betaRef):
362+
"Invalid Beta Ref"
352363
}
353364
let tide = (&self.tides[id] as &Tide?)!
354365
tide.deposit(from: <-from)
355366
}
356-
/// Withdraws the specified Tide, reverting if none exists with the provided ID
357-
access(FungibleToken.Withdraw) fun withdrawTide(id: UInt64): @Tide {
367+
access(self) fun _withdrawTide(id: UInt64): @Tide {
358368
pre {
359369
self.tides[id] != nil:
360370
"No Tide with ID \(id) found"
361371
}
362372
return <- self.tides.remove(key: id)!
363373
}
374+
/// Withdraws the specified Tide, reverting if none exists with the provided ID
375+
access(FungibleToken.Withdraw) fun withdrawTide(betaRef: auth(TidalYieldClosedBeta.Beta) &TidalYieldClosedBeta.BetaBadge, id: UInt64): @Tide {
376+
pre {
377+
self.tides[id] != nil:
378+
"No Tide with ID \(id) found"
379+
380+
TidalYieldClosedBeta.validateBeta(self.owner?.address!, betaRef):
381+
"Invalid Beta Ref"
382+
}
383+
return <- self._withdrawTide(id: id)!
384+
}
364385
/// Withdraws funds from the specified Tide in the given amount. The resulting Vault Type will be whatever
365386
/// denomination is supported by the Tide, so callers should examine the Tide to know the resulting Vault to
366387
/// expect
@@ -379,7 +400,7 @@ access(all) contract TidalYield {
379400
self.tides[id] != nil:
380401
"No Tide with ID \(id) found"
381402
}
382-
let tide <- self.withdrawTide(id: id)
403+
let tide <- self._withdrawTide(id: id)
383404
let res <- tide.withdraw(amount: tide.getTideBalance())
384405
Burner.burn(<-tide)
385406
return <-res
@@ -406,7 +427,7 @@ access(all) contract TidalYield {
406427
return <- self._borrowFactory().createStrategy(type, uniqueID: uniqueID, withFunds: <-withFunds)
407428
}
408429
/// Creates a TideManager used to create and manage Tides
409-
access(all) fun createTideManager(): @TideManager {
430+
access(all) fun createTideManager(betaRef: auth(TidalYieldClosedBeta.Beta) &TidalYieldClosedBeta.BetaBadge): @TideManager {
410431
return <-create TideManager()
411432
}
412433
/// Creates a StrategyFactory resource
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
access(all) contract TidalYieldClosedBeta {
2+
3+
access(all) entitlement Admin
4+
access(all) entitlement Beta
5+
6+
access(all) resource BetaBadge {
7+
access(all) let assignedTo: Address
8+
init(_ addr: Address) {
9+
self.assignedTo = addr
10+
}
11+
access(all) view fun getOwner(): Address {
12+
return self.assignedTo
13+
}
14+
}
15+
16+
// --- Paths ---
17+
access(all) let UserBetaCapStoragePath: StoragePath
18+
access(all) let AdminHandleStoragePath: StoragePath
19+
20+
// --- Registry: which capability was issued to which address, and revocation flags ---
21+
access(all) struct AccessInfo {
22+
access(all) let capID: UInt64
23+
access(all) let isRevoked: Bool
24+
25+
init(_ capID: UInt64, _ isRevoked: Bool) {
26+
self.capID = capID
27+
self.isRevoked = isRevoked
28+
}
29+
}
30+
access(all) var issuedCapIDs: {Address: AccessInfo}
31+
32+
// --- Events ---
33+
access(all) event BetaGranted(addr: Address, capID: UInt64)
34+
access(all) event BetaRevoked(addr: Address, capID: UInt64?)
35+
36+
/// Per-user badge storage path (under the *contract/deployer* account)
37+
access(contract) fun _badgePath(_ addr: Address): StoragePath {
38+
return StoragePath(identifier: "TY_BetaBadge_".concat(addr.toString()))!
39+
}
40+
41+
/// Ensure the admin-owned badge exists for the user
42+
access(contract) fun _ensureBadge(_ addr: Address) {
43+
let p = self._badgePath(addr)
44+
if self.account.storage.type(at: p) == nil {
45+
self.account.storage.save(<-create BetaBadge(addr), to: p)
46+
}
47+
}
48+
49+
access(contract) fun _destroyBadge(_ addr: Address) {
50+
let p = self._badgePath(addr)
51+
if let badge <- self.account.storage.load<@BetaBadge>(from: p) {
52+
destroy badge
53+
}
54+
}
55+
56+
/// Issue a capability from the contract/deployer account and record its ID
57+
access(contract) fun _issueBadgeCap(_ addr: Address): Capability<auth(Beta) &BetaBadge> {
58+
let p = self._badgePath(addr)
59+
let cap: Capability<auth(Beta) &BetaBadge> =
60+
self.account.capabilities.storage.issue<auth(Beta) &BetaBadge>(p)
61+
62+
self.issuedCapIDs[addr] = AccessInfo(cap.id, false)
63+
64+
if let ctrl = self.account.capabilities.storage.getController(byCapabilityID: cap.id) {
65+
ctrl.setTag("tidalyield-beta")
66+
}
67+
68+
emit BetaGranted(addr: addr, capID: cap.id)
69+
return cap
70+
}
71+
72+
/// Delete the recorded controller, revoking *all copies* of the capability
73+
access(contract) fun _revokeByAddress(_ addr: Address) {
74+
let info = self.issuedCapIDs[addr] ?? panic("No cap recorded for address")
75+
let ctrl = self.account.capabilities.storage.getController(byCapabilityID: info.capID)
76+
?? panic("Missing controller for recorded cap ID")
77+
ctrl.delete()
78+
self.issuedCapIDs[addr] = AccessInfo(info.capID, true)
79+
self._destroyBadge(addr)
80+
emit BetaRevoked(addr: addr, capID: info.capID)
81+
}
82+
83+
// 2) A small in-account helper resource that performs privileged ops
84+
access(all) resource AdminHandle {
85+
access(Admin) fun grantBeta(addr: Address): Capability<auth(TidalYieldClosedBeta.Beta) &TidalYieldClosedBeta.BetaBadge> {
86+
TidalYieldClosedBeta._ensureBadge(addr)
87+
return TidalYieldClosedBeta._issueBadgeCap(addr)
88+
}
89+
90+
access(Admin) fun revokeByAddress(addr: Address) {
91+
TidalYieldClosedBeta._revokeByAddress(addr)
92+
}
93+
}
94+
95+
/// Read-only check used by any gated entrypoint
96+
access(all) view fun getBetaCapID(_ addr: Address): UInt64? {
97+
if let info = self.issuedCapIDs[addr] {
98+
if info.isRevoked {
99+
assert(info.isRevoked, message: "Beta access revoked")
100+
return nil
101+
}
102+
return info.capID
103+
}
104+
return nil
105+
}
106+
107+
access(all) view fun validateBeta(_ addr: Address?, _ betaRef: auth(Beta) &BetaBadge): Bool {
108+
if (addr == nil) {
109+
assert(addr == nil, message: "Address is required for Beta verification")
110+
return false
111+
}
112+
let recordedID: UInt64? = self.getBetaCapID(addr!);
113+
if recordedID == nil {
114+
assert(recordedID == nil, message: "No Beta access")
115+
return false
116+
}
117+
118+
if betaRef.getOwner() != addr {
119+
assert(betaRef.getOwner() != addr, message: "BetaBadge may only be used by its assigned owner")
120+
return false
121+
}
122+
123+
return true
124+
}
125+
126+
init() {
127+
self.AdminHandleStoragePath = StoragePath(
128+
identifier: "TidalYieldClosedBetaAdmin_\(self.account.address)"
129+
)!
130+
self.UserBetaCapStoragePath = StoragePath(
131+
identifier: "TidalYieldUserBetaCap_\(self.account.address)"
132+
)!
133+
134+
self.issuedCapIDs = {}
135+
136+
// Create and store the admin handle in *this* (deployer) account
137+
self.account.storage.save(<-create AdminHandle(), to: self.AdminHandleStoragePath)
138+
}
139+
}

cadence/contracts/TidalYieldStrategies.cdc

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import "SwapConnectors"
88
// Lending protocol
99
import "TidalProtocol"
1010
// TidalYield platform
11+
import "TidalYieldClosedBeta"
1112
import "TidalYield"
1213
import "TidalYieldAutoBalancers"
1314
// tokens
@@ -167,12 +168,21 @@ access(all) contract TidalYieldStrategies {
167168
let abaSwapSource = SwapConnectors.SwapSource(swapper: yieldToMoetSwapper, source: abaSource, uniqueID: uniqueID)
168169

169170
// open a TidalProtocol position
170-
let position = TidalProtocol.openPosition(
171-
collateral: <-withFunds,
171+
let poolCap = TidalYieldStrategies.account.storage.load<Capability<auth(TidalProtocol.EParticipant, TidalProtocol.EPosition) &TidalProtocol.Pool>>(
172+
from: TidalProtocol.PoolCapStoragePath
173+
) ?? panic("Missing pool capability")
174+
175+
let poolRef = poolCap.borrow() ?? panic("Invalid Pool Cap")
176+
177+
let pid = poolRef.createPosition(
178+
funds: <-withFunds,
172179
issuanceSink: abaSwapSink,
173180
repaymentSource: abaSwapSource,
174181
pushToDrawDownSink: true
175182
)
183+
let position = TidalProtocol.Position(id: pid, pool: poolCap)
184+
TidalYieldStrategies.account.storage.save(poolCap, to: TidalProtocol.PoolCapStoragePath)
185+
176186
// get Sink & Source connectors relating to the new Position
177187
let positionSink = position.createSinkWithOptions(type: collateralType, pushToDrawDownSink: true)
178188
let positionSource = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true) // TODO: may need to be false
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import "TidalYieldClosedBeta"
2+
3+
access(all) fun main(addr: Address): Bool {
4+
let acct = getAuthAccount<auth(Storage) &Account>(addr)
5+
let betaCapID = TidalYieldClosedBeta.getBetaCapID(addr)
6+
let existingCap = acct.storage.borrow<&Capability<auth(TidalYieldClosedBeta.Beta) &TidalYieldClosedBeta.BetaBadge>>(
7+
from: TidalYieldClosedBeta.UserBetaCapStoragePath
8+
)
9+
return betaCapID != nil && existingCap?.id == betaCapID
10+
}

cadence/tests/rebalance_scenario1_test.cdc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ fun setup() {
5656
// open wrapped position (pushToDrawDownSink)
5757
// the equivalent of depositing reserves
5858
let openRes = executeTransaction(
59-
"../transactions/mocks/position/create_wrapped_position.cdc",
59+
"../../lib/TidalProtocol/cadence/tests/transactions/mock-tidal-protocol-consumer/create_wrapped_position.cdc",
6060
[reserveAmount/2.0, /storage/flowTokenVault, true],
6161
protocolAccount
6262
)
@@ -101,6 +101,7 @@ fun test_RebalanceTideScenario1() {
101101
// Likely 0.0
102102
let flowBalanceBefore = getBalance(address: user.address, vaultPublicPath: /public/flowTokenReceiver)!
103103
mintFlow(to: user, amount: fundingAmount)
104+
grantBeta(tidalYieldAccount, user)
104105

105106
createTide(
106107
signer: user,

cadence/tests/rebalance_scenario2_test.cdc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ fun setup() {
142142
// open wrapped position (pushToDrawDownSink)
143143
// the equivalent of depositing reserves
144144
let openRes = executeTransaction(
145-
"../transactions/mocks/position/create_wrapped_position.cdc",
145+
"../../lib/TidalProtocol/cadence/tests/transactions/mock-tidal-protocol-consumer/create_wrapped_position.cdc",
146146
[reserveAmount/2.0, /storage/flowTokenVault, true],
147147
protocolAccount
148148
)
@@ -182,6 +182,7 @@ fun test_RebalanceTideScenario2() {
182182
// Likely 0.0
183183
let flowBalanceBefore = getBalance(address: user.address, vaultPublicPath: /public/flowTokenReceiver)!
184184
mintFlow(to: user, amount: fundingAmount)
185+
grantBeta(tidalYieldAccount, user)
185186

186187
createTide(
187188
signer: user,

cadence/tests/rebalance_scenario3a_test.cdc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ fun setup() {
8585
// open wrapped position (pushToDrawDownSink)
8686
// the equivalent of depositing reserves
8787
let openRes = executeTransaction(
88-
"../transactions/mocks/position/create_wrapped_position.cdc",
88+
"../../lib/TidalProtocol/cadence/tests/transactions/mock-tidal-protocol-consumer/create_wrapped_position.cdc",
8989
[reserveAmount/2.0, /storage/flowTokenVault, true],
9090
protocolAccount
9191
)
@@ -122,6 +122,7 @@ fun test_RebalanceTideScenario3A() {
122122
let flowBalanceBefore = getBalance(address: user.address, vaultPublicPath: /public/flowTokenReceiver)!
123123
log("[TEST] flow balance before \(flowBalanceBefore)")
124124
mintFlow(to: user, amount: fundingAmount)
125+
grantBeta(tidalYieldAccount, user)
125126

126127
createTide(
127128
signer: user,

cadence/tests/rebalance_scenario3b_test.cdc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ fun setup() {
8585
// open wrapped position (pushToDrawDownSink)
8686
// the equivalent of depositing reserves
8787
let openRes = executeTransaction(
88-
"../transactions/mocks/position/create_wrapped_position.cdc",
88+
"../../lib/TidalProtocol/cadence/tests/transactions/mock-tidal-protocol-consumer/create_wrapped_position.cdc",
8989
[reserveAmount/2.0, /storage/flowTokenVault, true],
9090
protocolAccount
9191
)
@@ -122,6 +122,7 @@ fun test_RebalanceTideScenario3B() {
122122
let flowBalanceBefore = getBalance(address: user.address, vaultPublicPath: /public/flowTokenReceiver)!
123123
log("[TEST] flow balance before \(flowBalanceBefore)")
124124
mintFlow(to: user, amount: fundingAmount)
125+
grantBeta(tidalYieldAccount, user)
125126

126127
createTide(
127128
signer: user,

0 commit comments

Comments
 (0)