Check out the full code here Learn more about Aiken here
In order to use your Aiken script within your Naumachia contract you will need a few steps:
- Create your Aiken project
- Compile your empty project
- Load the script from file
- Write tests for your Scripts
- Implement and iterate...
Follow the Aiken documentation to learn how to create an Aiken project.
For now, we are still just using the always succeeds script. Here is an example of that code:
validator {
fn spend(_datum: Void, _redeemer: Void, _ctx) -> Bool {
False
}
}
Your Rust project root should include a build.rs
file that will compile your Aiken script into a binary. Here is an example of that file:
const PROJECT: &str = "./always_succeeds";
fn main() {
let mut project = Project::new(PROJECT.into(), Terminal::default())
.expect(&format!("Project not found: {:?}", PROJECT));
let build_result = project.build(false, Tracing::KeepTraces);
if let Err(err) = build_result {
err.iter().for_each(|e| e.report());
panic!("🍂 Failed to build Aiken code 🍂");
}
}
Where PROJECT
is the path to your Aiken project.
Now that you have compiled your Aiken script, you can load it into your Naumachia contract. Here is an example of how to do that:
const BLUEPRINT: &str = include_str!("../../always_succeeds/plutus.json");
const VALIDATOR_NAME: &str = "always_true.spend";
pub fn get_script() -> ScriptResult<RawPlutusValidator<(), ()>> {
let script_file: BlueprintFile = serde_json::from_str(BLUEPRINT)
.map_err(|e| ScriptError::FailedToConstruct(e.to_string()))?;
let validator_blueprint =
script_file
.get_validator(VALIDATOR_NAME)
.ok_or(ScriptError::FailedToConstruct(format!(
"Validator not listed in Blueprint: {:?}",
VALIDATOR_NAME
)))?;
let raw_script_validator = RawPlutusValidator::from_blueprint(validator_blueprint)
.map_err(|e| ScriptError::FailedToConstruct(e.to_string()))?;
Ok(raw_script_validator)
}
Where BLUEPRINT
is the path to your compiled Aiken Blueprint, and VALIDATOR_NAME
is the name of the validator you want
to use within the Blueprint file generated by Aiken.
Now that you have your script loaded, you can write tests for it. Here is an example of how to do that:
#[test]
fn test() {
let script = get_script().unwrap();
let owner = Address::from_bech32("addr_test1qpmtp5t0t5y6cqkaz7rfsyrx7mld77kpvksgkwm0p7en7qum7a589n30e80tclzrrnj8qr4qvzj6al0vpgtnmrkkksnqd8upj0").unwrap();
let owner_pkh = pub_key_hash_from_address_if_available(&owner).unwrap();
let ctx = ContextBuilder::new(owner_pkh).build_spend(&vec![], 0);
script.execute((), (), ctx).unwrap();
}
Since this validator always succeeds, there isn't a lot of testing to do.
The execute()
method takes three parameters:
- The datum
- The redeemer
- The context
If you look at the above test, you can see that we are using a ContextBuilder
to build our script context.
All scripts take a context as its final parameter, and you can use the ContextBuilder
to specifiy all the values
in your context for doing a full range of tests. As of now, the builder is overly-expressive, in that it can define
contexts that could never exist on chain. So expect improvements over time.
You may have noticed the script we included above would fail the test. We had the script always return False
instead
of True
. Now that you have a failing test, you can go back and update your code:
validator {
fn spend(_datum: Void, _redeemer: Void, _ctx) -> Bool {
True
}
}
The test should now pass! That's Test Driven Development! Feel free to use the testing framework how you see fit though :).