| jmcph4 |

Using Dasy From the Comfort of Foundry

[2024-02-08 18:00:00 +1000]

Achille Duchêne

LLL is back, baby! (It's all thanks to z80). Okay, so you want to use Dasy but you've settled on Foundry as your smart contract development environment. Fortunately, there's already a plugin — but how do you use it? Let's dive in!

Step 1: Installation

These instructions assume we have both working Rust and Python installations.

Foundry

Foundry is pretty self-explanatory:

$ curl -L https://foundry.paradigm.xyz | bash

Dasy

Next, we'll need to install the Dasy compiler so that we can invoke it from Foundry (via its FFI facility). The Dasy documentation is pretty clear:

$ pipx install dasy

Step 2: Initialise the Forge Project

We need to set up a Forge project as per usual but also install our plugin that enables interoperability between the Dasy compiler and Foundry.

$ forge new foobar
$ cd foobar
$ forge install dasylang/foundry-dasy

Step 3: Write a Dasy Contract

Let's use z80's skunkworks example:

(defvar val :uint256)

(defn setValue [:uint256 val] :external
    (set-self val))

(defn getValue [] :uint256 :external
    (return self/val))

Step 4: Write Some Tests

Unfortunately, tests still need to be written in Solidity. In order to achieve interoperability between the two languages, we'll have to make use of the DasyDeployer library provided by the plugin. This works in much the same way as the Huff and Vyper equivalents, if you've used those before.

Essentially, we'll deploy our Dasy contract directly from disk, cast it to a shared Solidity interface, and then write tests as per usual.

// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.15;

import "forge-std/Test.sol";
import "forge-std/console.sol";

import { DasyDeployer } from "foundry-dasy/DasyDeployer.sol";

contract SimpleStoreTest is Test {
    /// @dev Address of the SimpleStore contract.
    SimpleStore public foo;

    /// @dev Setup the testing environment.
    function setUp() public {
        foo = SimpleStore(DasyDeployer.deploy("SimpleStore"));
    }

    /// @dev Ensure that you can set and get the value.
    function testSetAndGetValue(uint256 value) public {
        foo.setValue(value);
    }
}

interface SimpleStore {
    function setValue(uint256) external;
    function getValue() external returns (uint256);
}

Step 5: Run the Tests

As of this step, our project structure should look something like this (I've ignored the build output and the dependencies for now):

$ tree .
.
├── dependencies.txt
├── foundry.toml
├── lib
├── README.md
├── script
├── src
│   └── SimpleStore.dasy
└── test
    └── SimpleStore.t.sol

4 directories, 7 files
$ forge test
[⠰] Compiling...
No files changed, compilation skipped

Running 1 test for test/SimpleStore.t.sol:SimpleStoreTest
[PASS] testSetAndGetValue(uint256) (runs: 256, μ: 26477, ~: 27410)
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 930.25ms
 
Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)

Conclusion

At this stage, we have a working development environment that can both compile and test Dasy-language smart contracts.

I've recently made a few contributions to the Dasy plugin for Foundry in order to smooth things over with the developer experience and I'm happy with the current state of things. I'm very excited to deploy Dasy to production!