Compare commits
5 Commits
e2db317b30
...
62b4aa0523
Author | SHA1 | Date |
---|---|---|
![]() |
62b4aa0523 | |
![]() |
194492234e | |
![]() |
712c7cac44 | |
![]() |
c3f39d430d | |
![]() |
b340cfd2f0 |
|
@ -1,6 +1,6 @@
|
|||
.direnv/
|
||||
|
||||
_site
|
||||
docs/
|
||||
_cache
|
||||
|
||||
dist
|
||||
|
|
17
README.md
|
@ -1,16 +1,5 @@
|
|||
## Commands
|
||||
# Kompact.io site
|
||||
|
||||
recompile css
|
||||
```sh
|
||||
tailwindcss -i ./content/css/main.css -o ./content/css/mini.css --minify
|
||||
```
|
||||
## Commands
|
||||
|
||||
build, serve and watch
|
||||
```sh
|
||||
cabal run site -- watch
|
||||
```
|
||||
|
||||
deploy
|
||||
```sh
|
||||
rsync -r --delete ./_site/* genesis:/var/www/kompactio-landing/
|
||||
```
|
||||
Enter devshell, and run `menu` See flake for details.
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@font-face {
|
||||
/* Set in tailwindconfig */
|
||||
font-family: "jetbrains-mono";
|
||||
src:
|
||||
local("jetbrains-mono"),
|
||||
url("/fonts/JetBrainsMono-Medium.woff2") format("woff2");
|
||||
}
|
||||
|
||||
article {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
article > section > :is(pre, p, h1, h2, h3, h4, h5, h6) {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
article > section {
|
||||
font-family:
|
||||
"Lucida" Grande,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
article > section > :is(h1, h2, h3, h4, h5, h6, code) {
|
||||
font-family: "jetbrains-mono";
|
||||
}
|
||||
|
||||
article > section > blockquote {
|
||||
padding: 1rem;
|
||||
border-left-width: 4px;
|
||||
border-color: rgb(239 68 68);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
article > section > h1 {
|
||||
margin-top: 2rem;
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
article > section > h1::before {
|
||||
content: "# ";
|
||||
}
|
||||
|
||||
article > section > h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
article > section > h2::before {
|
||||
content: "## ";
|
||||
}
|
||||
|
||||
article > section > h3 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
article > section > h3::before {
|
||||
content: "### ";
|
||||
}
|
||||
|
||||
article > section > h4 {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
article > section > h4::before {
|
||||
content: "#### ";
|
||||
}
|
||||
|
||||
article > section {
|
||||
margin-top: 4rem;
|
||||
}
|
||||
|
||||
article a {
|
||||
text-decoration-color: rgb(239 68 68);
|
||||
text-decoration-thickness: 4px;
|
||||
text-decoration-line: underline;
|
||||
transition-duration: 70ms;
|
||||
}
|
||||
|
||||
article a:hover {
|
||||
text-decoration-thickness: 8px;
|
||||
text-decoration-color: rgb(185 28 28);
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
article ul {
|
||||
margin-left: 1rem;
|
||||
list-style-type: "- ";
|
||||
}
|
||||
|
||||
article ol {
|
||||
margin-left: 1rem;
|
||||
list-style: decimal inside;
|
||||
}
|
||||
|
||||
#footnotes {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
#footnotes > ol > li {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
#footnotes > ol > li > p {
|
||||
display: inline;
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
/* PrismJS 1.29.0
|
||||
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+bash+haskell+json+nix+racket+rust+scheme */
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: #000;
|
||||
background: 0 0;
|
||||
text-shadow: 0 1px #fff;
|
||||
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
|
||||
font-size: 1em;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
code[class*="language-"] ::-moz-selection,
|
||||
code[class*="language-"]::-moz-selection,
|
||||
pre[class*="language-"] ::-moz-selection,
|
||||
pre[class*="language-"]::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
code[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection,
|
||||
pre[class*="language-"] ::selection,
|
||||
pre[class*="language-"]::selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
@media print {
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: 0.5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #f5f2f0;
|
||||
}
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: 0.1em;
|
||||
border-radius: 0.3em;
|
||||
white-space: normal;
|
||||
}
|
||||
.token.cdata,
|
||||
.token.comment,
|
||||
.token.doctype,
|
||||
.token.prolog {
|
||||
color: #708090;
|
||||
}
|
||||
.token.punctuation {
|
||||
color: #999;
|
||||
}
|
||||
.token.namespace {
|
||||
opacity: 0.7;
|
||||
}
|
||||
.token.boolean,
|
||||
.token.constant,
|
||||
.token.deleted,
|
||||
.token.number,
|
||||
.token.property,
|
||||
.token.symbol,
|
||||
.token.tag {
|
||||
color: #905;
|
||||
}
|
||||
.token.attr-name,
|
||||
.token.builtin,
|
||||
.token.char,
|
||||
.token.inserted,
|
||||
.token.selector,
|
||||
.token.string {
|
||||
color: #690;
|
||||
}
|
||||
.language-css .token.string,
|
||||
.style .token.string,
|
||||
.token.entity,
|
||||
.token.operator,
|
||||
.token.url {
|
||||
color: #9a6e3a;
|
||||
background: hsla(0, 0%, 100%, 0.5);
|
||||
}
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword {
|
||||
color: #07a;
|
||||
}
|
||||
.token.class-name,
|
||||
.token.function {
|
||||
color: #dd4a68;
|
||||
}
|
||||
.token.important,
|
||||
.token.regex,
|
||||
.token.variable {
|
||||
color: #e90;
|
||||
}
|
||||
.token.bold,
|
||||
.token.important {
|
||||
font-weight: 700;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 672 B After Width: | Height: | Size: 672 B |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
@ -0,0 +1,19 @@
|
|||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// Get all "navbar-burger" elements
|
||||
var $navbarBurgers = Array.prototype.slice.call(
|
||||
document.querySelectorAll(".navbar-burger"),
|
||||
0,
|
||||
);
|
||||
// Check if there are any navbar burgers
|
||||
if ($navbarBurgers.length > 0) {
|
||||
// Add a click event on each of them
|
||||
$navbarBurgers.forEach(function ($el) {
|
||||
$el.addEventListener("click", function () {
|
||||
// Get the "main-nav" element
|
||||
var $target = document.getElementById("main-nav");
|
||||
// Toggle the class on "main-nav"
|
||||
$target.classList.toggle("hidden");
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
|
@ -5,7 +5,8 @@
|
|||
@font-face {
|
||||
/* Set in tailwindconfig */
|
||||
font-family: "jetbrains-mono";
|
||||
src: local("jetbrains-mono"),
|
||||
src:
|
||||
local("jetbrains-mono"),
|
||||
url("/fonts/JetBrainsMono-Medium.woff2") format("woff2");
|
||||
}
|
||||
|
||||
|
@ -13,37 +14,61 @@ article {
|
|||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
article>section> :is(p, h1, h2, h3, h4, h5, h6) {
|
||||
article > section > :is(pre, p, h1, h2, h3, h4, h5, h6) {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
article>section>h1 {
|
||||
article > section {
|
||||
font-family:
|
||||
"Lucida" Grande,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
article > section > :is(h1, h2, h3, h4, h5, h6, code) {
|
||||
font-family: "jetbrains-mono";
|
||||
}
|
||||
|
||||
article > section > blockquote {
|
||||
padding: 1rem;
|
||||
border-left-width: 4px;
|
||||
border-color: rgb(239 68 68);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
article > section > h1 {
|
||||
margin-top: 2rem;
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
article>section>h1::before {
|
||||
article > section > h1::before {
|
||||
content: "# ";
|
||||
}
|
||||
|
||||
article>section>h2 {
|
||||
margin-top: 2rem;
|
||||
article > section > h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
article>section>h2::before {
|
||||
article > section > h2::before {
|
||||
content: "## ";
|
||||
}
|
||||
|
||||
article>section>h3::before {
|
||||
article > section > h3 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
article > section > h3::before {
|
||||
content: "### ";
|
||||
}
|
||||
|
||||
article>section>h4::before {
|
||||
article > section > h4 {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
article > section > h4::before {
|
||||
content: "#### ";
|
||||
}
|
||||
|
||||
article>section {
|
||||
article > section {
|
||||
margin-top: 4rem;
|
||||
}
|
||||
|
||||
|
@ -61,6 +86,23 @@ article a:hover {
|
|||
}
|
||||
|
||||
article ul {
|
||||
margin-left: 2rem;
|
||||
list-style-type: "❯ ";
|
||||
}
|
||||
margin-left: 1rem;
|
||||
list-style-type: "- ";
|
||||
}
|
||||
|
||||
article ol {
|
||||
margin-left: 1rem;
|
||||
list-style: decimal inside;
|
||||
}
|
||||
|
||||
#footnotes {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
#footnotes > ol > li {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
#footnotes > ol > li > p {
|
||||
display: inline;
|
||||
}
|
||||
|
|
|
@ -1,275 +0,0 @@
|
|||
Aims:
|
||||
|
||||
> Describe the pipeline and components getting from Aiken to Uplc.
|
||||
|
||||
## The Preface
|
||||
|
||||
### Motivations
|
||||
|
||||
The motivation for writing this came from a desire to add additional features to Aiken not yet available.
|
||||
One such feature would evaluate an arbitrary function in Aiken callable from JavaScript.
|
||||
This would help a lot with testing trying to align on and off-chain code.
|
||||
|
||||
Another more pipe dreamy, ad-hoc function extraction - from a span of code, generate a function.
|
||||
A digression to answer _why would this be at all helpful?!_
|
||||
Validator logic often needs a broad context throughout.
|
||||
How then to best factor code?
|
||||
Possible solutions:
|
||||
|
||||
1. Introduce types / structs
|
||||
2. Have functions with lots of arguments
|
||||
3. Don't
|
||||
|
||||
The problems are:
|
||||
|
||||
1. Requires relentless constructing and deconstructing across the function call.
|
||||
And this is adds costs in Aiken.
|
||||
2. Becomes tedious aligning the definition and function call.
|
||||
3. End up with very long validators which are hard to unit test.
|
||||
|
||||
My current preferred way is to accept that validator functions are long.
|
||||
Ad-hoc function extraction would allow for sections of code to be tested without needing to be factored out.
|
||||
|
||||
To do either of these, we need to get to grips with the Aiken compilation pipeline.
|
||||
|
||||
### This won't age well
|
||||
|
||||
Aiken is undergoing active development.
|
||||
This post was started life with Aiken ~v1.14.
|
||||
With Aiken v1.15, there were already reasonably significant changes to the compilation pipeline.
|
||||
The word is that there aren't as big changes in the near future,
|
||||
but this article will undoubtedly begin to diverge from the current code-base even before publishing.
|
||||
|
||||
### Limitations of narrating code
|
||||
|
||||
Narrating code becomes a compromise between being honest and accurate, and being readable and digestible.
|
||||
Following the command `aiken build` covers well in excess of 10,000 LoC.
|
||||
The writing of this post ground slowly to a halt as it progressed deeper into the code
|
||||
with the details seeming to increase in importance.
|
||||
At some point I had to draw a line and resign to fact that some parts will remain black boxes for now.
|
||||
|
||||
## Aiken build
|
||||
|
||||
Tracing `aiken build`, the pipeline is roughly:
|
||||
```
|
||||
. -> Project::read_source_files ->
|
||||
Vec<Source> -> Project::parse_sources ->
|
||||
ParsedModules -> Project::type_check ->
|
||||
CheckedModules -> CodeGenerator::build ->
|
||||
AirTree -> AirTree::to_vec ->
|
||||
Vec<Air> -> CodeGenerator::uplc_code_gen ->
|
||||
Program / Term<Name> -> serialize ->
|
||||
.
|
||||
```
|
||||
We'll pick our way through these steps
|
||||
|
||||
At a high level we are trying to do something straightforward: reformulate Aiken code as Uplc.
|
||||
Some Aiken expressions are relatively easy to handle for example an Aiken `Int` goes to an `Int` in Uplc.
|
||||
Some Aiken expressions require more involved handling, for example an Aiken `If... If Else... Else `
|
||||
must have the branches "nested" in Uplc.
|
||||
Aiken also have lots of nice-to-haves like pattern matching, modules, and generics.
|
||||
Uplc has none of these.
|
||||
|
||||
### The Preamble
|
||||
|
||||
#### Cli handling
|
||||
|
||||
The cli enters at `aiken/src/cmd/mod.rs` which parses the command.
|
||||
With some establishing of context, the program enters `Project::build` (`crates/aiken-project/src/lib.rs`),
|
||||
which in turn calls `Project::compile`.
|
||||
|
||||
#### File crawl
|
||||
|
||||
The program looks for Aiken files in both `./lib` and `./validator` sub-directories.
|
||||
For each it walks over all contents (recursively) looking for `.ak` extensions.
|
||||
It treats these two sets of files a little differently.
|
||||
For example, only validator files can contain the special validator functions.
|
||||
|
||||
#### Parse and Type check
|
||||
|
||||
`Project::parse_sources` parses the module source code.
|
||||
The heavy lifting is done by `aiken_lang::parser::module`, which is evaluated on each file.
|
||||
It produces a `Module` containing a list of parsed definitions of the file: functions, types _etc_,
|
||||
together with metadata like docstrings and the file path.
|
||||
|
||||
`Project::type_check` inspects the parsed modules and, as the name implies, checks the types.
|
||||
It flags type level warnings and errors and constructs a hash map of `CheckedModule`s.
|
||||
|
||||
#### Code generator
|
||||
|
||||
The code generator `CodeGenerator` (`aiken-lang/src/gen_uplc.rs`) is given
|
||||
the definitions found from the previous step,
|
||||
together with the plutus builtins.
|
||||
It has additional fields for things like debugging.
|
||||
|
||||
This is handed over to a `Blueprint` (`aiken-project/src/blueprint/mod.rs`).
|
||||
The blueprint does little more than find the validators on which to run the code gen.
|
||||
The heavy lifting is done by `CodeGenerator::generate`.
|
||||
|
||||
We are now ready to take the source code and create plutus.
|
||||
|
||||
### In the air
|
||||
|
||||
Things become a bit intimidating at this point in terms of sheer lines of code:
|
||||
`gen_uplc.rs` and three modules in `gen_uplc/` totals > 8500 LoC.
|
||||
|
||||
Aiken has its own _intermediate representation_ called `air` (as in Aiken Intermediate Representation).
|
||||
These are common in compiled languages.
|
||||
`Air` is defined in `aiken-lang/src/gen_uplc/air.rs`.
|
||||
Unsurprisingly, it looks little bit like a language between Aiken and plutus.
|
||||
|
||||
In fact, Aiken has another intermediate representation: `AirTree`.
|
||||
This is constructed between the `TypedExpr` and `Vec<Air>` ie between parsed Aiken and air.
|
||||
|
||||
#### Climbing the AirTree
|
||||
|
||||
Within `CodeGenerator::generate`, `CodeGenerator::build` is called on the function body.
|
||||
This takes a `TypedExpr` and constructs and returns an `AirTree`.
|
||||
The construction is recursive as it traverses the recursive `TypedExpr` data structure.
|
||||
More on what an airtree is and its construction below.
|
||||
At the same time `self` is treated as `mut`, so we need to keep an eye on this too.
|
||||
The method which is called and uses this mutability of self is `self.assignment`.
|
||||
It does so by
|
||||
```sample
|
||||
self.assignment >> self.expect_type_assign >> self.code_gen_functions.insert
|
||||
```
|
||||
and thus is creating a hashmap of all the functions that appear in the definition.
|
||||
From the call to return of `assign` covers > 600 LoC so we'll leave this as otherwise a black box.
|
||||
(`self.handle_each_clause` is also called with `mut` which in turn calls `self.build` for which `mut` it is needed.)
|
||||
|
||||
Validators in Aiken are boolean functions while in Uplc they are unit-valued (aka void-valued) functions.
|
||||
Thus the air tree is wrapped such that `false` results in an error (`wrap_validator_condition`).
|
||||
I don't know why there is a prevailing thought that boolean functions are preferable than functions
|
||||
that error if anything is wrong - which is what validators are.
|
||||
|
||||
`check_validator_args` again extends the airtree from the previous step,
|
||||
and again calls `self.assignment` mutating self.
|
||||
Something interesting is happening here.
|
||||
Script context is the final argument of a validator - for any script purpose.
|
||||
`check_validator_args` treats the script context like it is an unused argument.
|
||||
The importance of this is not immediate, and I've still yet to appreciate why this happens.
|
||||
|
||||
Let's take a look at what AirTree actually is
|
||||
```rust
|
||||
pub enum AirTree {
|
||||
Statement {
|
||||
statement: AirStatement,
|
||||
hoisted_over: Option<Box<AirTree>>,
|
||||
},
|
||||
Expression(AirExpression),
|
||||
UnhoistedSequence(Vec<AirTree>),
|
||||
}
|
||||
```
|
||||
Note that `AirStatement` and `AirExpression` are mutually recursive definitions with `AirTree`.
|
||||
Otherwise, it would be unclear from first inspection how tree-like this really is.
|
||||
|
||||
`AirExpression` has multiple constructors. These include (non-exhaustive)
|
||||
|
||||
- air primitives (including all the ones that appear in plutus)
|
||||
- constructors `Call` and `Fn` to handle anonymous functions
|
||||
- binary and unary operators
|
||||
- handling when and if
|
||||
- handling error and tracing
|
||||
|
||||
`AirStatement` also has multiple constructors. These include
|
||||
|
||||
- let assignments and named function definitions
|
||||
- handling expect assignments
|
||||
- pattern matching
|
||||
- unwrapping data structures
|
||||
|
||||
Note that `AirTree` has many methods that are partial functions,
|
||||
as in there are possible states that are not considered legitimate
|
||||
at different points of its construction and use.
|
||||
For example `hoist_over` will throw an error if called on an `Expression`.
|
||||
As `AirTree` is for internal use only, the scope for potential problems is reasonably contained.
|
||||
It seems likely this is to avoid similar-yet-different IRs between steps.
|
||||
However, the trade off is that it partially obfuscates what is a valid state where.
|
||||
|
||||
What is hoisting? hoisting gives the airtree depth.
|
||||
The motivation is that by the time we hit Uplc it is "generally better"
|
||||
that
|
||||
|
||||
- function definitions appear once rather than being inlined multiple times
|
||||
- the definition appears as close to use as possible
|
||||
|
||||
Hoisting creates tree paths.
|
||||
The final airtree to airtree step is`self.hoist_functions_to_validator` traverses the paths.
|
||||
There is a lot of mutating of self, making it quite hard to keep a handle on things.
|
||||
In all this (several thousand?) LoC, it is essentially ascertaining in which node of the tree
|
||||
to insert each function definition.
|
||||
In a resource constrained environment like plutus, this effort is warranted.
|
||||
|
||||
At the same time this function deals with
|
||||
|
||||
- monomophisation - no more generics
|
||||
- erasing opaque types
|
||||
|
||||
Neither of which exist at the Uplc level.
|
||||
|
||||
#### Into Air
|
||||
|
||||
The `to_vec : AirTree -> Vec<Air>` is much easier to digest.
|
||||
For one, it is not evaluated in the context of the code generator,
|
||||
and two, there is no mutation of the airtree.
|
||||
The function recursively takes nodes of the tree and maps them to entries in a mutable vector.
|
||||
It flattens the tree to a vec.
|
||||
|
||||
### Down to Uplc
|
||||
|
||||
Next we go from `Vec<Air> -> Term<Name>`.
|
||||
This step is a little more involved than the previous.
|
||||
For one, this is executed in the context of the code generator.
|
||||
Moreover, the code generator is treated mutable - ouch.
|
||||
|
||||
On further inspection we see that the only mutation is setting `self.needs_field_access = true`.
|
||||
This flag informs the compiler that, if true, additional terms must be added in one of the final steps
|
||||
(see `CodeGenerator::finalize`).
|
||||
|
||||
As noted above, some of the mappings from air to terms are immediate like `Air::Bool -> Term::bool`.
|
||||
Others are less so.
|
||||
Some examples:
|
||||
|
||||
- `Air::Var` require 100 LoC to do case handling on different constructors.
|
||||
- Lists in air have no immediate analogue in uplc
|
||||
- builtins, as in built-in functions (standard shorthand), have to mediated
|
||||
with some combination of `force` and `delay` in order to behave as they should.
|
||||
- user functions must be "uncurried", ie treated as a sequence of single argument functions,
|
||||
and recursion must be handled
|
||||
- Do some magic in order to efficiently allow "record updates".
|
||||
|
||||
#### Cranking the Optimizer
|
||||
|
||||
There is a sequence of operations performed on the Uplc mapping `Term<Name> -> Term<Name>`.
|
||||
These remove inconsequential parts of the logic which will appear.
|
||||
These include:
|
||||
|
||||
- removing application of the identity function
|
||||
- directly substituting where apply lambda is applied to a constant or builtin
|
||||
- inline or simplify where apply lambda is applied to a parameter that appears once or not at all
|
||||
|
||||
Each of these optimizing methods has a its own relatively narrow focus,
|
||||
and so although there is a fair number of LoC, it's reasonably straightforward to follow.
|
||||
Some are applied multiple times.
|
||||
|
||||
### The End
|
||||
|
||||
The generated program can now be serialized and included in the blueprint.
|
||||
|
||||
### Plutus Core Signposting
|
||||
|
||||
All this fuss is to get us to a point where we can write Uplc - and good Uplc at that.
|
||||
Note that there's many ways to generate code and most of them are bad.
|
||||
The various design decisions and compilation steps make more sense
|
||||
when we have a better understanding of the target language.
|
||||
|
||||
Uplc is a lambda calculus.
|
||||
For a comprehensive definition on Uplc checkout the specification found
|
||||
[here](https://github.com/input-output-hk/plutus/#specifications-and-design) from the plutus GitHub repo.
|
||||
(I imagine this link will be maintained longer than the current actual link.)
|
||||
If you're not at all familiar with lambda calculus I recommend
|
||||
[an unpacking](https://crypto.stanford.edu/~blynn/lambda/) by Ben Lynn.
|
||||
|
||||
### What next?
|
||||
|
||||
I think it would be helpful to have some examples... Watch this space.
|
|
@ -1,3 +1,5 @@
|
|||
---
|
||||
title: Kompact.io
|
||||
---
|
||||
---
|
||||
|
||||
## Hero
|
||||
|
|
|
@ -3,74 +3,74 @@ title: Are we zk-Cardano yet?
|
|||
date: 2023-08-07
|
||||
---
|
||||
|
||||
Not so long ago Emurgo announced they were doing a Cardano centered hackathon.
|
||||
It was a welcome prospect - very few similar such events seem to exist in the space.
|
||||
Things went monotonically south ever since the announcement, but that's a different story.
|
||||
Not so long ago Emurgo announced they were doing a Cardano centered hackathon.
|
||||
It was a welcome prospect - very few similar such events seem to exist in the
|
||||
space. Things went monotonically south ever since the announcement, but that's a
|
||||
different story.
|
||||
|
||||
One particularly interesting quirk was that of the three "tracks" of the hackathon,
|
||||
one was _Zero Knowledge_ (aka zk).
|
||||
Why particularly interesting quirk? In some sense it is not surprising:
|
||||
zk has been very trendy these last few years around blockchains.
|
||||
However, building on Cardano is notoriously challenging.
|
||||
Building with zk on a zk-native blockchain is itself a very steep learning curve.
|
||||
So combining the two, zk on Cardano seemed... a bit mad.
|
||||
One particularly interesting quirk was that of the three "tracks" of the
|
||||
hackathon, one was _Zero Knowledge_ (aka zk). Why particularly interesting
|
||||
quirk? In some sense it is not surprising: zk has been very trendy these last
|
||||
few years around blockchains. However, building on Cardano is notoriously
|
||||
challenging. Building with zk on a zk-native blockchain is itself a very steep
|
||||
learning curve. So combining the two, zk on Cardano seemed... a bit mad.
|
||||
|
||||
This post is borne out of a best effort of how far "zk on Cardano" can be pushed.
|
||||
This post is borne out of a best effort of how far "zk on Cardano" can be
|
||||
pushed.
|
||||
|
||||
## What is zk?
|
||||
|
||||
There is no shortage of explanations describing what zk is
|
||||
( _eg_ [by Vitalik](https://vitalik.ca/general/2021/01/26/snarks.html){target="_blank"} or
|
||||
[a full mooc](https://zk-learning.org/){target="_blank"} ).
|
||||
There is also a reasonable breath to the field of zk that includes things like distributed compute.
|
||||
Zk involves some really neat maths that lets you do some seemingly magical feats
|
||||
and pairs well with blockchain in extending what is functionally possible.
|
||||
Let's stick to a simple and prototypical example.
|
||||
There is no shortage of explanations describing what zk is ( _eg_
|
||||
[by Vitalik](https://vitalik.ca/general/2021/01/26/snarks.html){target="\_blank"}
|
||||
or [a full mooc](https://zk-learning.org/){target="\_blank"} ). There is also a
|
||||
reasonable breath to the field of zk that includes things like distributed
|
||||
compute. Zk involves some really neat maths that lets you do some seemingly
|
||||
magical feats and pairs well with blockchain in extending what is functionally
|
||||
possible. Let's stick to a simple and prototypical example.
|
||||
|
||||
Suppose Alice and Bob are playing battleships.
|
||||
The game begins with Alice and Bob placing their ships within their own coordinate grid.
|
||||
They then take turns picking coordinates to "strike".
|
||||
If they hit nothing then their turn ends, but if they hit a ship then they strike again.
|
||||
The winner is the first to strike all coordinates containing their opponent's ships.
|
||||
Suppose Alice and Bob are playing battleships. The game begins with Alice and
|
||||
Bob placing their ships within their own coordinate grid. They then take turns
|
||||
picking coordinates to "strike". If they hit nothing then their turn ends, but
|
||||
if they hit a ship then they strike again. The winner is the first to strike all
|
||||
coordinates containing their opponent's ships.
|
||||
|
||||
Alice knows Bob as being a notorious liar; how can she enjoy the game?
|
||||
Each guess she makes, Bob gleefully shouts "Miss!".
|
||||
She can't ask Bob to show he's not lying by revealing the actual locations of the ships.
|
||||
She could ask Charlie to independently verify Bob's not lying,
|
||||
but then what if Charlie is actually on team Bob and also lies.
|
||||
Or Bob might suspect Charlie is actually on team Alice, slyly brought in to give Alice some hints.
|
||||
Each guess she makes, Bob gleefully shouts "Miss!". She can't ask Bob to show
|
||||
he's not lying by revealing the actual locations of the ships. She could ask
|
||||
Charlie to independently verify Bob's not lying, but then what if Charlie is
|
||||
actually on team Bob and also lies. Or Bob might suspect Charlie is actually on
|
||||
team Alice, slyly brought in to give Alice some hints.
|
||||
|
||||
Is there a way that Bob can prove to Alice that each guess is a miss,
|
||||
but without revealing the locations of the ships either to Alice or anyone else?
|
||||
Is there a way that Bob can prove to Alice that each guess is a miss, but
|
||||
without revealing the locations of the ships either to Alice or anyone else?
|
||||
|
||||
The answer is yes.
|
||||
Using zk Bob can produce a proof each time Alice's guess misses if and only if it honestly does.
|
||||
Alice can inspect each proof and verify Bob's response.
|
||||
Alice can interrogate the proof as much as she wants, but she won't learn anything more than
|
||||
her guess was a miss.
|
||||
The answer is yes. Using zk Bob can produce a proof each time Alice's guess
|
||||
misses if and only if it honestly does. Alice can inspect each proof and verify
|
||||
Bob's response. Alice can interrogate the proof as much as she wants, but she
|
||||
won't learn anything more than her guess was a miss.
|
||||
|
||||
There are a multitude of different ways to do this,
|
||||
but essentially it involves modeling the problem as a bunch of algebra
|
||||
over finite fields - like a lot of cryptography.
|
||||
|
||||
What's the _snark_ of zk-snark?
|
||||
Snark stands for _Succinct Non-Interactive Argument of Knowledge_.
|
||||
And without saying anything more, it means that Alice has to do way less algebra than Bob.
|
||||
In applications this is important because Bob might not be able to lie anymore but he could still waste Alice's time.
|
||||
There are a multitude of different ways to do this, but essentially it involves
|
||||
modeling the problem as a bunch of algebra over finite fields - like a lot of
|
||||
cryptography.
|
||||
|
||||
What's the _snark_ of zk-snark? Snark stands for _Succinct Non-Interactive
|
||||
Argument of Knowledge_. And without saying anything more, it means that Alice
|
||||
has to do way less algebra than Bob. In applications this is important because
|
||||
Bob might not be able to lie anymore but he could still waste Alice's time.
|
||||
|
||||
## Sudoku snark
|
||||
|
||||
Sudoku snark was the entrant to Emurgo's hackathon.
|
||||
The summary-pitch-story deck is [here](https://pub.kompact.io/sudoku-snark){target="_blank"}.
|
||||
Links to the associated repos: [plutus-zk](https://github.com/waalge/plutus-zk){target="_blank"}
|
||||
and [sudoku-snark](https://github.com/waalge/sudoku-snark){target="_blank"}.
|
||||
Sudoku snark was the entrant to Emurgo's hackathon. The summary-pitch-story deck
|
||||
is [here](https://pub.kompact.io/sudoku-snark){target="\_blank"}. Links to the
|
||||
associated repos:
|
||||
[plutus-zk](https://github.com/waalge/plutus-zk){target="\_blank"} and
|
||||
[sudoku-snark](https://github.com/waalge/sudoku-snark){target="\_blank"}.
|
||||
|
||||
Just after the hackathon got underway there was a
|
||||
[large PR merged](https://github.com/input-output-hk/plutus/pull/5231){target="_blank"}
|
||||
into the main branch of plutus.
|
||||
It's a mammoth culmination of many many months of work.
|
||||
In it were some fundamental primitives needed for running zk algorithms.
|
||||
Just after the hackathon got underway there was a
|
||||
[large PR merged](https://github.com/input-output-hk/plutus/pull/5231){target="\_blank"}
|
||||
into the main branch of plutus. It's a mammoth culmination of many many months
|
||||
of work. In it were some fundamental primitives needed for running zk
|
||||
algorithms.
|
||||
|
||||
The idea of the project was as follows:
|
||||
|
||||
|
@ -79,49 +79,55 @@ The idea of the project was as follows:
|
|||
- try to get a version of hydra running this newest version of plutus
|
||||
- wrap up in a gui
|
||||
|
||||
Unsurprisingly to anyone who's hung around the Cardano ecosystem long enough,
|
||||
this third part is where things got stuck.
|
||||
We did get as far as running a cluster of nodes in the Conway era with the latest version of plutus
|
||||
but unrelated changes seemed to thwart any chance of building transactions here.
|
||||
Unsurprisingly to anyone who's hung around the Cardano ecosystem long enough,
|
||||
this third part is where things got stuck. We did get as far as running a
|
||||
cluster of nodes in the Conway era with the latest version of plutus but
|
||||
unrelated changes seemed to thwart any chance of building transactions here.
|
||||
|
||||
A quick shout-out to the [modulo-p.io](https://modulo-p.io/){target="_blank"} team.
|
||||
They had a different approach and managed to implement a zk algorithm with the existing plutus primitives.
|
||||
This spared the need to play the foolhardy dependency bumping game with the Cardano node.
|
||||
However, because zk is so arithmetically intense,
|
||||
the app wont run outside a hydra head and with very generous max unit budgets (afaics).
|
||||
This approach won't be necessary when we have the new version of plutus available.
|
||||
Nonetheless, it's very neat to see it done and they packaged it very nicely.
|
||||
A quick shout-out to the [modulo-p.io](https://modulo-p.io/){target="\_blank"}
|
||||
team. They had a different approach and managed to implement a zk algorithm with
|
||||
the existing plutus primitives. This spared the need to play the foolhardy
|
||||
dependency bumping game with the Cardano node. However, because zk is so
|
||||
arithmetically intense, the app wont run outside a hydra head and with very
|
||||
generous max unit budgets (afaics). This approach won't be necessary when we
|
||||
have the new version of plutus available. Nonetheless, it's very neat to see it
|
||||
done and they packaged it very nicely.
|
||||
|
||||
The validator in Sudoku snark uses [groth16](https://eprint.iacr.org/2016/260.pdf).
|
||||
In part because this was already mostly available from the plutus repo itself.
|
||||
It is also the most obvious candidate to begin with.
|
||||
It's relatively mature, relatively simple, can be implemented from the new primitives,
|
||||
and importantly in Cardano land has small proof size.
|
||||
(As far as I know, the smallest of comparable algorithms.)
|
||||
The validator in Sudoku snark uses
|
||||
[groth16](https://eprint.iacr.org/2016/260.pdf). In part because this was
|
||||
already mostly available from the plutus repo itself. It is also the most
|
||||
obvious candidate to begin with. It's relatively mature, relatively simple, can
|
||||
be implemented from the new primitives, and importantly in Cardano land has
|
||||
small proof size. (As far as I know, the smallest of comparable algorithms.)
|
||||
|
||||
The program to generate the setup and proofs uses the Arkworks framework.
|
||||
Again this choice was initially inspired by a script from the IOG team,
|
||||
but again it seems like a smart choice.
|
||||
Arkworks is a well conceived, highly modular framework for zk,
|
||||
which makes it easy to pull in the bits we need to perform our off-chain logic.
|
||||
The program to generate the setup and proofs uses the Arkworks framework. Again
|
||||
this choice was initially inspired by a script from the IOG team, but again it
|
||||
seems like a smart choice. Arkworks is a well conceived, highly modular
|
||||
framework for zk, which makes it easy to pull in the bits we need to perform our
|
||||
off-chain logic.
|
||||
|
||||
The choice of game, sudoku, was in turn inspired by an arkworks example.
|
||||
It's not the most compelling of choices, but it's simple and it did for now.
|
||||
Battleships would have been more compelling or mastermind as the modulo-p team used.
|
||||
The choice of game, sudoku, was in turn inspired by an arkworks example. It's
|
||||
not the most compelling of choices, but it's simple and it did for now.
|
||||
Battleships would have been more compelling or mastermind as the modulo-p team
|
||||
used.
|
||||
|
||||
The intended game play involved locking Ada at a utxo correspondinig to a sudoku puzzle,
|
||||
and spendable only if a player could provide proof they knew the solution.
|
||||
Through the magic of zk they'd not disclose to the other competitors the solution itself.
|
||||
Other details were TBC: is it first and second prizes? are players whitelisted? _etc_.
|
||||
The intended game play involved locking Ada at a utxo correspondinig to a sudoku
|
||||
puzzle, and spendable only if a player could provide proof they knew the
|
||||
solution. Through the magic of zk they'd not disclose to the other competitors
|
||||
the solution itself. Other details were TBC: is it first and second prizes? are
|
||||
players whitelisted? _etc_.
|
||||
|
||||
## So are we zk-Cardano yet?
|
||||
|
||||
We're close.
|
||||
|
||||
There is potentially still quite a while before these new primitives in plutus reach mainnet.
|
||||
The word on the street is that it might happen before the end of 2023.
|
||||
There is potentially still quite a while before these new primitives in plutus
|
||||
reach mainnet. The word on the street is that it might happen before the end
|
||||
of 2023.
|
||||
|
||||
Even sooner, there will be versions of the Cardano node available with the new primitives,
|
||||
and so possibly plumb-able into hydra without causing oneself an aneurysm.
|
||||
Even sooner, there will be versions of the Cardano node available with the new
|
||||
primitives, and so possibly plumb-able into hydra without causing oneself an
|
||||
aneurysm.
|
||||
|
||||
In development time that's not so long: we can start thinking about what to build with zk on Cardano.
|
||||
In development time that's not so long: we can start thinking about what to
|
||||
build with zk on Cardano.
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
---
|
||||
title: Are we zk-Cardano yet?
|
||||
date: 2023-08-07
|
||||
---
|
||||
|
||||
Not so long ago Emurgo announced they were doing a Cardano centered hackathon.
|
||||
It was a welcome prospect - very few similar such events seem to exist in the space.
|
||||
Things went monotonically south ever since the announcement, but that's a different story.
|
||||
|
||||
One particularly interesting quirk was that of the three "tracks" of the hackathon,
|
||||
one was _Zero Knowledge_ (aka zk).
|
||||
Why particularly interesting quirk? In some sense it is not suprising:
|
||||
zK has been very trendy these last few years around blockchains.
|
||||
However, building on Cardano is notoriously challenging.
|
||||
Building with zk on a zk-native blockchain is itself a very steep learning curve.
|
||||
So combining the two, zk on Cardano seemed... a bit mad.
|
||||
|
||||
This post is bourne out of a best effort of how far "zk on cardano" can be pushed.
|
||||
|
||||
## What is zk?
|
||||
|
||||
There is no shortage of explanations describing what zk is [TODO: Links].
|
||||
There is also a reasonable breath to the field of zk that includes things like distributed compute.
|
||||
Zk involves some really neat maths that lets you do some seemingly magical feats,
|
||||
and pairs well with blockchain in extending what is functionally possible.
|
||||
Let's stick to a simple and prototypical example.
|
||||
|
||||
Suppose Alice and Bob are playing battleships.
|
||||
The game begins with Alice and Bob placing their ships within their own coordinate grid.
|
||||
They then take in terms picking coordinates to "bomb".
|
||||
If they hit nothing, then their turn ends, but if they hit a ship then they guess again.
|
||||
The winner is the first to sink all their oponents ships.
|
||||
|
||||
Alice knows Bob has a reputation of being a notorious liar; how can she enjoy the game?
|
||||
Each guess she makes, Bob says gleefully shouts "Miss!".
|
||||
She can't ask Bob to show he's not lying by revealing the actual locations of the ships.
|
||||
She could ask Charlie to independently verify Bob's not lying,
|
||||
but then what if Charlie is actually on team Bob and also lies.
|
||||
Or Bob might suspect Charlie is actually on team Alice, slyly brought in to give could Alice some hints.
|
||||
|
||||
Is there a way that Bob can prove to Alice that each guess is a miss,
|
||||
but without revealing the locations of the ships either to Alice or anyone else?
|
||||
|
||||
The answer is yes.
|
||||
Using zk Bob can produce a proof each time Alice's guess misses if and only if it honestly does.
|
||||
Alice can inspect each proof and verify Bob's response.
|
||||
Alice can interogate the proof as much as she wants, but she won't learn anything more than
|
||||
her guess was a miss.
|
||||
|
||||
There are multiplitude of different ways to do this,
|
||||
but essentially it involves modelling the problem as a bunch of algebra
|
||||
over finite fields - like a lot of cryptography.
|
||||
|
||||
What's the snark of zk-snark?
|
||||
Snark stands for _Succinct Non-Interactive Argument of Knowledge_.
|
||||
And without saying anything more: it means that Alice has to do way less algebra than Bob.
|
||||
In applications this is important, because Bob might not be able to lie anymore,
|
||||
but he could still waste Alice's time.
|
||||
|
||||
|
||||
## Sudoku snark
|
||||
|
||||
Sudoku snark was the entrant to Emurgo hackathon.
|
||||
The summary/ pitch/ story deck is [here](https://pub.kompact.io/sudoku-snark).
|
||||
Links to associated repos [plutus-zk]() and [sudoku-snark]().
|
||||
|
||||
Just after the hackathon got underway there was a large PR merged into the main branch of plutus.
|
||||
It's a mammoth PR that is the culmination of many many months of work.
|
||||
In it were some fundamental primitives needed for running zk algos.
|
||||
|
||||
The idea of the project was as follows:
|
||||
|
||||
- write a validator implementing a zk algorithm with the new primitives
|
||||
- write a programme to generate the setup and proofs
|
||||
- try to get a version of hydra running this newest version of plutus.
|
||||
|
||||
Unsurprisingly to anyone who's hung around the Cardano repos long enough,
|
||||
this final part is where things got stuck.
|
||||
Things got as far as running a cluster of nodes in the conway era supposedly with the latest plutus
|
||||
but some unrelated changes seemed to thwart any chance of building transactions.
|
||||
|
||||
The validator uses [groth16].
|
||||
In part because this was already mostly available from the plutus repo itself.
|
||||
It is also the most obvious candidate to begin with.
|
||||
It's relatively mature, relatively simple, can be implemented from the new primitives,
|
||||
and, importantly in cardano land, has small proof size.
|
||||
(As far as I know, the smallest of comparable algos.)
|
||||
|
||||
The program to generate the setup and proofs uses the arkworks framework.
|
||||
Again this was initially inspired by a script from the IOG team.
|
||||
|
||||
The choice of game, sudoku, was in turn inspired by an arkworks example.
|
||||
It's not the most compelling of choices, but it did for now.
|
||||
|
||||
The intended game play involved locking ada at a utxo
|
||||
spendable only if a player could provide proof you knew the solution.
|
||||
And through the magic of zk, not disclosing to the competition the solution itself.
|
||||
Other details were TBC: is it first and second prizes? are players whitelisted?
|
||||
|
||||
## So are we zk-Cardano yet?
|
||||
|
||||
We're close.
|
||||
|
||||
There is potentially still quite a stretch between being in the plutus repo and being run on-chain.
|
||||
The word on the street is that it might happen before the end of 2023.
|
||||
|
||||
Before it's available on mainnet there will be versions the cardano node available,
|
||||
and so possibly plumbable into hydra without causing oneself an aneurysm.
|
|
@ -0,0 +1,103 @@
|
|||
---
|
||||
title: "Hydra is cool: You don't need Hydra"
|
||||
date: 2023-09-20
|
||||
---
|
||||
|
||||
## Hydra is cool
|
||||
|
||||
Hydra[^1] is a very cool project. It is a layer 2 for Cardano that is
|
||||
_isomorphic_ to the L1. Here isomorphic means that Plutus runs in Hydra just
|
||||
like it does on the L1. That dapp you've just toiled over for months to run on
|
||||
the L1 can be put in Hydra and 'just work'.
|
||||
|
||||
[^1]:
|
||||
This post does not distinguish between Hydra and Hydra Head referring to
|
||||
both as Hydra. If you want to know more about Hydra, then check out their
|
||||
[explainers](https://hydra.family/head-protocol/core-concepts).
|
||||
|
||||
## Hydra's compromise
|
||||
|
||||
Hydra boasts it can achieve higher throughput and lower transaction fees
|
||||
compared to the Cardano L1 as well as near instant settling and no roll-backs.
|
||||
You may be asking _If my dapp just works on Hydra and it's better in all key
|
||||
respects, then why don't we all just use Hydra?_. The answer is because these
|
||||
improvements come at a cost. Consensus in Hydra differs from that on the L1.
|
||||
Hydra doesn't use ouroboros. Instead all participating hydra nodes must sign-off
|
||||
on all updates to the chain state. Practically speaking, far fewer nodes can
|
||||
participate in Hydra and one quiet node stops the whole Hydra chain updating.
|
||||
Not great for an L1.
|
||||
|
||||
## You don't need Hydra
|
||||
|
||||
Hydra is an example of a way to do state channels. A state channel relies on the
|
||||
integrity of the L1, while accumulating state separately from it (L2). At some
|
||||
point the layers are brought into sync. This is when funds on the L1 can be
|
||||
unlocked, and/or the state of the L2 updated.
|
||||
|
||||
Hydra could be thought to be providing some future-proofing. It is possible for
|
||||
a Hydra instance to run indefinitely and Plutus scripts not yet written will be
|
||||
executable in some already running instance. However, because Hydra's consensus
|
||||
is so brittle the longevity of an instance is not something to depend on. Each
|
||||
and any transaction may be its last.
|
||||
|
||||
A key question when considering Hydra is _Do I need isomorphic-ness?_. If you
|
||||
know all your business logic before instantiation then the answer is **no, you
|
||||
don't care for isomorphic-ness**. Instead, you can roll-your-own L2. It depends
|
||||
on your use case as to how much work that ends up being. It can be very simple.
|
||||
|
||||
## You don't want Hydra
|
||||
|
||||
In Hydra, the latest agreed state in the L2 is the one that the L1 will accept
|
||||
as the most legitimate. This is a sensible default.
|
||||
|
||||
Suppose however you have a game of poker where one player learns that they've
|
||||
lost and rage quits. From the game's perspective, that final transaction should
|
||||
be forced through - the player's loss is inevitable. At present this isn't
|
||||
possible with Hydra. If a party doesn't sign, then a state isn't valid.
|
||||
|
||||
In another use case, suppose there is some particularly intense on-chain
|
||||
verification that would be prohibitive on the L1 but that you'd like the results
|
||||
of which to persist onto the L1 and/or be recovered in future L2 instances. This
|
||||
could be done with validity tokens but anything minted in the L2 won't persist
|
||||
onto the L1.
|
||||
|
||||
Another key question then is _What is the right way to sync the L1 and L2
|
||||
states?_. Hydra has a way of it doing it which might or might not be appropriate
|
||||
for your use case. Rolling your own L2 means that the sync logic can fit your
|
||||
business needs. Both the cases above are resolvable with custom sync logic.
|
||||
|
||||
## An Example: Subbit.xyz
|
||||
|
||||
Probably the simplest, non-trivial example using state channels is
|
||||
[Subbit.xyz](https://subbit.xyz). Subbit.xyz is premised on the observation that
|
||||
subscription is a very common use case: there are two parties where one pays the
|
||||
other incrementally. It sacrifices generality to gain absolutely minimal
|
||||
overhead for both parties.
|
||||
|
||||
In Subbit.xyz, Alice, a consumer, subscribes to some service of Bob, a provider.
|
||||
Alice instantiates the channel by locking funds, similar to Hydra. There are
|
||||
only two mechanisms for unlocking - one for Alice and the other for Bob. All
|
||||
logic is known at instantiation.
|
||||
|
||||
A consumer needs only to keep track of their account balance, ascertain the cost
|
||||
of each outgoing request, and produce valid signatures for a few dozen bytes of
|
||||
data at a time. They don't need to watch the L1 and it's a non-chatty protocol.
|
||||
The low resource needs opens it up to applications on intermittently connected
|
||||
user devices such as laptops and mobile, and even micro-controllers. High
|
||||
throughput remains achievable.
|
||||
|
||||
A provider must track each subscriber's account, and periodically check the
|
||||
state of the L1. This could conceivably be as little as once a week or once a
|
||||
month. The low resource needs for a provider means they have the ability to
|
||||
serve more with less.
|
||||
|
||||
## Hydra for QoL
|
||||
|
||||
When Hydra reaches a point of maturity that it's plug and play, it's potentially
|
||||
far easier to deploy with Hydra then roll-your-own L2. Isomorphic-ness gives
|
||||
Hydra incredible flexibility and generality. You don't need isomorphic-ness but
|
||||
because of it, Hydra could be an easy and convenient solution.
|
||||
|
||||
As for custom sync logic, it is surely the case that there is a tranche of
|
||||
interesting applications where it's far easier and more effective to reuse Hydra
|
||||
infra and modify it than creating your own L2 from scratch.
|
|
@ -0,0 +1,272 @@
|
|||
---
|
||||
title: "Principles of dapp design"
|
||||
date: 2024-11-26
|
||||
status: published
|
||||
---
|
||||
|
||||
There are a collection of disparate thoughts on what makes for good design that
|
||||
inform the decisions we make when designing dapps.
|
||||
We thought it helpful to articulate them for ourselves, for any future
|
||||
collaborators, and any wider audience interested.
|
||||
These thoughts are expressed as diktats but are intended as perspective
|
||||
from which to pick through there shortcomings.
|
||||
|
||||
These "principles" don't fit neatly into some SOLID like framework. Some are
|
||||
high-level general security concious software dev stuff, others are quite
|
||||
specifically reflecting on a "building an L2 on cardano". Some implications
|
||||
manifest at the software architecture level, while others inform the development
|
||||
process.
|
||||
|
||||
## Context
|
||||
|
||||
Before unpacking the principles, it's worth reflecting on _devving in
|
||||
Cardanoland_.
|
||||
|
||||
The Cardano ledger is a lean by design: it is to provide a kernel of integrity
|
||||
for larger applications. It was never to, say, host and execute all the
|
||||
components of an application. It is even a little too lean in places (eg using
|
||||
key hashes over keys in the script context).
|
||||
|
||||
On-chain code is the code executed in the ledger. All integrity guarantees of an
|
||||
application built over ledger ultimately rely on the on-chain code. On-chain
|
||||
code is purely discriminatory: show it a transaction and it will succeed or
|
||||
fail. It cannot generate valid transactions. That is the responsibility for
|
||||
other, off-chain code. Off-chain code is a necessary part of any such
|
||||
application, but does not provide integrity guarantees.
|
||||
|
||||
We'll call applications, structured such that their integrity is backed by the
|
||||
ledger, "dapps". Typically the term has other assumptions: a decentralised
|
||||
application should be run-able by anyone with internet access, reasonable
|
||||
hardware, and without need to seek permission from some authority.
|
||||
|
||||
On-chain code takes form as Plutus validators. Writing validators as
|
||||
**extreme programming**.
|
||||
Lets immediately disambiguate with the [XP methedology][xp-wiki].
|
||||
It's extreme in the sense that it is highly unusual.
|
||||
|
||||
[xp-wiki]: https://en.wikipedia.org/wiki/Extreme_programming
|
||||
|
||||
It's extreme in the following ways:
|
||||
|
||||
- Highly constrained execution environment, which has multiple implications
|
||||
- Diminishing returns of code re-use
|
||||
- Conflicting motivation on factorization
|
||||
- Un-patch-ablity
|
||||
- High cost of failure
|
||||
- Functions that do nothing but fail
|
||||
|
||||
Resource limitations at runtime are incredibly restrictive. Moreover, all
|
||||
resources used have to be paid for. This is a concern is shared to some extent
|
||||
with low level programming but even there is relatively niche on modern
|
||||
hardware. There are many implications of this. A key one is that implementation
|
||||
matter.
|
||||
|
||||
Libraries have limited use. As noted, the efficiency of an implementation
|
||||
matters. If one needs to aggregate multiple values over a list of inputs, it is
|
||||
generally cheaper to do this in a single fold, and without
|
||||
constructing/deconstructing across a function boundary. Implementation details
|
||||
cannot be abstracted away in simple one-size-fits-all manner, at least at zero
|
||||
cost (cf plutus apps and the bloated outputs it would return). In real world
|
||||
examples this cost is significant enough, that the use of stock library methods
|
||||
must be considered. One saving grace is that this is manageable. The resource
|
||||
limitations mean that anything over a couple thousand lines of code risks not
|
||||
being usable in a transaction anyway.
|
||||
|
||||
Factorizing code in way that communicates purpose to a reader should be a strong
|
||||
consideration. However, we already have another, possibly conflicting,
|
||||
consideration: implementation efficiency. Some might say this is a compiler
|
||||
problem, and that we should have clever compilers. To which the most immediate
|
||||
answer is "yes, but right now we don't". It is also not a full panacea. A clever
|
||||
compiler is harder to reason about, harder to verify its correctness, and it can
|
||||
become more obscure to prod the compiler into pathways known to be more
|
||||
efficient.
|
||||
|
||||
Validators are, a priori, not patchable. Depending on the validator, once
|
||||
'deployed' it may be impossible to update. There is no way to bump or patch a
|
||||
validator once it is in use, without such functionality being designed in. This
|
||||
not unique. It was standard in pre-internet software where rolling updates were
|
||||
infeasible, and still exists for devices that aren't internet enabled. However,
|
||||
it is now far more the niche than the mainstream.
|
||||
|
||||
The correctness of a validator is high stakes. This is a property shared with
|
||||
any security critical software. It is not the same league as, say, aviation
|
||||
software, but it is much closer to that than a game or web app. If there is a
|
||||
bug, then user funds are at risk. This compounds being not patchable. Great
|
||||
efforts must be spent verifying validator correctness.
|
||||
|
||||
Validators are very unusual functions particularly in a functional paradigm.
|
||||
They take as input a very restricted set of args, and either fail or return
|
||||
unit. That's all we care about: discriminating acceptable transactions from
|
||||
unacceptable transactions. Sure, this akin to writing a test, or an assert
|
||||
condition - but these are commonly auxiliary rather than the culmination of the
|
||||
code base. Writing Plutus is not akin to, say, some web based API or an ETL
|
||||
pipeline. There is the potential for the code to be utilized by third parties if
|
||||
they desired to build their own transactions that involve the validators.
|
||||
However this use is generally secondary to optimizing for the intended set of
|
||||
transaction builders.
|
||||
|
||||
## Principles
|
||||
|
||||
### On-chain code is to keep participants safe from others
|
||||
|
||||
On-chain code **is** responsible for keeping the user safe from others. It is
|
||||
its fundamental responsibility.
|
||||
|
||||
On-chain code is **not** responsible for keeping the user safe from themselves.
|
||||
A user can compromise themselves completely and totally by, say, publishing their signing key,
|
||||
voiding any guarantees provided by on-chain code.
|
||||
Thus such guarantees are generally superfluous.
|
||||
|
||||
Off-chain code is responsible for keeping the user safe, and it is off-chain
|
||||
code that should make it hard for them to shoot themselves in the foot.
|
||||
|
||||
On-chain code is also not there to facilitate politeness. Good and bad behaviour
|
||||
is a binary distinction, discriminated thoroughly by a validator. A partner may
|
||||
stop responding for legitimate or malicious reasons. We do not need to
|
||||
speculate; we need only ensure that the other partner's funds are safe and can
|
||||
be recovered.
|
||||
|
||||
Suppose there is a dapp that requires the depositing of funds, with a pathway in which the funds
|
||||
may be returned. Further, that this return is verified by a signature belonging
|
||||
associated to key provided by the depositor.
|
||||
|
||||
Some may wish on-chain code to check everything it is possible to check.
|
||||
This includes keeping the user safe from themself.
|
||||
From this perspective the validator should
|
||||
have a constraint that this key has signed the tx, verifying that the depositor
|
||||
has the associated signing key.
|
||||
|
||||
According to this principle, the validator should not.
|
||||
The larger code base should.
|
||||
The documentation should be crystal clear what this key is for and how it should be managed.
|
||||
But the on-chain code should be solely focused on keeping the user safe from others, and other from the user.
|
||||
|
||||
There is a loose sense of the [streetlight effect](https://en.wikipedia.org/wiki/Streetlight_effect) bourne out in code.
|
||||
|
||||
### Simplicity invites security
|
||||
|
||||
Particularly for on-chain code, err on the side of simplicity. Design and build
|
||||
such that reasoning around scenarios is straightforward, and answering "what if
|
||||
..." questions are easy.
|
||||
|
||||
This should not be at the expense of being feature complete, although the
|
||||
principle then becomes a little grey on application. In places it translates to:
|
||||
develop an abstraction in which the features become simple. In other places, we
|
||||
will have to find the happy compromise between simple-ness and feature-ful-ness.
|
||||
For example, in the case of designing [Cardano Lightning](cardano-lightning.org)
|
||||
can a partner close two of their channels in a single transaction?
|
||||
This might be important but perhaps could be handled by an abstraction in the application
|
||||
hiding this detail to the user.
|
||||
|
||||
There is overlap here with the previous principle.
|
||||
By having the on-chain code laser focused, we don't have nice-to-haves,
|
||||
cluttering the code base, possibly obscuring our view from otherwise glaring bug.
|
||||
|
||||
The current principle further justifies excluding logic in the validator
|
||||
that a self interested participant would be motivated from ensuring themselves.
|
||||
For example: a validator must check that a thread token never leaves a script address;
|
||||
it need not check that a participant has paid themselves their due funds.
|
||||
|
||||
### Prioritise user autonomy
|
||||
|
||||
Deprioritise collaborative actions.
|
||||
|
||||
This principle is only employable only in specific circumstances.
|
||||
|
||||
In Cardano Lightning a user is responsible for their own funds, and only their
|
||||
own funds. They cannot spend their partners funds. For example, when winding
|
||||
down a channel, it requires each user to submit evidence of what they are owed.
|
||||
The pathway could have enforced that one partner left an address to which the
|
||||
other partner's tx would output the correct funds. It would potentially save a
|
||||
transaction in the winding down process. However, it also invites questions of
|
||||
double satisfaction, and resolutions to this make it harder to reason about.
|
||||
|
||||
Instead, following this principle,
|
||||
the design prioritises multi-channel management.
|
||||
A fundamental participant type in a healthy Lightning network is the Gateway.
|
||||
A gateway participant is highly
|
||||
connected within the network and is (financially) motivated to route payments
|
||||
between participants. A gateway node in particular needs to manage their
|
||||
channels, and manage their capital amongst channels as efficiently as possible.
|
||||
This in the whole network's interest.
|
||||
|
||||
Cardano Lightning does permit mutual agreed actions.
|
||||
However, such actions is considered as a secondary
|
||||
pathway. Any channel has (at most) the funds of only the two partners of the
|
||||
channel. Mutual actions are verified by the presence of both partners'
|
||||
signatures on a transaction. As we assume that any participant will act in a
|
||||
self interested manner, and is responsible for keeping themselves safe, few
|
||||
checks are done beyond this.
|
||||
|
||||
### Distinguish safety from convenience
|
||||
|
||||
Safety comes first, but we also need things to be practical and preferably even
|
||||
convenient. Make explicit when a feature has been included for safety, or is to
|
||||
do with convenience.
|
||||
|
||||
In some respects, this is restating of the first and or second principles.
|
||||
The on-chain code is precisely what keeps users safe.
|
||||
The small distinction is that this extends to off-code too.
|
||||
There are aspects of off-chain code that are integral, and other parts that are convenient.
|
||||
|
||||
### Spec first, implement second
|
||||
|
||||
A spec:
|
||||
|
||||
- Bridges from intent to code
|
||||
- Expels ambiguity early
|
||||
- Says no to feature creep
|
||||
|
||||
A spec bridges the design intentions to the implementation. Code is halfway
|
||||
house between what a human can parse and what a machine can parse. Where that
|
||||
halfway falls is a question for language design(ers), and there is a full
|
||||
spectrum of positions available with all the possible languages. Very roughly,
|
||||
the closer it is to human readable, the less efficient it ends up being executed
|
||||
by the machine.
|
||||
|
||||
Regardless of a language's expressiveness, it doesn't replace the need for
|
||||
additional documentation.
|
||||
"Self-describing code" is a nice idea and not without strong merits. Naming
|
||||
should follow conventions, and should not be knowingly obscure. In software at
|
||||
large, the problem of separate documentation falling out of sync is observed ad
|
||||
nauseum. But. The idea that descriptive names and some inline comments are
|
||||
sufficient to communicate design and intent is not credible. [I agree with
|
||||
Jef][jef-raskin-blog].
|
||||
|
||||
[jef-raskin-blog]: https://queue.acm.org/detail.cfm?id=1053354
|
||||
|
||||
The above is especially acute in the context of the extreme programming paradigm
|
||||
that is Plutus development. We can and should demand more attention from a dev
|
||||
engaging with the application. They should not expect to "intuitively"
|
||||
understand how to interface with the code. Again - not to be justify any
|
||||
obscurity of design or code, but a validator is not simply just another library
|
||||
they'd be interfacing with. The stakes are too high. They must read the spec.
|
||||
|
||||
We suffer less from docs/code divergence than is experienced in "normal" development.
|
||||
For the same reason that we have un-patch-ablity we have a fixed feature set.
|
||||
It is in evolving software and its code base, by adding new features or modifying existing ones,
|
||||
when code diverges from docs.
|
||||
|
||||
A spec helps expel ambiguity early. It provides an opportunity to check that
|
||||
everyone is on the same page before any lines of code are written, and without
|
||||
having to unpick lines of code after the fact.
|
||||
|
||||
A spec helps make the implementation stage straightforward and intentional. It
|
||||
greatly reduces the required bandwidth since each part of the code has a
|
||||
prescribed purpose. This is also reduces the cost of writing wrong things,
|
||||
before settling on something acceptable.
|
||||
|
||||
Having a spec combats feature creep. Adding feature requirements part way
|
||||
through implementation can lead to convoluted and design and code, and
|
||||
ultimately greatly increase the chance of bugs. As discussed for on-chain code,
|
||||
rolling updates are not generally possible. We need to make sure from the start
|
||||
what the feature requirements are (and as importantly what they aren't).
|
||||
|
||||
## Summary
|
||||
|
||||
The principles have been arrived up over numerous projects,
|
||||
most explicitly and recently while working on Cardano Lightning.
|
||||
As alluded to in the introduction, these principles should ...
|
||||
well, be treated more like [guidelines](https://youtu.be/k9ojK9Q_ARE).
|
||||
|
||||
If you comments, questions, suggested, or criticisms, please get in touch.
|
|
@ -0,0 +1,295 @@
|
|||
---
|
||||
title: Tracing Aiken Build
|
||||
date: 2023-09-02
|
||||
---
|
||||
|
||||
Aims:
|
||||
|
||||
> Describe the pipeline and components getting from Aiken to Uplc.
|
||||
|
||||
## The Preface
|
||||
|
||||
### Motivations
|
||||
|
||||
The motivation for writing this came from a desire to add additional features to
|
||||
Aiken not yet available. One such feature would evaluate an arbitrary function
|
||||
in Aiken callable from JavaScript. This would help a lot with testing and when
|
||||
trying to align on and off-chain code.
|
||||
|
||||
Another more pipe dreamy, ad-hoc function extraction - from a span of code,
|
||||
generate a function. A digression to answer _why would this be at all helpful?!_
|
||||
Validator logic often needs a broad context throughout. How then to best factor
|
||||
code? Possible solutions:
|
||||
|
||||
1. Introduce types / structs
|
||||
2. Have functions with lots of arguments
|
||||
3. Don't
|
||||
|
||||
The problems are:
|
||||
|
||||
1. Requires relentless constructing and deconstructing across the function call.
|
||||
This adds costs.
|
||||
2. Becomes tedious aligning the definition and function call.
|
||||
3. Ends up with very long validators which are hard to unit test.
|
||||
|
||||
My current preferred way is to accept that validator functions are long. Ad-hoc
|
||||
function extraction would allow for sections of code to be tested without
|
||||
needing to be factored out.
|
||||
|
||||
To do either of these, we need to get to grips with the Aiken compilation
|
||||
pipeline.
|
||||
|
||||
### This won't age well
|
||||
|
||||
Aiken is undergoing active development. This post started life with Aiken
|
||||
~v1.14. Aiken v1.15 introduced reasonably significant changes to the compilation
|
||||
pipeline. The word is that there aren't any more big changes in the near future,
|
||||
but this article will undoubtedly begin to diverge from the current code-base
|
||||
even before publishing.
|
||||
|
||||
### Limitations of narrating code
|
||||
|
||||
Narrating code becomes a compromise between being honest and accurate, and being
|
||||
readable and digestible. The command `aiken build` covers well in excess of
|
||||
10,000 LoC. The writing of this post ground to a halt as it reached deeper into
|
||||
the code-base. To redeem it, some (possibly large) sections remain black boxes.
|
||||
|
||||
## Aiken build
|
||||
|
||||
Tracing `aiken build`, the pipeline is roughly:
|
||||
|
||||
```sample
|
||||
. -> Project::read_source_files ->
|
||||
Vec<Source> -> Project::parse_sources ->
|
||||
ParsedModules -> Project::type_check ->
|
||||
CheckedModules -> CodeGenerator::build ->
|
||||
AirTree -> AirTree::to_vec ->
|
||||
Vec<Air> -> CodeGenerator::uplc_code_gen ->
|
||||
Program / Term<Name> -> serialize ->
|
||||
.
|
||||
```
|
||||
|
||||
We'll pick our way through these steps
|
||||
|
||||
At a high level we are trying to do something straightforward: reformulate Aiken
|
||||
code as Uplc. Some Aiken expressions are relatively easy to handle for example
|
||||
an Aiken `Int` goes to an `Int` in Uplc. Some Aiken expressions require more
|
||||
involved handling, for example an Aiken `If... If Else... Else ` must have the
|
||||
branches "nested" in Uplc. Aiken has lots of nice-to-haves like pattern
|
||||
matching, modules, and generics; Uplc has none of these.
|
||||
|
||||
### The Preamble
|
||||
|
||||
#### Cli handling
|
||||
|
||||
The cli enters at `aiken/src/cmd/mod.rs` which parses the command. With some
|
||||
establishing of context, the program enters `Project::build`
|
||||
(`crates/aiken-project/src/lib.rs`), which in turn calls `Project::compile`.
|
||||
|
||||
#### File crawl
|
||||
|
||||
The program looks for Aiken files in both `./lib` and `./validator`
|
||||
sub-directories. For each it walks over all contents (recursively) looking for
|
||||
`.ak` extensions. It treats these two sets of files a little differently. For
|
||||
example, only validator files can contain the special validator functions.
|
||||
|
||||
#### Parse and Type check
|
||||
|
||||
`Project::parse_sources` parses the module source code. The heavy lifting is
|
||||
done by `aiken_lang::parser::module`, which is evaluated on each file. It
|
||||
produces a `Module` containing a list of parsed definitions of the file:
|
||||
functions, types _etc_, together with metadata like docstrings and the file
|
||||
path.
|
||||
|
||||
`Project::type_check` inspects the parsed modules and, as the name implies,
|
||||
checks the types. It flags type level warnings and errors and constructs a hash
|
||||
map of `CheckedModule`s.
|
||||
|
||||
#### Code generator
|
||||
|
||||
The code generator `CodeGenerator` (`aiken-lang/src/gen_uplc.rs`) is given the
|
||||
definitions found from the previous step, together with the plutus builtins. It
|
||||
has additional fields for things like debugging.
|
||||
|
||||
This is handed over to a `Blueprint` (`aiken-project/src/blueprint/mod.rs`). The
|
||||
blueprint does little more than find the validators on which to run the code
|
||||
gen. The heavy lifting is done by `CodeGenerator::generate`.
|
||||
|
||||
We are now ready to take the source code and create plutus.
|
||||
|
||||
### In the air
|
||||
|
||||
Things become a bit intimidating at this point in terms of sheer lines of code:
|
||||
`gen_uplc.rs` and three modules in `gen_uplc/` totals > 8500 LoC.
|
||||
|
||||
Aiken has its own _intermediate representation_ called `air` (as in Aiken
|
||||
Intermediate Representation). Intermediate representations are common in
|
||||
compiled languages. `Air` is defined in `aiken-lang/src/gen_uplc/air.rs`.
|
||||
Unsurprisingly, it looks a little bit like a language between Aiken and plutus.
|
||||
|
||||
In fact, Aiken has another intermediate representation: `AirTree`. This is
|
||||
constructed between the `TypedExpr` and `Vec<Air>` ie between parsed Aiken and
|
||||
air.
|
||||
|
||||
#### Climbing the AirTree
|
||||
|
||||
Within `CodeGenerator::generate`, `CodeGenerator::build` is called on the
|
||||
function body. This takes a `TypedExpr` and constructs and returns an `AirTree`.
|
||||
The construction is recursive as it traverses the recursive `TypedExpr` data
|
||||
structure. More on what an airtree is and its construction below. At the same
|
||||
time `self` is treated as `mut`, so we need to keep an eye on this too. The
|
||||
method which is called and uses this mutability of self is `self.assignment`. It
|
||||
does so by
|
||||
|
||||
```sample
|
||||
- self.assignment
|
||||
└ self.expect_type_assign
|
||||
└ self.code_gen_functions.insert
|
||||
```
|
||||
|
||||
and thus is creating a hashmap of all the functions that appear in the
|
||||
definition. From the call to return of `assign` covers > 600 LoC so we'll leave
|
||||
this as a black box. (`self.handle_each_clause` is also called with `mut` which
|
||||
in turn calls `self.build` for which `mut` it is needed.)
|
||||
|
||||
Validators in Aiken are boolean functions while in Uplc they are unit-valued
|
||||
(aka void-valued) functions. Thus the air tree is wrapped such that `false`
|
||||
results in an error (`wrap_validator_condition`). I don't know why there is a
|
||||
prevailing thought that boolean functions are preferable to functions that error
|
||||
if anything is wrong - which is what validators are.
|
||||
|
||||
`check_validator_args` again extends the airtree from the previous step, and
|
||||
again calls `self.assignment` mutating self. Something interesting is happening
|
||||
here. Script context is the final argument of a validator - for any script
|
||||
purpose. `check_validator_args` treats the script context like it is an unused
|
||||
argument. The importance of this is not immediate, and I've still yet to
|
||||
appreciate why this happens.
|
||||
|
||||
Let's take a look at what AirTree actually is
|
||||
|
||||
```language-rust
|
||||
pub enum AirTree {
|
||||
Statement {
|
||||
statement: AirStatement,
|
||||
hoisted_over: Option<Box<AirTree>>,
|
||||
},
|
||||
Expression(AirExpression),
|
||||
UnhoistedSequence(Vec<AirTree>),
|
||||
}
|
||||
```
|
||||
|
||||
Note that `AirStatement` and `AirExpression` are mutually recursive definitions
|
||||
with `AirTree`. Otherwise, it would be unclear from first inspection how
|
||||
tree-like this really is.
|
||||
|
||||
`AirExpression` has multiple constructors. These include (non-exhaustive)
|
||||
|
||||
- air primitives (including all the ones that appear in plutus)
|
||||
- constructors `Call` and `Fn` to handle anonymous functions
|
||||
- binary and unary operators
|
||||
- handling when and if
|
||||
- handling error and tracing
|
||||
|
||||
`AirStatement` also has multiple constructors. These include
|
||||
|
||||
- let assignments and named function definitions
|
||||
- handling expect assignments
|
||||
- pattern matching
|
||||
- unwrapping data structures
|
||||
|
||||
Note that `AirTree` has many methods that are partial functions, as in there are
|
||||
possible states that are not considered legitimate at different points of its
|
||||
construction and use. For example `hoist_over` will throw an error if called on
|
||||
an `Expression`. As `AirTree` is for internal use only, the scope for potential
|
||||
problems is reasonably contained. It seems likely this is to avoid
|
||||
similar-yet-different IRs between steps. However, the trade off is that it
|
||||
partially obfuscates what is a valid state where.
|
||||
|
||||
What is hoisting? Hoisting gives the airtree depth. The motivation is that by
|
||||
the time we hit Uplc it is "generally better" that
|
||||
|
||||
- function definitions appear once rather than being inlined multiple times
|
||||
- the definition appears as close to use as possible
|
||||
|
||||
Hoisting creates tree paths. The final airtree to airtree step,
|
||||
`self.hoist_functions_to_validator`, traverses these paths. There is a lot of
|
||||
mutating of self, making it quite hard to keep a handle on things. In all this
|
||||
(several thousand?) LoC, it is essentially ascertaining in which node of the
|
||||
tree to insert each function definition. In a resource constrained environment
|
||||
like plutus, this effort is warranted.
|
||||
|
||||
At the same time this function deals with
|
||||
|
||||
- monomophisation - no more generics
|
||||
- erasing opaque types
|
||||
|
||||
Neither of which exist at the Uplc level.
|
||||
|
||||
#### Into Air
|
||||
|
||||
The `to_vec : AirTree -> Vec<Air>` is much easier to digest. For one, it is not
|
||||
evaluated in the context of the code generator, and two, there is no mutation of
|
||||
the airtree. The function recursively takes nodes of the tree and maps them to
|
||||
entries in a mutable vector. It flattens the tree to a vec.
|
||||
|
||||
### Down to Uplc
|
||||
|
||||
Next we go from `Vec<Air> -> Term<Name>`. This step is a little more involved
|
||||
than the previous. For one, this is executed in the context of the code
|
||||
generator. Moreover, the code generator is treated as mutable - ouch.
|
||||
|
||||
On further inspection we see that the only mutation is setting
|
||||
`self.needs_field_access = true`. This flag informs the compiler that, if true,
|
||||
additional terms must be added in one of the final steps (see
|
||||
`CodeGenerator::finalize`).
|
||||
|
||||
As noted above, some of the mappings from air to terms are immediate like
|
||||
`Air::Bool -> Term::bool`.
|
||||
Others are less so. Some examples:
|
||||
|
||||
- `Air::Var` require 100 LoC to do case handling on different constructors.
|
||||
- Lists in air have no immediate analogue in uplc
|
||||
- builtins, as in built-in functions (standard shorthand), have to be mediated
|
||||
with some combination of `force` and `delay` in order to behave as they
|
||||
should.
|
||||
- user functions must be "uncurried", ie treated as a sequence of single
|
||||
argument functions, and recursion must be handled
|
||||
- Do some magic in order to efficiently allow "record updates".
|
||||
|
||||
#### Cranking the Optimizer
|
||||
|
||||
There is a sequence of operations performed on the Uplc, mapping
|
||||
`Term<Name> -> Term<Name>`. This removes inconsequential parts of the logic
|
||||
which have been generated, including:
|
||||
|
||||
- removing application of the identity function
|
||||
- directly substituting where apply lambda is applied to a constant or builtin
|
||||
- inline or simplify where apply lambda is applied to a parameter that appears
|
||||
once or not at all
|
||||
|
||||
Each of these optimizing methods has a its own relatively narrow focus, and so
|
||||
although there is a fair number of LoC, it's reasonably straightforward to
|
||||
follow. Some are applied multiple times.
|
||||
|
||||
### The End
|
||||
|
||||
The generated program can now be serialized and included in the blueprint.
|
||||
|
||||
### Plutus Core Signposting
|
||||
|
||||
All this fuss is to get us to a point where we can write Uplc - and good Uplc at
|
||||
that. Note that there are many ways to generate code and most of them are bad.
|
||||
The various design decisions and compilation steps make more sense when we have
|
||||
a better understanding of the target language.
|
||||
|
||||
Uplc is a lambda calculus. For a comprehensive definition on Uplc checkout the
|
||||
specification found
|
||||
[here](https://github.com/input-output-hk/plutus/#specifications-and-design)
|
||||
from the plutus GitHub repo. (I imagine this link will be maintained longer than
|
||||
the current actual link.) If you're not at all familiar with lambda calculus I
|
||||
recommend [an unpacking](https://crypto.stanford.edu/~blynn/lambda/) by Ben
|
||||
Lynn.
|
||||
|
||||
### What next?
|
||||
|
||||
I think it would be helpful to have some examples... Watch this space.
|
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
title: why is building txs hard?
|
||||
draft: true
|
||||
---
|
||||
|
||||
## What is a dapp?
|
||||
|
||||
A typical dapp has a number of components:
|
||||
|
||||
- Validators: also called the _on-chain_ part.
|
||||
The decentralized network of nodes that maintain the chain run this code as part of the process of deciding
|
||||
whether a tx is to be added to the chain or rejected.
|
||||
- Chain indexing: watches the chain and records the data relevant to the dapp.
|
||||
- Pretty front-end: typically how a user interacts with a dapp.
|
||||
- Tx-building code: A component of the frontend.
|
||||
It takes data from the user, the user's wallet, the chain-indexer and possibly elsewhere
|
||||
to construct txs to be submitted to the chain that the validators will deem acceptable.
|
||||
|
||||
Here we have really described a browser-based plutus dapp.
|
||||
Considering the term _dapp_ more generally, the Daedalus wallet is a dapp which is neither browser based nor involves any Plutus.
|
||||
Cli-based dapps also exist, such as multi-sig using native scripts.
|
||||
|
||||
## What is a tx?
|
||||
|
||||
At its core, the chain is a list of transaction outputs.
|
||||
The chain is changed by submitting a tx which "spends" existing unspent transaction outputs (utxos) and appending new ones.
|
||||
(There's other possible modifications too, like minting native assets and staking _etc_, but the key part is spending.)
|
||||
|
||||
The on-chain part is where the dapp has its integrity, but users can only interact with the on-chain part of the dapp by submitting txs.
|
||||
|
||||
The on-chain part is relatively simple.
|
||||
It inspects each tx that it is concerned with, and if it does not like what it sees, it fails.
|
||||
Cardano is, in this sense, a lean chain.
|
||||
|
||||
The off-chain part is relatively complex.
|
||||
There may be no, one, or many potential valid txs that would satisfy the user's intent.
|
||||
|
||||
At its core, a transaction is a list of inputs and outputs.
|
||||
The inputs are spent, and the outputs created.
|
||||
It must also contain the necessary signatures.
|
||||
|
||||
## Some history
|
||||
|
||||
When Plutus was first dreamed up, it wasn't just a language.
|
||||
It was whole environment in which dapps would be engaged with.
|
||||
The on-chain and off-chain code were coupled into a single framework with seamless extensive testing.
|
||||
Dapps ran in the _PAB_, Plutus Application Backend.
|
||||
Everything was great.
|
||||
|
||||
Except that it didn't work.
|
||||
The chain-indexer would periodically fall-over,
|
||||
it required users had to maintain a full node,
|
||||
and the api never matured in to something stable, complete, and bug-free.
|
||||
In addition, the validators it produced were bloated and un-optimized and would quickly hit the constraints of the cardano blockchain.
|
||||
|
||||
As a result teams turned to coming up with alternatives.
|
||||
One of the first was MLabs and co in creating pluto and plutarch.
|
||||
Later Aiken appeared to meet similar needs.
|
||||
These began resolving issues with the on-chain part.
|
||||
This left the off-chain part wanting.
|
|
@ -1,16 +0,0 @@
|
|||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Get all "navbar-burger" elements
|
||||
var $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
||||
// Check if there are any navbar burgers
|
||||
if ($navbarBurgers.length > 0) {
|
||||
// Add a click event on each of them
|
||||
$navbarBurgers.forEach(function ($el) {
|
||||
$el.addEventListener('click', function () {
|
||||
// Get the "main-nav" element
|
||||
var $target = document.getElementById('main-nav');
|
||||
// Toggle the class on "main-nav"
|
||||
$target.classList.toggle('hidden');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
86
flake.lock
|
@ -2,15 +2,14 @@
|
|||
"nodes": {
|
||||
"devshell": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"systems": "systems"
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1688380630,
|
||||
"narHash": "sha256-8ilApWVb1mAi4439zS3iFeIT0ODlbrifm/fegWwgHjA=",
|
||||
"lastModified": 1735644329,
|
||||
"narHash": "sha256-tO3HrHriyLvipc4xr+Ewtdlo7wM1OjXNjlWRgmM7peY=",
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"rev": "f9238ec3d75cefbb2b42a44948c4e8fb1ae9a205",
|
||||
"rev": "f7795ede5b02664b57035b3b757876703e2c3eac",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -24,11 +23,11 @@
|
|||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1683560683,
|
||||
"narHash": "sha256-XAygPMN5Xnk/W2c1aW0jyEa6lfMDZWlQgiNtmHXytPc=",
|
||||
"lastModified": 1738453229,
|
||||
"narHash": "sha256-7H9XgNiGLKN1G1CgRh0vUL4AheZSYzPm+zmZ7vxbJdo=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "006c75898cf814ef9497252b022e91c946ba8e17",
|
||||
"rev": "32ea77a06711b758da0ad9bd6a844c5740a87abd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -38,11 +37,11 @@
|
|||
},
|
||||
"flake-root": {
|
||||
"locked": {
|
||||
"lastModified": 1680964220,
|
||||
"narHash": "sha256-dIdTYcf+KW9a4pKHsEbddvLVSfR1yiAJynzg2x0nfWg=",
|
||||
"lastModified": 1723604017,
|
||||
"narHash": "sha256-rBtQ8gg+Dn4Sx/s+pvjdq3CB2wQNzx9XGFq/JVGCB6k=",
|
||||
"owner": "srid",
|
||||
"repo": "flake-root",
|
||||
"rev": "f1c0b93d05bdbea6c011136ba1a135c80c5b326c",
|
||||
"rev": "b759a56851e10cb13f6b8e5698af7b59c44be26e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -53,11 +52,11 @@
|
|||
},
|
||||
"haskell-flake": {
|
||||
"locked": {
|
||||
"lastModified": 1684180957,
|
||||
"narHash": "sha256-qtEZf4gcmQU5ePbFtltqpAS0PajWLURVC7nuoS46dSk=",
|
||||
"lastModified": 1739669127,
|
||||
"narHash": "sha256-2s3wYTqKq7aBa41VHWg/G2XAOii8MW+WAMtLdgy1cek=",
|
||||
"owner": "srid",
|
||||
"repo": "haskell-flake",
|
||||
"rev": "4e1c76de8795608bb47295c018b37a563c492fd2",
|
||||
"rev": "eabf8cf32e5f6a267ea637e1b3eabc9b7ddf29e1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -68,11 +67,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1677383253,
|
||||
"narHash": "sha256-UfpzWfSxkfXHnb4boXZNaKsAcUrZT9Hw+tao1oZxd08=",
|
||||
"lastModified": 1722073938,
|
||||
"narHash": "sha256-OpX0StkL8vpXyWOGUD6G+MA26wAXK6SpT94kLJXo6B4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9952d6bc395f5841262b006fbace8dd7e143b634",
|
||||
"rev": "e36e9f57337d0ff0cf77aceb58af4c805472bfae",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -84,29 +83,23 @@
|
|||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"dir": "lib",
|
||||
"lastModified": 1682879489,
|
||||
"narHash": "sha256-sASwo8gBt7JDnOOstnps90K1wxmVfyhsTPPNTGBPjjg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "da45bf6ec7bbcc5d1e14d3795c025199f28e0de0",
|
||||
"type": "github"
|
||||
"lastModified": 1738452942,
|
||||
"narHash": "sha256-vJzFZGaCpnmo7I6i416HaBLpC+hvcURh/BQwROcGIp8=",
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz"
|
||||
},
|
||||
"original": {
|
||||
"dir": "lib",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1684385584,
|
||||
"narHash": "sha256-O7y0gK8OLIDqz+LaHJJyeu09IGiXlZIS3+JgEzGmmJA=",
|
||||
"lastModified": 1739446958,
|
||||
"narHash": "sha256-+/bYK3DbPxMIvSL4zArkMX0LQvS7rzBKXnDXLfKyRVc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "48a0fb7aab511df92a17cf239c37f2bd2ec9ae3a",
|
||||
"rev": "2ff53fe64443980e139eaa286017f53f88336dd0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -118,16 +111,16 @@
|
|||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1680945546,
|
||||
"narHash": "sha256-8FuaH5t/aVi/pR1XxnF0qi4WwMYC+YxlfdsA0V+TEuQ=",
|
||||
"lastModified": 1735554305,
|
||||
"narHash": "sha256-zExSA1i/b+1NMRhGGLtNfFGXgLtgo+dcuzHzaWA6w3Q=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d9f759f2ea8d265d974a6e1259bd510ac5844c5d",
|
||||
"rev": "0e82ab234249d8eee3e8c91437802b32c74bb3fd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
|
@ -142,31 +135,16 @@
|
|||
"treefmt-nix": "treefmt-nix"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1684416994,
|
||||
"narHash": "sha256-KkZ9diPRl3Y05TngWYs/QhZKnI/3tA3s+2Hhmei8FnE=",
|
||||
"lastModified": 1738953846,
|
||||
"narHash": "sha256-yrK3Hjcr8F7qS/j2F+r7C7o010eVWWlm4T1PrbKBOxQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "42045102f90cfd23ca44ae4ef8362180fefcd7fd",
|
||||
"rev": "4f09b473c936d41582dd744e19f34ec27592c5fd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
152
flake.nix
|
@ -9,7 +9,7 @@
|
|||
flake-root.url = "github:srid/flake-root";
|
||||
};
|
||||
|
||||
outputs = inputs@{ flake-parts, ... }:
|
||||
outputs = inputs @ { flake-parts, ... }:
|
||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||
imports = [
|
||||
# To import a flake module
|
||||
|
@ -22,70 +22,118 @@
|
|||
inputs.flake-root.flakeModule
|
||||
];
|
||||
systems = [ "x86_64-linux" "aarch64-darwin" ];
|
||||
perSystem = { config, self', inputs', pkgs, system, ... }: {
|
||||
# Per-system attributes can be defined here. The self' and inputs'
|
||||
# module parameters provide easy access to attributes of the same
|
||||
# system.
|
||||
perSystem =
|
||||
{ config
|
||||
, self'
|
||||
, inputs'
|
||||
, pkgs
|
||||
, system
|
||||
, ...
|
||||
}: {
|
||||
# Per-system attributes can be defined here. The self' and inputs'
|
||||
# module parameters provide easy access to attributes of the same
|
||||
# system.
|
||||
|
||||
haskellProjects.default = {
|
||||
# packages.haskell-template.root = ./.; # Auto-discovered by haskell-flake
|
||||
overrides = self: super: { };
|
||||
devShell = {
|
||||
tools = hp: {
|
||||
treefmt = config.treefmt.build.wrapper;
|
||||
} // config.treefmt.build.programs;
|
||||
hlsCheck.enable = false;
|
||||
haskellProjects.default = {
|
||||
devShell = {
|
||||
tools = hp:
|
||||
{
|
||||
fourmolu = hp.fourmolu;
|
||||
hoogle = hp.hoogle;
|
||||
haskell-language-server = hp.haskell-language-server;
|
||||
treefmt = config.treefmt.build.wrapper;
|
||||
}
|
||||
// config.treefmt.build.programs;
|
||||
hlsCheck.enable = false;
|
||||
};
|
||||
autoWire = [ "packages" "apps" "checks" ]; # Wire all but the devShell
|
||||
};
|
||||
autoWire = [ "packages" "apps" "checks" ]; # Wire all but the devShell
|
||||
};
|
||||
|
||||
packages.default = self'.packages.kompact-site;
|
||||
packages.default = self'.packages.kompact-site;
|
||||
|
||||
treefmt.config = {
|
||||
inherit (config.flake-root) projectRootFile;
|
||||
package = pkgs.treefmt;
|
||||
flakeFormatter = false; # For https://github.com/numtide/treefmt-nix/issues/55
|
||||
treefmt.config = {
|
||||
inherit (config.flake-root) projectRootFile;
|
||||
package = pkgs.treefmt;
|
||||
flakeFormatter = false; # For https://github.com/numtide/treefmt-nix/issues/55
|
||||
|
||||
programs.ormolu.enable = true;
|
||||
programs.nixpkgs-fmt.enable = true;
|
||||
programs.cabal-fmt.enable = true;
|
||||
programs.hlint.enable = true;
|
||||
programs.ormolu.enable = true;
|
||||
programs.nixpkgs-fmt.enable = true;
|
||||
programs.cabal-fmt.enable = true;
|
||||
programs.hlint.enable = true;
|
||||
programs.alejandra.enable = true;
|
||||
|
||||
# We use fourmolu
|
||||
programs.ormolu.package = pkgs.haskellPackages.fourmolu;
|
||||
settings.formatter.ormolu = {
|
||||
options = [
|
||||
"--ghc-opt"
|
||||
"-XImportQualifiedPost"
|
||||
];
|
||||
# We use fourmolu
|
||||
programs.ormolu.package = pkgs.haskellPackages.fourmolu;
|
||||
settings.formatter.ormolu = {
|
||||
options = [
|
||||
"--ghc-opt"
|
||||
"-XImportQualifiedPost"
|
||||
];
|
||||
};
|
||||
|
||||
programs.prettier = {
|
||||
enable = true;
|
||||
settings = {
|
||||
printWidth = 80;
|
||||
proseWrap = "always";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
programs.prettier.enable = true;
|
||||
};
|
||||
|
||||
|
||||
# Equivalent to inputs'.nixpkgs.legacyPackages.hello;
|
||||
devShells.default = pkgs.mkShell {
|
||||
inputsFrom = [
|
||||
config.haskellProjects.default.outputs.devShell
|
||||
config.flake-root.devShell
|
||||
];
|
||||
packages = with pkgs; [
|
||||
caddy
|
||||
nil
|
||||
nodePackages_latest.vscode-langservers-extracted
|
||||
nodePackages_latest.tailwindcss
|
||||
nodePackages_latest.typescript-language-server
|
||||
haskellPackages.hakyll
|
||||
zlib
|
||||
];
|
||||
# Equivalent to inputs'.nixpkgs.legacyPackages.hello;
|
||||
devShells.default =
|
||||
let
|
||||
menu =
|
||||
pkgs.writeShellScriptBin "menu"
|
||||
''
|
||||
echo -e "\nCommands available: \n${
|
||||
builtins.foldl' (x: y: x + " -> " + (pkgs.lib.getName y) + "\n") "" my-packages
|
||||
}"
|
||||
'';
|
||||
my-packages = [
|
||||
menu
|
||||
build
|
||||
watch
|
||||
tailwind
|
||||
deploy
|
||||
];
|
||||
tailwind = pkgs.writeShellScriptBin "tailwind" ''
|
||||
tailwindcss -i ./content/css/main.css -o ./assets/css/mini.css --minify
|
||||
'';
|
||||
build = pkgs.writeShellScriptBin "build" ''
|
||||
${tailwind}/bin/tailwind
|
||||
cabal run site -- build
|
||||
'';
|
||||
watch = pkgs.writeShellScriptBin "watch" ''
|
||||
${tailwind}/bin/tailwind
|
||||
cabal run site -- watch
|
||||
'';
|
||||
deploy = pkgs.writeShellScriptBin "deploy" ''
|
||||
rsync -r --delete ./docs/* genesis:/var/www/kompactio-landing/
|
||||
'';
|
||||
in
|
||||
pkgs.mkShell {
|
||||
inputsFrom = [
|
||||
config.haskellProjects.default.outputs.devShell
|
||||
config.flake-root.devShell
|
||||
];
|
||||
packages = with pkgs;
|
||||
[
|
||||
caddy
|
||||
nil
|
||||
nodePackages_latest.vscode-langservers-extracted
|
||||
nodePackages_latest.tailwindcss
|
||||
nodePackages_latest.typescript-language-server
|
||||
haskellPackages.hakyll
|
||||
zlib
|
||||
]
|
||||
++ my-packages;
|
||||
};
|
||||
};
|
||||
};
|
||||
flake = {
|
||||
# The usual flake attributes can be defined here, including system-
|
||||
# agnostic ones like nixosModule and system-enumerating ones, although
|
||||
# those are more easily expressed in perSystem.
|
||||
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
name: example
|
||||
version: 0.1.0.0
|
||||
build-type: Simple
|
||||
cabal-version: >= 1.10
|
||||
|
||||
executable site
|
||||
main-is: site.hs
|
||||
build-depends: base == 4.*
|
||||
, hakyll == 4.15.*
|
||||
, hip == 1.5.*
|
||||
, filepath
|
||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
||||
default-language: Haskell2010
|
|
@ -0,0 +1,15 @@
|
|||
name: site
|
||||
version: 0.1.0.0
|
||||
build-type: Simple
|
||||
cabal-version: >=1.10
|
||||
|
||||
executable site
|
||||
main-is: site.hs
|
||||
build-depends:
|
||||
base >=4 && <5
|
||||
, filepath
|
||||
, hakyll >=4.16 && <4.17
|
||||
, hip >=1.5 && <1.6
|
||||
|
||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
||||
default-language: Haskell2010
|
56
site.hs
|
@ -1,55 +1,62 @@
|
|||
--------------------------------------------------------------------------------
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
import Data.Monoid (mappend)
|
||||
import Hakyll
|
||||
import System.FilePath (splitExtension, joinPath, splitDirectories, replaceExtension)
|
||||
|
||||
import Data.Monoid (mappend)
|
||||
import Hakyll
|
||||
import System.FilePath (joinPath, replaceExtension, splitDirectories, splitExtension)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
myConfiguration :: Configuration
|
||||
myConfiguration =
|
||||
defaultConfiguration
|
||||
{ destinationDirectory = "docs"
|
||||
}
|
||||
|
||||
main :: IO ()
|
||||
main = hakyll $ do
|
||||
match "content/favicon.png" $ do
|
||||
main = hakyllWith myConfiguration $ do
|
||||
match "assets/favicon.png" $ do
|
||||
route rmPrefix
|
||||
compile copyFileCompiler
|
||||
|
||||
match "content/images/*" $ do
|
||||
match "assets/images/*" $ do
|
||||
route rmPrefix
|
||||
compile copyFileCompiler
|
||||
|
||||
match "content/scripts/*" $ do
|
||||
route rmPrefix
|
||||
match "assets/scripts/*" $ do
|
||||
route rmPrefix
|
||||
compile copyFileCompiler
|
||||
|
||||
match "content/css/*" $ do
|
||||
route rmPrefix
|
||||
match "assets/css/*" $ do
|
||||
route rmPrefix
|
||||
compile compressCssCompiler
|
||||
|
||||
match "content/fonts/*" $ do
|
||||
route rmPrefix
|
||||
match "assets/fonts/*" $ do
|
||||
route rmPrefix
|
||||
compile copyFileCompiler
|
||||
|
||||
match "content/posts/*.md" $ do
|
||||
matchMetadata "content/posts/*.md" ((Just "true" /=) . lookupString "draft") $ do
|
||||
route rmPrefixMd
|
||||
compile $ pandocCompiler
|
||||
>>= loadAndApplyTemplate "templates/post.html" postCtx
|
||||
>>= loadAndApplyTemplate "templates/default.html" postCtx
|
||||
>>= relativizeUrls
|
||||
compile $
|
||||
pandocCompiler
|
||||
>>= loadAndApplyTemplate "templates/post.html" postCtx
|
||||
>>= loadAndApplyTemplate "templates/default.html" postCtx
|
||||
>>= relativizeUrls
|
||||
|
||||
create ["blog.html"] $ do
|
||||
route idRoute
|
||||
compile $ do
|
||||
posts <- recentFirst =<< loadAll "content/posts/*.md"
|
||||
let archiveCtx =
|
||||
listField "posts" postCtx (return posts) `mappend`
|
||||
constField "title" "Blog" `mappend`
|
||||
defaultContext
|
||||
listField "posts" postCtx (return posts)
|
||||
`mappend` constField "title" "Blog"
|
||||
`mappend` defaultContext
|
||||
|
||||
makeItem ""
|
||||
>>= loadAndApplyTemplate "templates/blog.html" archiveCtx
|
||||
>>= loadAndApplyTemplate "templates/default.html" archiveCtx
|
||||
>>= relativizeUrls
|
||||
|
||||
|
||||
match "content/index.md" $ do
|
||||
route rmPrefixMd
|
||||
compile $ do
|
||||
|
@ -63,18 +70,17 @@ main = hakyll $ do
|
|||
|
||||
match "templates/*" $ compile templateBodyCompiler
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
postCtx :: Context String
|
||||
postCtx =
|
||||
dateField "date" "%Y-%m-%d" `mappend`
|
||||
defaultContext
|
||||
dateField "date" "%Y-%m-%d"
|
||||
`mappend` defaultContext
|
||||
|
||||
setExtensionInner :: String -> FilePath -> FilePath
|
||||
setExtensionInner = flip replaceExtension
|
||||
|
||||
rmPrefixInner :: FilePath -> FilePath
|
||||
rmPrefixInner = joinPath . tail . splitDirectories
|
||||
rmPrefixInner = joinPath . tail . splitDirectories
|
||||
|
||||
rmPrefix :: Routes
|
||||
rmPrefix = customRoute $ rmPrefixInner . toFilePath
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./content/**/*.{html,js}",
|
||||
"./templates/**/*.{html,js}",
|
||||
],
|
||||
content: ["./content/**/*.{html,js}", "./templates/**/*.{html,js}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
fontFamily: {
|
||||
'sans' : ['jetbrains-mono',],
|
||||
sans: ["jetbrains-mono"],
|
||||
},
|
||||
typography: (theme) => ({}),
|
||||
},
|
||||
darkMode: 'class',
|
||||
darkMode: "class",
|
||||
variants: {},
|
||||
// plugins: [require('@tailwindcss/typography')],
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -1,27 +1,15 @@
|
|||
<section id="about" class="py-12 px-2 flex flex-col gap-12">
|
||||
<header class="text-3xl">
|
||||
# about
|
||||
</header>
|
||||
<header class="text-3xl"># about</header>
|
||||
<div>
|
||||
Kompact.io is dapp dev house.
|
||||
|
||||
Our focus:
|
||||
Kompact.io is dapp dev house. Our focus:
|
||||
<ul class="list-decoration">
|
||||
<li>
|
||||
safety-first
|
||||
</li>
|
||||
<li>
|
||||
fast turn around
|
||||
</li>
|
||||
<li>
|
||||
integration support
|
||||
</li>
|
||||
<li>safety-first</li>
|
||||
<li>fast turn around</li>
|
||||
<li>integration support</li>
|
||||
</ul>
|
||||
<div>
|
||||
Our typical process:
|
||||
<div>
|
||||
Idea -> Spec -> Impl -> Test -> Handover
|
||||
</div>
|
||||
<div>Idea -> Spec -> Impl -> Test -> Handover</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<section id="services" class="py-6 px-2 flex flex-col gap-12">
|
||||
<header class="text-3xl">
|
||||
# blog
|
||||
</header>
|
||||
<header class="text-3xl"># blog</header>
|
||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
A nascent initiative sharing some of the things happening at Kompact.io.
|
||||
</div>
|
||||
|
@ -9,4 +7,4 @@
|
|||
|
||||
<section class="py-6 px-2 flex flex-col gap-12">
|
||||
$partial("templates/post-list.html")$
|
||||
</section>
|
||||
</section>
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
<section id="contact" class="py-12 px-2 flex flex-col gap-12">
|
||||
<header class="text-3xl">
|
||||
# contact
|
||||
</header>
|
||||
<header class="text-3xl"># contact</header>
|
||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
Questions? We'll be happy to help answer any of your questions. Send us an email and we'll get back to you shortly.
|
||||
Questions? We'll be happy to help answer any of your questions. Send us an
|
||||
email and we'll get back to you shortly.
|
||||
</div>
|
||||
<div>
|
||||
Reach us on : <a href="mailto:kompactio@proton.me">kompactio@proton.me</a>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
|
|
@ -1,40 +1,41 @@
|
|||
<!doctype html>
|
||||
<html class="">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.png">
|
||||
<link href="/css/mini.css" rel="stylesheet">
|
||||
<title>$title$</title>
|
||||
</head>
|
||||
|
||||
<script>
|
||||
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
|
||||
function updateTheme() {
|
||||
if (
|
||||
localStorage.theme === 'dark' ||
|
||||
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||
) {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.png" />
|
||||
<link href="/css/mini.css" rel="stylesheet" />
|
||||
<link href="/css/prism.css" rel="stylesheet" />
|
||||
<title>$title$</title>
|
||||
</head>
|
||||
<script>
|
||||
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
|
||||
function updateTheme() {
|
||||
if (
|
||||
localStorage.theme === "dark" ||
|
||||
(!("theme" in localStorage) &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||
) {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
}
|
||||
updateTheme();
|
||||
</script>
|
||||
|
||||
}
|
||||
updateTheme()
|
||||
</script>
|
||||
<body
|
||||
class="bg-white text-gray-900 min-h-screen dark:bg-gradient-to-br dark:from-slate-950 dark:to-black dark:text-white"
|
||||
>
|
||||
<div class="container mx-auto">
|
||||
<hr />
|
||||
$partial("templates/nav.html")$
|
||||
<hr />
|
||||
$body$
|
||||
<hr />
|
||||
$partial("templates/footer.html")$
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<body class="bg-white text-gray-900 min-h-screen
|
||||
dark:bg-gradient-to-br dark:from-slate-950 dark:to-black dark:text-white">
|
||||
<div class="container mx-auto ">
|
||||
<hr />
|
||||
$partial("templates/nav.html")$
|
||||
<hr />
|
||||
$body$
|
||||
<hr />
|
||||
$partial("templates/footer.html")$
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<script src="/scripts/prism.js"></script>
|
||||
</html>
|
||||
|
|
|
@ -1,22 +1,34 @@
|
|||
<section id="footer" class="py-12 px-2 flex flex-row gap-12 mx-2 sm:mx-4 items-start justify-between
|
||||
text-gray-800 dark:text-gray-200 dark:fill-white">
|
||||
<div class="text-sm">
|
||||
® 2023 kompact.io ™ All Rights Reserved.
|
||||
</div>
|
||||
<section
|
||||
id="footer"
|
||||
class="py-12 px-2 flex flex-row gap-12 mx-2 sm:mx-4 items-start justify-between text-gray-800 dark:text-gray-200 dark:fill-white"
|
||||
>
|
||||
<div class="text-sm">® 2023 kompact.io ™ All Rights Reserved.</div>
|
||||
<div class="flex flex-row gap-4">
|
||||
<a href="https://www.linkedin.com/in/dominic-algernon-wallis-123b42187/">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" height="20" preserveAspectRatio="xMidYMid meet">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 448 512"
|
||||
height="20"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<path
|
||||
d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z" />
|
||||
d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://twitter.com/waalge">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="20" preserveAspectRatio="xMidYMid meet">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
height="20"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<path
|
||||
d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z" />
|
||||
d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
<section id="hero" class="py-8 px-2 h-96 min-h-[50vh] m-auto">
|
||||
<div class="h-full flex justify-around align-center items-center">
|
||||
<div class="text-6xl">
|
||||
⟨K⟩
|
||||
</div>
|
||||
<div class="text-6xl">⟨K⟩</div>
|
||||
<div class="flex flex-col gap-2 truncate">
|
||||
<div>withKompact $ <span class="text-red-500 dark:text-yellow-400">do</span> </div>
|
||||
<div><span class="text-gray-400">· ·</span> dapp <- lean dev </div>
|
||||
<div><span class="text-gray-400">· ·</span> run dapp </div>
|
||||
<div>
|
||||
withKompact $
|
||||
<span class="text-red-500 dark:text-yellow-400">do</span>
|
||||
</div>
|
||||
<div><span class="text-gray-400">· ·</span> dapp <- lean dev</div>
|
||||
<div><span class="text-gray-400">· ·</span> run dapp</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -4,4 +4,4 @@ $partial("templates/services.html")$
|
|||
<hr />
|
||||
$partial("templates/pricing.html")$
|
||||
<hr />
|
||||
$partial("templates/contact.html")$
|
||||
$partial("templates/contact.html")$
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<div class="text-1xl font-bold">
|
||||
$title$
|
||||
</div>
|
||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
$body$
|
||||
</div>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<div class="text-1xl font-bold">
|
||||
$title$
|
||||
</div>
|
||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
$body$
|
||||
</div>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<div class="index flex flex-col justify-between gap-4 sm:gap-8">
|
||||
$for(sections)$
|
||||
$body$
|
||||
$endfor$
|
||||
</div>
|
|
@ -9,7 +9,8 @@
|
|||
<div>
|
||||
<ul class="flex flex-row gap-4 md:gap-8">
|
||||
<li>
|
||||
<button onClick="
|
||||
<button
|
||||
onClick="
|
||||
(() => {
|
||||
if (!('theme' in localStorage)) {
|
||||
localStorage.theme = 'light'
|
||||
|
@ -22,19 +23,18 @@
|
|||
}
|
||||
updateTheme()
|
||||
})()
|
||||
">◧</button>
|
||||
"
|
||||
>
|
||||
◧
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/index.html#contact">
|
||||
contact
|
||||
</a>
|
||||
<a href="/index.html#contact"> contact </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/blog.html">
|
||||
blog
|
||||
</a>
|
||||
<a href="/blog.html"> blog </a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
|
|
|
@ -2,11 +2,9 @@
|
|||
$for(posts)$
|
||||
<li class="mt-4">
|
||||
<a href="$url$">
|
||||
<span class="text-gray-800 dark:text-gray-200">
|
||||
$date$ ::
|
||||
</span>
|
||||
<span class="text-gray-800 dark:text-gray-200"> $date$ :: </span>
|
||||
$title$
|
||||
</a>
|
||||
</li>
|
||||
$endfor$
|
||||
</ul>
|
||||
</ul>
|
||||
|
|
|
@ -1,20 +1,11 @@
|
|||
<article>
|
||||
<article class="mx-auto px-4 max-w-prose">
|
||||
<section class="header">
|
||||
<h1>
|
||||
$title$
|
||||
</h1>
|
||||
<h1>$title$</h1>
|
||||
$if(date)$
|
||||
<p>
|
||||
Posted on $date$
|
||||
</p>
|
||||
$endif$
|
||||
$if(author)$
|
||||
<p>
|
||||
by $author$
|
||||
</p>
|
||||
<p>Posted on $date$</p>
|
||||
$endif$ $if(author)$
|
||||
<p>by $author$</p>
|
||||
$endif$
|
||||
</section>
|
||||
<section>
|
||||
$body$
|
||||
</section>
|
||||
</article>
|
||||
<section>$body$</section>
|
||||
</article>
|
||||
|
|
|
@ -1,46 +1,39 @@
|
|||
<section id="pricing" class="py-12 px-2 flex flex-col gap-12">
|
||||
<header class="text-3xl">
|
||||
# pricing
|
||||
</header>
|
||||
<header class="text-3xl"># pricing</header>
|
||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
Plutus development has traditionally meant long development schedules, and expensive ( $ 25k+/mo FTE) engineers.
|
||||
We can work with you at competitive rates in either deliverable or retainer based engagements.
|
||||
Plutus development has traditionally meant long development schedules, and
|
||||
expensive ( $ 25k+/mo FTE) engineers. We can work with you at
|
||||
competitive rates in either deliverable or retainer based engagements.
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-8 md:mx-24">
|
||||
<div class="max-w-48">
|
||||
<div class="text-1xl font-bold">
|
||||
## retainer
|
||||
</div>
|
||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
Time-based
|
||||
</div>
|
||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
<div class="flex flex-col justify-between gap-4 sm:flex-row sm:gap-8">
|
||||
<div class="flex-1">
|
||||
<div class="text-1xl font-bold">## retainer</div>
|
||||
<p class="text-gray-800 dark:text-gray-200 mt-4">Time-based</p>
|
||||
<p class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
Still figuring out your project scope?
|
||||
</div>
|
||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
</p>
|
||||
<p class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
Need an extra pair of hands on an existing project?
|
||||
</div>
|
||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
</p>
|
||||
<p class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
Then a retainer based engagement is for you.
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
<div class="max-w-48">
|
||||
<div class="text-1xl font-bold">
|
||||
## deliverable
|
||||
</div>
|
||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
Output-based
|
||||
</div>
|
||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
<div class="flex-1">
|
||||
<div class="text-1xl font-bold">## deliverable</div>
|
||||
<p class="text-gray-800 dark:text-gray-200 mt-4">Output-based</p>
|
||||
<p class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
You know what you want and need help implementing it?
|
||||
</div>
|
||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
</p>
|
||||
<p class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
We'll first produce a spec on how the dapp will operate technically.
|
||||
This involves discussing different options and trade-offs on things from UX to validator complexity.
|
||||
</div>
|
||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
Once settled we'll begin the implementation phase and finally integration phase.
|
||||
</div>
|
||||
This involves discussing different options and trade-offs on things from
|
||||
UX to validator complexity.
|
||||
</p>
|
||||
<p class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
Once settled we'll begin the implementation phase and finally
|
||||
integration phase.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
|
|
@ -1,35 +1,30 @@
|
|||
<section id="services" class="py-12 px-2 flex flex-col gap-12">
|
||||
<header class="text-3xl">
|
||||
# services
|
||||
</header>
|
||||
<header class="text-3xl"># services</header>
|
||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
We are cardano native dapp dev outfit focused on helping you going from 0 to launch ASAP.
|
||||
We are cardano native dapp dev outfit focused on helping you going from 0 to
|
||||
launch ASAP.
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3 sm:gap-8">
|
||||
<div class="max-w-48">
|
||||
<div class="text-1xl font-bold">
|
||||
## strategy
|
||||
</div>
|
||||
<div class="text-1xl font-bold">## strategy</div>
|
||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
We'll work with you to validate your concept, and translate it into an implementable Proof of Concept
|
||||
We'll work with you to validate your concept, and translate it into an
|
||||
implementable Proof of Concept
|
||||
</div>
|
||||
</div>
|
||||
<div class="max-w-48">
|
||||
<div class="text-1xl font-bold">
|
||||
## implementation
|
||||
</div>
|
||||
<div class="text-1xl font-bold">## implementation</div>
|
||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
Cook up appropriate Plutus validators to meet your needs
|
||||
</div>
|
||||
</div>
|
||||
<div class="max-w-48">
|
||||
<div class="text-1xl font-bold">
|
||||
## deployment
|
||||
</div>
|
||||
<div class="text-1xl font-bold">## deployment</div>
|
||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||
We facilitate integrating the on-chain aspects with the rest of your stack
|
||||
We facilitate integrating the on-chain aspects with the rest of your
|
||||
stack
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
|
|
@ -3,55 +3,135 @@
|
|||
<div class="relative flex h-16 items-center justify-between">
|
||||
<div class="absolute inset-y-0 left-0 flex items-center sm:hidden">
|
||||
<!-- Mobile menu button-->
|
||||
<button type="button" class="inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white" aria-controls="mobile-menu" aria-expanded="false">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
|
||||
aria-controls="mobile-menu"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="sr-only">Open main menu</span>
|
||||
<!--
|
||||
Icon when menu is closed.
|
||||
|
||||
Menu open: "hidden", Menu closed: "block"
|
||||
-->
|
||||
<svg class="block h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||
<svg
|
||||
class="block h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
|
||||
/>
|
||||
</svg>
|
||||
<!--
|
||||
Icon when menu is open.
|
||||
|
||||
Menu open: "block", Menu closed: "hidden"
|
||||
-->
|
||||
<svg class="hidden h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
<svg
|
||||
class="hidden h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start">
|
||||
<div
|
||||
class="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start"
|
||||
>
|
||||
<div class="flex flex-shrink-0 items-center">
|
||||
<img class="block h-8 w-auto lg:hidden" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=500" alt="Your Company">
|
||||
<img class="hidden h-8 w-auto lg:block" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=500" alt="Your Company">
|
||||
<img
|
||||
class="block h-8 w-auto lg:hidden"
|
||||
src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=500"
|
||||
alt="Your Company"
|
||||
/>
|
||||
<img
|
||||
class="hidden h-8 w-auto lg:block"
|
||||
src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=500"
|
||||
alt="Your Company"
|
||||
/>
|
||||
</div>
|
||||
<div class="hidden sm:ml-6 sm:block">
|
||||
<div class="flex space-x-4">
|
||||
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
|
||||
<a href="#" class="bg-gray-900 text-white rounded-md px-3 py-2 text-sm font-medium" aria-current="page">Dashboard</a>
|
||||
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium">Team</a>
|
||||
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium">Projects</a>
|
||||
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium">Calendar</a>
|
||||
<a
|
||||
href="#"
|
||||
class="bg-gray-900 text-white rounded-md px-3 py-2 text-sm font-medium"
|
||||
aria-current="page"
|
||||
>Dashboard</a
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
class="text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium"
|
||||
>Team</a
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
class="text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium"
|
||||
>Projects</a
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
class="text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium"
|
||||
>Calendar</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
|
||||
<button type="button" class="rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800">
|
||||
<div
|
||||
class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
|
||||
>
|
||||
<span class="sr-only">View notifications</span>
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" />
|
||||
<svg
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Profile dropdown -->
|
||||
<div class="relative ml-3">
|
||||
<div>
|
||||
<button type="button" class="flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800" id="user-menu-button" aria-expanded="false" aria-haspopup="true">
|
||||
<button
|
||||
type="button"
|
||||
class="flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
|
||||
id="user-menu-button"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
>
|
||||
<span class="sr-only">Open user menu</span>
|
||||
<img class="h-8 w-8 rounded-full" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="">
|
||||
<img
|
||||
class="h-8 w-8 rounded-full"
|
||||
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
|
||||
alt=""
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
@ -65,11 +145,38 @@
|
|||
From: "transform opacity-100 scale-100"
|
||||
To: "transform opacity-0 scale-95"
|
||||
-->
|
||||
<div class="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1">
|
||||
<div
|
||||
class="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||
role="menu"
|
||||
aria-orientation="vertical"
|
||||
aria-labelledby="user-menu-button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<!-- Active: "bg-gray-100", Not Active: "" -->
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-0">Your Profile</a>
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-1">Settings</a>
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-2">Sign out</a>
|
||||
<a
|
||||
href="#"
|
||||
class="block px-4 py-2 text-sm text-gray-700"
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
id="user-menu-item-0"
|
||||
>Your Profile</a
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
class="block px-4 py-2 text-sm text-gray-700"
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
id="user-menu-item-1"
|
||||
>Settings</a
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
class="block px-4 py-2 text-sm text-gray-700"
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
id="user-menu-item-2"
|
||||
>Sign out</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -80,10 +187,27 @@
|
|||
<div class="sm:hidden" id="mobile-menu">
|
||||
<div class="space-y-1 px-2 pb-3 pt-2">
|
||||
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
|
||||
<a href="#" class="bg-gray-900 text-white block rounded-md px-3 py-2 text-base font-medium" aria-current="page">Dashboard</a>
|
||||
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md px-3 py-2 text-base font-medium">Team</a>
|
||||
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md px-3 py-2 text-base font-medium">Projects</a>
|
||||
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md px-3 py-2 text-base font-medium">Calendar</a>
|
||||
<a
|
||||
href="#"
|
||||
class="bg-gray-900 text-white block rounded-md px-3 py-2 text-base font-medium"
|
||||
aria-current="page"
|
||||
>Dashboard</a
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
class="text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md px-3 py-2 text-base font-medium"
|
||||
>Team</a
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
class="text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md px-3 py-2 text-base font-medium"
|
||||
>Projects</a
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
class="text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md px-3 py-2 text-base font-medium"
|
||||
>Calendar</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
@ -12,13 +12,13 @@
|
|||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<title>$title$</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Lean dapp development"
|
||||
<meta name="description" content="Lean dapp development" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/css/normalize.css" />
|
||||
<link rel="stylesheet" href="/css/terminal.css" />
|
||||
<link rel="stylesheet" href="/css/custom.css" />
|
||||
|
|