autodefender
v0.7.2
Published
Manage OpenZeppelin Defender Deployments without a hassle
Downloads
18
Readme
Plug-in-templates for Defender as Code
This repository contains templates and tooling to setup web3 monitoring on OpenZeppelin Defender without hassle.
Structure of this codebase is made with plug-in-ability in mind, so adding new templates is very easy as well!
Why use it?
- Generate monitoring configuration with current on-chain data as source of truth
- Manage only one configuration file and reuse code with use of templates
- Combine matchiing logic templates with monitoring templates to get what you need.
- Run this in CI/CD pipeline to automatically keep up to date with any new deployments
- Typescript support for configuring Defender
Installing and using as package
pnpm add autodefender
or
yarn add autodefender
then create config file defender.config.js
:
const {getProcessEnv, getSlackNotifyChannel} = require('autodefender/utils');
const polygonRPC = getProcessEnv(false, 'POLYGON_RPC_URL');
const mainnerRPC = getProcessEnv(false, 'MAINNET_RPC_URL');
const {
generateOwnableMonitor,
} = require('autodefender/templates/monitors/ownership');
const {all} = require('autodefender/templates/matchers/all');
/** @type import('autodefender/src/types').DefenderConfigType */
const config = {
projectName: 'WORKSHOP1',
ssot: false,
// path: 'https://api.github.com/repos/thesandboxgame/sandbox-smart-contracts/contents/packages/core/deployments',
path: './deployments',
networks: {
matic: {rpc: polygonRPC, directoryName: 'polygon'},
mainnet: {rpc: mainnerRPC, directoryName: 'mainnet'},
},
monitors: {
Ownership: {
filter: all(),
monitor: generateOwnableMonitor(),
notification: {
channels: [getSlackNotifyChannel(getProcessEnv(false, 'SLACK_URL'))],
},
},
},
outDir: './out',
extractedAccountsMonitoring: {},
excludeDeployments: ['Old_*'],
excludeAccounts: ['0x000000000000AAeB6D7670E522A718067333cd4E'],
};
module.exports = config;
To deploy contracts:
yarn autodefender contracts --config defender.config.js
To deploy monitors:
yarn autodefender monitors --config defender.config.js
Setting up dev enviroment:
Run
pnpm install && pnpm types
Open or create new defender.config.ts
in root of this repository.
{
projectName: '', //StackName
path: // url or path to directory that contains /<network>/<contractName>.json. File must contain {abi, address} properties.
networks: { // these are needded for matchers that require web3 connection
matic: {rpc: polygonRPC, directoryName: 'polygon'}, // RPC URL and directory names in path ^^
mainnet: {rpc: mainnerRPC, directoryName: 'mainnet'},
},
monitors: {
'Large-Mint-ERC20': { //Custom name, create as amany as you like
notification: { // Notifyconfig
channels: [getSlackNotifyChannel(getProcessEnv(false, 'SLACK_URL'))], //availible getters in /src/templates/notifications
},
filter: findERC20Contracts(), //see availible matchers in src/templates/matchers
monitor: mintMonitor('ERC20', '100'), //see availible monitors in src/templates/monitors
//ToDo: Add trigger templates here
},
},
outDir: './out', //Directory where serverless resources will be generated in form of json files. You don't need to commit these, but if you want to work with typescript-serverless bypassing this config file - you can use those output files.
extractedAccountsMonitoring: { //These are same as monitors, but the addres space supplied to them is a union of all relatedAccounts that all matchers of monitors have found. Use this to setup monitoring over owner accounts, admins etc.
EthBalance: {
monitor: accountEthMonitor('10'),
filter: all(), //Use additional filtering to narrow down address space for priveledged accounts in particular template
notification: {
channels: [getSlackNotifyChannel(getProcessEnv(false, 'SLACK_URL'))],
},
},
excludeDeployments: [ //Exclude glob pattern files from path
'QUICKSWAP_SAND_MATIC',
'FXCHILD*',
'CHILD_CHAIN_MANAGER',
'*_Implementation',
'*_Proxy',
'TRUSTED_FORWARDER',
'PolygonLand_V1',
'FAKE*',
'DAIMedianize',
],
excludeAccounts: ['0x000000000000AAeB6D7670E522A718067333cd4E'], //Exclude accounts from all monitoring
};
Generating & Deploying
Export env variables
export DEFENDER_SECRET = ""
export DEFENDER_KEY = ""
export POLYGON_RPC_URL="url"
export SLACK_URL="url"
export MAINNET_RPC_URL="url"
NB: Im not a fun of using dotenv. Use export
keyword in env files/variables and source <your_env_file>
or add sorucing to yarn commands, or add dotenv if you like using it.
yarn contracts
yarn monitors
Kinds of Templates
Create new file in /templates/
. Add it to index.
Follow typing definitions of DefenderConfigType
Matchers
Matchers are the templates intended to take some given address space, provider already connected to network and any extra arguments, and it must return array of MatcherFindings
. Each finding has address
of finding and relatedAccounts[]
array (i.e. roles in AccessControl contracts)
Monitors
Findings from Matcher function are passed to Monitor Getter template along with other arguments and generate a monitoring template that consists of Monitor itself, and optional dependencies. Dependencies might be: (trigger/condition) Functions, connected to them Relayers and Secrets.
Messages
md files that must be parsed in to strings and be returned from Monitor template
Notifications
Templates to generate YNotification
Functions
Functions are javascript templates that are ready to be deployed in Defender scripts. They are intended to run in the Defender Node enviroment and therefore best practice is to rollup or use webpack to genetrate them.
Convinient development way is to create your template in src/templates
directory and then add it to the build process in rollup.config.js
. Then by simply running yarn prebuild
your function will be added to templates/functions
.
Creating a new monitor
Monitor getter
Monitor getters are described by
TSentinelGetter<Record<string, string | never>, Record<string, string | never>>;
where inputs are
contractAddresses: string[]// - array of addresses to monitor for
provider: JsonRpcProvider // For convinience of reading chain during generation you have provider availible (running on future monitors network)
networkName: Network //Network name in Defender naming convention
and return is Promise of TSentinelOutput
which consists of
export interface TSentinelOutput<T, K> {
newMonitor: TSentinel; //Template for the monitor
defaultMessage: string; //Default message to send as notificaiton
actionsParams?: ActionsParams<T, K>; //Extra parameters such as secrets or relay definitions
}
Adding functions
Simply add in your templates newMonitor
properties: autotask-condition
or autotask-trigger
with path from repo root to compiled template.
Connecting functions to relays
If your functions require relay to operate you can use either default relay for read operations generated automatically, or specify key for custom relay. In second case you must specify relay configuration in your config file with same key.
To add relay return object must contain actionsParams
object. I.e. connecting to default relay in same network as Monitor is run for conditional autotask may look like this:
return {
//Return object from monitor template
newMonitor, //Monitor object itself
defaultMessage, //Notification message (string)
actionsParams: {
condition: {
relayNetwork: sentinelNetwork, //Specifying relayNetwork will automatically add default_reader relay on that network
customRelay: 'myRelayer', //Adding this will use your custom defined relayer from config file instead of default
},
trigger: trigger //if trigger is defined it will require relay in trigger autotask.
? {relayNetwork: trigger.params.relayNetwork, secrets: trigger.params}
: undefined,
},
};
Specifying secrets
In the example above, monitoring template actually requires LOW_ETH_THRESHOLD
secret to exist in Defender stack. In order to add id, pass required key-values as actionsParams.condition
or actionsParams.trigger
. Having trigger/conditon differentiator will allow that secret to be used in scoped secrets workflow
return {
newMonitor,
defaultMessage,
actionsParams: {
condition: {
relayNetwork: sentinelNetwork,
secrets: {LOW_ETH_THRESHOLD: threshold}, //This is custom parameter that autotask needs to consume from secrets
customRelay: 'myRelayer',
},
trigger: trigger
? {relayNetwork: trigger.params.relayNetwork, secrets: trigger.params}
: undefined,
},
};
Using scoped secrets
If you need to pass some argument from the configuration to function, this is possible to do with scoped secrets defined in src/templates/utils
Scoped secrets use secret name and function visible name to combine it to a secret key that can be red from enviroment.
Function getter
Are not yet supported but it's easy to implement. Add your PR ;)