Executing actions on behalf of the DAO
Using the DAO Executor
Executing actions on behalf of the DAO is done through the execute
function from the DAO.sol
contract. This function allows us to pass an array of actions) to be executed by the DAO contract itself.
However, for the execute
call to work, the address calling the function (the msg.sender
) needs to have the EXECUTE_PERMISSION
. This is to prevent anyone from being able to execute actions on behalf of the DAO and keep your assets safe from malicious actors.
How to grant the Execute Permission
Usually, the EXECUTE_PERMISSION
is granted to a governance plugin of the DAO so that only the approved proposals can be executed. For example, we'd grant the EXECUTE_PERMISSION
to the address of the Multisig Plugin. That way, when a proposal is approved by the required members of the multisig, the plugin is able to call the execute
function on the DAO in order to get the actions executed.
To grant the EXECUTE_PERMISSION
to an address, you'll want to call on the PermissionManager
's grant
function and pass it 4 parameters:
where
: the address of the contract containing the functionwho
wants access towho
: the address (EOA or contract) receiving the permissionpermissionId
: the permission identifier the caller needs to have in order to be able to execute the actioncondition
: the address of the condition contract that will be asked (if any) before authorizing the call to happen
You probably don't want to grant EXECUTE_PERMISSION
to any random address, since this gives the address access to execute any action on behalf of the DAO. We recommend you only grant EXECUTE_PERMISSION
to governance plugins to ensure the safety of your assets. Granting EXECUTE_PERMISSION
to an externally owned account is considered an anti-pattern.
Examples
Calling a DAO Function
Imagine you want to call an internal function inside the DAO
contract, for example, to manually grant or revoke a permission. The corresponding Action
and execute
function call look as follows:
function exampleGrantCall(
DAO dao,
bytes32 _callId,
address _where,
address _who,
bytes32 _permissionId
) {
// Create the action array
IDAO.Action[] memory actions = new IDAO.Action[](1);
actions[0] = IDAO.Action({
to: address(dao),
value: 0,
data: abi.encodeWithSelector(PermissionManager.grant.selector, _where, _who, _permissionId)
});
// Execute the action array
(bytes[] memory execResults, ) = dao.execute({
_callId: _callId,
_actions: actions,
_allowFailureMap: 0
});
}
Here we use the selector of the grant
function. To revoke the permission, the selector of the revoke
function must be used.
If the caller possesses the ROOT_PERMISSION_ID
permission on the DAO contract, the call becomes simpler and cheaper:
Granting the ROOT_PERMISSION_ID
permission to other contracts other than the DAO
contract is dangerous and considered as an anti-pattern.
function exampleGrantFunction(DAO dao, address _where, address _who, bytes32 _permissionId) {
dao.grant(_where, _who, _permissionId); // For this to work, the `msg.sender` needs the `ROOT_PERMISSION_ID`
}
Sending Native Tokens
Send 0.1 ETH
from the DAO treasury to Alice.
The corresponding Action
and execute
function call would look as follows:
function exampleNativeTokenTransfer(IDAO dao, bytes32 _callId, address _receiver) {
// Create the action array
IDAO.Action[] memory actions = new IDAO.Action[](1);
actions[0] = IDAO.Action({to: _receiver, value: 0.1 ether, data: ''});
// Execute the action array
dao.execute({_callId: _callId, _actions: actions, _allowFailureMap: 0});
}
Calling a Function from an External Contract
Imagine that you want to call an external function, let's say in a Calculator
contract that adds two numbers for you. The corresponding Action
and execute
function call look as follows:
contract ICalculator {
function add(uint256 _a, uint256 _b) external pure returns (uint256 sum);
}
function exampleFunctionCall(
IDAO dao,
bytes32 _callId,
ICalculator _calculator,
uint256 _a,
uint256 _b
) {
// Create the action array
IDAO.Action[] memory actions = new IDAO.Action[](1);
actions[0] = IDAO.Action({
to: address(_calculator),
value: 0, // 0 native tokens must be sent with this call
data: abi.encodeCall(_calculator.add, (_a, _b))
});
// Execute the action array
(bytes[] memory execResults, ) = dao.execute({
_callId: _callId,
_actions: actions,
_allowFailureMap: 0
});
// Decode the action results
uint256 sum = abi.decode(execResults[0], (uint256)); // the result of `add(_a,_b)`
}
Calling a Payable Function
Wrap 0.1 ETH
from the DAO treasury into wETH
by depositing it into the Goerli WETH9 contract.
The corresponding Action
and execute
function call look as follows:
interface IWETH9 {
function deposit() external payable;
function withdraw(uint256 _amount) external;
}
function examplePayableFunctionCall(IDAO dao, bytes32 _callId, IWETH9 _wethToken) {
// Create the action array
IDAO.Action[] memory actions = new IDAO.Action[](1);
actions[0] = IDAO.Action({
to: address(_wethToken),
value: 0.1 ether,
data: abi.encodeCall(IWETH9.deposit, ())
});
// Execute the action array
dao.execute({_callId: _callId, _actions: actions, _allowFailureMap: 0});
}