Skip to content

Commit 6fd8c4e

Browse files
committed
Merge branch 'main' into moet-redemption
Resolved conflicts by keeping moet-redemption versions: - .gitignore: Keep our version - test_helpers.cdc: Keep our version with redemption test functions - flow.json: Auto-merged successfully
2 parents 562db39 + ea94fb9 commit 6fd8c4e

File tree

12 files changed

+499
-130
lines changed

12 files changed

+499
-130
lines changed

.dockerignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
db
2+
cache
3+
broadcast

Dockerfile

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,87 @@ FROM debian:stable-slim
22

33
ENV FLOW_INSTALL_URL=https://raw.githubusercontent.com/onflow/flow-cli/master/install.sh \
44
APP_HOME=/app \
5-
SEED_DIR=/seed/state
5+
SEED_DIR=/seed/state \
6+
FOUNDRY_DIR=/root/.foundry
67

8+
# Base deps + build essentials for Foundry (Rust) toolchain
79
RUN apt-get update && apt-get install -y --no-install-recommends \
810
curl ca-certificates jq bash openssl netcat-openbsd git \
11+
build-essential pkg-config libssl-dev \
912
&& rm -rf /var/lib/apt/lists/*
1013

14+
# --- Install Foundry ---
15+
RUN bash -lc 'curl -L https://foundry.paradigm.xyz | bash' \
16+
&& bash -lc '"$FOUNDRY_DIR/bin/foundryup"' \
17+
&& ln -s "$FOUNDRY_DIR/bin/forge" /usr/local/bin/forge \
18+
&& ln -s "$FOUNDRY_DIR/bin/anvil" /usr/local/bin/anvil \
19+
&& ln -s "$FOUNDRY_DIR/bin/cast" /usr/local/bin/cast \
20+
&& ln -s "$FOUNDRY_DIR/bin/chisel" /usr/local/bin/chisel \
21+
&& forge --version && anvil --version && cast --version
22+
1123
# Install Flow CLI
1224
RUN bash -lc 'curl -fsSL "$FLOW_INSTALL_URL" | bash' \
1325
&& mv /root/.local/bin/flow /usr/local/bin/flow \
1426
&& chmod +x /usr/local/bin/flow \
1527
&& flow version
1628

1729
WORKDIR ${APP_HOME}
18-
# Bring in your project files (flow.json, contracts/, scripts/, transactions/, etc.)
1930
COPY . ${APP_HOME}
20-
RUN chmod +x ${APP_HOME}/scripts/*.sh || true
31+
RUN chmod +x ${APP_HOME}/local/*.sh || true
32+
33+
# Use bash as default shell for subsequent RUNs
34+
SHELL ["/bin/bash", "-lc"]
2135

2236
# ---------- PRE-SEED AT BUILD TIME ----------
23-
# Start emulator in background with --persist, wait, seed, then stop.
24-
RUN bash -lc '\
25-
set -euo pipefail; \
37+
RUN set -euo pipefail; \
2638
mkdir -p "$SEED_DIR"; \
2739
echo "▶ Start emulator (build-time) with --persist to ${SEED_DIR}"; \
28-
flow emulator start --verbose --persist "$SEED_DIR" > /tmp/emulator-build.log 2>&1 & \
40+
flow emulator start --verbose --contracts --persist "$SEED_DIR" > /tmp/emulator-build.log 2>&1 & \
2941
EM_PID=$!; \
42+
cleanup() { \
43+
echo "▶ Stop EVM Gateway"; \
44+
[[ -n "${GW_PID:-}" ]] && kill "$GW_PID" 2>/dev/null || true; \
45+
[[ -n "${GW_PID:-}" ]] && wait "$GW_PID" 2>/dev/null || true; \
46+
echo "▶ Stop emulator (build-time)"; \
47+
kill "$EM_PID" 2>/dev/null || true; \
48+
wait "$EM_PID" 2>/dev/null || true; \
49+
}; \
50+
trap cleanup EXIT; \
3051
echo -n "⏳ Waiting for emulator ... "; \
3152
for i in {1..60}; do nc -z 127.0.0.1 3569 && break || { echo -n "."; sleep 1; }; done; echo; \
3253
echo "▶ Seeding"; \
33-
# Your seed scripts can use `--network emulator` exactly like at runtime:
34-
[ -x ./local/setup_wallets.sh ] && ./local/setup_wallets.sh || true; \
35-
[ -x ./local/setup_emulator.sh ] && ./local/setup_emulator.sh || true; \
36-
echo "▶ Stop emulator (build-time)"; \
37-
kill $EM_PID && wait $EM_PID || true \
38-
'
54+
./local/setup_wallets.sh; \
55+
echo "▶ Start EVM Gateway"; \
56+
rm -rf db/; \
57+
flow evm gateway \
58+
--flow-network-id=emulator \
59+
--evm-network-id=preview \
60+
--coinbase=FACF71692421039876a5BB4F10EF7A439D8ef61E \
61+
--coa-address=e03daebed8ca0615 \
62+
--coa-key=7549ce91aa82b6b42b060df5ab60d1246ae61e83177b5adb81c697e41d9e587a \
63+
--gas-price=0 \
64+
--rpc-port 8545 \
65+
> /tmp/evm-gateway-build.log 2>&1 & \
66+
GW_PID=$!; \
67+
echo -n "⏳ Waiting for EVM Gateway (8545) ... "; \
68+
for i in {1..120}; do \
69+
if nc -z 127.0.0.1 8545; then echo " ready."; break; fi; \
70+
# bail out if the gateway died and show logs
71+
if ! kill -0 "$GW_PID" 2>/dev/null; then \
72+
echo; echo "❌ Gateway exited early. Last 200 lines:"; \
73+
tail -n 200 /tmp/evm-gateway-build.log || true; \
74+
exit 1; \
75+
fi; \
76+
echo -n "."; sleep 1; \
77+
done; echo; \
78+
./local/punchswap/setup_punchswap.sh; \
79+
./local/punchswap/e2e_punchswap.sh; \
80+
./local/setup_emulator.sh; \
81+
./local/setup_bridged_tokens.sh; \
82+
echo "✅ Build-time seeding complete."
3983

4084
# ---------- RUNTIME ----------
41-
EXPOSE 3569 8080
42-
ENV FLOW_EMULATOR_FLAGS="--verbose --persist /seed/state"
85+
EXPOSE 3569 8080 8545
4386

44-
# At runtime we just start the emulator that already contains the baked state.
87+
ENV FLOW_EMULATOR_FLAGS="--verbose --contracts --persist /seed/state"
4588
ENTRYPOINT [ "bash", "-lc", "flow emulator start $FLOW_EMULATOR_FLAGS" ]

cadence/contracts/FlowVaultsStrategies.cdc

Lines changed: 217 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ import "FlowVaultsAutoBalancers"
1515
// tokens
1616
import "YieldToken"
1717
import "MOET"
18-
// amm integration - TODO: Uncomment when bridge integration is ready
19-
// import "UniswapV3SwapConnectors"
20-
// vm bridge - TODO: Uncomment when bridge integration is ready
21-
// import "FlowEVMBridgeConfig"
22-
// import "FlowEVMBridgeUtils"
23-
// import "FlowEVMBridge"
18+
// amm integration
19+
import "UniswapV3SwapConnectors"
20+
// vm bridge
21+
import "FlowEVMBridgeConfig"
22+
import "FlowEVMBridgeUtils"
23+
import "FlowEVMBridge"
2424
// mocks
2525
import "MockOracle"
2626
import "MockSwapper"
@@ -168,36 +168,211 @@ access(all) contract FlowVaultsStrategies {
168168
outVault: yieldTokenType,
169169
uniqueID: uniqueID
170170
)
171-
// TODO: Update to use UniswapV3SwapConnectors when bridge integration is ready
172-
// let stableToYieldSwapper = UniswapV3SwapConnectors.Swapper(
173-
// factoryAddress: FlowVaultsStrategies.univ3FactoryEVMAddress,
174-
// routerAddress: FlowVaultsStrategies.univ3RouterEVMAddress,
175-
// quoterAddress: FlowVaultsStrategies.univ3QuoterEVMAddress,
176-
// tokenPath: [moetTokenEVMAddress, FlowVaultsStrategies.yieldTokenEVMAddress],
177-
// feePath: [3000],
178-
// inVault: moetTokenType,
179-
// outVault: yieldTokenType,
180-
// coaCapability: FlowVaultsStrategies._getCOACapability(),
181-
// uniqueID: uniqueID
182-
// )
183171
// YieldToken -> Stable
184172
let yieldToStableSwapper = MockSwapper.Swapper(
185173
inVault: yieldTokenType,
186174
outVault: moetTokenType,
187175
uniqueID: uniqueID
188176
)
189-
// TODO: Update to use UniswapV3SwapConnectors when bridge integration is ready
190-
// let yieldToStableSwapper = UniswapV3SwapConnectors.Swapper(
191-
// factoryAddress: FlowVaultsStrategies.univ3FactoryEVMAddress,
192-
// routerAddress: FlowVaultsStrategies.univ3RouterEVMAddress,
193-
// quoterAddress: FlowVaultsStrategies.univ3QuoterEVMAddress,
194-
// tokenPath: [FlowVaultsStrategies.yieldTokenEVMAddress, moetTokenEVMAddress],
195-
// feePath: [3000],
196-
// inVault: yieldTokenType,
197-
// outVault: moetTokenType,
198-
// coaCapability: FlowVaultsStrategies._getCOACapability(),
199-
// uniqueID: uniqueID
200-
// )
177+
178+
// init SwapSink directing swapped funds to AutoBalancer
179+
//
180+
// Swaps provided Stable to YieldToken & deposits to the AutoBalancer
181+
let abaSwapSink = SwapConnectors.SwapSink(swapper: stableToYieldSwapper, sink: abaSink, uniqueID: uniqueID)
182+
// Swaps YieldToken & provides swapped Stable, sourcing YieldToken from the AutoBalancer
183+
let abaSwapSource = SwapConnectors.SwapSource(swapper: yieldToStableSwapper, source: abaSource, uniqueID: uniqueID)
184+
185+
// open a FlowALP position
186+
let poolCap = FlowVaultsStrategies.account.storage.load<Capability<auth(FlowALP.EParticipant, FlowALP.EPosition) &FlowALP.Pool>>(
187+
from: FlowALP.PoolCapStoragePath
188+
) ?? panic("Missing pool capability")
189+
190+
let poolRef = poolCap.borrow() ?? panic("Invalid Pool Cap")
191+
192+
let pid = poolRef.createPosition(
193+
funds: <-withFunds,
194+
issuanceSink: abaSwapSink,
195+
repaymentSource: abaSwapSource,
196+
pushToDrawDownSink: true
197+
)
198+
let position = FlowALP.Position(id: pid, pool: poolCap)
199+
FlowVaultsStrategies.account.storage.save(poolCap, to: FlowALP.PoolCapStoragePath)
200+
201+
// get Sink & Source connectors relating to the new Position
202+
let positionSink = position.createSinkWithOptions(type: collateralType, pushToDrawDownSink: true)
203+
let positionSource = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true) // TODO: may need to be false
204+
205+
// init YieldToken -> FLOW Swapper
206+
let yieldToFlowSwapper = MockSwapper.Swapper(
207+
inVault: yieldTokenType,
208+
outVault: collateralType,
209+
uniqueID: uniqueID
210+
)
211+
// allows for YieldToken to be deposited to the Position
212+
let positionSwapSink = SwapConnectors.SwapSink(swapper: yieldToFlowSwapper, sink: positionSink, uniqueID: uniqueID)
213+
214+
// set the AutoBalancer's rebalance Sink which it will use to deposit overflown value,
215+
// recollateralizing the position
216+
autoBalancer.setSink(positionSwapSink, updateSinkID: true)
217+
218+
return <-create TracerStrategy(
219+
id: DeFiActions.createUniqueIdentifier(),
220+
collateralType: collateralType,
221+
position: position
222+
)
223+
}
224+
}
225+
226+
/// This strategy uses mUSDC vaults
227+
access(all) resource mUSDCStrategy : FlowVaults.Strategy, DeFiActions.IdentifiableResource {
228+
/// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol-
229+
/// specific Identifier to associated connectors on construction
230+
access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
231+
access(self) let position: FlowALP.Position
232+
access(self) var sink: {DeFiActions.Sink}
233+
access(self) var source: {DeFiActions.Source}
234+
235+
init(id: DeFiActions.UniqueIdentifier, collateralType: Type, position: FlowALP.Position) {
236+
self.uniqueID = id
237+
self.position = position
238+
self.sink = position.createSink(type: collateralType)
239+
self.source = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true)
240+
}
241+
242+
// Inherited from FlowVaults.Strategy default implementation
243+
// access(all) view fun isSupportedCollateralType(_ type: Type): Bool
244+
245+
access(all) view fun getSupportedCollateralTypes(): {Type: Bool} {
246+
return { self.sink.getSinkType(): true }
247+
}
248+
/// Returns the amount available for withdrawal via the inner Source
249+
access(all) fun availableBalance(ofToken: Type): UFix64 {
250+
return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0
251+
}
252+
/// Deposits up to the inner Sink's capacity from the provided authorized Vault reference
253+
access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
254+
self.sink.depositCapacity(from: from)
255+
}
256+
/// Withdraws up to the max amount, returning the withdrawn Vault. If the requested token type is unsupported,
257+
/// an empty Vault is returned.
258+
access(FungibleToken.Withdraw) fun withdraw(maxAmount: UFix64, ofToken: Type): @{FungibleToken.Vault} {
259+
if ofToken != self.source.getSourceType() {
260+
return <- DeFiActionsUtils.getEmptyVault(ofToken)
261+
}
262+
return <- self.source.withdrawAvailable(maxAmount: maxAmount)
263+
}
264+
/// Executed when a Strategy is burned, cleaning up the Strategy's stored AutoBalancer
265+
access(contract) fun burnCallback() {
266+
FlowVaultsAutoBalancers._cleanupAutoBalancer(id: self.id()!)
267+
}
268+
access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
269+
return DeFiActions.ComponentInfo(
270+
type: self.getType(),
271+
id: self.id(),
272+
innerComponents: []
273+
)
274+
}
275+
access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
276+
return self.uniqueID
277+
}
278+
access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
279+
self.uniqueID = id
280+
}
281+
}
282+
283+
/// This StrategyComposer builds a mUSDCStrategy
284+
access(all) resource mUSDCStrategyComposer : FlowVaults.StrategyComposer {
285+
/// Returns the Types of Strategies composed by this StrategyComposer
286+
access(all) view fun getComposedStrategyTypes(): {Type: Bool} {
287+
return { Type<@mUSDCStrategy>(): true }
288+
}
289+
290+
/// Returns the Vault types which can be used to initialize a given Strategy
291+
access(all) view fun getSupportedInitializationVaults(forStrategy: Type): {Type: Bool} {
292+
return { Type<@FlowToken.Vault>(): true }
293+
}
294+
295+
/// Returns the Vault types which can be deposited to a given Strategy instance if it was initialized with the
296+
/// provided Vault type
297+
access(all) view fun getSupportedInstanceVaults(forStrategy: Type, initializedWith: Type): {Type: Bool} {
298+
return { Type<@FlowToken.Vault>(): true }
299+
}
300+
301+
/// Composes a Strategy of the given type with the provided funds
302+
access(all) fun createStrategy(
303+
_ type: Type,
304+
uniqueID: DeFiActions.UniqueIdentifier,
305+
withFunds: @{FungibleToken.Vault}
306+
): @{FlowVaults.Strategy} {
307+
// this PriceOracle is mocked and will be shared by all components used in the TracerStrategy
308+
// TODO: add ERC4626 price oracle
309+
let oracle = MockOracle.PriceOracle()
310+
311+
// assign EVM token addresses & types
312+
313+
let moetTokenType: Type = Type<@MOET.Vault>()
314+
let moetTokenEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: moetTokenType)
315+
?? panic("MOET not registered in bridge")
316+
317+
let yieldTokenType = FlowEVMBridgeConfig.getTypeAssociated(with: FlowVaultsStrategies.yieldTokenEVMAddress)
318+
?? panic("YieldToken associated with EVM address \(FlowVaultsStrategies.yieldTokenEVMAddress.toString()) not found in VM Bridge config")
319+
// assign collateral & flow token types
320+
let collateralType = withFunds.getType()
321+
322+
// configure and AutoBalancer for this stack
323+
let autoBalancer = FlowVaultsAutoBalancers._initNewAutoBalancer(
324+
oracle: oracle, // used to determine value of deposits & when to rebalance
325+
vaultType: yieldTokenType, // the type of Vault held by the AutoBalancer
326+
lowerThreshold: 0.95, // set AutoBalancer to pull from rebalanceSource when balance is 5% below value of deposits
327+
upperThreshold: 1.05, // set AutoBalancer to push to rebalanceSink when balance is 5% below value of deposits
328+
rebalanceSink: nil, // nil on init - will be set once a PositionSink is available
329+
rebalanceSource: nil, // nil on init - not set for TracerStrategy
330+
uniqueID: uniqueID // identifies AutoBalancer as part of this Strategy
331+
)
332+
// enables deposits of YieldToken to the AutoBalancer
333+
let abaSink = autoBalancer.createBalancerSink() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)")
334+
// enables withdrawals of YieldToken from the AutoBalancer
335+
let abaSource = autoBalancer.createBalancerSource() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)")
336+
337+
// init Stable <> YIELD swappers
338+
//
339+
// Stable -> YieldToken
340+
// TODO: Update to use UniswapV3SwapConnectors
341+
// let stableToYieldSwapper = MockSwapper.Swapper(
342+
// inVault: moetTokenType,
343+
// outVault: yieldTokenType,
344+
// uniqueID: uniqueID
345+
// )
346+
// TODO: consider how we're going to pass the user's COA capability to the Swapper
347+
let stableToYieldSwapper = UniswapV3SwapConnectors.Swapper(
348+
factoryAddress: FlowVaultsStrategies.univ3FactoryEVMAddress,
349+
routerAddress: FlowVaultsStrategies.univ3RouterEVMAddress,
350+
quoterAddress: FlowVaultsStrategies.univ3QuoterEVMAddress,
351+
tokenPath: [moetTokenEVMAddress, FlowVaultsStrategies.yieldTokenEVMAddress],
352+
feePath: [3000],
353+
inVault: moetTokenType,
354+
outVault: yieldTokenType,
355+
coaCapability: FlowVaultsStrategies._getCOACapability(),
356+
uniqueID: uniqueID
357+
)
358+
// YieldToken -> Stable
359+
// TODO: Update to use UniswapV3SwapConnectors
360+
// let yieldToStableSwapper = MockSwapper.Swapper(
361+
// inVault: yieldTokenType,
362+
// outVault: moetTokenType,
363+
// uniqueID: uniqueID
364+
// )
365+
let yieldToStableSwapper = UniswapV3SwapConnectors.Swapper(
366+
factoryAddress: FlowVaultsStrategies.univ3FactoryEVMAddress,
367+
routerAddress: FlowVaultsStrategies.univ3RouterEVMAddress,
368+
quoterAddress: FlowVaultsStrategies.univ3QuoterEVMAddress,
369+
tokenPath: [FlowVaultsStrategies.yieldTokenEVMAddress, moetTokenEVMAddress],
370+
feePath: [3000],
371+
inVault: yieldTokenType,
372+
outVault: moetTokenType,
373+
coaCapability: FlowVaultsStrategies._getCOACapability(),
374+
uniqueID: uniqueID
375+
)
201376

202377
// init SwapSink directing swapped funds to AutoBalancer
203378
//
@@ -273,24 +448,22 @@ access(all) contract FlowVaultsStrategies {
273448
return coaCap
274449
}
275450

276-
init() {
277-
// TODO: Initialize these when UniswapV3 integration is ready
278-
// For now using MockSwapper so these are not needed
279-
self.univ3FactoryEVMAddress = EVM.EVMAddress(bytes: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])
280-
self.univ3RouterEVMAddress = EVM.EVMAddress(bytes: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])
281-
self.univ3QuoterEVMAddress = EVM.EVMAddress(bytes: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])
282-
self.yieldTokenEVMAddress = EVM.EVMAddress(bytes: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])
451+
init(factoryAddress: String, routerAddress: String, quoterAddress: String, yieldTokenAddress: String) {
452+
self.univ3FactoryEVMAddress = EVM.addressFromString(factoryAddress)
453+
self.univ3RouterEVMAddress = EVM.addressFromString(routerAddress)
454+
self.univ3QuoterEVMAddress = EVM.addressFromString(quoterAddress)
455+
self.yieldTokenEVMAddress = EVM.addressFromString(yieldTokenAddress)
283456

284457
self.IssuerStoragePath = StoragePath(identifier: "FlowVaultsStrategyComposerIssuer_\(self.account.address)")!
285458

286459
self.account.storage.save(<-create StrategyComposerIssuer(), to: self.IssuerStoragePath)
287460

288-
// TODO: COA creation will be needed when UniswapV3 integration is ready
289-
// For now using MockSwapper so COA is not needed
290-
// if self.account.storage.type(at: /storage/evm) == nil {
291-
// self.account.storage.save(<-EVM.createCadenceOwnedAccount(), to: /storage/evm)
292-
// let cap = self.account.capabilities.storage.issue<&EVM.CadenceOwnedAccount>(/storage/evm)
293-
// self.account.capabilities.publish(cap, at: /public/evm)
294-
// }
461+
// TODO: this is temporary until we have a better way to pass user's COAs to inner connectors
462+
// create a COA in this account
463+
if self.account.storage.type(at: /storage/evm) == nil {
464+
self.account.storage.save(<-EVM.createCadenceOwnedAccount(), to: /storage/evm)
465+
let cap = self.account.capabilities.storage.issue<&EVM.CadenceOwnedAccount>(/storage/evm)
466+
self.account.capabilities.publish(cap, at: /public/evm)
467+
}
295468
}
296469
}

0 commit comments

Comments
 (0)