Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8d60bc0
wip
HinsonSIDAN Mar 12, 2026
e8e9d43
feat: update transferal logic
HinsonSIDAN Mar 12, 2026
453dccc
chore: update scripts
HinsonSIDAN Mar 12, 2026
a5c34b7
chore: update scripts
HinsonSIDAN Mar 12, 2026
ff5d7e2
change only minted check to policy mint check
twwu123 Mar 16, 2026
a38f10d
feat: vault deposit and withdrawal
kenlau666 Mar 17, 2026
93b3a56
fix: handle l2 unit
kenlau666 Mar 18, 2026
4f2aaf6
fix: l2 initial deposit
kenlau666 Mar 18, 2026
78cd62c
fix: vault accoutn master key wrong check
kenlau666 Mar 18, 2026
f4e24f5
fix: operator min check
kenlau666 Mar 19, 2026
54e87f4
feat: operator min check
kenlau666 Mar 19, 2026
86d95d8
fix: total_fee_share_collected
kenlau666 Mar 19, 2026
695268a
fix: withdrawer == operator no share
kenlau666 Mar 23, 2026
8ad0bb5
feat: convert decimal prices to integers with dynamic scale
kenlau666 Mar 23, 2026
5fc231b
fix: initial deposit equity = 0
kenlau666 Mar 24, 2026
2f3f23a
fix: remove hydra phk check
kenlau666 Mar 25, 2026
2dc64fb
chore: aiken build
kenlau666 Mar 25, 2026
55817f7
fix: debug datum
kenlau666 Mar 25, 2026
41e3e4c
chore: add trace
kenlau666 Mar 25, 2026
6d3a258
fix: operator withdrawal mpf type
kenlau666 Mar 26, 2026
329910c
chore: merkle test
kenlau666 Mar 26, 2026
84346ca
fix: remove migration in account
kenlau666 Mar 27, 2026
9cd1074
Revert "fix: remove migration in account"
kenlau666 Mar 27, 2026
f880c77
fix: missing merkle and message test
kenlau666 Apr 1, 2026
1d6db2b
feat: dexOrderBook migration
kenlau666 Apr 1, 2026
485e86b
Revert "feat: dexOrderBook migration"
kenlau666 Apr 1, 2026
ad46fac
fix: redeemer type
kenlau666 Apr 1, 2026
d8dfa82
fix: revert intent change
kenlau666 Apr 1, 2026
dfa8776
feat: dexOrderBook migration
kenlau666 Apr 1, 2026
795b27e
chore: update plutus json
kenlau666 Apr 8, 2026
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
192 changes: 164 additions & 28 deletions docs/aiken-mpf-fixture-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ npm install @anastasia-labs/cardano-merkle-patricia-forestry
### 1. MPF Tree Structure

MPF trees store key-value pairs where:

- **Key**: A ByteArray (e.g., `order_id`, `account_id`)
- **Value**: CBOR-serialized data
- **Path**: `blake2b_256(key)` - determines position in tree
Expand All @@ -27,19 +28,25 @@ MPF trees store key-value pairs where:

Aiken and TypeScript serialize data differently. Here's how to match Aiken's format:

| Aiken Type | Aiken CBOR | TypeScript Equivalent |
|------------|------------|----------------------|
| Tuple `(a, b)` | Indefinite array `9f...ff` | `{ list: [a, b] }` |
| Bool `False` | `d87980` (conStr0) | `conStr0([])` |
| Bool `True` | `d87a80` (conStr1) | `conStr1([])` |
| Integer | Direct encoding | `{ int: value }` |
| ByteArray | Direct encoding | `byteString(hex)` |
| Constructor | `d879xx` / `d87axx` | `conStr0([...])` / `conStr1([...])` |
| Aiken Type | Aiken CBOR | TypeScript Equivalent |
| -------------- | -------------------------- | ----------------------------------- |
| Tuple `(a, b)` | Indefinite array `9f...ff` | `{ list: [a, b] }` |
| Bool `False` | `d87980` (conStr0) | `conStr0([])` |
| Bool `True` | `d87a80` (conStr1) | `conStr1([])` |
| Integer | Direct encoding | `{ int: value }` |
| ByteArray | Direct encoding | `byteString(hex)` |
| Constructor | `d879xx` / `d87axx` | `conStr0([...])` / `conStr1([...])` |

## TypeScript Helper Functions

```typescript
import { Cbor, CborBytes, CborArray, CborUInt, CborTag } from "@harmoniclabs/cbor";
import {
Cbor,
CborBytes,
CborArray,
CborUInt,
CborTag,
} from "@harmoniclabs/cbor";
import * as crypto from "crypto";

// Helper to create ByteString
Expand Down Expand Up @@ -129,17 +136,17 @@ For order merkle trees, the value is `MerklizedOrderDatum`, not just `Order`:
const mvalue = {
map: [
{
k: byteString(""), // policy_id (empty for ADA)
k: byteString(""), // policy_id (empty for ADA)
v: {
map: [
{
k: byteString(""), // asset_name (empty for lovelace)
v: { int: BigInt(250000000) } // amount
}
]
}
}
]
k: byteString(""), // asset_name (empty for lovelace)
v: { int: BigInt(250000000) }, // amount
},
],
},
},
],
};

// MerklizedOrderDatum = conStr0([order, mvalue])
Expand All @@ -152,7 +159,9 @@ const value = Cbor.encode(merklizedOrderDatum);
```typescript
import { Trie } from "@anastasia-labs/cardano-merkle-patricia-forestry";

async function computeRootHash(keyValuePairs: Array<{ key: Buffer; value: Buffer }>) {
async function computeRootHash(
keyValuePairs: Array<{ key: Buffer; value: Buffer }>,
) {
let trie = new Trie();

for (const { key, value } of keyValuePairs) {
Expand All @@ -173,12 +182,20 @@ console.log("Root hash:", rootHash);
## Generating MPF Proofs

```typescript
async function generateInsertProof(existingTrie: Trie, key: Buffer, value: Buffer) {
async function generateInsertProof(
existingTrie: Trie,
key: Buffer,
value: Buffer,
) {
const proof = await existingTrie.insert(key, value);
return proof.proof; // Array of proof steps
return proof.proof; // Array of proof steps
}

async function generateDeleteProof(existingTrie: Trie, key: Buffer, value: Buffer) {
async function generateDeleteProof(
existingTrie: Trie,
key: Buffer,
value: Buffer,
) {
const proof = await existingTrie.delete(key, value);
return proof.proof;
}
Expand All @@ -192,6 +209,7 @@ async function generateDeleteProof(existingTrie: Trie, key: Buffer, value: Buffe
### 1. Single-Key Trie Deletion

When deleting the only key in a trie:

- `new_root` = `null_hash` (empty trie)
- `proof` = `[]` (empty proof)

Expand Down Expand Up @@ -221,7 +239,7 @@ const hydraTokenName = blake2b256(Buffer.concat([policyId, assetName]));

// Token map lookup converts back to L1 tokens
const tokenMap = new Map([
["", ["", ""]], // ADA
["", ["", ""]], // ADA
[hydraUsdxHash, [USDX_POLICY_ID, USDX_ASSET_NAME]],
]);
```
Expand All @@ -235,6 +253,7 @@ console.log("TypeScript CBOR:", Cbor.encode(data).toString("hex"));
```

Compare with Aiken debug test:

```aiken
test debug_cbor() {
trace cbor.serialise(my_data)
Expand All @@ -252,12 +271,12 @@ console.log(`Key: ${key}, Path: ${path.toString("hex")}, Nibble: ${nibble}`);

### 3. Common CBOR Mismatches

| Issue | Symptom | Fix |
|-------|---------|-----|
| Tuple as constructor | `d8799f...ff` instead of `9f...ff` | Use `{ list: [...] }` not `conStr0([...])` |
| Bool encoding | Wrong root hash | `False` = `conStr0([])`, `True` = `conStr1([])` |
| Missing int wrapper | Type error | Use `{ int: BigInt(n) }` not just `n` |
| String vs ByteArray | Wrong serialization | Always use `byteString(hex)` |
| Issue | Symptom | Fix |
| -------------------- | ---------------------------------- | ----------------------------------------------- |
| Tuple as constructor | `d8799f...ff` instead of `9f...ff` | Use `{ list: [...] }` not `conStr0([...])` |
| Bool encoding | Wrong root hash | `False` = `conStr0([])`, `True` = `conStr1([])` |
| Missing int wrapper | Type error | Use `{ int: BigInt(n) }` not just `n` |
| String vs ByteArray | Wrong serialization | Always use `byteString(hex)` |

## Complete Example: Order Cancel Test

Expand Down Expand Up @@ -310,9 +329,126 @@ test my_mpf_test() {
}
```

## Alice & Bob Test Suite

A comprehensive test suite demonstrating all MPF operations and Ed25519 signature verification is located in `validators/tests/alice_bob/`.

### Test Files

| File | Description |
| ------------------ | -------------------------------------------------------- |
| `merkle_proofs.ak` | All MPF operations (insert, update, delete) |
| `price_message.ak` | Ed25519 signature verification for price oracle messages |

### Keypairs Used

| User | Private Key | Public Key | Key Hash (Blake2b-224) |
| ----- | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ---------------------------------------------------------- |
| Alice | `f9fddf3603743e56af455da28a63999451011cc443ea09134a97089a8775e153` | `f665d3a9cbb3d8068aece5700771893780dbbf30302eb7ff6835ae68c9efe690` | `376ca49fdbff2ebc082141951e55038468b51230e90cfc0000004204` |
| Bob | `644674b75aba7f1faa3af927a4ec99648f6b5c8cf03d74f0bef11c4d23fd66e6` | `827444459fa6466ad8fd670179e23b00ea0566547a44cd67a1b37675634280f1` | `9720b632701aabb325d5928b66811d624d650287c2e0e099e1eeb510` |

**Note:** Key hash is computed using Blake2b-224 (28 bytes) of the public key:

```typescript
import { blake2bHex } from "blakejs";
const keyHash = blake2bHex(Buffer.from(publicKey, "hex"), undefined, 28);
```

### MPF Operations Tested

| Test | Operation | Description | Root Hash |
| ------------------------------- | ------------ | -------------------------------- | ------------- |
| `ab_alice_insert_empty_trie` | `mpf.insert` | Insert Alice into empty trie | `512be26f...` |
| `ab_bob_insert_after_alice` | `mpf.insert` | Insert Bob into non-empty trie | `7287001c...` |
| `ab_alice_update_add_shares` | `mpf.update` | Alice deposits more shares | `a5172b6c...` |
| `ab_bob_partial_withdrawal` | `mpf.update` | Bob withdraws partial shares | `29769521...` |
| `ab_bob_full_withdrawal_delete` | `mpf.delete` | Bob withdraws all (delete entry) | `5bb79ce6...` |

### Data Structures

```aiken
// MPF Key - UserTradeAccount serialized with cbor.serialise()
type UserTradeAccount {
account: Account { account_id, master_key, operation_key },
trading_logic: ScriptHash,
}

// MPF Value - SharesRecordEntry serialized with cbor.serialise()
type SharesRecordEntry {
shares: Int,
total_deposited: Int,
}
```

### Regenerating MPF Fixtures

Use the belvedere test to regenerate merkle proofs:

```bash
cd /path/to/belvedere/backend/mesh
npm test -- generateAliceBobProof.test.ts
```

The test outputs all root hashes and proofs needed for Aiken tests.

### Regenerating Signature Fixtures

Use the distiller test to regenerate signed messages:

```bash
cd /path/to/distiller
ALICE_KEY="f9fddf3603743e56af455da28a63999451011cc443ea09134a97089a8775e153" \
BOB_KEY="644674b75aba7f1faa3af927a4ec99648f6b5c8cf03d74f0bef11c4d23fd66e6" \
cargo test test_generate_aiken_sigs -- --nocapture
```

The test outputs CBOR messages and Ed25519 signatures in Aiken constant format.

### MPF Proof Format

The Aiken MPF library uses these proof step types:

```aiken
type Proof =
List<ProofStep>

type ProofStep {
Branch { skip: Int, neighbors: ByteArray }
Fork { skip: Int, neighbor: Neighbor }
Leaf { skip: Int, key: ByteArray, value: ByteArray }
}
```

Example proof for Bob insert (sibling is Alice's leaf):

```aiken
let proof = [
mpf.Leaf {
skip: 0,
key: #"9a286602a7a703340604bba770eb01da6196925fe5a7f0f20cefd7422a9d1640",
value: #"89d017515bd4cc392d560d2cb009359b944c1ee6826e379ecfe7cc09165477ea",
},
]
```

### Ed25519 Signature Verification

```aiken
use aiken/builtin

test verify_signature() {
let pub_key = #"f665d3a9cbb3d8068aece5700771893780dbbf30302eb7ff6835ae68c9efe690"
let message = #"d8799f1a59682f00..." // CBOR-encoded price message
let signature = #"fbb6537a77aa2fa4..." // 64-byte Ed25519 signature

builtin.verify_ed25519_signature(pub_key, message, signature)
}
```

## Summary

1. **Understand the data structure** - Know what Aiken expects (Order vs MerklizedOrderDatum)
2. **Match CBOR serialization** - Tuples as arrays, booleans as constructors
3. **Compute correct hashes** - Use blake2b_256 for paths, MPF library for roots
4. **Test incrementally** - Use debug traces to compare CBOR hex values
5. **Use Alice/Bob tests** - Reference `validators/tests/alice_bob/` for complete MPF operation coverage
28 changes: 28 additions & 0 deletions lib/hydra_dex/account_utils.ak
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,34 @@ pub fn create_account_outputs_filter(
}
}

pub fn create_master_key_inputs_filter(
master_key: Credential,
hydra_account_script_hash: ByteArray,
) {
fn(input: Input) {
if input.output.address == from_script(hydra_account_script_hash) {
expect input_datum: UserAccount = input_inline_datum(input)
get_master_key(input_datum) == master_key
} else {
False
}
}
}

pub fn create_master_key_outputs_filter(
master_key: Credential,
hydra_account_script_hash: ByteArray,
) {
fn(output: Output) {
if output.address == from_script(hydra_account_script_hash) {
expect output_datum: UserAccount = output_inline_datum(output)
get_master_key(output_datum) == master_key
} else {
False
}
}
}

// Account Value related

pub fn hash_token(policy_id: PolicyId, asset_name: AssetName) -> AssetName {
Expand Down
49 changes: 49 additions & 0 deletions lib/hydra_dex/deposit_utils.ak
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use aiken/cbor
use aiken/collection/dict
use cardano/assets.{Value}
use hydra_dex/types.{MValue}

pub fn to_mvalue(value: Value) -> MValue {
let data = cbor.serialise(value)
expect Some(mvalue_raw): Option<Data> = cbor.deserialise(data)
expect mvalue: MValue = mvalue_raw
mvalue
}

pub fn combine_m_value(left_m_value: MValue, right_m_value: MValue) -> MValue {
let left_dict = dict.from_pairs(left_m_value)
let right_dict = dict.from_pairs(right_m_value)

dict.union_with(
left_dict,
right_dict,
fn(_k, v1, v2) {
let v1_dict = dict.from_pairs(v1)
let v2_dict = dict.from_pairs(v2)
let all =
dict.union_with(
v1_dict,
v2_dict,
fn(_k, v_v_1, v_v_2) { Some(v_v_1 + v_v_2) },
)
|> dict.to_pairs()
Some(all)
},
)
|> dict.to_pairs()
}

// Calculate shares to mint for a deposit
// Initial deposit (total_shares == 0): share price = 1.0, so shares = usd_value
// Subsequent deposits: shares = (usd_value * total_shares) / vault_equity (round DOWN)
pub fn cal_shares_amount(
usd_value: Int,
vault_equity: Int,
total_shares: Int,
) -> Int {
if total_shares == 0 {
usd_value
} else {
usd_value * total_shares / vault_equity
}
}
Loading