Skip to content

Commit

Permalink
Sketch out what a proxy script for DRep would look like.
Browse files Browse the repository at this point in the history
  • Loading branch information
KtorZ committed Sep 5, 2024
0 parents commit 2b32b55
Show file tree
Hide file tree
Showing 8 changed files with 832 additions and 0 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Continuous Integration

on:
push:
branches: ["main"]
pull_request:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: aiken-lang/setup-aiken@v1
with:
version: v1.1.0
- run: aiken fmt --check
- run: aiken check -D
- run: aiken build
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Aiken compilation artifacts
artifacts/
# Aiken's project working directory
build/
# Aiken's default documentation export
docs/
373 changes: 373 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Proxy DReps

A validator to provide hot/cold account management to DReps. The scripts provides an authentication mecanism around an administrator multisig script (m-of-n type), itself granting powers to two sub-scripts to manage stake in two contexts:

- For block-production; or specifically delegation to stake pools and withdrawal of rewards.
- For governance; or specifically voting on governance action and management of DReps metadata.

This is achieved through the use of receipt tokens that are minted alongside the publication of certificates. The minting (resp. burning) of those tokens is tied to the registration (resp. unregistration) of their corresponding certificates.

## Configuration

The validator itself is bound to a particular administrator which can be configured directly in the `aiken.toml`.

```toml
[config.default]
threshold = 1 # How many administrors signatories are required to approve actions

# List of administators (verification key hashes)
[[config.default.administrators]]
bytes = "00000000000000000000000000000000000000000000000000000000"
encoding = "base16"

[[config.default.administrators]]
bytes = "00000000000000000000000000000000000000000000000000000001"
encoding = "base16"
```
38 changes: 38 additions & 0 deletions aiken.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# This file was generated by Aiken
# You typically do not need to edit this file

[[requirements]]
name = "aiken-lang/stdlib"
version = "v2.0.0"
source = "github"

[[requirements]]
name = "KtorZ/aicone"
version = "a9ae9ef8b6bdb183ea020ea97f6b648f9343924e"
source = "github"

[[requirements]]
name = "aiken-lang/fuzz"
version = "v2"
source = "github"

[[packages]]
name = "aiken-lang/stdlib"
version = "v2.0.0"
requirements = []
source = "github"

[[packages]]
name = "KtorZ/aicone"
version = "a9ae9ef8b6bdb183ea020ea97f6b648f9343924e"
requirements = []
source = "github"

[[packages]]
name = "aiken-lang/fuzz"
version = "v2"
requirements = []
source = "github"

[etags]
"aiken-lang/fuzz@v2" = [{ secs_since_epoch = 1725527905, nanos_since_epoch = 856547000 }, "34ffec10cce786bf823c7505589a3b5e0663792ef8efd31f870d7bcc37e0f593"]
38 changes: 38 additions & 0 deletions aiken.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name = "cardanosolutions/proxy-dreps"
version = "0.0.0"
compiler = "v1.1.0"
plutus = "v3"
license = "MPL-2.0"
description = "Aiken contracts for project 'cardanosolutions/proxy-dreps'"

[repository]
user = "cardanosolutions"
project = "proxy-dreps"
platform = "github"

[[dependencies]]
name = "aiken-lang/stdlib"
version = "v2.0.0"
source = "github"

[[dependencies]]
name = "KtorZ/aicone"
version = "a9ae9ef8b6bdb183ea020ea97f6b648f9343924e"
source = "github"

[[dependencies]]
name = "aiken-lang/fuzz"
version = "v2"
source = "github"

[config.default]
threshold = 1 # How many administrors signatories are required to approve actions

# List of administators (verification key hashes)
[[config.default.administrators]]
bytes = "00000000000000000000000000000000000000000000000000000000"
encoding = "base16"

[[config.default.administrators]]
bytes = "00000000000000000000000000000000000000000000000000000001"
encoding = "base16"
161 changes: 161 additions & 0 deletions lib/cardano/credential/proxy.ak
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
use aiken/collection/dict
use aiken/collection/list
use cardano/address.{Credential, Script}
use cardano/assets.{AssetName}
use cardano/transaction.{InlineDatum, Input, Output, Transaction}
use sundae/multisig.{MultisigScript}

pub type State {
block_production_delegate: Option<MultisigScript>,
governance_delegate: Option<MultisigScript>,
}

pub type Update {
Register
Unregister
}

pub type DelegateKind {
BlockProduction
Governance
}

pub fn delegate_kind_to_asset_name(kind: DelegateKind) -> AssetName {
when kind is {
BlockProduction -> "blk"
Governance -> "gov"
}
}

pub fn select_state(st: State, kind: DelegateKind) -> Option<MultisigScript> {
when kind is {
BlockProduction -> st.block_production_delegate
Governance -> st.governance_delegate
}
}

pub fn must_forward_script(outputs: List<Output>, from: Input) -> Bool {
expect Some(to) =
list.find(outputs, fn(output) { output.address == from.output.address })

// Ensures strict following of assets.
let from_assets = assets.without_lovelace(from.output.value)
let to_assets = assets.without_lovelace(to.value)
expect from_assets == to_assets

// Ensures ADA also follows, but may increase.
let from_lovelace = assets.lovelace_of(from.output.value)
let to_lovelace = assets.lovelace_of(to.value)
expect from_lovelace >= to_lovelace

// Ensure datum is identical
from.output.datum == to.datum
}

pub fn update_delegate(
self: Transaction,
redeemer: Data,
delegate: Credential,
kind: DelegateKind,
update: Update,
) -> Bool {
// Credential is necessarily a script. Note that the policy_id and
// the _actual_ delegate representative script hash are identical
// since they come from the same script hash.
expect Script(policy_id) = delegate

let asset_name = delegate_kind_to_asset_name(kind)

let expected_quantity =
when update is {
Register -> {
// When registering, we must ensure that one output targets the script.
// This outputs is assumed to hold the minted token.
expect Some(authorization) =
list.find(
self.outputs,
fn(output) { output.address.payment_credential == delegate },
)

// Ensures that the output holds the token.
expect
assets.quantity_of(authorization.value, policy_id, asset_name) > 0

// Ensures that the script is structurally valid, to avoid problems later.
expect script: MultisigScript = redeemer

// Ensures the state holds the delegate multisig script.
expect InlineDatum(datum) = authorization.datum
expect state: State = datum
expect select_state(state, kind) == Some(script)

1
}
// NOTE: We could also enforce that the state is reset back to 'None'
// when de-registering. But this isn't *necessary* since the delegate
// can only be approved if the corresponding token is present.
Unregister -> -1
}

let minted_quantity =
self.mint
|> assets.quantity_of(policy_id, asset_name)

// Ensure that the right amount of token is minted or burnt during the update.
(minted_quantity == expected_quantity)?
}

pub fn must_be_approved_by_delegate(
self: Transaction,
delegate: Credential,
kind: DelegateKind,
) {
expect Script(policy_id) = delegate

let expected_asset_name = delegate_kind_to_asset_name(kind)

let state =
list.foldr(
self.inputs,
fn() {
fail @"no authorized delegate"
},
fn(input, get_state) {
let tokens = assets.tokens(input.output.value, policy_id)
when dict.to_pairs(tokens) is {
[Pair(asset_name, 1)] ->
if (asset_name == expected_asset_name)? {
fn() {
expect InlineDatum(state) = input.output.datum
expect state: State = state
state
}
} else {
get_state
}
_ -> get_state
}
},
)()

expect Some(script) = select_state(state, kind)

multisig.satisfied(
script,
self.extra_signatories,
self.validity_range,
self.withdrawals,
)?
}

pub fn must_be_approved_by_administrator(
self: Transaction,
administrator: MultisigScript,
) {
multisig.satisfied(
administrator,
self.extra_signatories,
self.validity_range,
self.withdrawals,
)?
}
Loading

0 comments on commit 2b32b55

Please sign in to comment.