5. Monitor and activate Maker's ESM
We build a canvas that can be used to automatically shutdown Maker during a crisis
Goal
Maker is a popular lending protocol that allows its governance token's (MKR) holders to decide when it is unsafe for the protocol to operate and, if necessary, shutdown all of its modules.
In order to shutdown the protocol, Maker deployed an Emergency Shutdown Module (ESM). Holders of MKR can deposit their tokens at any time. Once the amount of deposited tokens reaches a threshold, the emergency kill-switch can be used by any caller to the contract's fire()
method. It shuts down all Maker contracts.
In this example, we show how we can monitor the amount of deposited tokens and send reports via a webhook to a server that will automatically call fire()
when the threshold is reached.
If you want to skip to the end, here's the canvas that already has everything built:
Steps
Before you start
You need a place to run your server that receives the webhooks and calls fire()
if necessary. We are using Heroku, but you can use any provider that can receive webhooks.
Setting up the pipeline
Adding components
- Start by adding a Data source component. We want our pipeline to run at the tip, so we leave To blank and set From to the ESM's deployment block (14125857).
- Add an EVM Lambda component and connect it to the right handle of the data source. For now, leave it as is. We'll come back to it in a bit.
- Then we add a Schema with the data we need for our webhook. Let's call it
sum_reached
. We want our webhook to receive reports for every change inSum
(the amount of deposited MKR tokens) and we only want the webhook to callfire()
whenSum > min
. Draw a connection from the lambda to the schema component and populate the schema as follows:
Name | Type |
---|---|
sum | uint256 |
threshold_reached | bool |
- Finally we add a Webhook component. Point it to the server instance you have running and set a rate limit if you want. Then connect the schema to the webhook component.
- You're all set! 🎉
Writing the code
We want to hook on all changes to Sum
.
- Select
Address
for hook type and put the ESM address (0x09e05ff6142f2f9de8b6b65855a1d56b6cfe4c58
) in the input. We'll use this evm.storage link to hook on just (post) on theSum
variable. - In the callback, we simply call the
min()
function to get the threshold and emit a message to theSchema
we created before. We could emit this message only in cases where the threshold is reached, but here we're emitting it in either case along with a flag representing whether the threshold is reached.
function handleSumChange() public {
handleSumChangeContext storage ctx = getHandleSumChangeContext();
if (ctx.valueAfter >= IESM(ctx.txn.codeAddress).min()) {
simEmitToSchema_sum_reached(
ctx.valueAfter, // sum
true // reached
);
} else {
simEmitToSchema_sum_reached(
ctx.valueAfter, // sum
false // reached
);
}
}
A server to handle the webhooks
When a webhook message reaches the server, we need to inspect the data and decide if we call fire()
or not.
- We have a flask server running waiting for a request, we then post a transaction on-chain to the ESM contract if necessary. Here's the code:
from flask import Flask, request, jsonify
from web3 import Web3
app = Flask(__name__)
# Configure your Ethereum node provider
# For example, using Infura:
web3 = Web3(Web3.HTTPProvider('https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID'))
# The address that will send the transactions
from_address = web3.toChecksumAddress('your-wallet-address')
private_key = 'your-private-key'
# ABI and address for the smart contract
contract_address = web3.toChecksumAddress('contract-address')
# We can use a partial ABI that contains only the `fire()` function.
contract_abi = json.loads('[{"inputs":[],"name":"fire","outputs":[],"stateMutability":"nonpayable","type":"function"}]')
contract = web3.eth.contract(address=contract_address, abi=contract_abi)
@app.route('/webhook', methods=['POST'])
def webhook():
data = request.json
if data['threshold_reached']:
nonce = web3.eth.getTransactionCount(from_address)
txn_dict = contract.functions.fire().buildTransaction({
'chainId': 1, # Mainnet chain ID
'gas': 2000000,
'gasPrice': web3.toWei('50', 'gwei'),
'nonce': nonce,
})
# Sign the transaction
signed_txn = web3.eth.account.signTransaction(txn_dict, private_key=private_key)
# Send the transaction
txn_receipt = web3.eth.sendRawTransaction(signed_txn.rawTransaction)
txn_hash = web3.toHex(txn_receipt)
return jsonify({'transaction_hash': txn_hash}), 200
Key takeaways
- Monitoring your protocol's contracts' state becomes an easy task when using sim's powerful storage hooks.
- You can use webhooks in order to send on-chain transactions and respond to events that happen in your protocol (liquidity of pools, balance of accounts, state variable changes, etc.)
Updated about 1 month ago