Skip to content

Getting started

Geas compiles contracts into shared libraries that any language can drive across the C ABI. This page builds the toolchain, compiles a contract, and runs it end to end.

  • The dusk toolchain, version 1.5.3 or newer. The Geas compiler is written in dusk.
  • A C compiler. Both cc and clang are used.
  • make.

Clone the repository and run the smoke check:

Terminal window
git clone https://github.com/choice404/geas
cd geas
make smoke

make smoke builds the runtime library libgeasrt.so, builds the geas compiler, compiles the skeleton contract, builds a C host, and runs the whole loop: the host signs the contract, fulfills a pledge, checks the result, exercises the error paths, and breaks the instance. If it prints a passing result, everything works.

To run the same check under the address and leak sanitizers:

Terminal window
make smoke-asan

The skeleton contract lives at skeleton/hello.geas:

GreetError is either Empty
contract Greeter {
vow prefix: String = "hello, "
pledge greet(name: String) -> Result<String, GreetError> {
return Ok(prefix + name)
}
// Abstract: no body here, the host binds an implementation before sign.
pledge shout(name: String) -> Result<String, GreetError>
}

Three things to notice.

The vow is an immutable field. It has a default here, and a host can override it at sign time. Once the contract is signed, nothing can change it.

The first pledge has a body, so the contract fulfills it itself. The second has no body. It is abstract, and a host in another language must bind an implementation before the contract will sign.

Every pledge returns Result or Option. Errors are values in Geas, and nothing throws across a language boundary.

Terminal window
target/dusk-out/geas build skeleton/hello.geas

This emits C, compiles it, and links target/geas-out/libhello.geas.so, a loadable module that carries descriptor tables spelling out every contract, pledge, and vow inside it.

The runtime enforces the terms at the boundary. A host does five things: initialize the runtime, load the module, sign the contract, fulfill pledges, and break the instance. In C that looks like this:

GeasRuntime* rt = NULL;
geas_runtime_init(NULL, &rt);
geas_module_load(rt, "target/geas-out/libhello.geas.so");
GeasContract* c = NULL;
geas_contract_sign(rt, "Greeter", NULL, 0, 0, &c);
GeasValue out;
GeasValue name = str_arg("world");
geas_pledge_fulfill_sync(c, "greet", &name, 1, NULL, 0, &out);
// out is Ok("hello, world")
geas_contract_break(c);

The same module can be driven from Python with the ctypes wrapper, with no C written and no bindings generated. See Python hosts for that side of the story.

Geas also works on its own. A program is a contract named Main with a run pledge:

main_demo.geas
DemoError is either BadMood(reason: String) or Empty
contract Main {
vow greeting: String = "counted"
pledge run(args: List<String>) -> Result<Int, DemoError> {
let mut n = 0
for a in args {
if a == "fail" {
return Err(BadMood("asked to fail"))
}
n = n + 1
}
return Ok(n)
}
}

Build and run it:

Terminal window
target/dusk-out/geas build --bin skeleton/main_demo.geas

Ok(n) becomes the exit code. An Err prints to stderr and exits with 1.