Skip to content
This repository was archived by the owner on Oct 8, 2024. It is now read-only.

Instant actions #96

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ import {DssAction} from "lib/dss-exec-lib/src/DssAction.sol";

contract SpellAction is DssAction {

constructor(address lib, bool officeHours) DssAction(lib, officeHours) public {}

uint256 constant MILLION = 10 ** 6;

function actions() public override {
setGlobalDebtCeiling(1500 * MILLION);
setIlkDebtCeiling("ETH-A", 10 * MILLION);
DssExecLib.setGlobalDebtCeiling(1500 * MILLION);
DssExecLib.setIlkDebtCeiling("ETH-A", 10 * MILLION);
}

// (Optional) Use instantActions() for IAM functions
function instantActions() public override {
DssExecLib.disable(directMom, directJoin);
}
}
```
Expand All @@ -39,7 +42,9 @@ The `SpellAction.sol` file must always inherit `DssAction` from `lib/dss-exec-li

The developer must override the `actions()` function and place all spell actions within. This is called by the `execute()` function in the pause, which is subject to an optional limiter for office hours.

*Note:* All variables within the SpellAction MUST be defined as constants, or assigned at runtime inside of the `actions()` function. Variable memory storage is not available within a Spell Action due to the underlying delegatecall mechanisms.
The developer may optionally override the `instantActions()` function and place any contract calls to instant action modules in the function. The instant actions function will be a no-op by default if it is not implemented.

*Note:* All variables within the SpellAction MUST be defined as constants, or assigned at runtime inside of the `actions()` or `instantActions()` function. Variable memory storage is not available within a Spell Action due to the underlying delegatecall mechanisms.

The spell itself is deployed as follows:

Expand Down Expand Up @@ -102,6 +107,7 @@ Below is an outline of all functions used in the library.
- `authorize(address _base, address _ward)`: Give an address authorization to perform auth actions on the contract.
- `deauthorize(address _base, address _ward)`: Revoke contract authorization from an address.
- `setAuthority(address _base, address _authority)`: Give an address authority to a base contract using authority pattern.
- `disable(address _base, address _target)`: Calls the disable(target) function on a base contract.
- `delegateVat(address _usr)`: Delegate vat authority to the specified address.
- `undelegateVat(address _usr)`: Revoke vat authority to the specified address.

Expand Down
9 changes: 8 additions & 1 deletion src/DssAction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,18 @@ abstract contract DssAction {
actions();
}

// DssAction developer must override `actions()` and place all actions to be called inside.
// DssAction developer must override `actions()` and place all actions
// subject to the pause delay to be called inside.
// The DssExec function will call this subject to the officeHours limiter
// By keeping this function public we allow simulations of `execute()` on the actions outside of the cast time.
function actions() public virtual;

// DssAction developer may override `instantActions()` and place all actions
// to be performed via an instant access module. This function is called
// during the `schedule()` call, once the spell has achieved the hat.
// These functions will not be limited by the officeHours modifier.
function instantActions() public virtual {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if would be better to define it without empty implementation so we we make sure to always overwrite it by an empty one in the spell action being deployed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do them so rarely that I didn't want to force it into every spellaction.


// Provides a descriptive tag for bot consumption
// This should be modified weekly to provide a summary of the actions
// Hash: seth keccak -- "$(wget https://<executive-vote-canonical-post> -q -O - 2>/dev/null)"
Expand Down
3 changes: 3 additions & 0 deletions src/DssExec.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ interface SpellAction {
function officeHours() external view returns (bool);
function description() external view returns (string memory);
function nextCastTime(uint256) external view returns (uint256);
function instantActions() external;
}

contract DssExec {
Expand Down Expand Up @@ -81,6 +82,8 @@ contract DssExec {
require(eta == 0, "This spell has already been scheduled");
eta = now + PauseAbstract(pause).delay();
pause.plot(action, tag, sig, eta);
(bool ok,) = address(action).delegatecall(abi.encodeWithSignature("instantActions()"));
require(ok);
}

function cast() public {
Expand Down
12 changes: 12 additions & 0 deletions src/DssExecLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ interface Authorizable {
function setAuthority(address) external;
}

interface Disableable {
function disable(address) external;
}

interface Fileable {
function file(bytes32, address) external;
function file(bytes32, uint256) external;
Expand Down Expand Up @@ -282,6 +286,14 @@ library DssExecLib {
function setAuthority(address _base, address _authority) public {
Authorizable(_base).setAuthority(_authority);
}
/**
@dev Deactivates a module with a disable function
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding other mom-related breaker pattern instant actions (e.g. osm-mom stop, clipper-mom setBreaker, ...).

@param _base The address of the contract with the `disable(address)` pattern
@param _target The contract that will be disabled
*/
function disable(address _base, address _target) public {
Disableable(_base).disable(_target);
}
/**
@dev Delegate vat authority to the specified address.
@param _usr Address to be authorized
Expand Down
33 changes: 19 additions & 14 deletions src/test/DssAction.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ import {DssAutoLine} from "dss-auto-line/DssAutoLine.sol";
import {LerpFactory} from "dss-lerp/LerpFactory.sol";
import {DssDirectDepositAaveDai}
from "dss-direct-deposit/DssDirectDepositAaveDai.sol";
import {AaveMock} from "./fixtures/AaveMock.sol";
import {DirectDepositMom} from "dss-direct-deposit/DirectDepositMom.sol";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

D3M repo won't be able to be imported here anymore as it is using solc 0.8.x. We will need to find another way to test D3M stuff.

import {RwaLiquidationOracle}
from "MIP21-RWA-Example/RwaLiquidationOracle.sol";
import {AuthGemJoin} from "dss-gem-joins/join-auth.sol";
Expand Down Expand Up @@ -85,20 +87,6 @@ contract UniPairMock {
}
}

contract AaveMock {
// https://docs.aave.com/developers/the-core-protocol/lendingpool
function getReserveData(address dai) public view returns (
uint256, uint128, uint128, uint128, uint128, uint128, uint40, address, address, address, address, uint8
) {
address _dai = dai; // avoid stack too deep
return (0,0,0,0,0,0,0, _dai, _dai, _dai, address(this), 0);
}

function getMaxVariableBorrowRate() public pure returns (uint256) {
return type(uint256).max;
}
}

contract ActionTest is DSTest {
Hevm hevm;

Expand Down Expand Up @@ -506,6 +494,22 @@ contract ActionTest is DSTest {
assertEq(clipperMom.authority(), address(1));
}

function test_disable() public {
AaveMock aave = new AaveMock();
DssDirectDepositAaveDai d3m = new DssDirectDepositAaveDai(address(clog), "tungsten", address(aave), address(0));
d3m.rely(address(action));
action.setD3MTargetInterestRate_test(address(d3m), 500); // set to 5%
assertEq(d3m.bar(), 5 * RAY / 100);
DirectDepositMom directDepositMom = new DirectDepositMom();
directDepositMom.setAuthority(address(govGuard));
assertEq(directDepositMom.authority(), address(govGuard));
d3m.rely(address(directDepositMom));
assertEq(d3m.wards(address(directDepositMom)), 1);

action.disable_test(address(directDepositMom), address(d3m));
assertEq(d3m.bar(), 0);
}

function test_delegateVat() public {
assertEq(vat.can(address(action), address(1)), 0);
action.delegateVat_test(address(1));
Expand Down Expand Up @@ -1037,6 +1041,7 @@ contract ActionTest is DSTest {
assertEq(d3m.bar(), 99 * RAY / 100);
}


/*****************************/
/*** Collateral Onboarding ***/
/*****************************/
Expand Down
82 changes: 56 additions & 26 deletions src/test/DssExec.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import "dss/join.sol";
import "dss/abaci.sol";

import "dss-interfaces/Interfaces.sol";
import {DssDirectDepositAaveDai} from "dss-direct-deposit/DssDirectDepositAaveDai.sol";
import {DirectDepositMom} from "dss-direct-deposit/DirectDepositMom.sol";

import "../DssExec.sol";
import "../DssAction.sol";
Expand Down Expand Up @@ -60,12 +62,12 @@ contract DssLibSpellAction is DssAction { // This could be changed to a library
function actions() public override {

// Basic cob setup
DSToken xmpl_gem = DSToken(0xCE4F3774620764Ea881a8F8840Cbe0F701372283);
ClipAbstract xmpl_clip = ClipAbstract(ClipFabLike(0x0716F25fBaAae9b63803917b6125c10c313dF663).newClip(DssExecLib.pauseProxy(), DssExecLib.vat(), DssExecLib.spotter(), DssExecLib.dog(), "XMPL-A"));
DSToken xmpl_gem = DSToken(0xCE4F3774620764Ea881a8F8840Cbe0F701372283); // Dummy token deployed to mainnet
ClipAbstract xmpl_clip = ClipAbstract(ClipFabLike(DssExecLib.getChangelogAddress("CLIP_FAB")).newClip(DssExecLib.pauseProxy(), DssExecLib.vat(), DssExecLib.spotter(), DssExecLib.dog(), "XMPL-A"));
GemJoin xmpl_join = new GemJoin(DssExecLib.vat(), "XMPL-A", address(xmpl_gem));
xmpl_clip.rely(DssExecLib.pauseProxy());
xmpl_join.rely(DssExecLib.pauseProxy());
address xmpl_pip = 0x7a5918670B0C390aD25f7beE908c1ACc2d314A3C; // Using USDT pip as a dummy
address xmpl_pip = DssExecLib.getChangelogAddress("PIP_USDT"); // Using USDT pip as a dummy

LinearDecrease xmpl_calc = new LinearDecrease();
DssExecLib.setLinearDecrease(address(xmpl_calc), 1);
Expand Down Expand Up @@ -104,7 +106,11 @@ contract DssLibSpellAction is DssAction { // This could be changed to a library
DssExecLib.setAuctionTimeBeforeReset("LINK-A", 2 hours);
DssExecLib.setKeeperIncentivePercent("LINK-A", 2); // 0.02% keeper incentive
DssExecLib.setGlobalDebtCeiling(10000 * MILLION);
}

// Test the instant action capability
function instantActions() public override {
DssExecLib.disable(DssExecLib.getChangelogAddress("DIRECT_MOM"), DssExecLib.getChangelogAddress("MCD_JOIN_DIRECT_AAVEV2_DAI"));
}
}

Expand Down Expand Up @@ -145,32 +151,35 @@ contract DssLibExecTest is DSTest, DSMath {
event Debug(uint256);

// MAINNET ADDRESSES
PauseAbstract pause = PauseAbstract( 0xbE286431454714F511008713973d3B053A2d38f3);
address pauseProxy = 0xBE8E3e3618f7474F8cB1d074A26afFef007E98FB;
DSChiefAbstract chief = DSChiefAbstract( 0x0a3f6849f78076aefaDf113F5BED87720274dDC0);
VatAbstract vat = VatAbstract( 0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B);
VowAbstract vow = VowAbstract( 0xA950524441892A31ebddF91d3cEEFa04Bf454466);
CatAbstract cat = CatAbstract( 0xa5679C04fc3d9d8b0AaB1F0ab83555b301cA70Ea);
DogAbstract dog = DogAbstract( 0x135954d155898D42C90D2a57824C690e0c7BEf1B);
PotAbstract pot = PotAbstract( 0x197E90f9FAD81970bA7976f33CbD77088E5D7cf7);
JugAbstract jug = JugAbstract( 0x19c0976f590D67707E62397C87829d896Dc0f1F1);
SpotAbstract spot = SpotAbstract( 0x65C79fcB50Ca1594B025960e539eD7A9a6D434A3);

DSTokenAbstract gov = DSTokenAbstract( 0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2);
EndAbstract end = EndAbstract( 0xBB856d1742fD182a90239D7AE85706C2FE4e5922);
IlkRegistryAbstract reg = IlkRegistryAbstract(0x5a464C28D19848f44199D003BeF5ecc87d090F87);

OsmMomAbstract osmMom = OsmMomAbstract( 0x76416A4d5190d071bfed309861527431304aA14f);
ClipperMomAbstract clipMom = ClipperMomAbstract( 0x79FBDF16b366DFb14F66cE4Ac2815Ca7296405A0);
ChainlogAbstract chainlog = ChainlogAbstract( 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F);
PauseAbstract pause = PauseAbstract( chainlog.getAddress("MCD_PAUSE"));
address pauseProxy = chainlog.getAddress("MCD_PAUSE_PROXY");
DSChiefAbstract chief = DSChiefAbstract( chainlog.getAddress("MCD_ADM"));
VatAbstract vat = VatAbstract( chainlog.getAddress("MCD_VAT"));
VowAbstract vow = VowAbstract( chainlog.getAddress("MCD_VOW"));
CatAbstract cat = CatAbstract( chainlog.getAddress("MCD_CAT"));
DogAbstract dog = DogAbstract( chainlog.getAddress("MCD_DOG"));
PotAbstract pot = PotAbstract( chainlog.getAddress("MCD_POT"));
JugAbstract jug = JugAbstract( chainlog.getAddress("MCD_JUG"));
SpotAbstract spot = SpotAbstract( chainlog.getAddress("MCD_SPOT"));

DSTokenAbstract gov = DSTokenAbstract( chainlog.getAddress("MCD_GOV"));
EndAbstract end = EndAbstract( chainlog.getAddress("MCD_END"));
IlkRegistryAbstract reg = IlkRegistryAbstract(chainlog.getAddress("ILK_REGISTRY"));

OsmMomAbstract osmMom = OsmMomAbstract( chainlog.getAddress("OSM_MOM"));
ClipperMomAbstract clipMom = ClipperMomAbstract( chainlog.getAddress("CLIPPER_MOM"));

// XMPL-A specific
GemAbstract xmpl = GemAbstract( 0xCE4F3774620764Ea881a8F8840Cbe0F701372283);
GemJoinAbstract joinXMPLA;
OsmAbstract pipXMPL = OsmAbstract( 0x7a5918670B0C390aD25f7beE908c1ACc2d314A3C);
ClipAbstract clipXMPLA;
MedianAbstract medXMPLA = MedianAbstract( 0x56D4bBF358D7790579b55eA6Af3f605BcA2c0C3A); // USDT median
GemAbstract xmpl = GemAbstract( 0xCE4F3774620764Ea881a8F8840Cbe0F701372283);
GemJoinAbstract joinXMPLA;
OsmAbstract pipXMPL = OsmAbstract( chainlog.getAddress("PIP_USDT"));
ClipAbstract clipXMPLA;
MedianAbstract medXMPLA = MedianAbstract( pipXMPL.src()); // USDT median

ChainlogAbstract chainlog = ChainlogAbstract( 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F);
// D3M
DssDirectDepositAaveDai d3m = DssDirectDepositAaveDai(chainlog.getAddress("MCD_JOIN_DIRECT_AAVEV2_DAI"));
DirectDepositMom directMom = DirectDepositMom(chainlog.getAddress("DIRECT_MOM"));

SystemValues afterSpell;

Expand Down Expand Up @@ -637,4 +646,25 @@ contract DssLibExecTest is DSTest, DSMath {
dog.bark("XMPL-A", address(this), address(this));
assertEq(clipXMPLA.kicks(), 1);
}

function testSpellInstantActions() public {
hevm.store(
address(d3m),
bytes32(uint256(2)),
bytes32(uint256(WAD))
);
assertEq(d3m.bar(), WAD);
vote();
spell.schedule();

// Test value has been updated after scheduling
assertEq(d3m.bar(), 0);

assertTrue(!spell.done());

// Ensure spell execution continues
hevm.warp(spell.nextCastTime());
spell.cast();
assertTrue(spell.done());
}
}
4 changes: 4 additions & 0 deletions src/test/DssTestAction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ contract DssTestAction is DssAction {
DssExecLib.setAuthority(base, authority);
}

function disable_test(address base, address target) public {
DssExecLib.disable(base, target);
}

function delegateVat_test(address usr) public {
DssExecLib.delegateVat(usr);
}
Expand Down
16 changes: 16 additions & 0 deletions src/test/fixtures/AaveMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.6.12;

contract AaveMock {
// https://docs.aave.com/developers/the-core-protocol/lendingpool
function getReserveData(address dai) public view returns (
uint256, uint128, uint128, uint128, uint128, uint128, uint40, address, address, address, address, uint8
) {
address _dai = dai; // avoid stack too deep
return (0,0,0,0,0,0,0, _dai, _dai, _dai, address(this), 0);
}

function getMaxVariableBorrowRate() public pure returns (uint256) {
return type(uint256).max;
}
}