-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Sketch out what a proxy script for DRep would look like.
- Loading branch information
0 parents
commit 2b32b55
Showing
8 changed files
with
832 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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/ |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
)? | ||
} |
Oops, something went wrong.