Actions
An Action is a contract that will perform a specific operation. In the context of DeFi, that includes actions such as supplying an asset, making a token swap, making a deposit, etc.
Actions are registered in the DFSRegistry
and can be combined into recipes. Actions are always called through a user's wallet (Safe, DSProxy
) and cannot hold any state. Flashloan Actions are a special type of actions which can hold state (reentrancy field) but they are not called through user's wallet. There are currently only two types of actions: Standard action and FlashLoan
action. We need to differentiate between these, as they will have different execution paths in the RecipeExecutor
.
When calling a FlashLoan action, it only makes sense to call them as part of a Recipe. You also need to add an extra empty input in the callData if it's a FlashLoan action.
Each action should inherit the ActionBase
contract, which implements a standard interface to call the actions and some helper functions or contracts to make development easier. Actions can be called directly through a user's walelt with executeActionDirect()
which takes an array of bytes representing the inputs of that action. Each action can have different inputs and in order to keep a universal interface, all the inputs are converted into a bytes array before calling. Action always returns one bytes32
value, which can later be used as input for other actions.
While a single Action can be executed as a Recipe through the RecipeExecutor
, it is more gas efficient to call a single Action directly when needed.
Each action should also produce a log message in the standard DFSLogger
.
Actions can be bundled into Recipes, which can then be executed manually or as part of a Strategy. Because of this, some of the inputs of an Action can be hardcoded in Subscription data or it can be an output of a different Action. That's why when an Action is executed inside of a recipe a different function is used executeAction()
. Here subData
is passed along as well as paramMapping
data and returnValues
from previous functions.
Return values and subscription data mapping
paramMapping
is an array of uint8
values representing if any inputs of the actions need to be switched out, either with subData
or by returnValues
.
If the value is 0, this means the inputs are used and not modified. Values in the range of [1-127]
are used for returnValues
mapping, meaning that 1 means the return value of the first action, 2 represents the second action return values, etc... going up to 127.
If values are in the [127-255]
range that means that the Subscriptions
subData is used to replace inputs, following the same logic as return values in how they are mapped.
The last values in the subData range, 254 and 255 are 'reserved'. That means that the ActionBase contract and the related parseParam methods will inject:
for 254 the address of the user's wallet
for 255 will inject the address of the:
owner of the DSProxy if wallet is DSProxy.
owner of Safe, in case of 1/1 wallet, and safe wallet itself in case of n/m wallet
Below is the interface of the ActionBase:
Last updated