Skip to content

Commit dc98433

Browse files
authored
Merge pull request #45 from onflow/nialexsan/non-public-pool
Nialexsan/non public pool
2 parents 31c0afa + daa8979 commit dc98433

24 files changed

Lines changed: 388 additions & 60 deletions

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
1+
.DS_Store
22
# flow
33
*.pkey
44
imports
55
coverage.lcov
6-
coverage.json
6+
coverage.json

DeFiActions

cadence/contracts/TidalProtocol.cdc

Lines changed: 17 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ access(all) contract TidalProtocol {
1818
/// The canonical PublicPath where the primary TidalProtocol Pool can be accessed publicly
1919
access(all) let PoolPublicPath: PublicPath
2020

21+
access(all) let PoolCapStoragePath: StoragePath
22+
2123
/* --- EVENTS ---- */
2224

2325
access(all) event Opened(pid: UInt64, poolUUID: UInt64)
@@ -31,6 +33,9 @@ access(all) contract TidalProtocol {
3133
access(all) entitlement EGovernance
3234
access(all) entitlement EImplementation
3335

36+
access(all) entitlement EParticipant
37+
38+
3439
/// InternalBalance
3540
///
3641
/// A structure used internally to track a position's balance for a particular token
@@ -55,7 +60,7 @@ access(all) contract TidalProtocol {
5560
/// provided TokenState. It's assumed the TokenState and InternalBalance relate to the same token Type, but
5661
/// since neither struct have values defining the associated token, callers should be sure to make the arguments
5762
/// do in fact relate to the same token Type.
58-
access(all) fun recordDeposit(amount: UInt128, tokenState: auth(EImplementation) &TokenState) {
63+
access(contract) fun recordDeposit(amount: UInt128, tokenState: auth(EImplementation) &TokenState) {
5964
if self.direction == BalanceDirection.Credit {
6065
// Depositing into a credit position just increases the balance.
6166

@@ -216,7 +221,7 @@ access(all) contract TidalProtocol {
216221
/// deposits or the operation will revert.
217222
access(EImplementation) fun setDrawDownSink(_ sink: {DeFiActions.Sink}?) {
218223
pre {
219-
sink?.getSinkType() ?? Type<@MOET.Vault>() == Type<@MOET.Vault>():
224+
(sink?.getSinkType() ?? Type<@MOET.Vault>()) == Type<@MOET.Vault>():
220225
"Invalid Sink provided - Sink \(sink.getType().identifier) must accept MOET"
221226
}
222227
self.drawDownSink = sink
@@ -743,7 +748,7 @@ access(all) contract TidalProtocol {
743748
access(all) fun getPositionDetails(pid: UInt64): PositionDetails {
744749
log(" [CONTRACT] getPositionDetails(pid: \(pid))")
745750
let position = self._borrowPosition(pid: pid)
746-
let balances: [PositionBalance] = []
751+
var balances: [PositionBalance] = []
747752

748753
for type in position.balances.keys {
749754
let balance = position.balances[type]!
@@ -1298,7 +1303,7 @@ access(all) contract TidalProtocol {
12981303
/// Creates a lending position against the provided collateral funds, depositing the loaned amount to the
12991304
/// given Sink. If a Source is provided, the position will be configured to pull loan repayment when the loan
13001305
/// becomes undercollateralized, preferring repayment to outright liquidation.
1301-
access(all) fun createPosition(
1306+
access(EParticipant) fun createPosition(
13021307
funds: @{FungibleToken.Vault},
13031308
issuanceSink: {DeFiActions.Sink},
13041309
repaymentSource: {DeFiActions.Source}?,
@@ -1333,7 +1338,7 @@ access(all) contract TidalProtocol {
13331338

13341339
/// Allows anyone to deposit funds into any position. If the provided Vault is not supported by the Pool, the
13351340
/// operation reverts.
1336-
access(all) fun depositToPosition(pid: UInt64, from: @{FungibleToken.Vault}) {
1341+
access(EParticipant) fun depositToPosition(pid: UInt64, from: @{FungibleToken.Vault}) {
13371342
self.depositAndPush(pid: pid, from: <-from, pushToDrawDownSink: false)
13381343
}
13391344

@@ -1845,9 +1850,9 @@ access(all) contract TidalProtocol {
18451850
/// The unique ID of the Position used to track deposits and withdrawals to the Pool
18461851
access(self) let id: UInt64
18471852
/// An authorized Capability to which the Position was opened
1848-
access(self) let pool: Capability<auth(EPosition) &Pool>
1853+
access(self) let pool: Capability<auth(EPosition, EParticipant) &Pool>
18491854

1850-
init(id: UInt64, pool: Capability<auth(EPosition) &Pool>) {
1855+
init(id: UInt64, pool: Capability<auth(EPosition, EParticipant) &Pool>) {
18511856
pre {
18521857
pool.check(): "Invalid Pool Capability provided - cannot construct Position"
18531858
}
@@ -1905,13 +1910,13 @@ access(all) contract TidalProtocol {
19051910
}
19061911
/// Deposits funds to the Position without pushing to the drawDownSink if the deposit puts the Position above
19071912
/// its maximum health
1908-
access(all) fun deposit(from: @{FungibleToken.Vault}) {
1913+
access(EParticipant) fun deposit(from: @{FungibleToken.Vault}) {
19091914
let pool = self.pool.borrow()!
19101915
pool.depositAndPush(pid: self.id, from: <-from, pushToDrawDownSink: false)
19111916
}
19121917
/// Deposits funds to the Position enabling the caller to configure whether excess value should be pushed to the
19131918
/// drawDownSink if the deposit puts the Position above its maximum health
1914-
access(all) fun depositAndPush(from: @{FungibleToken.Vault}, pushToDrawDownSink: Bool) {
1919+
access(EParticipant) fun depositAndPush(from: @{FungibleToken.Vault}, pushToDrawDownSink: Bool) {
19151920
let pool = self.pool.borrow()!
19161921
pool.depositAndPush(pid: self.id, from: <-from, pushToDrawDownSink: pushToDrawDownSink)
19171922
}
@@ -1973,7 +1978,7 @@ access(all) contract TidalProtocol {
19731978
/// Each position can have only one source, and the source must accept the default token type configured for the
19741979
/// pool. Providing a new source will replace the existing source. Pass nil to configure the position to not
19751980
/// pull tokens.
1976-
access(all) fun provideSource(source: {DeFiActions.Source}?) {
1981+
access(EParticipant) fun provideSource(source: {DeFiActions.Source}?) {
19771982
let pool = self.pool.borrow()!
19781983
pool.provideTopUpSource(pid: self.id, source: source)
19791984
}
@@ -2157,38 +2162,6 @@ access(all) contract TidalProtocol {
21572162
}
21582163

21592164
/* --- PUBLIC METHODS ---- */
2160-
2161-
/// Takes out a TidalProtocol loan with the provided collateral, returning a Position that can be used to manage
2162-
/// collateral and borrowed fund flows
2163-
///
2164-
/// @param collateral: The collateral used as the basis for a loan. Only certain collateral types are supported, so
2165-
/// callers should be sure to check the provided Vault is supported to prevent reversion.
2166-
/// @param issuanceSink: The DeFiActions Sink connector where the protocol will deposit borrowed funds. If the
2167-
/// position becomes overcollateralized, additional funds will be borrowed (to maintain target LTV) and
2168-
/// deposited to the provided Sink.
2169-
/// @param repaymentSource: An optional DeFiActions Source connector from which the protocol will attempt to source
2170-
/// borrowed funds in the event of undercollateralization prior to liquidating. If none is provided, the
2171-
/// position health will not be actively managed on the down side, meaning liquidation is possible as soon as
2172-
/// the loan becomes undercollateralized.
2173-
///
2174-
/// @return the Position via which the caller can manage their position
2175-
///
2176-
access(all) fun openPosition(
2177-
collateral: @{FungibleToken.Vault},
2178-
issuanceSink: {DeFiActions.Sink},
2179-
repaymentSource: {DeFiActions.Source}?,
2180-
pushToDrawDownSink: Bool
2181-
): Position {
2182-
let pid = self._borrowPool().createPosition(
2183-
funds: <-collateral,
2184-
issuanceSink: issuanceSink,
2185-
repaymentSource: repaymentSource,
2186-
pushToDrawDownSink: pushToDrawDownSink
2187-
)
2188-
let cap = self.account.capabilities.storage.issue<auth(EPosition) &Pool>(self.PoolStoragePath)
2189-
return Position(id: pid, pool: cap)
2190-
}
2191-
21922165
/// Returns a health value computed from the provided effective collateral and debt values where health is a ratio
21932166
/// of effective collateral over effective debt
21942167
access(all) view fun healthComputation(effectiveCollateral: UInt128, effectiveDebt: UInt128): UInt128 {
@@ -2214,7 +2187,7 @@ access(all) contract TidalProtocol {
22142187

22152188
/// Returns the compounded interest index reflecting the passage of time
22162189
/// The result is: newIndex = oldIndex * perSecondRate ^ seconds
2217-
access(all) fun compoundInterestIndex(oldIndex: UInt128, perSecondRate: UInt128, elapsedSeconds: UFix64): UInt128 {
2190+
access(all) view fun compoundInterestIndex(oldIndex: UInt128, perSecondRate: UInt128, elapsedSeconds: UFix64): UInt128 {
22182191
var result = oldIndex
22192192
var current = UInt128(perSecondRate)
22202193
var secondsCounter = UInt128(elapsedSeconds)
@@ -2258,12 +2231,6 @@ access(all) contract TidalProtocol {
22582231

22592232
/* --- INTERNAL METHODS --- */
22602233

2261-
/// Returns an authorized reference to the contract account's Pool resource
2262-
access(self) view fun _borrowPool(): auth(EPosition) &Pool {
2263-
return self.account.storage.borrow<auth(EPosition) &Pool>(from: self.PoolStoragePath)
2264-
?? panic("Could not borrow reference to internal TidalProtocol Pool resource")
2265-
}
2266-
22672234
/// Returns a reference to the contract account's MOET Minter resource
22682235
access(self) view fun _borrowMOETMinter(): &MOET.Minter {
22692236
return self.account.storage.borrow<&MOET.Minter>(from: MOET.AdminStoragePath)
@@ -2274,6 +2241,7 @@ access(all) contract TidalProtocol {
22742241
self.PoolStoragePath = StoragePath(identifier: "tidalProtocolPool_\(self.account.address)")!
22752242
self.PoolFactoryPath = StoragePath(identifier: "tidalProtocolPoolFactory_\(self.account.address)")!
22762243
self.PoolPublicPath = PublicPath(identifier: "tidalProtocolPool_\(self.account.address)")!
2244+
self.PoolCapStoragePath = StoragePath(identifier: "tidalProtocolPoolCap_\(self.account.address)")!
22772245

22782246
// save PoolFactory in storage
22792247
self.account.storage.save(
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import "DeFiActions"
2+
import "FungibleToken"
3+
import "MOET"
4+
5+
access(all) contract DummyConnectors {
6+
7+
// Minimal sink for tests that accepts MOET and does nothing.
8+
access(all) struct DummySink: DeFiActions.Sink {
9+
// Must match the interface’s access modifier
10+
access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
11+
12+
init() {
13+
self.uniqueID = nil
14+
}
15+
16+
// ---- DeFiActions.Sink API ----
17+
access(all) view fun getSinkType(): Type {
18+
return Type<@MOET.Vault>()
19+
}
20+
21+
access(all) fun minimumCapacity(): UFix64 {
22+
return UFix64.max
23+
}
24+
25+
access(all) fun depositCapacity(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
26+
// no-op
27+
}
28+
29+
access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
30+
return DeFiActions.ComponentInfo(
31+
type: self.getType(),
32+
id: self.id(),
33+
innerComponents: []
34+
)
35+
}
36+
37+
// ---- Identity helpers (must be access(contract) to match interface) ----
38+
access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
39+
return self.uniqueID
40+
}
41+
42+
access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
43+
self.uniqueID = id
44+
}
45+
}
46+
}

cadence/contracts/mocks/MockTidalProtocolConsumer.cdc

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,22 @@ access(all) contract MockTidalProtocolConsumer {
2323
repaymentSource: {DeFiActions.Source}?,
2424
pushToDrawDownSink: Bool
2525
): @PositionWrapper {
26-
return <- create PositionWrapper(
27-
position: TidalProtocol.openPosition(
28-
collateral: <-collateral,
26+
let poolCap = self.account.storage.load<Capability<auth(TidalProtocol.EParticipant, TidalProtocol.EPosition) &TidalProtocol.Pool>>(
27+
from: TidalProtocol.PoolCapStoragePath
28+
) ?? panic("Missing pool capability")
29+
30+
let poolRef = poolCap.borrow() ?? panic("Invalid Pool Cap")
31+
32+
let pid = poolRef.createPosition(
33+
funds: <-collateral,
2934
issuanceSink: issuanceSink,
3035
repaymentSource: repaymentSource,
3136
pushToDrawDownSink: pushToDrawDownSink
3237
)
38+
let position = TidalProtocol.Position(id: pid, pool: poolCap)
39+
self.account.storage.save(poolCap, to: TidalProtocol.PoolCapStoragePath)
40+
return <- create PositionWrapper(
41+
position: position
3342
)
3443
}
3544

cadence/tests/auto_borrow_behavior_test.cdc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,18 @@ import "TidalProtocol"
66
import "test_helpers.cdc"
77

88
access(all) let protocolAccount = Test.getAccount(0x0000000000000007)
9+
access(all) let protocolConsumerAccount = Test.getAccount(0x0000000000000008)
910
access(all) let flowTokenIdentifier = "A.0000000000000003.FlowToken.Vault"
1011
access(all) let moetTokenIdentifier = "A.0000000000000007.MOET.Vault"
1112
access(all) let flowVaultStoragePath = /storage/flowTokenVault
1213

1314
access(all)
1415
fun setup() {
1516
deployContracts()
17+
18+
let betaTxResult = grantBeta(protocolAccount, protocolConsumerAccount)
19+
20+
Test.expect(betaTxResult, Test.beSucceeded())
1621
}
1722

1823
access(all)
@@ -134,4 +139,4 @@ fun testNoAutoBorrowWhenPushToDrawDownSinkFalse() {
134139
let moetVaultBalanceAfter = getBalance(address: user.address, vaultPublicPath: MOET.VaultPublicPath) ?? 0.0
135140
Test.assert(moetVaultBalanceAfter == moetVaultBalanceBefore,
136141
message: "User's MOET Vault balance should remain unchanged when no borrow occurs")
137-
}
142+
}

cadence/tests/cap_test.cdc

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import Test
2+
import BlockchainHelpers
3+
4+
import "MOET"
5+
import "test_helpers.cdc"
6+
7+
// -----------------------------------------------------------------------------
8+
// Pool Creation Workflow Test
9+
// -----------------------------------------------------------------------------
10+
// Validates that a pool can be created and that essential invariants hold.
11+
// -----------------------------------------------------------------------------
12+
13+
access(all) let protocolAccount = Test.getAccount(0x0000000000000007)
14+
access(all) let testerAccount = Test.getAccount(0x0000000000000008)
15+
access(all) var snapshot: UInt64 = 0
16+
17+
// -----------------------------------------------------------------------------
18+
// SETUP
19+
// -----------------------------------------------------------------------------
20+
access(all)
21+
fun setup() {
22+
deployContracts()
23+
24+
createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: defaultTokenIdentifier, beFailed: false)
25+
26+
let exists = poolExists(address: protocolAccount.address)
27+
Test.assert(exists)
28+
29+
// Reserve balance should be zero for default token
30+
let reserveBal = getReserveBalance(vaultIdentifier: defaultTokenIdentifier)
31+
Test.assertEqual(0.0, reserveBal)
32+
33+
snapshot = getCurrentBlockHeight()
34+
}
35+
36+
// -----------------------------------------------------------------------------
37+
// TEST CASES
38+
// -----------------------------------------------------------------------------
39+
40+
access(all)
41+
fun testPositionCreationFail() {
42+
43+
let txResult = _executeTransaction(
44+
"../tests/transactions/tidal-protocol/pool-management/01_negative_no_eparticipant_fail.cdc",
45+
[],
46+
protocolAccount
47+
)
48+
Test.expect(txResult, Test.beFailed())
49+
}
50+
51+
access(all)
52+
fun testPositionCreationSuccess() {
53+
Test.reset(to: snapshot)
54+
55+
let txResult = _executeTransaction(
56+
"../tests/transactions/tidal-protocol/pool-management/02_positive_with_eparticipant_pass.cdc",
57+
[],
58+
protocolAccount
59+
)
60+
61+
Test.expect(txResult, Test.beSucceeded())
62+
}
63+
64+
access(all)
65+
fun testNegativeCap() {
66+
Test.reset(to: snapshot)
67+
68+
let negativeResult = _executeTransaction("../tests/transactions/tidal-protocol/pool-management/05_negative_cap.cdc", [], testerAccount)
69+
Test.expect(negativeResult, Test.beFailed())
70+
}
71+
72+
access(all)
73+
fun testPublishClaimCap() {
74+
Test.reset(to: snapshot)
75+
76+
let publishCapResult = _executeTransaction("../transactions/tidal-protocol/beta/publish_beta_cap.cdc", [protocolAccount.address], protocolAccount)
77+
Test.expect(publishCapResult, Test.beSucceeded())
78+
79+
let claimCapResult = _executeTransaction("../transactions/tidal-protocol/beta/claim_and_save_beta_cap.cdc", [protocolAccount.address], protocolAccount)
80+
Test.expect(claimCapResult, Test.beSucceeded())
81+
82+
let createPositionResult = _executeTransaction("../tests/transactions/tidal-protocol/pool-management/04_create_position.cdc", [], protocolAccount)
83+
}

cadence/tests/funds_available_above_target_health_test.cdc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import "MOET"
77
import "TidalProtocol"
88

99
access(all) let protocolAccount = Test.getAccount(0x0000000000000007)
10+
access(all) let protocolConsumerAccount = Test.getAccount(0x0000000000000008)
1011
access(all) let userAccount = Test.createAccount()
1112

1213
access(all) let flowTokenIdentifier = "A.0000000000000003.FlowToken.Vault"
@@ -53,6 +54,10 @@ fun setup() {
5354

5455
deployContracts()
5556

57+
let betaTxResult = grantBeta(protocolAccount, protocolConsumerAccount)
58+
59+
Test.expect(betaTxResult, Test.beSucceeded())
60+
5661
// price setup
5762
setMockOraclePrice(signer: protocolAccount, forTokenIdentifier: flowTokenIdentifier, price: flowStartPrice)
5863

0 commit comments

Comments
 (0)