Aragon Triggers: enhanced forwarding interface for improved composability


#1

Aragon Triggers

A Trigger or Hook is a type of app that can be used to connect an external or internal action that when done it can perform an intent in a DAO. It is the equivalent to a webhook in traditional web development.

Triggers allow for maximum customization of DAOs without requiring users to write code. Triggers could also be composable in the future, so complex actions could be performed through a chain of triggers.

Triggers can be implemented as a special type* of forwarder that could follow the current forwarding interface (at least in one of the two possible technical implementations).

*special type in the sense that we would need a way to communicate to the client the pre-conditions before executing the forwarder (i.e. create a token allowance to the Trigger address)

User story: DAppNode NFT purchases

DAppNode created a NFT app to issue tokens as software licenses to hardware vendors. Something that we weren’t able to tackle in the first iteration is the ability for vendors or end users to directly purchase the tokens from the DAO by sending some DAI to it. Currently a trusted account can mint tokens and when the vendor pays them they transfer the tokens, then DAppNode deposits the funds into their Vault.

Even though this process was good enough for the first version (specially given the short project timeline), it requires a fair amount of trust as the transaction is non-atomic, and there is some manual interaction every time one token is sold.

The flow, had the Triggers app existed by then, would have been:

  • DAppNode assigns the permission to mint tokens to anyone pays who 100 DAI.

  • A Payment Trigger app instance is deployed in the background and the permission to mint tokens is granted to it.

  • A purchaser willing to get tokens would just use the NFT app and click on the Mint action. They will be prompted send tokens along with their transaction in the confirmation panel.

  • When the final transaction is mined (in case of a token transfer 2 txs are needed, for ETH just 1) the purchaser will have their token minted for their account and the funds will be deposited in the Vault.

Trigger usecases

1. Payment Trigger

Extending the user story above, Triggers easily allow for users to grant permissions in a DAO to anyone that pays the DAO a certain amount.

This type of Trigger could have a time limit, or a limit on how many times it can be executed before it deactivates itself. Just with Payment Triggers alone basic fundraising can be implemented and can be customized without the need to write any fundraising specific code.

Payment Triggers can also be used for actions that require a deposit, for example requiring the creator of a proposal to deposit a certain amount in order to create it, which gets returned to them if the proposal passes. Just by setting a Payment Trigger as the only entity that can create new proposals this effect can be achieved, and proposers can add a call that transfers their deposit back from the Vault as the final part of the script that is executed if the proposal passes.

2. Staking Trigger

In a similar fashion, instances of this Trigger can pull tokens from the user, stake and lock them on their account before forwarding a certain intent.

The workflow would be really similar to the one exposed above (the user would be prompted to approve the trigger to pull some tokens from their account), but this Trigger would have a bit more of logic before performing the execution.

Implementation proposals

1. Preset scripts with ParametrizableCallsScript

When deploying the Trigger instance, the EVMScript (or a hash of it, depending on gas optimizations) that can be executed if the conditions are met is provided and saved in storage.

In order for these scripts to be parametrized depending on runtime parameters (for example if buying tokens who the receiver of such tokens is, given that the minting call sender will be the Trigger address) we can introduce a new type of EVMScript executor that allows for some basic parametrization: ParametrizableCallsScript.

The ParametrizableCallsScript inherits most of its logic from CallsScript but these scripts define a few script locations that can be modified depending on the script’s input.

A rough outline of how these script payloads could look like:

We add the concept of placeholders before the actual CallsScript. The valid placeholders payload defines the sections of the CallsScript that can be modified depending on the input: it specifies the needle in the script where the modification starts and the amount of bytes that will be modified. The input to this script is expected to be a concatenation of all the bytes that need to be changed, in case the input length doesn’t match all the placeholders length, the script execution should error.

An extra check could be added to make sure that the bytes being modified in the script are all 0s or another arbitrary pattern (though zero is probably better as having zeros in the transaction data is cheaper than non-zero bytes).

2. Liberal triggers with call-limited CallsScript

Another less complex alternative for implementing this would be to upgrade the CallsScript executor to take as its input parameter the maximum number of calls that the script can perform (which could be performed by the caller before executing the script but it would require logic specific to this executor type).

The input to the CallsScript execution would have to be either empty (and assume that there is no limit, as it is working right now, providing backwards compatibility) or 2 bytes representing an integer that is the maximum amount of calls that the script execution can perform.

By having this limit in the CallsScript we can have Triggers that are merely a Forwarder that just gets assigned some permissions, but doesn’t have a previously knowledge of the kinds of scripts that it can execute (as it can do anything as long as the Trigger preconditions are met). Thanks to this we can make sure that even if the Trigger can forward anything, it cannot perform multiple actions with just one token deposit for example.

The Triggers should however make sure that the script being executed will do so using the modified CallsScript executor.

Potential required changes to aragonOS

  • In order for the “Payment Trigger app instance is deployed in the background and the permission to mint tokens is granted to it” to actually be done securely in a non-atomic way (it is impossible to do it atomically with a CallsScript) we need to add CREATE2 support when deploying app instances from the Kernel or implement another EVMScript executor that supports passing the result of a call as the parameter to another one.

  • For proposal #2, the CallsScript executor would need to be slightly modified and DAOs upgraded to this new version before being able to use Triggers securely.

Resources


#2

I feel once again, like when we were discussing ACLs with state, that eternal question about the levels of abstraction

This is the key question: do we really want this? Do we want to avoid code being written? Maybe I’m just fearing losing my job, haha, but I’m not so sure. In those levels of abstraction, I see we have now, roughly speaking:

1 Radspec
2 Solidity
3 EVM

And it seems like we are trying to get rid of #2 (poor guy, Solidity, it’s getting better! ;-P), and replace it by code in the frontend, which would produce low level scripts. I’m not sure that’s a good idea and how far we should go that way, although it’s certainly funny and challenging.

For instance, for the first use case, buying NFTs. It seems quite easy to write a small smart contract, which has MINT_ROLE assigned, and that when receives a certain amount mints that NFT for the sender. On the UI side, instead of:

the user would have a “Buy NFT” button that would call that function. While we stil have that issue in aragon API which prevents interacting with more than 1 contract, we could embed that simple logic in the NFT app.

It could be made the argument that we are already doing this for Voting, and that’s true. But I see in this case a very well-defined and inherent pattern: the vote passes and one action is executed. Otherwise it doesn’t make sense, it’s more like a survey. Maybe Payments are the same, you usually pay for something. Yes, maybe, I have been using all the time language like “I’m not sure” ;-). But things like “This type of Trigger could have a time limit, or a limit on how many times it can be executed before it deactivates itself” or “which gets returned to them if the proposal passes”, make me think that maybe this case is broader. And even in the case we assume that Payment is like Voting, which may be very likely the case, at least I would avoid calling it “Trigger”, to avoid trying to think about it generally, the same way we don’t call Voting app “Voting Trigger”

On another topic, I think scripts with parameters are a good idea, as right now our forwarding mechanism is a little bit limiting. E.g., recently LevelK Nest team have experienced it with Futarchy app.

I think I don’t get this. If Payment Trigger :stuck_out_tongue_winking_eye: is going to be an app, can’t you just have a Kit that creates the Token Manager, then the Payment app and finally assigns the MINT_ROLE to it?


#3

Actually it is here:

    function getNFT(address _to, uint256 _tokenId) public payable isInitialized {
        require(tokenPrice != 0);
        require(msg.value == tokenPrice);
        vault.send(tokenPrice);
        aragonNFT.mint(_to, _tokenId);
    } 

    function getNFTwithERC20(address _to, uint256 _tokenId, address _tokenERC20 ) public isInitialized {
        uint256 price = ERC20Payments[_tokenERC20];
        require(price != 0);
        require(ERC20(_tokenERC20).transferFrom(msg.sender, vault, price));
        aragonNFT.mint(_to, _tokenId);
    } 

#4

We do not yet, but it is an interesting idea we should explore more!

That’s certainly doable if the Trigger is created in the initial setup of the DAO through a Kit. However I was thinking here more about installing a Trigger to an already deployed DAO. With CREATE2 you could have a regular CallsScript that does dao.newAppInstance and then set the permissions to the app, and you would be able to deterministically know the address that the app will have when creating the script (which is not the case right now, as if the Kernel deploys another app after you created the vote, it will use the app address that you already set in your script, granting the permission to another random app).

I didn’t doubt you could write that in less than 5 minutes. However I think there is a lot of value in creating small components that are interoperable with each other, so users without programming knowledge (Solidity may look easy, but the potential for fucking it up is as big as it gets) can design their DAOs to do whatever they want. In the same way that Unix programs are small and can be piped together to do complex actions without requiring an advanced user like myself to write 100 lines of C every time I want to do something the authors didn’t think of.

We will have to agree to disagreeing here :wink:


#5

Yes, that certainly makes sense, but it’s what I’m not sure about. The way I imagine it is with a very rich ecosystem of apps, with totally concrete functionality (they would be the green circles in that video), where all common use cases are covered (and if you are not lucky just hire somebody to do it, and publish it in the app store). Following your example, how many non-devs (or non-“advanced user”) do you know that pipe *nix commands together in the console?

Anyway, I’m aware of my limitations envisioning future and people’s needs (just to name some examples, I still remember what an stupid idea I thought Instagram, or even Facebook, were when I heard of them for the first time, haha). So let’s do this! At least I hope that challenging the idea a little bit makes it stronger :wink:


#6

Absolutely, that’s why we post here! I think Triggers (which are really ‘rich forwarders’) can be really interesting for a lot of use cases that we cannot even think of right now! Just the ones related to paying some amount to do an arbitrary action in the DAO is quite important I believe.