Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 1 addition & 26 deletions .github/workflows/e2e_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,32 +80,6 @@ jobs:

echo "CONTRACT_ADDRESS=$FLOW_VAULTS_REQUESTS_CONTRACT" >> $GITHUB_ENV

- name: Detect Strategy Identifier
run: |
echo "Detecting supported strategy identifier..."
YIELDVAULT_CHECK=$(flow scripts execute ./cadence/scripts/check_yieldvault_details.cdc 0x045a1763c93006ca)
echo "$YIELDVAULT_CHECK"

SUPPORTED_STRATEGIES=$(echo "$YIELDVAULT_CHECK" | grep -oE '"supportedStrategies": \[[^]]*\]' || true)
if [ -z "$SUPPORTED_STRATEGIES" ]; then
echo "❌ Could not parse supported strategy list"
exit 1
fi

STRATEGY_LIST=$(echo "$SUPPORTED_STRATEGIES" | sed -E 's/^"supportedStrategies": \[(.*)\]$/\1/' | tr -d '"' | tr ',' '\n' | sed 's/^ *//;s/ *$//' | sed '/^$/d')
STRATEGY_IDENTIFIER=$(echo "$STRATEGY_LIST" | grep 'TracerStrategy' | head -n 1 || true)
if [ -z "$STRATEGY_IDENTIFIER" ]; then
STRATEGY_IDENTIFIER=$(echo "$STRATEGY_LIST" | head -n 1)
fi

if [ -z "$STRATEGY_IDENTIFIER" ]; then
echo "❌ No supported strategy identifier found"
exit 1
fi

echo "Using strategy identifier: $STRATEGY_IDENTIFIER"
echo "STRATEGY_IDENTIFIER=$STRATEGY_IDENTIFIER" >> $GITHUB_ENV

# === TEST 1: BASIC YIELDVAULT CREATION ===
- name: Test 1 - Create YieldVault (10 FLOW)
run: |
Expand All @@ -120,6 +94,7 @@ jobs:
--legacy
env:
AMOUNT: 10000000000000000000
CREATE_VAULT_CONFIG_ID: 1

- name: Process Create Request
run: |
Expand Down
26 changes: 0 additions & 26 deletions .github/workflows/worker_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,32 +55,6 @@ jobs:
- name: Deploy Full Stack
run: ./local/deploy_full_stack.sh

- name: Detect Strategy Identifier
run: |
echo "Detecting supported strategy identifier..."
YIELDVAULT_CHECK=$(flow scripts execute ./cadence/scripts/check_yieldvault_details.cdc 0x045a1763c93006ca)
echo "$YIELDVAULT_CHECK"

SUPPORTED_STRATEGIES=$(echo "$YIELDVAULT_CHECK" | grep -oE '"supportedStrategies": \[[^]]*\]' || true)
if [ -z "$SUPPORTED_STRATEGIES" ]; then
echo "❌ Could not parse supported strategy list"
exit 1
fi

STRATEGY_LIST=$(echo "$SUPPORTED_STRATEGIES" | sed -E 's/^"supportedStrategies": \[(.*)\]$/\1/' | tr -d '"' | tr ',' '\n' | sed 's/^ *//;s/ *$//' | sed '/^$/d')
STRATEGY_IDENTIFIER=$(echo "$STRATEGY_LIST" | grep 'TracerStrategy' | head -n 1 || true)
if [ -z "$STRATEGY_IDENTIFIER" ]; then
STRATEGY_IDENTIFIER=$(echo "$STRATEGY_LIST" | head -n 1)
fi

if [ -z "$STRATEGY_IDENTIFIER" ]; then
echo "❌ No supported strategy identifier found"
exit 1
fi

echo "Using strategy identifier: $STRATEGY_IDENTIFIER"
echo "STRATEGY_IDENTIFIER=$STRATEGY_IDENTIFIER" >> $GITHUB_ENV

# === RUN WORKER TESTS ===
- name: Run Worker Tests
run: ./local/run_worker_tests.sh
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ flow deps install --skip-alias --skip-deployments # Install dependencies

1. **EVM User** calls `FlowYieldVaultsRequests.sol` (creates request, escrows funds)
2. **FlowYieldVaultsEVMWorkerOps.cdc** SchedulerHandler schedules WorkerHandlers to process requests
3. **FlowYieldVaultsEVM.cdc** Worker fetches pending requests via `getPendingRequestsUnpacked()`
3. **FlowYieldVaultsEVM.cdc** Worker fetches pending requests via `getPendingRequestsUnpacked()`, then resolves `createVaultConfigId` against the local Cadence config registry for `CREATE_YIELDVAULT`
4. **Two-phase commit**: `startProcessingBatch()` marks PROCESSING and deducts balance, `completeProcessing()` marks COMPLETED/FAILED (refunds credited to `claimableRefunds` on failure)

### Contract Components
Expand All @@ -61,6 +61,7 @@ flow deps install --skip-alias --skip-deployments # Install dependencies
### Key Design Patterns

- **COA Bridge**: Cadence Owned Account bridges funds between EVM and Cadence via FlowEVMBridge
- **Immutable CREATE Configs**: EVM requests store `createVaultConfigId`; Cadence resolves identifiers locally; config onboarding should use `cadence/transactions/register_create_yieldvault_config_everywhere.cdc`
- **Sentinel Values**: `NATIVE_FLOW = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF`, `NO_YIELDVAULT_ID = type(uint64).max`
- **Ownership Tracking**: Parallel mappings on both EVM (`userOwnsYieldVault`) and Cadence (`yieldVaultOwnershipLookup`) for O(1) lookups
- **Scheduler/Worker Split**: SchedulerHandler runs at fixed interval, schedules WorkerHandlers for individual requests
Expand Down
91 changes: 82 additions & 9 deletions FLOW_YIELD_VAULTS_EVM_BRIDGE_DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ Request queue and fund escrow contract.

**Responsibilities:**
- Accept and queue user requests (CREATE_YIELDVAULT, DEPOSIT_TO_YIELDVAULT, WITHDRAW_FROM_YIELDVAULT, CLOSE_YIELDVAULT)
- Maintain the immutable CREATE_YIELDVAULT config registry keyed by `createVaultConfigId`
- Escrow deposited funds until processing
- Track user balances and pending request counts
- Enforce access control (allowlist/blocklist)
Expand Down Expand Up @@ -126,6 +127,7 @@ Worker contract that processes EVM requests and manages YieldVault positions.

**Responsibilities:**
- Fetch pending requests from EVM via `getPendingRequestsUnpacked()`
- Resolve `createVaultConfigId` values against the local Cadence config registry before processing `CREATE_YIELDVAULT`
- Execute two-phase commit (startProcessingBatch → operation → completeProcessing)
- Create, deposit to, withdraw from, and close YieldVaults
- Bridge funds between EVM and Cadence via COA
Expand All @@ -136,7 +138,10 @@ Worker contract that processes EVM requests and manages YieldVault positions.
// YieldVault ownership tracking
access(all) let yieldVaultRegistry: {String: {UInt64: Bool}}

// Configuration (stored as contract-only vars; exposed via getters)
// Registered immutable CREATE_YIELDVAULT configs
access(all) let createYieldVaultConfigs: {UInt64: CreateYieldVaultConfig}

// Configuration
var flowYieldVaultsRequestsAddress: EVM.EVMAddress?

// Constants
Expand Down Expand Up @@ -211,10 +216,31 @@ struct Request {
uint64 yieldVaultId; // Target YieldVault Id (NO_YIELDVAULT_ID for CREATE_YIELDVAULT until completed; for others set at request creation)
uint256 timestamp; // Block timestamp when created
string message; // Status message or error reason
uint64 createVaultConfigId; // Immutable CREATE_YIELDVAULT config ID (0 for non-create requests)
}

struct RequestView {
uint256 id;
address user;
RequestType requestType;
RequestStatus status;
address tokenAddress;
uint256 amount;
uint64 yieldVaultId;
uint256 timestamp;
string message;
uint64 createVaultConfigId;
string vaultIdentifier; // Cadence vault type (e.g., "A.xxx.FlowToken.Vault")
string strategyIdentifier; // Cadence strategy type (e.g., "A.xxx.Strategy.Type")
}

struct CreateYieldVaultConfig {
bool exists;
bool enabled;
string vaultIdentifier;
string strategyIdentifier;
}

enum RequestType {
CREATE_YIELDVAULT, // 0
DEPOSIT_TO_YIELDVAULT, // 1
Expand All @@ -236,6 +262,8 @@ struct TokenConfig {
}
```

`Request` is the canonical stored payload on EVM. `RequestView` is the resolved read model returned by `getRequest()`.

### EVMRequest (Cadence)

```cadence
Expand All @@ -246,14 +274,17 @@ access(all) struct EVMRequest {
access(all) let status: UInt8
access(all) let tokenAddress: EVM.EVMAddress
access(all) let amount: UInt256
access(all) let yieldVaultId: UInt64
access(all) let yieldVaultId: UInt64?
access(all) let timestamp: UInt256
access(all) let message: String
access(all) let createVaultConfigId: UInt64?
access(all) let vaultIdentifier: String
access(all) let strategyIdentifier: String
}
```

`getPendingRequestsUnpacked()` returns `createVaultConfigId` values only. The worker resolves `vaultIdentifier` and `strategyIdentifier` from the Cadence config registry before constructing `EVMRequest`.

### ProcessResult (Cadence)

```cadence
Expand All @@ -280,7 +311,7 @@ access(all) struct ProcessResult {
│ │ │ │
│ createYieldVault( │ │ │
│ token, amount, │ │ │
vault, strategy) │ │ │
createVaultConfigId)│ │ │
│────────────────────▶│ │ │
│ │ Escrow funds │ │
│ │ Create PENDING request │ │
Expand All @@ -289,7 +320,8 @@ access(all) struct ProcessResult {
│ │ │ │
│ │ getPendingRequestsUnpacked │ │
│ │◀───────────────────────│ │
│ │ [EVMRequest] │ │
│ │ [request arrays + │ │
│ │ createVaultConfigIds] │ │
│ │───────────────────────▶│ │
│ │ │ │
│ │ startProcessingBatch([id], []) │ │
Expand Down Expand Up @@ -321,6 +353,18 @@ access(all) struct ProcessResult {
│ │───────────────────────▶│ │
```

Canonical CREATE requests carry `createVaultConfigId` on EVM. The legacy `(vaultIdentifier, strategyIdentifier)` overload resolves the pair to a registered config ID before the request is stored.

### CREATE_YIELDVAULT Config Registration

```
1. Admin submits register_create_yieldvault_config_everywhere.cdc
2. Transaction borrows FlowYieldVaultsEVM.Admin and FlowYieldVaultsEVM.Worker from the same signer account
3. Admin validates the identifiers locally and writes the Cadence config registry entry
4. Worker calls Solidity registerCreateYieldVaultConfig(uint64,string,string) through the COA
5. If the EVM call fails, the entire Cadence transaction reverts and the Cadence write is rolled back
```

### DEPOSIT_TO_YIELDVAULT

```
Expand Down Expand Up @@ -544,14 +588,43 @@ function doesUserOwnYieldVault(address user, uint64 yieldVaultId) returns (bool)
// Get pending request IDs array
function getPendingRequestIds() returns (uint256[] memory);

// Get immutable CREATE_YIELDVAULT config by ID
function getCreateYieldVaultConfig(uint64 configId)
returns (bool exists, bool enabled, string memory vaultIdentifier, string memory strategyIdentifier);

// Get pending requests in unpacked arrays (pagination)
function getPendingRequestsUnpacked(uint256 startIndex, uint256 count) returns (...);
function getPendingRequestsUnpacked(uint256 startIndex, uint256 count)
returns (
uint256[] memory ids,
address[] memory users,
uint8[] memory requestTypes,
uint8[] memory statuses,
address[] memory tokenAddresses,
uint256[] memory amounts,
uint64[] memory yieldVaultIds,
uint256[] memory timestamps,
string[] memory messages,
uint64[] memory createVaultConfigIds
);

// Get pending requests for a user in unpacked arrays (includes native FLOW balances)
function getPendingRequestsByUserUnpacked(address user) returns (...);

// Get single request by ID
function getRequest(uint256 requestId) returns (Request memory);
function getPendingRequestsByUserUnpacked(address user)
returns (
uint256[] memory ids,
uint8[] memory requestTypes,
uint8[] memory statuses,
address[] memory tokenAddresses,
uint256[] memory amounts,
uint64[] memory yieldVaultIds,
uint256[] memory timestamps,
string[] memory messages,
uint64[] memory createVaultConfigIds,
uint256 pendingBalance,
uint256 claimableRefund
);

// Get single request by ID (resolved read model)
function getRequest(uint256 requestId) returns (RequestView memory);

// Check if YieldVault Id is valid
function isYieldVaultIdValid(uint64 yieldVaultId) returns (bool);
Expand Down
10 changes: 5 additions & 5 deletions FRONTEND_INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ Note: counts include both `PENDING` and `PROCESSING` requests (they remain in th

```typescript
const request = await contract.getRequest(requestId);
// Returns: { id, user, requestType, status, tokenAddress, amount, yieldVaultId, timestamp, message, vaultIdentifier, strategyIdentifier }
// Returns: { id, user, requestType, status, tokenAddress, amount, yieldVaultId, timestamp, message, createVaultConfigId, vaultIdentifier, strategyIdentifier }
```

#### Get User's Pending Requests (Unpacked)
Expand All @@ -177,14 +177,14 @@ const [
yieldVaultIds,
timestamps,
messages,
vaultIdentifiers,
strategyIdentifiers,
createVaultConfigIds,
pendingBalance,
claimableRefund,
] = await contract.getPendingRequestsByUserUnpacked(userAddress);
// pendingBalance = escrowed funds for active pending requests (native FLOW only)
// claimableRefund = funds available to claim via claimRefund() (native FLOW only)
// Use getUserPendingBalance/getClaimableRefund for a specific token
// Use getCreateYieldVaultConfig(createVaultConfigIds[i]) to resolve vault/strategy identifiers for CREATE requests
```

#### Get All Pending Requests (Paginated, Admin)
Expand All @@ -200,14 +200,14 @@ const [
yieldVaultIds,
timestamps,
messages,
vaultIdentifiers,
strategyIdentifiers,
createVaultConfigIds,
] = await contract.getPendingRequestsUnpacked(startIndex, count);

// Filter for specific user client-side
const userRequests = ids.filter(
(_, i) => users[i].toLowerCase() === userAddress.toLowerCase()
);
// Use getCreateYieldVaultConfig(createVaultConfigIds[i]) to resolve vault/strategy identifiers for CREATE requests
```

---
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,18 +104,19 @@ Recommended sequence (run from repo root):
Notes:
- These scripts expect `flow`, `forge`, `cast`, `curl`, `bc`, `lsof`, and `git` on PATH.
- `./local/deploy_full_stack.sh` writes the deployed EVM contract address to `./local/.deployed_contract_address`.
- `./local/deploy_full_stack.sh` also registers the default local `CREATE_YIELDVAULT` config as ID `1`.
- The E2E scripts read `./local/.deployed_contract_address` or use `FLOW_VAULTS_REQUESTS_CONTRACT` if set.

Local script reference:
- `./local/setup_and_run_emulator.sh`: Initializes submodules, clears `./db` and `./imports`, kills processes on ports 8080/8545/3569/8888, starts Flow emulator + EVM gateway, and sets up FlowYieldVaults dependencies.
- `./local/deploy_full_stack.sh`: Funds local EVM EOAs, deploys `FlowYieldVaultsRequests` to the local EVM, deploys Cadence contracts, sets up the Worker, and writes `./local/.deployed_contract_address`.
- `./local/deploy_full_stack.sh`: Funds local EVM EOAs, deploys `FlowYieldVaultsRequests` to the local EVM, deploys Cadence contracts, sets up the Worker, registers the default local `CREATE_YIELDVAULT` config, and writes `./local/.deployed_contract_address`.
- `./local/run_e2e_tests.sh`: Runs end-to-end user flows (create/deposit/withdraw/close/cancel). Requires emulator/gateway running and a deployed contract address.
- `./local/run_admin_e2e_tests.sh`: Runs end-to-end admin flows (allowlist/blocklist, token config, max requests, admin cancel/drop). Requires emulator/gateway running and a deployed contract address.
- `./local/run_worker_tests.sh`: Runs scheduled worker tests (SchedulerHandler, WorkerHandler, pause/unpause, crash recovery). Requires emulator/gateway running and a deployed contract address.
- `./local/run_cadence_tests.sh`: Runs Cadence tests with `flow test`. Cleans `./db` and `./imports` first (stop emulator if you need to preserve state).
- `./local/run_solidity_tests.sh`: Runs Solidity tests with `forge test`.
- `./local/testnet-e2e.sh`: Testnet CLI for state checks and user/admin actions. Run `./local/testnet-e2e.sh --help` for commands. Uses `PRIVATE_KEY` and `TESTNET_RPC_URL` if set; admin commands require `testnet-account` in `flow.json`. Update the hardcoded `CONTRACT` address in the script when deploying a new version.
- `./local/deploy_and_verify.sh`: Testnet deploy/verify flow using COA and KMS. Requires a `.env` file (see the script header for required values) and a configured `testnet-account` signer.
- `./local/deploy_and_verify.sh`: Testnet deploy/verify flow using COA and KMS. Requires a `.env` file, a configured `testnet-account` signer, and an initial `CREATE_YIELDVAULT` config via `INITIAL_CREATE_VAULT_CONFIG_ID`, `INITIAL_CREATE_VAULT_IDENTIFIER`, and `INITIAL_CREATE_STRATEGY_IDENTIFIER` unless you explicitly skip registration.

Testnet sequence (optional):
1. Create `.env` with the variables expected by `./local/deploy_and_verify.sh` (KMS/signing config, RPCs, etc).
Expand All @@ -132,8 +133,7 @@ All user operations are available through `FlowYieldVaultsYieldVaultOperations.s
|----------|-------------|---------|
| `USER_PRIVATE_KEY` | Private key for signing transactions | `0x3` (test account) |
| `AMOUNT` | Amount in wei for create/deposit operations | `10000000000000000000` (10 FLOW) |
| `VAULT_IDENTIFIER` | Cadence vault type identifier | `A.0ae53cb6e3f42a79.FlowToken.Vault` |
| `STRATEGY_IDENTIFIER` | Cadence strategy type identifier | `A.045a1763c93006ca.MockStrategies.TracerStrategy` |
| `CREATE_VAULT_CONFIG_ID` | Registered `CREATE_YIELDVAULT` config ID | `1` |

#### Commands

Expand All @@ -144,8 +144,8 @@ forge script ./solidity/script/FlowYieldVaultsYieldVaultOperations.s.sol:FlowYie
--sig "createYieldVault(address)" $FLOW_VAULTS_REQUESTS_CONTRACT \
--rpc-url http://localhost:8545 --broadcast --legacy

# CREATE_YIELDVAULT - Custom amount (100 FLOW) with custom signer
USER_PRIVATE_KEY=0xYOUR_PRIVATE_KEY AMOUNT=100000000000000000000 \
# CREATE_YIELDVAULT - Custom amount (100 FLOW) and config ID with custom signer
USER_PRIVATE_KEY=0xYOUR_PRIVATE_KEY AMOUNT=100000000000000000000 CREATE_VAULT_CONFIG_ID=2 \
forge script ./solidity/script/FlowYieldVaultsYieldVaultOperations.s.sol:FlowYieldVaultsYieldVaultOperations \
--root ./solidity \
--sig "createYieldVault(address)" $FLOW_VAULTS_REQUESTS_CONTRACT \
Expand Down
Loading
Loading