Giter Club home page Giter Club logo

clarity-wasm's Introduction

   / / /     โ–ถ clarity-wasm
  | | |        Compile Clarity to Wasm.
   \ \ \       Generate WebAssembly from your Clarity code for fast and portable execution.

Introduction Features Quick Start Documentation Contribute codecov


Introduction

clar2wasm is a compiler for generating WebAssembly from Clarity.

Features

Quick-start

Clone the repository

This repository includes the stacks-blockchain as a submodule, to keep in sync with the proper version of the clarity crate defined there. To clone this repo and its submodule, use:

git clone --recurse-submodules https://github.com/stacks-network/clarity-wasm.git

If you have cloned this repository without the --recurse-submodules flag, you can use:

git submodule update --init --recursive

Command line tool

Install the command line tool, clar2wasm with:

cargo install --path clar2wasm

Once installed, try compiling one of our examples:

clar2wasm tests/contracts/define-read-only-0.clar

This will generate a wasm file, tests/contracts/define-read-only-0.wasm, from the Clarity source code.

You can view the text format of the generated Wasm by using a tool like wasm2wat:

wasm2wat tests/contracts/define-read-only-0.wasm

The output should contain the definition of the simple function:

  (func $simple (type 2) (result i64 i64)
    (local i32)
    global.get 0
    local.set 0
    block (result i64 i64)  ;; label = @1
      i64.const 42
      i64.const 0
    end
    local.get 0
    global.set 0)

Crate

clar2wasm is also available as a Rust library crate, to embed into other Rust projects.

Documentation

Top-Level Expressions

Any top-level expressions from a Clarity contract are added into a .top-level function that is exported from the generated Wasm module. This function should be called only once, during contract deployment. This top-level code also includes calls to the host-interface functions to register definitions in the contract.

ABI

WebAssembly only supports basic number types, i32, i64, f32, and f64. We need to decide how to map Clarity types into these Wasm types.

  • int: pair of i64s (upper, lower)
  • uint: pair of i64s (upper, lower)
  • bool: i32 (0 for false, 1 for true)
  • principal: i32 offset in call stack, i32 length; call stack contains 21 bytes for standard principal (1 byte version, 20 byte Hash160) followed by 1 byte indicating the length of the contract name, which, if non-zero, is followed by the contract name string.
  • buff: i32 offset in call stack, i32 length
  • string-ascii: i32 offset in call stack, i32 length
  • string-utf8: i32 offset in call stack, i32 length; the string is represented as array of 4 byte (big-endian) Unicode scalars
  • list: i32 offset in call stack, i32 length
  • tuple: each value in the tuple concatenated
  • optional: i32 indicator (0 for none, 1 for some), followed by value for some
  • response: i32 indicator (0 for err, 1 for ok) followed by ok value, then err value
  • When a type is not known, for example, in a response where either the ok or the err are never used, the NoType is represented with an i32 with a value of 0.

When the return value of a function requires memory space, this space should be allocated by the caller and the offset for that space should be passed to the callee, following the arguments. For example, we can look at the following function:

(define-read-only (get-boolean-string (b bool))
  (if b "true" "false")
)

This function takes a bool and returns a (string-ascii 5). The generated Wasm function would take as arguments:

  • I32 representing the boolean
  • I32 representing the offset of the return value's memory space
  • I32 representing the length of the return value's memory space

For consistency with other types, the Wasm function would still return these two I32s for offset and length of the return value, even though that is not necessary for the caller.

Memory Management

Web Assembly provides a simple linear memory, accessible with load/store operations. This memory is also exported for access from the host. For the Clarity VM, at the base of this memory, starting at offset 0, we are storing literals that do not fit into the scalar types supported by Wasm, for example, string literals. When used in the code, the literals are loaded from a constant offset. During compilation, the top of the literal memory is tracked by the field literal_memory_end in the WasmGenerator structure.

Constants defined in the contract (with define-constant) are also stored in this literal memory space. The values for the constants are written into the preallocated memory inside of the .top-level function.

After the literals, space may be allocated for passing arguments into the contract being called. Simple arguments are passed directly to the function, but those that require stack space (see ABI) will be written to this location. If the return value from the contract call requires stack space, then this will follow the arguments' space.

After this argument space, we build a call stack, where function local values that do not fit into scalars are stored. A global variable is defined in the Wasm module to maintain a stack pointer. At the beginning of every function, we insert the function prologue, which saves the current stack pointer to a local variable, which we can call the frame pointer. The frame pointer is the base of the current function's frame, its space in the call stack, and the function's local values can be accessed via offsets from this frame pointer. The stack pointer is then incremented for any space that gets reserved for the current function. Every function also has a function epilogue, which must be called upon exit from the function. The epilogue pops the function's frame from the call stack, since its locals are no longer needed, by setting the stack pointer equal to its frame pointer.

It may be helpful to clarify this with an example. Consider the following Clarity code:

(define-private (do-concat (a (string-ascii 16)) (b (string-ascii 16)))
  (len (concat a b))
)

(define-read-only (hello (to (string-ascii 16)))
  (do-concat "hello " to)
)

The concat expression in the do-concat function creates a new string by concatenating the two input strings. This new string needs to be stored in the call stack, in that function's frame. The type of this expression is (string-ascii 32), so in this case, we need to allocate 32 bytes on the call stack for the result. Before exiting the do-concat function, our linear memory will look like this:

stack pointer ->     +-------------------+
                     |         .         |
                     |  32 byte string   | <- Frame for do-concat
                     |         .         |
frame pointer ->     +-------------------+ <- Frame for example (no space allocated)
                     |         to        | <- Argument memory
                     +-------------------+
                     |      "hello "     | <- Literal memory
0 ->                 +-------------------+

In this diagram, the "frame pointer" is actually the frame pointer for both the example function and the do-concat function, because example does not require any space in its frame.

Standard Library

Certain Clarity operations are implemented as functions in standard.wat. This text format is then used during the build process to generate standard.wasm which gets loaded into clar2wasm. Any operations that are cleaner to implement as a function call instead of directly generating Wasm instructions go into this library. For example, you can find the Clarity-style implementation of arithmetic operations in this library. These need to be written out manually because WebAssembly only supports 64-bit integers. The library implements 128-bit arithmetic, with the overflow checks that Clarity requires.

Host Interface

When executing the compiled Clarity code, it needs to interact with the host - for example reading/writing to the MARF, emitting events, etc. We define a host interface that the generated Wasm code can call to perform these operations. Since these functions are type-agnostic, values are passed back and forth on the stack. The host function is responsible for marshalling/unmarshalling values to/from the Wasm format as needed (see ABI section above). These functions are imported by the standard library module, and it is the responsibility of the host to provide implementations of them.

Clarity Operation Host Function Inputs Outputs
var-get get_variable - var_name: string (offset: i32, length: i32) -
- result: stack pointer (offset: i32, length: i32)
var-set set_variable - var_name: string (offset: i32, length: i32) -
- value: stack pointer (offset: i32, length: i32)

Benchmarking

Generate a flamegraph

Run the bench command with --features flamegraph and --profile-time <seconds> flags.

For example:

cargo bench --bench benchmark --features flamegraph -- --profile-time 10 "add: clarity"

Output target/criterion/add_ clarity/profile/flamegraph.svg preview:

bench-flamegraph

Generate a protobuf and svg graph

Run the bench command with --features pb and --profile-time <seconds> flags. Then use pprof to generate a graph.

For example:

cargo bench --bench benchmark --features pb -- --profile-time 10 "add: clarity"
$GOPATH/bin/pprof -svg "target/criterion/add_ clarity/profile/profile.pb"

Output profile001.svg preview:

bench-protobuf-graph

Contribute

Using local paths for dependencies

When modifying both the clar2wasm crate and the stacks-core crates, you can use the [patch] section in .cargo/config to specify local paths for these dependencies. This will allow you to test your changes to both crates together, without the need to first untested changes push to GitHub.

Add the following to clarity-wasm/.cargo/config

[patch.'https://github.com/stacks-network/stacks-core.git']
clarity = { path = "./stacks-core/clarity" }

Similarly, in the stacks-core directory, you can add the following to stacks-core/.cargo/config

[patch.'https://github.com/stacks-network/stacks-core.git']
clarity = { path = "clarity" }
stacks-common = { path = "stacks-common" }
pox-locking = { path = "pox-locking" }
libstackerdb = { path = "libstackerdb" }
stx-genesis = { path = "stx-genesis"}
stacks = { package = "stackslib", path = "stackslib" }
libsigner = { path = "libsigner" }
stacks-signer = { path = "stacks-signer" }

[patch.'https://github.com/stacks-network/clarity-wasm.git']
clar2wasm = { package = "clar2wasm", path = "../clar2wasm/." }

Note that these patch configurations should not be checked into the repositories, because we want the default behavior to be to use the git repo paths.

Formatting

To standardize the formatting of the code, we use rustfmt. To format your changes using the standard options, run:

cargo +nightly fmt-stacks

clarity-wasm's People

Contributors

acaccia avatar ameeratgithub avatar csgui avatar cylewitruk avatar hugocaillard avatar krl avatar obycode avatar saralab avatar zone117x avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

clarity-wasm's Issues

Investigate and document performance and pros/cons between Wasm runtimes

As Wasmtime and Wasmer are the two prominent runtime libraries for Rust, these are the two which will be focused on.

Performance
A fast initial comparison using add using externrefs, as well as a native add_i128 function which passes and returns two i64's (to achieve rust's i128 through bit manipulation) shows that the two frameworks perform, in general, relatively similar.

Benchmarks reveal that Wasmtime does have a slight performance edge at least in the above two tests, however the difference is for all purposes negligible when using Rust's default memory allocator. One interesting find is that Wasmtime's performance dramatically increased for more complex functions when I (out of random curiosity) swapped the default memory allocator out for mimalloc.

Documentation
Both frameworks provide relatively OK API documentation, however Wasmer's documentation in general is more complete and includes a much larger array of examples and tests.

API
A thing of taste, perhaps, but I do find the Wasmer API to be more natural and clean.

Community
Both frameworks seem to have relatively large communities across a multitude of language ports.

Property tests for standard functions generates mostly big ints

In the property tests in property_tests.rs, the generated i128 for the property tests are mostly big integers, and don't really test that functions still works for smaller ints.

We should split the int128() into several prop_compose, to have more control on the size we want to get.

I would propose at least:

  • small_uint128: i <= u64::MAX
  • big_uint128: i > u64::MAX
  • positive_int128
  • negative_int128

[ ๐Ÿ’ป Clarity VM ] Support boot contracts

To support the boot contracts (see Clarity contracts in here), we need support for the following Clarity functions:

  • *
  • +
  • -
  • /
  • <
  • <=
  • >
  • >=
  • and
  • append
  • as-contract
  • as-max-len?
  • asserts!
  • begin
  • buff-to-uint-be
  • concat
  • default-to
  • define-constant
  • define-data-var
  • define-fungible-token
  • define-map
  • define-non-fungible-token
  • define-private
  • define-public
  • define-read-only
  • element-at
  • err
  • filter
  • fold
  • ft-mint?
  • ft-transfer?
  • get
  • get-block-info?
  • hash160
  • if
  • is-eq
  • is-none
  • is-some
  • len
  • let
  • list
  • log2
  • map-delete
  • map-get?
  • map-insert
  • map-set
  • match
  • merge
  • nft-get-owner?
  • nft-mint?
  • nft-transfer?
  • not
  • ok
  • or
  • pow
  • print
  • replace-at?
  • slice?
  • some
  • stx-account
  • stx-burn?
  • stx-get-balance
  • stx-transfer?
  • try!
  • tuple
  • unwrap!
  • unwrap-panic
  • var-get
  • var-set
  • xor

RUSTSEC-2022-0093: Double Public Key Signing Function Oracle Attack on `ed25519-dalek`

Double Public Key Signing Function Oracle Attack on ed25519-dalek

Details
Package ed25519-dalek
Version 1.0.0-pre.3
URL https://github.com/MystenLabs/ed25519-unsafe-libs
Date 2022-06-11
Patched versions >=2

Versions of ed25519-dalek prior to v2.0 model private and public keys as
separate types which can be assembled into a Keypair, and also provide APIs
for serializing and deserializing 64-byte private/public keypairs.

Such APIs and serializations are inherently unsafe as the public key is one of
the inputs used in the deterministic computation of the S part of the signature,
but not in the R value. An adversary could somehow use the signing function as
an oracle that allows arbitrary public keys as input can obtain two signatures
for the same message sharing the same R and only differ on the S part.

Unfortunately, when this happens, one can easily extract the private key.

Revised public APIs in v2.0 of ed25519-dalek do NOT allow a decoupled
private/public keypair as signing input, except as part of specially labeled
"hazmat" APIs which are clearly labeled as being dangerous if misused.

See advisory page for additional details.

[ ๐Ÿ’ป Clarity VM ] Regression testing

Compare results from old VM and new VM. Note, that this may go into the Clarity VM work, not clar2wasm, but added here so it is not forgotten.

  • Boot from genesis and validate state
  • New tests

Host interface functions

  • as-contract
  • ft-mint?
  • ft-transfer?
  • get-block-info?
  • map-delete
  • map-get?
  • map-insert
  • map-set
  • nft-get-owner?
  • nft-mint?
  • nft-transfer?
  • print
  • stx-account
  • stx-burn?
  • stx-get-balance
  • stx-transfer?

Implement keywords

We also need to support the built-in keywords. The following subset is required for the boot contracts:

  • block-height
  • burn-block-height
  • contract-caller
  • false
  • none
  • stx-liquid-supply
  • true
  • tx-sender

Explore `externref` & `funcref` in both Walrus and runtime libraries

This issue is about exploring the passing of opaque host references through the Wasm runtime and back to host-functions for use.

It has been found that the use of ExternalRefs and all of the marshaling involved results in a relatively large performance hit. It should be noted, however, that this construct does allow a very seamless way of passing complex object references along in the Wasm function chain.

The use of FunctionRefs and the calling of host-functions from Wasm using Wasm-native types, however, reaches near-pure-Wasm-performance. When benchmarked using an add i128 function, accepting four i64's (a_low, a_high, b_low, b_high) and returning two i64s (c_low, c_high), the performance difference is in the order of 5-10 nanoseconds (higher for host-function call via funcref).

Wasm Tables have been extended now to allow the storage of externrefs in addition to funcrefs. One possible next task is comparing if accessing the values via tables affects performance of externrefs or not.

Reorder Int and Uint bytes passed to webassembly

For now, we pass our 128 bits-sized integers to webassembly as (high, low), respectively the most and the least significant bits.
This is the opposite of the order which would be natural for webassembly, which uses a little-endian representation. We should pass them as (low, high).

With the current representation, if we need to store those integers in the memory, the bytes would be stored as 89abcde,1234567.

This is not a big issue per se, but we should deal with it as soon as possible, while standard.wat isn't too big yet.

Replace the ASCII art in the README

The README has Hiro ASCII art that I took from a template. It should be replaced with some cool ASCII art representing the Clarity logo.

       /     /   โ–ถ clarity-wasm
      / --- /      Compile Clarity to Wasm.
     /     /       Generate WebAssembly from your Clarity code for fast and portable execution.

image

Optional/Response functions

Implementation of Optional and Response types functions.

Functions to be implemented

Explore the feasibility of implementing an external memory allocator for Wasm memory

From discord:

I'm exploring a little now the feasibility of implementing an external memory allocator for wasm memory, partially thinking of @obycode 's concerns regarding return values of arbitrary length, while also trying to optimize how we use wasm memory instead of doing append-only. Something similar to malloc, only that it wouldn't actually allocate memory, just give you a valid offset that can hold the desired number of bytes. And then a host-function can be provided to the wasm-native stuff as well to retrieve an offset for desired length.

Our "mul-uint" doesn't work

Working on #54 , I could see that our multiplication function doesn't work for now.

Here is a test to add to the unit tests to replicate the issue:

    mul.call(
        &mut store,
        &[Val::I64(1), 0i64.into(), Val::I64(4294967296), 0i64.into()],
        &mut result,
    )
    .expect("call to mul-uint failed");
    eprintln!("{:?}", result);
    assert_eq!(result[0].unwrap_i64(), 4294967296);
    assert_eq!(result[1].unwrap_i64(), 0);

Expected result should be 4294967296 but is actually 0.

[ ๐Ÿ’ป Clarity VM ] Epoch-gating across calls

The new VM should only be used for executing Clarity 3 epoch 3.0 contracts. When calling into Clarity 1 or 2 contracts deployed in previous epochs, the old interpreter should be used. The cross-contract calling needs to handle this.

[ ๐Ÿ’ป Clarity VM ] Drop unused values

Since Wasm is stack-based, when we have unused values, they need to be dropped if the Clarity value is not used. For example, in the var-set test, the true that is returned from var-set goes unused:

(define-data-var something int 123)

(define-public (simple)
    (begin
        (var-set something 5368002525449479521366)
        (ok (var-get something))
    )
)

To manage this, we'd need to keep track of how many values have been pushed to the stack, and then when processing a list of expressions, the result value(s) from all but the last expression in the list should be dropped.

Improve trait granularity of the visitors/compiler implementation

At the moment, the visitor trait is very large, over 3000 lines of mostly duplicated code.

Providing a proof of concept of a different approach, that uses more fine-grained traits to de-duplicate the code needed to compile Clarity to Wasm.

Additionally, adding type information to this layer, in the style of forth (which values are consumed from the stack, and which are put back on)

In a POC, there are two traits something like this

pub trait Procedure: Sync + Emit {
    fn input(&self) -> &'static [ValType];
    fn output(&self) -> &'static [ValType];
}

pub trait Emit {
    fn emit(&self, builder: &mut InstrSeqBuilder);
}

[ ๐Ÿ’ป Clarity VM ] Optimize stack pointer management

Currently, we are incrementing the stack pointer (a Wasm global) each time a new value needs to be pushed to the execution stack (the current function's stack frame in the linear memory). Instead, we should do a compilation pass that computes the total stack size needed for a function, and increments the stack pointer once, during the function prologue, and save the offsets for each local to use during the compilation pass.

RUSTSEC-2022-0090: `libsqlite3-sys` via C SQLite CVE-2022-35737

libsqlite3-sys via C SQLite CVE-2022-35737

Details
Package libsqlite3-sys
Version 0.20.1
URL https://nvd.nist.gov/vuln/detail/CVE-2022-35737
Date 2022-08-03
Patched versions >=0.25.1

It was sometimes possible for SQLite versions >= 1.0.12, < 3.39.2 to allow an array-bounds overflow when large string were input into SQLite's printf function.

As libsqlite3-sys bundles SQLite, it is susceptible to the vulnerability. libsqlite3-sys was updated to bundle the patched version of SQLite here.

See advisory page for additional details.

[ ๐Ÿ’ป Clarity VM ] Eval hooks

In the existing Clarity VM, EvalHooks provide a way to hook into the execution. These are used to implement debugging, tracing, and code coverage in Clarinet. We'll need to provide a way to do all of these in the new VM.

Define functions

  • define-constant
  • define-fungible-token
  • define-map
  • define-non-fungible-token

Tests in lib_test don't assert the correct return type

For now, all tests in lib_test.rs have this structure when we check for the result:

if let ClarityWasmResult::Int { high, low } = *ok_value.unwrap() {
	assert_eq!(high, 0);
    assert_eq!(low, 2);
}

This means that if the returned value was anything else (e.g. a UInt), nothing would fail.

[ ๐Ÿ’ป Clarity VM ] General fuzz testing

The clar2wasm library needs to handle any random Clarity coming in from users (although it has passed through the parser and analysis passes before reaching here). It should be hardened to handle whatever comes in, handling errors gracefully.

[ ๐Ÿ’ป Clarity VM ] Cost tracking

Design how cost-tracking should work in this new system.

  • Should the cost be statically analyzed?
  • Should the cost tracking be inserted into the compiled Wasm?

Should we use the audit workflow in CI?

Potential unaligned read

Details
Status unsound
Package atty
Version 0.2.14
URL softprops/atty#50
Date 2021-07-04

On windows, atty dereferences a potentially unaligned pointer.

In practice however, the pointer won't be unaligned unless a custom global allocator is used.

In particular, the System allocator on windows uses HeapAlloc, which guarantees a large enough alignment.

atty is Unmaintained

A Pull Request with a fix has been provided over a year ago but the maintainer seems to be unreachable.

Last release of atty was almost 3 years ago.

Possible Alternative(s)

The below list has not been vetted in any way and may or may not contain alternatives;

See advisory page for additional details.

RUSTSEC-2020-0168: mach is unmaintained

mach is unmaintained

Details
Status unmaintained
Package mach
Version 0.3.2
URL fitzgen/mach#63
Date 2020-07-14

Last release was almost 4 years ago.

Maintainer(s) seem to be completely unreachable.

Possible Alternative(s)

These may or may not be suitable alternatives and have not been vetted in any way;

See advisory page for additional details.

Investigate Wasm options

There are various options that can be set in Wasmtime's Engine. See Config. We should review these and decide which we want to enable for the Clarity runtime, considering speed and safety.

Buffer functions

  • buff-to-uint-be

Although not needed for boot contracts, this task may as well include these too:

  • buff-to-uint-le
  • buff-to-int-be
  • buff-to-int-le

Decide on Wasm runtime

A placeholder issue for now, where we can document why a specific runtime was chosen (for the community).

RUSTSEC-2020-0071: Potential segfault in the time crate

Potential segfault in the time crate

Details
Package time
Version 0.1.45
URL time-rs/time#293
Date 2020-11-18
Patched versions >=0.2.23
Unaffected versions =0.2.0,=0.2.1,=0.2.2,=0.2.3,=0.2.4,=0.2.5,=0.2.6

Impact

Unix-like operating systems may segfault due to dereferencing a dangling pointer in specific circumstances. This requires an environment variable to be set in a different thread than the affected functions. This may occur without the user's knowledge, notably in a third-party library.

The affected functions from time 0.2.7 through 0.2.22 are:

  • time::UtcOffset::local_offset_at
  • time::UtcOffset::try_local_offset_at
  • time::UtcOffset::current_local_offset
  • time::UtcOffset::try_current_local_offset
  • time::OffsetDateTime::now_local
  • time::OffsetDateTime::try_now_local

The affected functions in time 0.1 (all versions) are:

  • at
  • at_utc
  • now

Non-Unix targets (including Windows and wasm) are unaffected.

Patches

Pending a proper fix, the internal method that determines the local offset has been modified to always return None on the affected operating systems. This has the effect of returning an Err on the try_* methods and UTC on the non-try_* methods.

Users and library authors with time in their dependency tree should perform cargo update, which will pull in the updated, unaffected code.

Users of time 0.1 do not have a patch and should upgrade to an unaffected version: time 0.2.23 or greater or the 0.3 series.

Workarounds

A possible workaround for crates affected through the transitive dependency in chrono, is to avoid using the default oldtime feature dependency of the chrono crate by disabling its default-features and manually specifying the required features instead.

Examples:

Cargo.toml:

chrono = { version = &quot;0.4&quot;, default-features = false, features = [&quot;serde&quot;] }
chrono = { version = &quot;0.4.22&quot;, default-features = false, features = [&quot;clock&quot;] }

Commandline:

cargo add chrono --no-default-features -F clock

Sources:

See advisory page for additional details.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.