diff --git a/on-chain/lib/aiken/merkle-patricia-forestry.ak b/on-chain/lib/aiken/merkle-patricia-forestry.ak index 92a8ad7..03de319 100644 --- a/on-chain/lib/aiken/merkle-patricia-forestry.ak +++ b/on-chain/lib/aiken/merkle-patricia-forestry.ak @@ -136,6 +136,29 @@ pub fn delete( MerklePatriciaForestry { root: excluding(key, proof) } } +/// **fails** when | the [Proof](#Proof) is invalid +/// --- | --- +/// **fails** when | there's no element in the trie at the given key +/// --- | --- +/// +/// Update an element in the trie with a a new value. This requires a [Proof](#Proof) +/// of the old element, to ensure its in the list, and a [Proof](#Proof) of the new +/// element, to re-add it. +/// +/// Can be thought of as a delete, followed by an insert, but is able to do it with one fewer +/// membership checks +pub fn update( + self: MerklePatriciaForestry, + key: ByteArray, + proof: Proof, + old_value: ByteArray, + new_value: ByteArray, +) { + expect including(key, old_value, proof) == self.root + // If we were doing a delete followed by an insert, we'd end up checking the `excluding` again here + MerklePatriciaForestry { root: including(key, new_value, proof) } +} + // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------- Proof // ----------------------------------------------------------------------------- diff --git a/on-chain/lib/aiken/merkle-patricia-forestry.tests.ak b/on-chain/lib/aiken/merkle-patricia-forestry.tests.ak index 4bd23cd..dd86528 100644 --- a/on-chain/lib/aiken/merkle-patricia-forestry.tests.ak +++ b/on-chain/lib/aiken/merkle-patricia-forestry.tests.ak @@ -256,6 +256,22 @@ test example_delete() { } } +test example_update() { + // https://media3.giphy.com/media/Bj5ILhCPm8EQ8/giphy.gif + mpf.update(trie(), banana, proof_banana(), "🍌", "🍆") == updated_banana() +} + +test example_idempotent_update() { + and { + mpf.update(trie(), banana, proof_banana(), "🍌", "🍌") == trie(), + mpf.update(updated_banana(), banana, proof_banana(), "🍆", "🍆") == updated_banana(), + } +} + +test example_fake_update() fail { + mpf.update(without_banana(), banana, proof_banana(), "🍌", "🍆") == updated_banana() +} + // -------------------- Some notable cases test example_insert_whatever() { @@ -470,6 +486,13 @@ fn without_banana() { ) } +// The root hash we get with `banana` mapped to 🍆 instead +fn updated_banana() { + mpf.from_root( + #"9057d02799a012a9d47fab6f9f5c43b4b2bf94584b339e3b4d3969fd95d55972", + ) +} + // ---------- blueberry const blueberry = "blueberry[uid: 0]"