Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/workflows/codegen_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Codegen Test

on:
push:
pull_request:
merge_group:

jobs:
detect-changes:
uses: ./.github/workflows/check_diff.yaml
with:
pattern: ^codegen.*

test:
name: codegen test
runs-on: ubuntu-latest
needs: detect-changes
if: needs.detect-changes.outputs.changed == 'true'
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
token: ${{ secrets.GITHUB_TOKEN }}

- name: install foundry
uses: foundry-rs/foundry-toolchain@v1.0.10
with:
version: nightly

- name: set up python
uses: actions/setup-python@v4
with:
python-version: "3.10"
cache: "pip"
token: ${{github.token}}

- name: install requirements
run: |
pip install -e codegen

- name: test
run: ./codegen/test/test.sh
3 changes: 3 additions & 0 deletions codegen/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Codegen codeowners

- @sentilesdal @cashd
105 changes: 105 additions & 0 deletions codegen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Integrations Codegen Guide

To make integrating hyperdrive with other protocols easier, we've created tooling to generate most of the boilerplate code. This guide walks though how to install and run the codegen tools.

Here's a quick overview of the directory.

```
hyperdrive/
├── codegen/ # Codegen tool directory
│ ├── hyperdrive-codegen/ # Python package for the codegen tool
│ │ ├── __init__.py # Makes hyperdrive-codegen a Python package
│ │ ├── main.py # Entry point of the tool, handles command line arguments, calls codegen.py
| | ├── codegen.py # Main script of the tool
│ │ └── ... # Other Python modules and package data
│ │
│ ├── templates/ # Jinja templates directory
│ │ └── ... # Template files
│ │
│ ├── example/ # An example result of the tool
│ │ └── config.yaml # The template configuration file
│ │ └── out/ # The generated code
│ │
│ ├── pyproject.toml # Configuration file for build system and dependencies
│ └── .venv/ # Virtual environment (excluded from version control)
├── contracts/ # Solidity contracts directory
│ └── ... # Solidity files
```

## Install

### 0. Install Python

You'll need to have python installed on your machine to use this tool. Installation varies by operating system, check the Python homepage for instructions.

### 1. Install Pyenv

Follow [Pyenv install instructions](https://github.com/pyenv/pyenv#installation).

### 2. Set up virtual environment

You can use any environment, but we recommend [venv](https://docs.python.org/3/library/venv.html), which is part of the standard Python library.

From hyperdrive's root directory, run:

```bash
pip install --upgrade pip
pyenv install 3.10
pyenv local 3.10
python -m venv .venv
source .venv/bin/activate
```

### 4. Install dependencies

To install the dependencies:

```bash
pip install -e codegen
```

### 5. Check Installation

Verify that your package was installed correctly by running something like `pip list`. You should see hyperdrive-codegen listed as a package.

## Usage

If installation was successful, from the `example/` directory you should be able to run the command:

```bash
hyperdrive-codegen --config config.yaml
```
Which will output all the generated code in `example/out`.

To actually start a new integration, you would run the following from the hyperdrive root directory:

```bash
hyperdrive-codegen --config codegen/example/config.yaml --out contracts/src
```

Which will populate all the new files into the appropriate folders within `contracts/src/`.

### 6. VSCode Debugging

Add the following VSCode launch configuration, to be able to debug the tool:

```json
// codegen/.vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Codegen Debugger",
"type": "debugpy",
"request": "launch",
"program": "./hyperdrive_codegen/main.py",
"console": "integratedTerminal",
"args": "--config ./example/config.yaml --out ./example/out"
}
]
}
```

Note that VSCode should be launched from the `codegen/` directory, not the hyperdrive directory.
23 changes: 23 additions & 0 deletions codegen/example/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Template configuration

# Vault naming formats.
name:
# Capitalized version of the name to be used i.e. for contract names, file
# names, and comments.
capitalized: "TestETH"

# All lower case name to be used for directories.
lowercase: "testeth"

# Camel case name to be used i.e. for variables.
camelcase: "testEth"

# Configuration parameters for the hyperdrive instance.
contract:
# If the contract is payable in Ether. If it is, then logic should be added
# to accept Ether, and deposit into the vault to obtain vault shares.
payable: true

# If the contract can accept base to convert to valut shares on behalf of the
# user.
as_base_allowed: true
Empty file.
Empty file.
30 changes: 30 additions & 0 deletions codegen/hyperdrive_codegen/codegen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""The main script to generate hyperdrive integration boilerplate code."""

from pathlib import Path

from jinja2 import Environment

from hyperdrive_codegen.config import get_template_config
from hyperdrive_codegen.file import get_output_folder_structure, setup_directory
from hyperdrive_codegen.jinja import get_jinja_env
from hyperdrive_codegen.templates import get_templates, write_templates_to_files


def codegen(config_file_path: Path | str, output_dir: Path | str, clear_existing: bool = False):
"""Main script to generate hyperdrive integration boilerplate code."""

# Load the configuration file that has all the variables used in the
# template files.
template_config = get_template_config(config_file_path)

# Get the templates to render.
env: Environment = get_jinja_env()
templates = get_templates(env)

# Setup the output directory.
folder_structure = get_output_folder_structure(template_config.name.lowercase)
output_path = Path(output_dir)
setup_directory(output_path, folder_structure, clear_existing)

# Write the templates to files.
write_templates_to_files(templates, output_path, template_config)
53 changes: 53 additions & 0 deletions codegen/hyperdrive_codegen/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Utilities for working with the template config file."""

from pathlib import Path

import yaml
from pydantic import BaseModel


class Name(BaseModel):
"""Holds different versions of the instance name."""

capitalized: str
lowercase: str
camelcase: str


class Contract(BaseModel):

payable: bool
as_base_allowed: bool


class TemplateConfig(BaseModel):
"""Configuration parameters for the codegen templates."""

name: Name

contract: Contract


def get_template_config(config_file_path: str | Path) -> TemplateConfig:
"""Loads a yaml configuation file into a TemplateConfig model. The
TemplateConfig holds all the variables the jinja template files use.

Parameters
----------
config_file_path : str | Path
The path to the yaml config.

Returns
-------
TemplateConfig
The template configuration.
"""

# Load the raw configuration data.
config_file_path = Path(config_file_path)
with open(config_file_path, "r", encoding="utf-8") as file:
config_data = yaml.safe_load(file)

# Populate the configuration model and return the result.
template_config = TemplateConfig(**config_data)
return template_config
62 changes: 62 additions & 0 deletions codegen/hyperdrive_codegen/file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Utilities for working with files."""

import os
import shutil
from pathlib import Path


def write_string_to_file(path: str | os.PathLike, code: str) -> None:
"""Writes a string to a file.

Parameters
----------
path : str | os.PathLike
The location of the output file.
code : str
The code to be written, as a single string.
"""
with open(path, "w", encoding="utf-8") as output_file:
output_file.write(code)


def get_output_folder_structure(lowercase_name: str) -> dict:
"""Returns a dictionary representation of the output folder structure.

Parameters
----------
lowercase_name: str
The name of the protocol we are generating boilerplate integration code for. Should be in all lowercase letters.

Returns
-------
dict
"""
return {"deployers": {lowercase_name: {}}, "instances": {lowercase_name: {}}, "interfaces": {}}


def setup_directory(base_path: Path | str, structure: dict, clear_existing: bool = False) -> None:
"""Recursively sets up a directory tree based on a given structure. Existing directories can be optionally cleared.

Parameters
----------
base_path : Path
The base path where the directory tree starts.
structure : dict
A nested dictionary representing the directory structure to be created. Each key is a directory name with its value being another dictionary for subdirectories.
clear_existing : bool, optional
Whether to clear existing directories before setting up the new structure. Defaults to False.
"""
base_path = Path(base_path)
if clear_existing and base_path.exists():
shutil.rmtree(base_path)
base_path.mkdir(parents=True, exist_ok=True)

for name, sub_structure in structure.items():
sub_path = base_path / name
if clear_existing and sub_path.exists():
shutil.rmtree(sub_path)
sub_path.mkdir(exist_ok=True)

if isinstance(sub_structure, dict):
# Recursively set up subdirectories
setup_directory(sub_path, sub_structure, clear_existing)
16 changes: 16 additions & 0 deletions codegen/hyperdrive_codegen/jinja.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Utilities for working with jinja."""

import os

from jinja2 import Environment, FileSystemLoader


def get_jinja_env() -> Environment:
"""Returns the jinja environment."""
script_dir = os.path.dirname(os.path.abspath(__file__))

# Construct the path to your templates directory.
templates_dir = os.path.join(script_dir, "../templates")
env = Environment(loader=FileSystemLoader(templates_dir))

return env
Loading