@majora-finance/developer-kit

0.0.81 • Public • Published

Majora developer kit

This developer kit aim to provide tools to implement Majora. Blocks

Instalation

Initialize a new hardhat project

mkdir myNewBlock
cd myNewBlock

npx hardhat

Install developer kit npm package

npm i @majora-finance/developer-kit

Import developer kit tasks to hardhat

Edit your hardhat.config.ts and add

import "@majora-finance/developer-kit/src/tasks";

Development steps

Write your block smart contracts

A block corresponding to an action specific to a DeFi protocol So to integrate a protocol, you´ll have to implement one block per actions doable on this one

By exemple, for our Aave V3 blocks, you can find 3 blocks

  • Deposit: Deposit asset on Aave pool
  • Borrow: Deposit asset on Aave pool and borrow a dept at a specified healthfactor
  • Leverage: Deposit asset on Aave pool and leverage at a specified healthfactor with dept swapped and deposited as collateral

You can have a look on Aave v3 blocks here

There is two types of block: strategy block and harvest block

Commons interface

There is two functions in common between every block present in IMajoraCommonBlock interface:

interface IMajoraCommonBlock {
    function ipfsHash() external view returns (string memory);

    function dynamicParamsInfo(
        DataTypes.BlockExecutionType _exec,
        bytes memory parameters,
        DataTypes.OracleState memory oracleState
    ) external view returns (bool, DataTypes.DynamicParamsType, bytes memory);
}

The function ipfsHash have to return the hash of an IPFS document where the blocks metadatas are stores and dynamicParamsInfo function have to return if the block needs dynamic parameters provided by operators on execution (exemple: Swap parameters). To learn more about dynamic parameters, have a look here

Strategy Block

The strategy block is dedicated to be executed when the vault will put funds to work by executing the strategy block list and when it will withdraw funds of the strategy.

This block type need to be complient with our protocol by implementing this interface:

interface IMajoraStrategyBlock is IMajoraCommonBlock {
    function enter(uint256 _index) external;

    function exit(uint256 _index, uint256 _percent) external;

    function oracleEnter(DataTypes.OracleState memory previous, bytes memory parameters)
        external
        view
        returns (DataTypes.OracleState memory);

    function oracleExit(DataTypes.OracleState memory previous, bytes memory parameters)
        external
        view
        returns (DataTypes.OracleState memory);
}

The enter & exit function are called by the vault with a delegate call. So they are executed with the vault's context. The enter function have to execute the action for which the block is written for. The exit function have to the the opposit of the enter function.

This functions receive an index number which allow the block to retrieve its parameters with a storage pointer pointing on a bytes variable. You'll have to write a Struct representing the data you'll have to store inside it. For immutables variables, prefer use the constructor to set them.

The enterOracle & exitOracle function are called by the vault to simulate enter or exit functions. this functions receive an OracleState with simulated tokens amounts at the moment of the block execution. The goal is to edit it like will do the execution function. The bytes parameters is the same block parameters than received on the corresponding execution function.

By exemple, for a Aave deposit block, the enter function have to deposit a token in the lending pool and the exit function have to withdraw funds. For the oracle functions, the enterOracle will remove the deposited $TKN and add the $aTKN amount.

There is many function in OracleState library to help to manage it:

  • findTokenAmount
  • addTokenAmount
  • setTokenAmount
  • removeTokenAmount
  • removeTokenPercent
  • removeAllTokenPercent

You can found the library here

Harvest Block

Harvest block is based on the same concept than strategy block, the harvest block list is executed on vault harvest. The oracleHarvest is a non view function to be usable with Curve like claims on which there is a need to update a local variable.

interface IMajoraStrategyBlock is IMajoraCommonBlock {
    function harvest(uint256 _index) external;

    function oracleHarvest(DataTypes.OracleState memory previous, bytes memory parameters)
        external
        returns (DataTypes.OracleState memory);
}

Block's metadata files

When your block smart contract is ready, you'll have to write contract NatSpec on the contract to generate metadata template with the developer kit.

This file will be used to index the block on our backend and retrive all the information of the block on our frontend.

NatSpec exemple:

/**
 * @title Aave V3 Deposit Majora. Block
 * @author Bliiitz
 * @notice Block to deposit a token on Aave V3
 * @custom:block-id AAVE_V3_DEPOSIT
 * @custom:block-type block
 * @custom:block-action Deposit
 * @custom:block-protocol-id AAVE_V3
 * @custom:block-protocol-name Aave v3
 * @custom:block-params-tuple tuple(uint256 tokenInPercent, address token)
 */
contract MajoraAaveV3DepositBlock is IMajoraStrategyBlock {

After have written this natspec you can call the hardhat task to generate YAML files.

npx hardhat generate-block-metadata

It will create the blocks-metadata folder ad create one metadata file per contract.

Yaml exemple:

id: AAVE_V3_DEPOSIT
name: Aave V3 Deposit Majora. Block
description: Block to deposit a token on Aave V3
type: block
action: Deposit
protocolId: AAVE_V3
protocolName: Aave v3
paramsTuple: tuple(uint256 tokenInPercent, address token)
params:
  - attribute: tokenInPercent
    name: Amount
    type: percent
    underlyingType: uint256
  - attribute: token
    name: Deposit
    type: ERC20
    underlyingType: address
    resolver: AaveV3Deposit

Here, the params object is dedicated to the backend / frontend.

  • Attribute: tuple attribute name
  • Name: the name of the input on the block configuration modal
  • Type: the type of input
  • Resolver: a lambda like function which will be executed to retrieve the data
  • Underlying type: low level type

List of parameters type

  • Type ERC20: display a list of ERC20 tokens returned by the resolver
  • Type percent: percent in basis point (10000 = 100%)
  • Type 1e18: display a number input and add a 10^18 multiplier on user input after validation
  • Type select: display a select based on a list of possibility returned by the resolver or the values parameter attribute
  • Type value: use the value by the specified resolver or the value parameter attribute

If you set types as solidity type (uin256 / address / etc) without resolver, an input of corresponding type will be display on the vault configuration

  • Type address: display a simple text input
  • Type bytes: display a simple text input
  • Type uint256: display a simple text input
  • Type bool: display a simple checkbox input

Write your block parameter resolvers

Publish block metadata and deploy your blocks

Use the devkit to test your blocks

generate yaml config with : npx hardhat generate-block-metadata

Dev-kit Automated Testing Guide

Step 1: Write your block smart contracts

First, write your block smart contracts following the step mentionned above and complete the blocks metadata yaml files.

Step 2: Generate Configuration

Generate the Dev-kit configuration by running the following command:

npx hardhat generate-devkit-config

This command will create YAML files in the devkit-config directory and corresponding tests in the tests/ directory.

Configuration Details

For each YAML file, you need to specify the following parameters:

  • blockParameters: Configuration parameters specific to each block. Ensure the order of these parameters matches the exact sequence outlined in the Solidity contract.

  • constructorArgs: Arguments required for the constructor within the block.

  • tokenIN information:

    • Holder: Address of a wallet that owns the tokenIN on mainnet. This simplifies the process as there's no need to mint or acquire tokens manually.

    • Amount: Amount of tokenIN that the holder should have.

    • Decimals: Number of decimals the tokenIN uses.

Step 3: Run Tests

To run the tests, execute the generated tests, which are named using the pattern DevKit + BlockName + test.ts, using this command :

npx hardhat test test/Devkit<BlockName>.test.ts

Each test corresponds to a YAML configuration file and tests the block as configured.

Readme

Keywords

none

Package Sidebar

Install

npm i @majora-finance/developer-kit

Weekly Downloads

8

Version

0.0.81

License

none

Unpacked Size

134 kB

Total Files

27

Last publish

Collaborators

  • murphylabs