Skip to content

Commit

Permalink
Add Script clauses to multisig
Browse files Browse the repository at this point in the history
This allows someone to require that a script participate in the withdrawals.  For example, in Sundae v3, this would allow a protocol to create an order that can be cancelled by the protocol itself, enforcing that the funds are paid back to the protocol
  • Loading branch information
Quantumplation committed Nov 26, 2023
1 parent ae0852d commit 9f98156
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 37 deletions.
2 changes: 1 addition & 1 deletion aiken.lock
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ requirements = []
source = "github"

[etags]
"aiken-lang/stdlib@main" = [{ secs_since_epoch = 1699157609, nanos_since_epoch = 401573412 }, "a721cf2738274f806efefb5a33c6ff9ae049476f0d45a42049b71793949f4d1d"]
"aiken-lang/stdlib@main" = [{ secs_since_epoch = 1700966727, nanos_since_epoch = 192028417 }, "cf946239d3dd481ed41f20e56bf24910b5229ea35aa171a708edc2a47fc20a7b"]
107 changes: 71 additions & 36 deletions lib/sundae/multisig.ak
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use aiken/bytearray
use aiken/interval.{Finite, Interval, IntervalBound}
use aiken/list
use aiken/dict.{Dict}
use aiken/time.{PosixTime}
use aiken/transaction.{ValidityRange}
use aiken/transaction/credential.{StakeCredential, Inline, ScriptCredential}

pub type MultisigScript {
Signature { key_hash: ByteArray }
Expand All @@ -10,23 +13,25 @@ pub type MultisigScript {
AtLeast { required: Int, scripts: List<MultisigScript> }
Before { time: PosixTime }
After { time: PosixTime }
Script { script_hash: ByteArray }
}

pub fn satisfied(
script: MultisigScript,
signatories: List<ByteArray>,
valid_range: ValidityRange,
withdrawals: Dict<StakeCredential, Int>,
) -> Bool {
when script is {
Signature { key_hash } -> list.has(signatories, key_hash)
AllOf { scripts } ->
list.all(scripts, fn(s) { satisfied(s, signatories, valid_range) })
list.all(scripts, fn(s) { satisfied(s, signatories, valid_range, withdrawals) })
AnyOf { scripts } ->
list.any(scripts, fn(s) { satisfied(s, signatories, valid_range) })
list.any(scripts, fn(s) { satisfied(s, signatories, valid_range, withdrawals) })
AtLeast { required, scripts } ->
required <= list.count(
scripts,
fn(s) { satisfied(s, signatories, valid_range) },
fn(s) { satisfied(s, signatories, valid_range, withdrawals) },
)
// TODO: could be simplified with https://github.com/aiken-lang/aiken/issues/566
Before { time } ->
Expand All @@ -49,6 +54,7 @@ pub fn satisfied(
}
_ -> False
}
Script { script_hash } -> dict.has_key(withdrawals, Inline(ScriptCredential(script_hash)))
}
}

Expand All @@ -65,12 +71,15 @@ test satisfying() {
let before = Before { time: 10 }
let after = After { time: 10 }
let between = AllOf { scripts: [After { time: 10 }, Before { time: 15 }] }
let script = Script { script_hash: "some_script" }
let vesting =
AnyOf {
scripts: [
AllOf { scripts: [before, sig1] },
// clawback
AllOf { scripts: [after, sig2] },
// dao clawback
script,
],
}
// vested
Expand All @@ -87,44 +96,70 @@ test satisfying() {
},
}
}
let no_w = dict.new()
let correct_credential = Inline(ScriptCredential("some_script"))
let incorrect_credential = Inline(ScriptCredential("other_script"))
let compare_credential = fn(a: StakeCredential, b: StakeCredential) -> Ordering {
when a is {
Inline(ScriptCredential(a))-> {
when b is {
Inline(ScriptCredential(b)) -> bytearray.compare(a, b)
_ -> Less
}
}
_ -> Greater
}
}
let correct_w = dict.new()
|> dict.insert(correct_credential, 0, compare_credential)
let incorrect_w = dict.new()
|> dict.insert(incorrect_credential, 0, compare_credential)
let both_w = dict.new()
|> dict.insert(correct_credential, 0, compare_credential)
|> dict.insert(incorrect_credential, 0, compare_credential)
// Helper method because ? binds more tightly than !
let unsatisfied =
fn(n: MultisigScript, s: List<ByteArray>, v: ValidityRange) {
!satisfied(n, s, v)
fn(n: MultisigScript, s: List<ByteArray>, v: ValidityRange, w: Dict<StakeCredential, Int>) {
!satisfied(n, s, v, w)
}
list.all(
[
satisfied(sig1, [key_hash1], valid_range(0, 1))?,
satisfied(sig2, [key_hash1, key_hash2], valid_range(0, 1))?,
satisfied(all_of, [key_hash1, key_hash2], valid_range(0, 1))?,
satisfied(any_of, [key_hash2], valid_range(0, 1))?,
satisfied(at_least, [key_hash2, key_hash3], valid_range(0, 1))?,
satisfied(before, [], valid_range(0, 5))?,
satisfied(after, [], valid_range(15, 20))?,
satisfied(after, [], valid_range(10, 15))?,
satisfied(between, [], valid_range(12, 13))?,
satisfied(vesting, [key_hash1], valid_range(0, 5))?,
satisfied(vesting, [key_hash2], valid_range(15, 20))?,
unsatisfied(sig1, [key_hash2], valid_range(0, 1))?,
unsatisfied(sig3, [key_hash1, key_hash2], valid_range(0, 1))?,
unsatisfied(all_of, [key_hash1, key_hash3], valid_range(0, 1))?,
unsatisfied(any_of, [key_hash3], valid_range(0, 1))?,
unsatisfied(at_least, [key_hash2], valid_range(0, 1))?,
unsatisfied(before, [], valid_range(5, 15))?,
unsatisfied(before, [], valid_range(5, 10))?,
unsatisfied(before, [], valid_range(10, 10))?,
unsatisfied(after, [], valid_range(5, 15))?,
unsatisfied(between, [], valid_range(0, 5))?,
unsatisfied(between, [], valid_range(0, 13))?,
unsatisfied(between, [], valid_range(0, 20))?,
unsatisfied(between, [], valid_range(13, 20))?,
unsatisfied(between, [], valid_range(13, 15))?,
unsatisfied(between, [], valid_range(15, 20))?,
unsatisfied(vesting, [key_hash2], valid_range(0, 5))?,
unsatisfied(vesting, [key_hash1], valid_range(15, 20))?,
unsatisfied(vesting, [key_hash3], valid_range(10, 10))?,
unsatisfied(vesting, [key_hash3], valid_range(0, 5))?,
unsatisfied(vesting, [key_hash3], valid_range(15, 20))?,
satisfied(sig1, [key_hash1], valid_range(0, 1), no_w)?,
satisfied(sig2, [key_hash1, key_hash2], valid_range(0, 1), no_w)?,
satisfied(all_of, [key_hash1, key_hash2], valid_range(0, 1), no_w)?,
satisfied(any_of, [key_hash2], valid_range(0, 1), no_w)?,
satisfied(at_least, [key_hash2, key_hash3], valid_range(0, 1), no_w)?,
satisfied(before, [], valid_range(0, 5), no_w)?,
satisfied(after, [], valid_range(15, 20), no_w)?,
satisfied(after, [], valid_range(10, 15), no_w)?,
satisfied(between, [], valid_range(12, 13), no_w)?,
satisfied(script, [], valid_range(0, 1), correct_w)?,
satisfied(script, [], valid_range(0, 1), both_w)?,
satisfied(vesting, [key_hash1], valid_range(0, 5), no_w)?,
satisfied(vesting, [key_hash2], valid_range(15, 20), no_w)?,
satisfied(vesting, [key_hash3], valid_range(0, 1), correct_w)?,
unsatisfied(sig1, [key_hash2], valid_range(0, 1), no_w)?,
unsatisfied(sig3, [key_hash1, key_hash2], valid_range(0, 1), no_w)?,
unsatisfied(all_of, [key_hash1, key_hash3], valid_range(0, 1), no_w)?,
unsatisfied(any_of, [key_hash3], valid_range(0, 1), no_w)?,
unsatisfied(at_least, [key_hash2], valid_range(0, 1), no_w)?,
unsatisfied(before, [], valid_range(5, 15), no_w)?,
unsatisfied(before, [], valid_range(5, 10), no_w)?,
unsatisfied(before, [], valid_range(10, 10), no_w)?,
unsatisfied(after, [], valid_range(5, 15), no_w)?,
unsatisfied(between, [], valid_range(0, 5), no_w)?,
unsatisfied(between, [], valid_range(0, 13), no_w)?,
unsatisfied(between, [], valid_range(0, 20), no_w)?,
unsatisfied(between, [], valid_range(13, 20), no_w)?,
unsatisfied(between, [], valid_range(13, 15), no_w)?,
unsatisfied(between, [], valid_range(15, 20), no_w)?,
unsatisfied(script, [], valid_range(0, 1), incorrect_w)?,
unsatisfied(vesting, [key_hash2], valid_range(0, 5), no_w)?,
unsatisfied(vesting, [key_hash1], valid_range(15, 20), no_w)?,
unsatisfied(vesting, [key_hash3], valid_range(10, 10), no_w)?,
unsatisfied(vesting, [key_hash3], valid_range(0, 5), no_w)?,
unsatisfied(vesting, [key_hash3], valid_range(15, 20), no_w)?,
unsatisfied(vesting, [key_hash3], valid_range(15, 20), incorrect_w)?,
],
fn(n) { n },
)
Expand Down

0 comments on commit 9f98156

Please sign in to comment.