Skip to content

Permissions for Zodiac Roles covering interactions with DeFi protocols

License

Notifications You must be signed in to change notification settings

karpatkey/defi-kit

Repository files navigation

DeFi Kit

Build Status Conventional Commits

Permissions for Zodiac Roles covering interactions with DeFi protocols

https://kit.karpatkey.com

Getting started

You can use DeFi Kit as a TypeScript SDK or via a REST API. Below you can find simple examples for both. For an in-depth overview refer to the docs.

TypeScript SDK

yarn add defi-kit
import { apply, allow } from "defi-kit"

const calls = await apply(roleKey, [allow.cowswap.swap(tokenIn, tokenOut)], {
  address: rolesModAddress,
  mode: "extend",
})

REST API

const res = await fetch(
  "https://kit.karpatkey.com/api/v1/eth:<MOD>/<ROLE>/allow/cowswap/swap?sell=<TOKEN_IN>&buy=<TOKEN_OUT>"
)
const calls = await res.json()

Contribute

Install all dependencies:

yarn

Fetch ABIs and generate types for the sdk package:

yarn setup

Start app dev server and watch sources for changes:

yarn dev

Build sdk and app:

yarn build

To run tests, you first need to install anvil, which comes with foundry. See installation instructions.

Then, you can run tests in watch mode using:

yarn test:watch

Recipes

Add a new protocol to the SDK

  1. Create folder named after the protocol you wanna add in sdk/src/protocols.

  2. Create a index.ts file in that folder.

  3. For every chain that the protocol supports export an object under the chain prefix, e.g.:

    export const eth = {}
    export const gno = {}
  4. For every action that the protocol support add a key to each of the chain exports, e.g.:

    export const eth = {
      deposit: async () => [],
    }
    
    export const gno = {
      deposit: async () => [],
    }
  5. In sdk/src/protocols/index.ts, register the new protocol adding it to the exports for all the chains that it supports, e.g.:

    import * as newProtocol from "./newProtocol"
    
    export const eth = {
      ..., // existing entries
      newProtocol: newProtocol.eth,
    } satisfies Record<string, ProtocolActions>
    
    export const gno = {
      ..., // existing entries
      newProtocol: newProtocol.gno,
    } satisfies Record<string, ProtocolActions>
  6. Implement the functions, adhering to the general API for that action type and returning an array of Permission[]. See following section for guidance.

Note: For the new protocol functions to become available in the SDK playground, the changes must first be published to npm as a new version of the defi-kit package.

Implement an action function using the allow kit

  1. Add entries for the contracts you want to allow calling to sdk/eth-sdk/config.ts. If there are multiple instances of the same contract, such as different pool instances, only add a single entry using any exemplary contract address, e.g.:

    mainnet: {
      curve: {
        regularPool: "0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7",
      }
    }
  2. Run yarn setup. This will automatically fetch the ABI for the listed contracts. In case this fails, you will have to manually add the ABI json file add the respective location in sdk/eth-sdk/abis.

  3. Use the typed allow kit that has been generated:

    import { allow } from "zodiac-roles-sdk/kit"
    
    const permissions = allow.curve.regularPool.exchange()

    In case VSCode IntelliSense does not reflect the newly added contract entries, restart the TypeScript server by pressing cmd+shift+p and selecting "TypeScript: Restart TS Server".

Note that you don't have to use typed allow kits but you can always also author permissions manually.

Test an action function using the test kit

All action functions should be covered with tests to make sure the returned permissions actually allow the desired protocol action and that the protocol action actually leads to the desired state.

  1. Use the applyPermissions helper function to apply the action's permission to a test role that is prepared as part of the global test setup:

    import { applyPermissions } from "../../../test/helpers"
    import { eth } from "."
    
    await applyPermissions(await eth.deposit({ targets: ["ETH", "USDC"] }))
  2. Use test kit to execute calls to any contract in eth-sdk/config.ts through the test role:

    import { testKit } from "../../../test/kit"
    
    await testKit.eth.maker.DSProxy.attach(proxyAddress).execute(...args)
  3. Use the custom jest matchers to check on the transaction outcome:

    import { testKit } from "../../../test/kit"
    
    await expect(
      testKit.eth.maker.DSProxy.attach(proxyAddress).execute(...args)
    ).not.toRevert()

Run specific tests

The test:watch script allows you to target specific test files rather than running the entire test suite:

yarn test:watch maker/deposit

You can pass any substring of the file paths to the targeted test files. Then you can also use the jest watch cli to control which tests shall be run whenever saving changes.

Add a new protocol to the API

Once a protocol has been added to the SDK, a couple of extra steps are necessary to make it available also via the REST API.

  1. Define the parameter schema for the protocol action. Create a schema file in the protocol folder (src/protocols//schema.ts) and add exports for every supported network and action:

    export const eth = {
      deposit: z.object({ targets: z.enum(...).array() }),
    }
    
    export const gno = {
      deposit: z.object({ targets: z.enum(...).array() }),
    }
  2. Register the protocol schema in sdk/src/protocols/schema.ts:

    import * as newProtocol from "./newProtocol/schema"
    
    export const eth = {
      ..., // existing entries
      newProtocol: newProtocol.eth,
    } satisfies Record<string, ProtocolSchemas>
    
    export const gno = {
      ..., // existing entries
      newProtocol: newProtocol.gno,
    } satisfies Record<string, ProtocolSchemas>
  3. Check that the new endpoints have been registered correctly, by opening https://localhost:3000/api-docs.

Update protocol information

We rely on information about each protocol for deriving types and configs. This information is stored in files with names starting with _ that are automatically generated by Python scripts. You find all Python scripts in the scripts directory.

All scripts are run as a nightly job in a Github workflow.

To run a script locally you need to first go through some setup steps:

  • Setup python env, e.g., using anaconda:

    conda create --name defi-kit python=3.1
    conda activate defi-kit
    python -m pip install --upgrade pip
  • Install dependencies:

    cd scripts
    pip install -r requirements.txt
  • Ask your colleagues for the config.json file for the defi-protocols package. Store it in scripts/config.json.

  • Create a file scripts/.env with the following content:

    CONFIG_PATH=<ABSOLUTE_PATH_TO_CONFIG.JSON>
    
  • Run any script inside the scripts folder using the following command:

    yarn run-script aave_v2.py