diff --git a/.github/workflows/codegen_test.yml b/.github/workflows/codegen_test.yml new file mode 100644 index 000000000..5ef968772 --- /dev/null +++ b/.github/workflows/codegen_test.yml @@ -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 diff --git a/codegen/CODEOWNERS b/codegen/CODEOWNERS new file mode 100644 index 000000000..b742718f4 --- /dev/null +++ b/codegen/CODEOWNERS @@ -0,0 +1,3 @@ +# Codegen codeowners + +- @sentilesdal @cashd diff --git a/codegen/README.md b/codegen/README.md new file mode 100644 index 000000000..5852b6f00 --- /dev/null +++ b/codegen/README.md @@ -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. diff --git a/codegen/example/config.yaml b/codegen/example/config.yaml new file mode 100644 index 000000000..cd2938221 --- /dev/null +++ b/codegen/example/config.yaml @@ -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 diff --git a/codegen/example/test_example.py b/codegen/example/test_example.py new file mode 100644 index 000000000..e69de29bb diff --git a/codegen/hyperdrive_codegen/__init__.py b/codegen/hyperdrive_codegen/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/codegen/hyperdrive_codegen/codegen.py b/codegen/hyperdrive_codegen/codegen.py new file mode 100644 index 000000000..1446aeee5 --- /dev/null +++ b/codegen/hyperdrive_codegen/codegen.py @@ -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) diff --git a/codegen/hyperdrive_codegen/config.py b/codegen/hyperdrive_codegen/config.py new file mode 100644 index 000000000..652f8da22 --- /dev/null +++ b/codegen/hyperdrive_codegen/config.py @@ -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 diff --git a/codegen/hyperdrive_codegen/file.py b/codegen/hyperdrive_codegen/file.py new file mode 100644 index 000000000..5c161a7df --- /dev/null +++ b/codegen/hyperdrive_codegen/file.py @@ -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) diff --git a/codegen/hyperdrive_codegen/jinja.py b/codegen/hyperdrive_codegen/jinja.py new file mode 100644 index 000000000..c7553ba93 --- /dev/null +++ b/codegen/hyperdrive_codegen/jinja.py @@ -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 diff --git a/codegen/hyperdrive_codegen/main.py b/codegen/hyperdrive_codegen/main.py new file mode 100644 index 000000000..1d4db07f3 --- /dev/null +++ b/codegen/hyperdrive_codegen/main.py @@ -0,0 +1,64 @@ +"""Main entrypoint for hyperdrive-codegen tool.""" + +import argparse +import sys +from typing import NamedTuple, Sequence + +from hyperdrive_codegen.codegen import codegen + + +def main(argv: Sequence[str] | None = None) -> None: + """Main entrypoint for the hyperdrive-codegen tool. Handles command-line arguments and calling codegen(). + + Parameters + ---------- + argv : Sequence[str] | None, optional + Command line arguments + """ + config_file_path, output_dir = parse_arguments(argv) + codegen(config_file_path, output_dir) + + +class Args(NamedTuple): + """Command line arguments for pypechain.""" + + config_file_path: str + output_dir: str + + +def namespace_to_args(namespace: argparse.Namespace) -> Args: + """Converts argprase.Namespace to Args.""" + return Args( + config_file_path=namespace.config, + output_dir=namespace.out, + ) + + +def parse_arguments(argv: Sequence[str] | None = None) -> Args: + """Parses input arguments""" + parser = argparse.ArgumentParser(description="Generates class files for a given abi.") + parser.add_argument( + "--config", + help="Path to the config yaml file.", + ) + + parser.add_argument( + "--out", + default="./out", + help="Path to the directory where files will be generated. Defaults to out/.", + ) + + # Use system arguments if none were passed + if argv is None: + argv = sys.argv + + # If no arguments were passed, display the help message and exit + if len(argv) == 1: + parser.print_help(sys.stderr) + sys.exit(1) + + return namespace_to_args(parser.parse_args()) + + +if __name__ == "__main__": + main() diff --git a/codegen/hyperdrive_codegen/templates.py b/codegen/hyperdrive_codegen/templates.py new file mode 100644 index 000000000..7df62b65b --- /dev/null +++ b/codegen/hyperdrive_codegen/templates.py @@ -0,0 +1,118 @@ +"""Template helpers.""" + +import os +from dataclasses import dataclass +from pathlib import Path + +from hyperdrive_codegen.config import TemplateConfig +from hyperdrive_codegen.file import write_string_to_file +from jinja2 import Environment, Template + + +@dataclass +class TemplatePathInfo: + """Path and name information for the template.""" + + path: str + base_name: str + folder: str + + +@dataclass +class TemplateInfo: + """Contains a jinja template and path information.""" + + template: Template + path_info: TemplatePathInfo + + +@dataclass +class FileInfo: + """Contains template information and the rendered code.""" + + template: TemplateInfo + rendered_code: str + + +deployer_templates = [ + TemplatePathInfo("deployers/HyperdriveCoreDeployer.sol.jinja", "HyperdriveCoreDeployer", "deployers"), + TemplatePathInfo("deployers/HyperdriveDeployerCoordinator.sol.jinja", "HyperdriveDeployerCoordinator", "deployers"), + TemplatePathInfo("deployers/Target0Deployer.sol.jinja", "Target0Deployer", "deployers"), + TemplatePathInfo("deployers/Target1Deployer.sol.jinja", "Target1Deployer", "deployers"), + TemplatePathInfo("deployers/Target2Deployer.sol.jinja", "Target2Deployer", "deployers"), + TemplatePathInfo("deployers/Target3Deployer.sol.jinja", "Target3Deployer", "deployers"), + TemplatePathInfo("deployers/Target4Deployer.sol.jinja", "Target4Deployer", "deployers"), +] + +instance_templates = [ + TemplatePathInfo("instances/Base.sol.jinja", "Base", "instances"), + TemplatePathInfo("instances/Hyperdrive.sol.jinja", "Hyperdrive", "instances"), + TemplatePathInfo("instances/Target0.sol.jinja", "Target0", "instances"), + TemplatePathInfo("instances/Target1.sol.jinja", "Target1", "instances"), + TemplatePathInfo("instances/Target2.sol.jinja", "Target2", "instances"), + TemplatePathInfo("instances/Target3.sol.jinja", "Target3", "instances"), + TemplatePathInfo("instances/Target4.sol.jinja", "Target4", "instances"), +] + +interface_templates = [ + TemplatePathInfo("interfaces/IHyperdrive.sol.jinja", "Hyperdrive", "interfaces"), + TemplatePathInfo("interfaces/IYieldSource.sol.jinja", "", "interfaces"), +] + + +def get_templates(env: Environment) -> list[TemplateInfo]: + """Returns a list of template files for generating customized Hyperdrive instances. + + Parameters + ---------- + env : Environment + A jinja2 environment that is informed where the templates/ directory is. + + Returns + ------- + list[Template] + The list of jinja2 templates. + """ + + # Gather the template file strings and return a list of TemplateInfo's. + path_infos = deployer_templates + instance_templates + interface_templates + return [TemplateInfo(template=env.get_template(path_info.path), path_info=path_info) for path_info in path_infos] + + +def write_templates_to_files(templates: list[TemplateInfo], output_path: Path, template_config: TemplateConfig): + """Writes a given list of templates to file. + + Parameters + ---------- + templates : list[TemplateInfo] + Template information containing path information and the rendered code. + output_path : Path + The directory to write files to. + template_config : TemplateConfig + Template configuration, has all the variables the jinja templates use. + """ + + for template in templates: + # Get the file information and rendered code. + file_info = FileInfo(template, rendered_code=template.template.render(template_config.model_dump())) + + # Get the contract file name + contract_file_name = f"{template_config.name.capitalized}{file_info.template.path_info.base_name}.sol" + + # Prepend 'I' to the file name if it is an interface file + is_interface_file = file_info.template.path_info.folder == "interfaces" + if is_interface_file: + contract_file_name = "I" + contract_file_name + # NOTE: don't place interface files in a subfolder + contract_file_path = Path( + os.path.join(output_path, file_info.template.path_info.folder, contract_file_name) + ) + else: + contract_file_path = Path( + os.path.join( + output_path, file_info.template.path_info.folder, template_config.name.lowercase, contract_file_name + ) + ) + + # Write the rendered code to file. + write_string_to_file(contract_file_path, file_info.rendered_code) diff --git a/codegen/pyproject.toml b/codegen/pyproject.toml new file mode 100644 index 000000000..708f23f21 --- /dev/null +++ b/codegen/pyproject.toml @@ -0,0 +1,25 @@ +[build-system] +requires = ["flit_core>=3.2"] +build-backend = "flit_core.buildapi" + +[tool.virtualenv] +create = true +env-dir = ".venv" + +[project] +name = "hyperdrive-codegen" +description = "A code generation tool for the Hyperdrive Solidity project." +version = "0.0.1" +readme = "README.md" +requires-python = ">=3.10" +authors = [{ name = "Matthew Brown", email = "matt@delv.tech" }] +dependencies = ["jinja2", "pydantic", "pyyaml"] + +[project.scripts] +hyperdrive-codegen = "hyperdrive_codegen.main:main" + +[tool.black] +line-length = 120 + +[tool.pylint] +max-line-length = 120 diff --git a/codegen/templates/deployers/HyperdriveCoreDeployer.sol.jinja b/codegen/templates/deployers/HyperdriveCoreDeployer.sol.jinja new file mode 100644 index 000000000..d06db0e55 --- /dev/null +++ b/codegen/templates/deployers/HyperdriveCoreDeployer.sol.jinja @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.20; + +import { I{{ name.capitalized }} } from "../../interfaces/I{{ name.capitalized }}.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { IHyperdriveCoreDeployer } from "../../interfaces/IHyperdriveCoreDeployer.sol"; +import { {{ name.capitalized }}Hyperdrive } from "../../instances/{{ name.lowercase }}/{{ name.capitalized }}Hyperdrive.sol"; + +/// @author DELV +/// @title {{ name.capitalized }}HyperdriveCoreDeployer +/// @notice The core deployer for the {{ name.capitalized }}Hyperdrive implementation. +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +contract {{ name.capitalized }}HyperdriveCoreDeployer is IHyperdriveCoreDeployer { + /// @notice Deploys a Hyperdrive instance with the given parameters. + /// @param _config The configuration of the Hyperdrive pool. + /// @param _target0 The target0 address. + /// @param _target1 The target1 address. + /// @param _target2 The target2 address. + /// @param _target3 The target3 address. + /// @param _target4 The target4 address. + /// @param _salt The create2 salt used in the deployment. + /// @return The address of the newly deployed {{ name.capitalized }}Hyperdrive instance. + function deploy( + IHyperdrive.PoolConfig memory _config, + bytes memory, // unused _extraData, + address _target0, + address _target1, + address _target2, + address _target3, + address _target4, + bytes32 _salt + ) external returns (address) { + return ( + address( + // NOTE: We hash the sender with the salt to prevent the + // front-running of deployments. + new {{ name.capitalized }}Hyperdrive{ + salt: keccak256(abi.encode(msg.sender, _salt)) + }(_config, _target0, _target1, _target2, _target3, _target4) + ) + ); + } +} diff --git a/codegen/templates/deployers/HyperdriveDeployerCoordinator.sol.jinja b/codegen/templates/deployers/HyperdriveDeployerCoordinator.sol.jinja new file mode 100644 index 000000000..9d1aa48e1 --- /dev/null +++ b/codegen/templates/deployers/HyperdriveDeployerCoordinator.sol.jinja @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.20; + +import { ERC20 } from "openzeppelin/token/ERC20/ERC20.sol"; +import { SafeERC20 } from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; +import { I{{ name.capitalized }} } from "../../interfaces/I{{ name.capitalized }}.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { I{{ name.capitalized }}Hyperdrive } from "../../interfaces/I{{ name.capitalized }}Hyperdrive.sol"; +import { IHyperdriveDeployerCoordinator } from "../../interfaces/IHyperdriveDeployerCoordinator.sol"; +import { ONE } from "../../libraries/FixedPointMath.sol"; +import { HyperdriveDeployerCoordinator } from "../HyperdriveDeployerCoordinator.sol"; + +/// @author DELV +/// @title {{ name.capitalized }}HyperdriveDeployerCoordinator +/// @notice The deployer coordinator for the {{ name.capitalized }}Hyperdrive +/// implementation. +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +contract {{ name.capitalized }}HyperdriveDeployerCoordinator is HyperdriveDeployerCoordinator { + using SafeERC20 for ERC20; + + /// @notice Instantiates the deployer coordinator. + /// @param _coreDeployer The core deployer. + /// @param _target0Deployer The target0 deployer. + /// @param _target1Deployer The target1 deployer. + /// @param _target2Deployer The target2 deployer. + /// @param _target3Deployer The target3 deployer. + /// @param _target4Deployer The target4 deployer. + constructor( + address _coreDeployer, + address _target0Deployer, + address _target1Deployer, + address _target2Deployer, + address _target3Deployer, + address _target4Deployer + ) + HyperdriveDeployerCoordinator( + _coreDeployer, + _target0Deployer, + _target1Deployer, + _target2Deployer, + _target3Deployer, + _target4Deployer + ) + {} + + /// @dev Prepares the coordinator for initialization by drawing funds from + /// the LP, if necessary. + /// @param _hyperdrive The Hyperdrive instance that is being initialized. + /// @param _lp The LP that is initializing the pool. + /// @param _contribution The amount of capital to supply. The units of this + /// quantity are either base or vault shares, depending on the value + /// of `_options.asBase`. + /// @param _options The options that configure how the initialization is + /// settled. + /// @return value The value that should be sent in the initialize transaction. + function _prepareInitialize( + IHyperdrive _hyperdrive, + address _lp, + uint256 _contribution, + IHyperdrive.Options memory _options +{% if contract.as_base_allowed and contract.payable %} + ) internal override returns (uint256) { + uint256 value; + // If base is the deposit asset, ensure that enough ether was sent to + // the contract and return the amount of ether that should be sent for + // the contribution. + if (_options.asBase) { + if (msg.value < _contribution) { + revert IHyperdriveDeployerCoordinator.InsufficientValue(); + } + value = _contribution; + } + + // Otherwise, transfer vault shares from the LP and approve the + // Hyperdrive pool. + else { + // **************************************************************** + // FIXME: Implement this for new instances. ERC20 example provided. + // Take custody of the contribution and approve Hyperdrive to pull + // the tokens. + address token = _hyperdrive.vaultSharesToken(); + ERC20(token).safeTransferFrom(_lp, address(this), _contribution); + ERC20(token).forceApprove(address(_hyperdrive), _contribution); + // **************************************************************** + } +{% elif contract.as_base_allowed and not contract.payable %} + ) internal override returns (uint256 value) { + // If base is the deposit asset, the initialization will be paid in the + // base token. + address token; + if (_options.asBase) { + token = _hyperdrive.baseToken(); + } + // Otherwise, the initialization will be paid in vault shares. + else { + token = _hyperdrive.vaultSharesToken(); + } + + // **************************************************************** + // FIXME: Implement this for new instances. ERC20 example provided. + // Take custody of the contribution and approve Hyperdrive to pull the + // tokens. + ERC20(token).safeTransferFrom(_lp, address(this), _contribution); + ERC20(token).forceApprove(address(_hyperdrive), _contribution); + // **************************************************************** +{% elif not contract.as_base_allowed and not contract.payable %} + ) internal override returns (uint256 value) { + // Depositing as base is disallowed. + if (_options.asBase) { + revert IHyperdrive.UnsupportedToken(); + } + + // **************************************************************** + // FIXME: Implement this for new instances. ERC20 example provided. + // Otherwise, transfer vault shares from the LP and approve the + // Hyperdrive pool. + address token = _hyperdrive.vaultSharesToken(); + ERC20(token).transferFrom(_lp, address(this), _contribution); + ERC20(token).approve(address(_hyperdrive), _contribution); + // **************************************************************** +{% endif %} + return value; + } + +{% if contract.payable %} + /// @dev Allows the contract to receive ether. + function _checkMessageValue() internal view override {} +{% else %} + /// @dev Prevents the contract from receiving ether. + function _checkMessageValue() internal view override { + if (msg.value != 0) { + revert IHyperdriveDeployerCoordinator.NotPayable(); + } + } +{% endif %} + + /// @notice Checks the pool configuration to ensure that it is valid. + /// @param _deployConfig The deploy configuration of the Hyperdrive pool. + function _checkPoolConfig( + IHyperdrive.PoolDeployConfig memory _deployConfig + ) internal view override { + // Perform the default checks. + super._checkPoolConfig(_deployConfig); + + // Ensure that the minimum share reserves are equal to 1e15. This value + // has been tested to prevent arithmetic overflows in the + // `_updateLiquidity` function when the share reserves are as high as + // 200 million. + if (_deployConfig.minimumShareReserves != 1e15) { + revert IHyperdriveDeployerCoordinator.InvalidMinimumShareReserves(); + } + + // Ensure that the minimum transaction amount are equal to 1e15. This + // value has been tested to prevent precision issues. + if (_deployConfig.minimumTransactionAmount != 1e15) { + revert IHyperdriveDeployerCoordinator + .InvalidMinimumTransactionAmount(); + } + } + + /// @dev Gets the initial vault share price of the Hyperdrive pool. + /// @return The initial vault share price of the Hyperdrive pool. + function _getInitialVaultSharePrice( + IHyperdrive.PoolDeployConfig memory, // unused _deployConfig + bytes memory // unused _extraData + ) internal pure override returns (uint256) { + // **************************************************************** + // FIXME: Implement this for new instances. + return ONE; + // **************************************************************** + } +} diff --git a/codegen/templates/deployers/Target0Deployer.sol.jinja b/codegen/templates/deployers/Target0Deployer.sol.jinja new file mode 100644 index 000000000..1b1ad8b99 --- /dev/null +++ b/codegen/templates/deployers/Target0Deployer.sol.jinja @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.20; + +import { {{ name.capitalized }}Target0 } from "../../instances/{{ name.lowercase }}/{{ name.capitalized }}Target0.sol"; +import { I{{ name.capitalized }} } from "../../interfaces/I{{ name.capitalized }}.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { IHyperdriveTargetDeployer } from "../../interfaces/IHyperdriveTargetDeployer.sol"; + +/// @author DELV +/// @title {{ name.capitalized }}Target0Deployer +/// @notice The target0 deployer for the {{ name.capitalized }}Hyperdrive implementation. +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +contract {{ name.capitalized }}Target0Deployer is IHyperdriveTargetDeployer { + /// @notice Deploys a target0 instance with the given parameters. + /// @param _config The configuration of the Hyperdrive pool. + /// @param _salt The create2 salt used in the deployment. + /// @return The address of the newly deployed {{ name.capitalized }}Target0 instance. + function deploy( + IHyperdrive.PoolConfig memory _config, + bytes memory, // unused _extraData + bytes32 _salt + ) external returns (address) { + return + address( + // NOTE: We hash the sender with the salt to prevent the + // front-running of deployments. + new {{ name.capitalized }}Target0{ + salt: keccak256(abi.encode(msg.sender, _salt)) + }(_config) + ); + } +} diff --git a/codegen/templates/deployers/Target1Deployer.sol.jinja b/codegen/templates/deployers/Target1Deployer.sol.jinja new file mode 100644 index 000000000..559cca208 --- /dev/null +++ b/codegen/templates/deployers/Target1Deployer.sol.jinja @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.20; + +import { {{ name.capitalized }}Target1 } from "../../instances/{{ name.lowercase }}/{{ name.capitalized }}Target1.sol"; +import { I{{ name.capitalized }} } from "../../interfaces/I{{ name.capitalized }}.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { IHyperdriveTargetDeployer } from "../../interfaces/IHyperdriveTargetDeployer.sol"; + +/// @author DELV +/// @title {{ name.capitalized }}Target1Deployer +/// @notice The target1 deployer for the {{ name.capitalized }}Hyperdrive implementation. +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +contract {{ name.capitalized }}Target1Deployer is IHyperdriveTargetDeployer { + /// @notice Deploys a target1 instance with the given parameters. + /// @param _config The configuration of the Hyperdrive pool. + /// @param _salt The create2 salt used in the deployment. + /// @return The address of the newly deployed {{ name.capitalized }}Target1 instance. + function deploy( + IHyperdrive.PoolConfig memory _config, + bytes memory, // unused _extraData + bytes32 _salt + ) external returns (address) { + return + address( + // NOTE: We hash the sender with the salt to prevent the + // front-running of deployments. + new {{ name.capitalized }}Target1{ + salt: keccak256(abi.encode(msg.sender, _salt)) + }(_config) + ); + } +} diff --git a/codegen/templates/deployers/Target2Deployer.sol.jinja b/codegen/templates/deployers/Target2Deployer.sol.jinja new file mode 100644 index 000000000..3e7b496e0 --- /dev/null +++ b/codegen/templates/deployers/Target2Deployer.sol.jinja @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.20; + +import { {{ name.capitalized }}Target2 } from "../../instances/{{ name.lowercase }}/{{ name.capitalized }}Target2.sol"; +import { I{{ name.capitalized }} } from "../../interfaces/I{{ name.capitalized }}.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { IHyperdriveTargetDeployer } from "../../interfaces/IHyperdriveTargetDeployer.sol"; + +/// @author DELV +/// @title {{ name.capitalized }}Target2Deployer +/// @notice The target2 deployer for the {{ name.capitalized }}Hyperdrive implementation. +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +contract {{ name.capitalized }}Target2Deployer is IHyperdriveTargetDeployer { + /// @notice Deploys a target2 instance with the given parameters. + /// @param _config The configuration of the Hyperdrive pool. + /// @param _salt The create2 salt used in the deployment. + /// @return The address of the newly deployed {{ name.capitalized }}Target2 instance. + function deploy( + IHyperdrive.PoolConfig memory _config, + bytes memory, // unused _extraData + bytes32 _salt + ) external returns (address) { + return + address( + // NOTE: We hash the sender with the salt to prevent the + // front-running of deployments. + new {{ name.capitalized }}Target2{ + salt: keccak256(abi.encode(msg.sender, _salt)) + }(_config) + ); + } +} diff --git a/codegen/templates/deployers/Target3Deployer.sol.jinja b/codegen/templates/deployers/Target3Deployer.sol.jinja new file mode 100644 index 000000000..d242c868f --- /dev/null +++ b/codegen/templates/deployers/Target3Deployer.sol.jinja @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.20; + +import { {{ name.capitalized }}Target3 } from "../../instances/{{ name.lowercase }}/{{ name.capitalized }}Target3.sol"; +import { I{{ name.capitalized }} } from "../../interfaces/I{{ name.capitalized }}.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { IHyperdriveTargetDeployer } from "../../interfaces/IHyperdriveTargetDeployer.sol"; + +/// @author DELV +/// @title {{ name.capitalized }}Target3Deployer +/// @notice The target3 deployer for the {{ name.capitalized }}Hyperdrive implementation. +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +contract {{ name.capitalized }}Target3Deployer is IHyperdriveTargetDeployer { + /// @notice Deploys a target3 instance with the given parameters. + /// @param _config The configuration of the Hyperdrive pool. + /// @param _salt The create2 salt used in the deployment. + /// @return The address of the newly deployed {{ name.capitalized }}Target3 instance. + function deploy( + IHyperdrive.PoolConfig memory _config, + bytes memory, // unused _extraData + bytes32 _salt + ) external returns (address) { + return + address( + // NOTE: We hash the sender with the salt to prevent the + // front-running of deployments. + new {{ name.capitalized }}Target3{ + salt: keccak256(abi.encode(msg.sender, _salt)) + }(_config) + ); + } +} diff --git a/codegen/templates/deployers/Target4Deployer.sol.jinja b/codegen/templates/deployers/Target4Deployer.sol.jinja new file mode 100644 index 000000000..ef67191dd --- /dev/null +++ b/codegen/templates/deployers/Target4Deployer.sol.jinja @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.20; + +import { {{ name.capitalized }}Target4 } from "../../instances/{{ name.lowercase }}/{{ name.capitalized }}Target4.sol"; +import { I{{ name.capitalized }} } from "../../interfaces/I{{ name.capitalized }}.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { IHyperdriveTargetDeployer } from "../../interfaces/IHyperdriveTargetDeployer.sol"; + +/// @author DELV +/// @title {{ name.capitalized }}Target4Deployer +/// @notice The target4 deployer for the {{ name.capitalized }}Hyperdrive implementation. +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +contract {{ name.capitalized }}Target4Deployer is IHyperdriveTargetDeployer { + /// @notice Deploys a target4 instance with the given parameters. + /// @param _config The configuration of the Hyperdrive pool. + /// @param _salt The create2 salt used in the deployment. + /// @return The address of the newly deployed {{ name.capitalized }}Target4 instance. + function deploy( + IHyperdrive.PoolConfig memory _config, + bytes memory, // unused _extraData + bytes32 _salt + ) external returns (address) { + return + address( + // NOTE: We hash the sender with the salt to prevent the + // front-running of deployments. + new {{ name.capitalized }}Target4{ + salt: keccak256(abi.encode(msg.sender, _salt)) + }(_config) + ); + } +} diff --git a/codegen/templates/instances/Base.sol.jinja b/codegen/templates/instances/Base.sol.jinja new file mode 100644 index 000000000..d1ce1e107 --- /dev/null +++ b/codegen/templates/instances/Base.sol.jinja @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.20; + +import { ERC20 } from "openzeppelin/token/ERC20/ERC20.sol"; +import { SafeERC20 } from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; +import { IERC4626 } from "../../interfaces/IERC4626.sol"; +import { I{{ name.capitalized }} } from "../../interfaces/I{{ name.capitalized }}.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { HyperdriveBase } from "../../internal/HyperdriveBase.sol"; + +/// @author DELV +/// @title {{ name.capitalized }}Base +/// @notice The base contract for the {{ name.capitalized }} Hyperdrive implementation. +/// @dev This Hyperdrive implementation is designed to work with standard +/// {{ name.capitalized }} vaults. Non-standard implementations may not work correctly +/// and should be carefully checked. +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +abstract contract {{ name.capitalized }}Base is HyperdriveBase { + using SafeERC20 for ERC20; + + /// Yield Source /// + + /// @dev Accepts a deposit from the user in base. +{%- if contract.as_base_allowed %} + /// @param _baseAmount The base amount to deposit. + /// @return The shares that were minted in the deposit. + /// @return The amount of ETH to refund. Since this yield source isn't + /// payable, this is always zero. + function _depositWithBase( + uint256 _baseAmount, + bytes calldata // unused + ) internal override returns (uint256, uint256) { + // **************************************************************** + // FIXME: Implement this for new instances. ERC4626 example provided. + // Take custody of the deposit in base. + ERC20(address(_baseToken)).safeTransferFrom( + msg.sender, + address(this), + _baseAmount + ); + + // Deposit the base into the yield source. + // + // NOTE: We increase the required approval amount by 1 wei so that + // the vault ends with an approval of 1 wei. This makes future + // approvals cheaper by keeping the storage slot warm. + ERC20(address(_baseToken)).forceApprove( + address(_vaultSharesToken), + _baseAmount + 1 + ); + uint256 sharesMinted = IERC4626(address(_vaultSharesToken)).deposit( + _baseAmount, + address(this) + ); + + return (sharesMinted, 0); + // **************************************************************** +{% else %} + /// @return The shares that were minted in the deposit. + /// @return The amount of ETH to refund. Since this yield source isn't + /// payable, this is always zero. + function _depositWithBase( + uint256, // unused _baseAmount + bytes calldata // unused _extraData + ) internal pure override returns (uint256, uint256) { + revert IHyperdrive.UnsupportedToken(); +{%- endif %} + } + + /// @dev Process a deposit in vault shares. + /// @param _shareAmount The vault shares amount to deposit. + function _depositWithShares( + uint256 _shareAmount, + bytes calldata // unused _extraData + ) internal override { + // **************************************************************** + // FIXME: Implement this for new instances. ERC20 example provided. + // Take custody of the deposit in vault shares. + ERC20(address(_vaultSharesToken)).safeTransferFrom( + msg.sender, + address(this), + _shareAmount + ); + // **************************************************************** + } + + /// @dev Process a withdrawal in base and send the proceeds to the + /// destination. +{% if contract.as_base_allowed %} + /// @param _shareAmount The amount of vault shares to withdraw. + /// @param _destination The destination of the withdrawal. + /// @return amountWithdrawn The amount of base withdrawn. + function _withdrawWithBase( + uint256 _shareAmount, + address _destination, + bytes calldata // unused + ) internal override returns (uint256 amountWithdrawn) { + // **************************************************************** + // FIXME: Implement this for new instances. ERC4626 example provided. + // Redeem from the yield source and transfer the + // resulting base to the destination address. + amountWithdrawn = IERC4626(address(_vaultSharesToken)).redeem( + _shareAmount, + _destination, + address(this) + ); + + return amountWithdrawn; + // **************************************************************** +{% else %} + /// @return amountWithdrawn The amount of base withdrawn. + function _withdrawWithBase( + uint256, // unused _shareAmount + address, // unused _destination + bytes calldata // unused _extraData + ) internal pure override returns (uint256) { + revert IHyperdrive.UnsupportedToken(); +{%- endif %} + } + + /// @dev Process a withdrawal in vault shares and send the proceeds to the + /// destination. + /// @param _shareAmount The amount of vault shares to withdraw. + /// @param _destination The destination of the withdrawal. + function _withdrawWithShares( + uint256 _shareAmount, + address _destination, + bytes calldata // unused + ) internal override { + // **************************************************************** + // FIXME: Implement this for new instances. ERC20 example provided. + // Transfer vault shares to the destination. + ERC20(address(_vaultSharesToken)).safeTransfer(_destination, _shareAmount); + // **************************************************************** + } + + /// @dev Convert an amount of vault shares to an amount of base. + /// @param _shareAmount The vault shares amount. + /// @return The base amount. + function _convertToBase( + uint256 _shareAmount + ) internal view override returns (uint256) { + // **************************************************************** + // FIXME: Implement this for new instances. + return + IERC4626(address(_vaultSharesToken)).convertToAssets(_shareAmount); + // **************************************************************** + } + + /// @dev Convert an amount of base to an amount of vault shares. + /// @param _baseAmount The base amount. + /// @return The vault shares amount. + function _convertToShares( + uint256 _baseAmount + ) internal view override returns (uint256) { + // **************************************************************** + // FIXME: Implement this for new instances. + return + IERC4626(address(_vaultSharesToken)).convertToShares(_baseAmount); + // **************************************************************** + } + + /// @dev Gets the total amount of base held by the pool. + /// @return baseAmount The total amount of base. + function _totalBase() internal view override returns (uint256) { + return _baseToken.balanceOf(address(this)); + } + + /// @dev Gets the total amount of shares held by the pool in the yield + /// source. + /// @return shareAmount The total amount of shares. + function _totalShares() + internal + view + override + returns (uint256 shareAmount) + { + return _vaultSharesToken.balanceOf(address(this)); + } + +{% if contract.payable %} + /// @dev We override the message value check since this integration is + /// payable. + function _checkMessageValue() internal pure override {} +{% else %} + /// @dev We override the message value check since this integration is + /// not payable. + function _checkMessageValue() internal view override { + if (msg.value > 0) { + revert IHyperdrive.NotPayable(); + } + } +{%- endif %} +} diff --git a/codegen/templates/instances/Hyperdrive.sol.jinja b/codegen/templates/instances/Hyperdrive.sol.jinja new file mode 100644 index 000000000..ee1b2f64e --- /dev/null +++ b/codegen/templates/instances/Hyperdrive.sol.jinja @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.20; + +import { ERC20 } from "openzeppelin/token/ERC20/ERC20.sol"; +import { SafeERC20 } from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; +import { Hyperdrive } from "../../external/Hyperdrive.sol"; +import { IERC20 } from "../../interfaces/IERC20.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { {{ name.capitalized }}Base } from "./{{ name.capitalized }}Base.sol"; + +/// ______ __ _________ _____ +/// ___ / / /____ ___________________________ /_________(_)__ ______ +/// __ /_/ /__ / / /__ __ \ _ \_ ___/ __ /__ ___/_ /__ | / / _ \ +/// _ __ / _ /_/ /__ /_/ / __/ / / /_/ / _ / _ / __ |/ // __/ +/// /_/ /_/ _\__, / _ ___/\___//_/ \__,_/ /_/ /_/ _____/ \___/ +/// /____/ /_/ +/// XXX ++ ++ XXX +/// ############ XXXXX ++0+ +0++ XXXXX ########### +/// ##////////////######## ++00++ ++00++ ########///////////## +/// ##////////////########## ++000++ ++000++ ##########///////////## +/// ##%%%%%%///// ###### ++0000+ +0000++ ###### /////%%%%%%## +/// %%%%%%%%&& ## ++0000+ +0000++ ## &&%%%%%%%%% +/// %&&& ## +o000+ +000o+ ## &&&% +/// ## ++00+- -+00++ ## +/// #% ++0+ +0++ %# +/// ###-:Oo.++++.oO:-### +/// ##: 00++++++00 :## +/// #S###########* 0++00+++00++0 *##########S# +/// #S % $ 0+++0 $ % S# +/// #S ---------- %+++++:#:+++++%----------- S# +/// #S ------------- %++++: ### :++++%------------ S# +/// S ---------------%++++*\ | /*++++%------------- S +/// #S --------------- %++++ ~W~ ++++%666--o UUUU o- S# +/// #S? --------------- %+++++~+++++%&&&8 o \ / o ?S# +/// ?*????**+++;::,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,::;+++**????*? +/// #?+////////////////////////////////////////////////////////////////+?# +/// #;;;;;//////////////////////////////////////////////////////////////;;;;;# +/// S;;;;;;;;;//////////////////////////////////////////////////////////;;;;;;;;;S +/// /;;;;;;;;;;;///////////////////////////////////////////////////////;;;;;;;;;;;;\ +/// |||OOOOOOOO||OOOOOOOO=========== __ ___ ===========OOOOOOOO||OOOOOOOO||| +/// |||OOOOOOOO||OOOOOOOO===========| \[__ | \ /===========OOOOOOOO||OOOOOOOO||| +/// |||OOOOOOOO||OOOOOOOO===========|__/[___|___ \/ ===========OOOOOOOO||OOOOOOOO||| +/// |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +/// |||////////000000000000\\\\\\\\|:::::::::::::::|////////00000000000\\\\\\\\\\||| +/// SSS\\\\\\\\000000000000////////|:::::0x666:::::|\\\\\\\\00000000000//////////SSS +/// SSS|||||||||||||||||||||||||||||:::::::::::::::||||||||||||||||||||||||||||||SSS +/// SSSSSSSS|_______________|______________||_______________|______________|SSSSSSSS +/// SSSSSSSS SSSSSSSS +/// SSSSSSSS SSSSSSSS +/// +/// @author DELV +/// @title {{ name.capitalized }}Hyperdrive +/// @notice A Hyperdrive instance that uses a {{ name.capitalized }} vault as the yield source. +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +contract {{ name.capitalized }}Hyperdrive is Hyperdrive, {{ name.capitalized }}Base { + using SafeERC20 for ERC20; + + /// @notice Instantiates Hyperdrive with a {{ name.capitalized }} vault as the yield source. + /// @param _config The configuration of the Hyperdrive pool. + /// @param _target0 The target0 address. + /// @param _target1 The target1 address. + /// @param _target2 The target2 address. + /// @param _target3 The target3 address. + /// @param _target4 The target4 address. + constructor( + IHyperdrive.PoolConfig memory _config, + address _target0, + address _target1, + address _target2, + address _target3, + address _target4 + ) + Hyperdrive(_config, _target0, _target1, _target2, _target3, _target4) + { + // Approve the base token with 1 wei. This ensures that all of the + // subsequent approvals will be writing to a dirty storage slot. + ERC20(address(_config.baseToken)).forceApprove( + address(_config.vaultSharesToken), + 1 + ); + } +} diff --git a/codegen/templates/instances/Target0.sol.jinja b/codegen/templates/instances/Target0.sol.jinja new file mode 100644 index 000000000..cc161964a --- /dev/null +++ b/codegen/templates/instances/Target0.sol.jinja @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.20; + +import { HyperdriveTarget0 } from "../../external/HyperdriveTarget0.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { {{ name.capitalized }}Base } from "./{{ name.capitalized }}Base.sol"; + +/// @author DELV +/// @title {{ name.capitalized }}Target0 +/// @notice {{ name.capitalized }}Hyperdrive's target0 logic contract. This contract contains +/// all of the getters for Hyperdrive as well as some stateful +/// functions. +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +contract {{ name.capitalized }}Target0 is HyperdriveTarget0, {{ name.capitalized }}Base { + /// @notice Initializes the target0 contract. + /// @param _config The configuration of the Hyperdrive pool. + constructor( + IHyperdrive.PoolConfig memory _config + ) HyperdriveTarget0(_config) {} + +} diff --git a/codegen/templates/instances/Target1.sol.jinja b/codegen/templates/instances/Target1.sol.jinja new file mode 100644 index 000000000..711208bf7 --- /dev/null +++ b/codegen/templates/instances/Target1.sol.jinja @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.20; + +import { HyperdriveTarget1 } from "../../external/HyperdriveTarget1.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { {{ name.capitalized }}Base } from "./{{ name.capitalized }}Base.sol"; + +/// @author DELV +/// @title {{ name.capitalized }}Target1 +/// @notice {{ name.capitalized }}Hyperdrive's target1 logic contract. This contract contains +/// several stateful functions that couldn't fit into the Hyperdrive +/// contract. +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +contract {{ name.capitalized }}Target1 is HyperdriveTarget1, {{ name.capitalized }}Base { + /// @notice Initializes the target1 contract. + /// @param _config The configuration of the Hyperdrive pool. + constructor( + IHyperdrive.PoolConfig memory _config + ) HyperdriveTarget1(_config) {} +} diff --git a/codegen/templates/instances/Target2.sol.jinja b/codegen/templates/instances/Target2.sol.jinja new file mode 100644 index 000000000..cf8b4326d --- /dev/null +++ b/codegen/templates/instances/Target2.sol.jinja @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.20; + +import { HyperdriveTarget2 } from "../../external/HyperdriveTarget2.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { {{ name.capitalized }}Base } from "./{{ name.capitalized }}Base.sol"; + +/// @author DELV +/// @title {{ name.capitalized }}Target2 +/// @notice {{ name.capitalized }}Hyperdrive's target2 logic contract. This contract contains +/// several stateful functions that couldn't fit into the Hyperdrive +/// contract. +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +contract {{ name.capitalized }}Target2 is HyperdriveTarget2, {{ name.capitalized }}Base { + /// @notice Initializes the target2 contract. + /// @param _config The configuration of the Hyperdrive pool. + constructor( + IHyperdrive.PoolConfig memory _config + ) HyperdriveTarget2(_config) {} +} diff --git a/codegen/templates/instances/Target3.sol.jinja b/codegen/templates/instances/Target3.sol.jinja new file mode 100644 index 000000000..3942f387a --- /dev/null +++ b/codegen/templates/instances/Target3.sol.jinja @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.20; + +import { HyperdriveTarget3 } from "../../external/HyperdriveTarget3.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { {{ name.capitalized }}Base } from "./{{ name.capitalized }}Base.sol"; + +/// @author DELV +/// @title {{ name.capitalized }}Target3 +/// @notice {{ name.capitalized }}Hyperdrive's target3 logic contract. This contract contains +/// several stateful functions that couldn't fit into the Hyperdrive +/// contract. +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +contract {{ name.capitalized }}Target3 is HyperdriveTarget3, {{ name.capitalized }}Base { + /// @notice Initializes the target3 contract. + /// @param _config The configuration of the Hyperdrive pool. + constructor( + IHyperdrive.PoolConfig memory _config + ) HyperdriveTarget3(_config) {} +} diff --git a/codegen/templates/instances/Target4.sol.jinja b/codegen/templates/instances/Target4.sol.jinja new file mode 100644 index 000000000..ba8736fbf --- /dev/null +++ b/codegen/templates/instances/Target4.sol.jinja @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.20; + +import { HyperdriveTarget4 } from "../../external/HyperdriveTarget4.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { {{ name.capitalized }}Base } from "./{{ name.capitalized }}Base.sol"; + +/// @author DELV +/// @title {{ name.capitalized }}Target4 +/// @notice {{ name.capitalized }}Hyperdrive's target4 logic contract. This contract contains +/// several stateful functions that couldn't fit into the Hyperdrive +/// contract. +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +contract {{ name.capitalized }}Target4 is HyperdriveTarget4, {{ name.capitalized }}Base { + /// @notice Initializes the target4 contract. + /// @param _config The configuration of the Hyperdrive pool. + constructor( + IHyperdrive.PoolConfig memory _config + ) HyperdriveTarget4(_config) {} +} diff --git a/codegen/templates/interfaces/IHyperdrive.sol.jinja b/codegen/templates/interfaces/IHyperdrive.sol.jinja new file mode 100644 index 000000000..389c462f8 --- /dev/null +++ b/codegen/templates/interfaces/IHyperdrive.sol.jinja @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.20; + +import { IHyperdrive } from "./IHyperdrive.sol"; + +interface I{{ name.capitalized }}Hyperdrive is + IHyperdrive +{ + /// @notice Gets the vault used as this pool's yield source. + /// @return The compatible yield source. + function vault() external view returns (address); +} + diff --git a/codegen/templates/interfaces/IYieldSource.sol.jinja b/codegen/templates/interfaces/IYieldSource.sol.jinja new file mode 100644 index 000000000..ed83dad2b --- /dev/null +++ b/codegen/templates/interfaces/IYieldSource.sol.jinja @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.20; + +import { IERC4626 } from "./IERC4626.sol"; + +// **************************************************************************** +// FIXME: Fill out the interface as needed. ERC4626 extended as an example. + +/// @author DELV +/// @title I{{ name.capitalized }} +/// @notice The interface file for {{ name.capitalized}} +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +abstract contract I{{ name.capitalized }} is IERC4626 {} +// **************************************************************************** \ No newline at end of file diff --git a/codegen/test/test.sh b/codegen/test/test.sh new file mode 100755 index 000000000..7ffeae8e0 --- /dev/null +++ b/codegen/test/test.sh @@ -0,0 +1,12 @@ + +echo "Testing payable and as_base_allowed" +hyperdrive-codegen --config codegen/test/test_payable_and_as_base_allowed.yaml --out contracts/src +make build-sol + +echo "Testing not payable and as_base_allowed" +hyperdrive-codegen --config codegen/test/test_not_payable_and_as_base_allowed.yaml --out contracts/src +make build-sol + +echo "Testing not payable and not as_base_allowed" +hyperdrive-codegen --config codegen/test/test_not_payable_and_not_as_base_allowed.yaml --out contracts/src +make build-sol \ No newline at end of file diff --git a/codegen/test/test_not_payable_and_as_base_allowed.yaml b/codegen/test/test_not_payable_and_as_base_allowed.yaml new file mode 100644 index 000000000..248881688 --- /dev/null +++ b/codegen/test/test_not_payable_and_as_base_allowed.yaml @@ -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: false + + # If the contract can accept base to convert to valut shares on behalf of the + # user. + as_base_allowed: false diff --git a/codegen/test/test_not_payable_and_not_as_base_allowed.yaml b/codegen/test/test_not_payable_and_not_as_base_allowed.yaml new file mode 100644 index 000000000..bc4518d78 --- /dev/null +++ b/codegen/test/test_not_payable_and_not_as_base_allowed.yaml @@ -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: false + + # If the contract can accept base to convert to valut shares on behalf of the + # user. + as_base_allowed: true diff --git a/codegen/test/test_payable_and_as_base_allowed.yaml b/codegen/test/test_payable_and_as_base_allowed.yaml new file mode 100644 index 000000000..cd2938221 --- /dev/null +++ b/codegen/test/test_payable_and_as_base_allowed.yaml @@ -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