EVMScripts in aragonOS allow forwarders to execute actions when some conditions are met. They are used by Aragon apps to perform actions that are approved by arbitrary logic, generally as the result of an account having some privilege (owning a token or being on a list) or when the governance mechanism allows it.
DAOs can install different EVMScriptExecutors that parse and execute scripts in different ways. In aragonOS 3, we introduced 3 types of executors:
CallsScript takes an array of addresses and calldata and executes them in an atomic manner (either all calls are successfully performed, or the entire execution is reverted). Because a
CallsScriptjust executes whatever calls it was provided with when it was created and it doesn’t allow to use the return data from one call as an input to the following ones, therefore it can only execute fairly simple sets of actions.
DeployDelegateScriptwere designed to allow for actual dynamic scripts that can execute in different ways depending on context available when the script is run. This context can be sourced from return data of calls the script makes, or the storage of the contract that executes the script. For simplicity, we decided on using the EVM itself as the ‘interpreter’ of these scripts rather than building one from scratch. In order to use the EVM, these scripts executors would
delegatecallinto an inputed account (in the case of
DelegateScript) or create a contract and
delegatecallto it (
Even though some protections were in place to avoid script contracts to
selfdestruct or modify critical storage, the WHG audit revealed a way to bypass our protection against scripts
selfdestructing that was undetectable. While it is possible to be certain that a contract didn’t destruct itself in the immediate child execution context (if more than 0 bytes of data are returned from the call), a malicious script could
delegatecall into itself,
selfdestruct and then have the top-level child call return some data. Given these findings, we decided to deprecate and remove these two executors as we couldn’t ensure their security.
Even though dynamic scripts cannot be executed for the time being, they are still an important idea worth bringing back, giving the flexibility they provide for executing complex actions after a governance decision is made. An immediate need for dynamic scripts are app installation scripts that cannot be executed just with
CallsScripts or potential storage migrations on app upgrades that could require direct access to storage in order to perform a migration.
Below are 3 different avenues of how dynamic scripts could be brought back with extra security assurances:
DelegateScript+ Opcode Checker: by using an on-chain opcode checker like this contract Nick Johnson built, before executing the script, all the opcodes the target contract has can be checked to ensure that some opcodes that could be potentially malicious are not in the contract. For removing the possibility of scripts causing a contract to
selfdestruct, one just needs to check that there are no
delegatecallopcodes in the target, if no storage modifications are desired either, a check for no
sstorecan be added. There is some computation overhead in analyzing the contract for the first time (which grows linearly with code size), but after doing it once all its opcodes are cached in a bitmask, that can very cheaply used to find the inclusion or exclusion of any number of opcodes.
Return by reverting + safe execution (technique introduced by auth-os and brought to my attention by Alex Wade): doing a
delegatecallto an untrusted contract is not dangerous if the call reverts, as all the state changes that could have been done are reverted, but the call can return some data when reverting. This could allow for executing scripts without any side-effects, that then can return some instructions for the executor to perform. Scripts would be executed in two steps, the first one to calculate the calls or storage modifications to be made at execution time, and then the actions would be performed in the trusted environment of the executor that can enforce extra checks such as not allowing to modify certain storage slots or not making calls to address in the address blacklist.
CallsScript+ memory + evaluable expressions: a superset of
CallsScriptcan be built that can save return data from calls to memory, and then implement conditional statements, transformations of memory values and using memory values as either the target or parameters in the calls made. This would be by far the most complex solution, as it would require building a limited VM inside the EVM.