Convenience Layer for Voucher Management on Cartesi

Convenience Layer for Voucher Management on Cartesi

Objective:

The objective of this proposal is to develop a convenience layer to simplify the process of managing, indexing, and tracking vouchers within the Cartesi ecosystem. This layer will enhance user experience by providing efficient access to voucher-related information and facilitate the monitoring of voucher execution status.

Proposed Solution:

  1. Voucher Creation Command Enhancement:

    Enhance the voucher creation command (createVoucher) to include parameters necessary for indexing and tracking, such as output index, input index, beneficiary address, amount, etc.

  2. Indexing and Tracking using Reports:

    Introduce a createReport specific payload to generate reports associated with voucher creation. These reports will contain essential details required for indexing and tracking vouchers. Each report will be labeled with the type of voucher (e.g., ERC-20, ERC-721, ERC-1155).

  3. Database Structure:

    Implement a database structure to store voucher-related information. This structure will consist of tables corresponding to different types of vouchers, such as native ethers tokens, ERC-20 tokens, ERC-721 tokens, and ERC-1155 tokens. Alternatively, a single table with a type column can be used for easier access.

  4. GraphQL Integration:

    Develop GraphQL queries to allow frontend applications to query voucher data efficiently. These queries will provide access to relevant information stored in the database, enabling seamless integration with frontend interfaces.

  5. Event Listening for Voucher Execution:

    Set up event listeners to monitor the execution status of vouchers. Specifically, listen for VoucherExecuted or OutputExecuted events to detect when vouchers are executed on the L1 smart contract. Update the executedAt field/column in the database to mark the voucher execution.

Addressing Challenges:

One of the main challenges is determining the number of tokens in transit from Cartesi Machine (CM) to L1. To overcome this challenge, we propose the following approach:

Implement a tracking mechanism within the convenience layer to monitor the movement of vouchers between CM and L1.
Integrate with Cartesi’s infrastructure to retrieve information about vouchers in transit.
Utilize the actual event architecture to capture voucher execution events and update the transit status accordingly.

Technical details

Code draft to explain the inner workings of the withdraw process, which triggers voucher and report creation.

// dapp backend within the CM
async function withdrawERC20(owner: string, token: Address, amount: bigint)
    const voucher = await _wallet.withdrawERC20(token, owner, amount)
    const outputIndex = await _dapp.createVoucher(token, payload)
    const voucherMetadata = {
        label: `erc-20-voucher`,
        outputIndex,
        amount,
        owner,
        token,
        /* other params */
    }
    await _dapp.createReport({ payload: createHexPayload(voucherMetadata) })
}

We will develop a Proof of Concept (PoC) in the NoNodo to both test and refine the idea.

graphConfig.Resolvers.Mutation().UpdateVoucherMetadata(context, model.VoucherExecutionEvent{
    InputIndex:  eventL1.InputIndex,
    OutputIndex: eventL1.OutputIndex,
    ExecutedAt:  eventL1.timestamp,
})

Conclusion:

By implementing this convenience layer, we aim to streamline voucher management within the Cartesi ecosystem, providing users with enhanced accessibility and transparency regarding voucher operations. This solution will improve the overall user experience and facilitate the seamless integration of voucher functionality into Cartesi-based applications.

1 Like

@milton-cartesi This proposal relies on the existing sendReport functionality, so we anticipate no blocks or changes to the underlying Cartesi Machine. Also, this way, we have the outputIndex :wink:

Why do you need this extra information coming from the machine in a report?

The voucher can be decoded by the application, by knowing the ABI of the destination smart contract.

We need the output index. We may also add more information, such as userdata, as a JSON field in the future. Additionally, @milton-cartesi raised a concern about method name collision.

I don’t see a reason for more information coming from inside the application. Everything is in the graphql voucher table, except for the contract ABI.

It could also be a good approach to assign meaning to other types of vouchers. In the Cartesi Store experiment, we may need to mint the NFT within the Cartesi Machine (CM) and then call a specific method to create the NFT on Layer 1, considering that the NFT originates from within the CM.

We can add the NFT attributes to the “userdata” field.

I tend to agree with @tuler that reports are not really necessary in order to extract “essential details required for indexing and tracking vouchers”. They can be extracted from vouchers themselves. More specifically, you can decode each voucher payload as a Solidity function data against a set of known ABIs.

From the examples you mentioned, you should be good with the ABIs of token standards (ERC-20, ERC-721, ERC-1155) and the application contract (for ETH withdrawals as of Cartesi Rollups SDK v1).

I believe this approach of inferring the beneficiary address from the voucher itself rather than from an accompanying report is more robust, because you are less prone to inconsistencies (e.g. you create an ERC-20 withdrawal voucher for Alice but emit a report saying it’s for Bob).

There are several ways to decode Solidity function data. I personally like viem’s decodeFunctionData.

One point I agree with @milton-cartesi is that, we know it’s difficult in practice, however it would be theoretically possible for a random method to collide with the 4 bytes of the signature hash of an ABI method (ethers, erc-20, 721, 1155) mapped.

We will encapsulate the common cases to prevent errors and inconsistencies.

While discussing that topic with @tuler , I came up with a similar idea to externalize part of the state to IPFS, this time involving notices. A special type of notice, with a special header, could be used to send a file to IPFS.

This is not possible within the context of a smart contract AFAIK.

Just to clarify our discussion, here is the method we’ve been referring to:

/**
 * Returns the function selector for a given function definition.
 *
 * @example
 * const selector = toFunctionSelector('function ownerOf(uint256 tokenId)')
 * // 0x6352211e
 */
export const toFunctionSelector = (fn: string | AbiFunction) =>
  slice(toSignatureHash(fn), 0, 4)

That code is from the viem package.

You can’t have a collision in a contract.

Not in the same contract

The point is, we can use the existing mechanics to externalize and give more meaning to the vouchers. I believe it’s worth exploring this path, it could be connected to a way to send files to IPFS, like NFT metadata.

I’m a bit confused here, but I think I understand some things. Here are my opinions and doubts:

  1. I believe the proposal is for a component/service to run alongside the node, which would monitor vouchers being emitted and provide an enhanced GraphQL endpoint for querying them in a more convenient way. Is that it @fabio.oshiro? And, on top of that, you are maybe also proposing some wrapper framework that would run inside the machine?

  2. @tuler the collision issue that is being considered is not for collision within a contract. Their original idea AFAIK is that the layer would only check the payload against a function signature, but without knowing whether the destination address implements a specific ABI or not. In other words, if the function signature matches that of transfer(address,uint256) then it would assume it is an ERC20 transfer, regardless of the destination. In that scenario, IMHO a collision is possible against some random method from some other random contract.

  3. @fabio.oshiro I’m really confused about your “userdata” idea. The way you put it, it seemed to be similar to the voucher’s parameters: if you want to call a method to create an NFT on Layer 1, just emit the voucher, right…? Or were you proposing something similar to the “labels” metadata idea?

Whos/what idea?

That’s a bad idea.
Use the contract address, and the ABI, and the payload, to properly present the voucher in the UI.

Yes, @milton-cartesi that’s correct. The proposal involves implementing a service to run in the node. This component would monitor the emission of vouchers and enhance the GraphQL endpoint for querying them in a more user-friendly manner. Additionally, it aims to address issues such as hash selector collisions and provide a wrapper framework to facilitate the development.

It could be similar to labels or even include additional information beyond what’s in the payload to L1, such as NFT attributes: attack, defense, hp, etc. When a token/NFT originates within the CM and is later converted into a voucher, it could be beneficial to include certain attributes for presentation purposes.

// L1 lacks the metadata
function mintNFT(address collector, uint256 tokenId, string memory _tokenURI) public {
    require(authority == msg.sender || owner() == msg.sender, "Only the authority can mint NFTs");
    _safeMint(collector, tokenId); 
    _tokenURIs[tokenId] = _tokenURI;
}

@fabio.oshiro’s / Calindra’s

I agree. I do not support the idea either (of automagically detecting voucher types), I was only explaining what they had in mind.

I talked to @milton-cartesi , @tuler and @guidanoli, and we’re going to use WhatsABI to try to download the ABI of the contract to have the correct method signature. If we can’t find it, we can infer what the method is and mark it as an inference.