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 7667f40
Show file tree
Hide file tree
Showing 8 changed files with 787 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"
134 changes: 134 additions & 0 deletions lib/cardano/credential/proxy.ak
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
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 fn for_block_production(st: State) -> Option<MultisigScript> {
st.block_production_delegate
}

pub fn for_governance(st: State) -> Option<MultisigScript> {
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,
script: Data,
delegate: Credential,
token_name: AssetName,
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 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, token_name) > 0

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

// Ensures the output holds the multisig script.
expect InlineDatum(script) == authorization.datum

1
}
Unregister -> -1
}

let minted_quantity =
self.mint
|> assets.quantity_of(policy_id, token_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,
choose_delegate: fn(State) -> Option<MultisigScript>,
) {
expect Script(policy_id) = delegate

let st =
list.foldr(
self.inputs,
fn() { fail },
fn(input, st) {
let tokens = assets.tokens(input.output.value, policy_id)
when dict.to_pairs(tokens) is {
[Pair(_, 1)] ->
fn() {
expect InlineDatum(st) = input.output.datum
expect st: State = st
st
}
_ -> st
}
},
)()

expect Some(script) = choose_delegate(st)
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 7667f40

Please sign in to comment.