ava-labs / teleporter

EVM cross-chain messaging protocol built on top of Avalanche Warp Messaging
Other
47 stars 22 forks source link

Contracts for fetching a value from another chain with an async callback #35

Open michaelkaplan13 opened 1 year ago

michaelkaplan13 commented 1 year ago

Context and scope One possible use of Teleporter that could prove very helpful for dApp developers is the ability to query the state of a contract on another chain. The data being queried could be the current value of an oracle, the current state of an account, a random value from a VRF contract, etc.

The query would be sent to the other chain as a Teleporter message with a specific request ID. When the message is delivered to the destination, the specified data would be looked up on chain, and then submitted in a subsequent Teleporter message sent back to the origin chain. The delivery of that second message would invoke a callback providing the value to be used by the original requesting contract.

In order to make this pattern as easy as possible, create contracts that demonstrate this flow on top of Teleporter. Ideally, the interface can be generic enough such that dApps could inherit a CrossChainRequester or CrossChainProvider contract that handles most of the logic.

Open questions

martineckardt commented 6 months ago

I was thinking about how to tackle this best and couldn't get an approach out of my head: Javascript has the Promise API. The approach is simple. I perform an action and provide two callbacks:

I believe this pattern could make developing with Teleporter in some cases more straight forward. Let's say I want to perform a simple bridging operation. The users send the tokens to the bridge. The bridge attempts to bridge the token. On success nothing happens, but on failure the user receives their funds back.

In Javascript the logic for the handling the success and failure case are passed in as functions. I did not find a straight forward way to do this in solidity, so I worked with contract interfaces. You can find a very rough draft (code is not deployable or testable) of how this pattern could be implemented below.

Full Code

abstract contract TeleporterPromiseSender is ITeleporterReceiver {
    function thenCallback(bytes32 messageId, bytes memory result) external virtual;
    function catchCallback(bytes32 messageId, bytes memory error) external virtual;

    function receiveTeleporterMessage(bytes32 messageId, bytes calldata message) external override {
        // Unpack message and call thenCallback or catchCallback
        (TeleporterPromiseOutcome outcome, bytes memory data) = abi.decode(message, (TeleporterPromiseOutcome, bytes));
        if (outcome == TeleporterPromiseOutcome.Success) {
            thenCallback(messageId, data);
        } else {
            catchCallback(messageId, data);
        }
    }
}

contract TeleporterPromiseReceiver {
    ITeleporterMessenger public messenger;
    ITeleporterPromiseProcessor public processor;

    function receiveTeleporterMessage(bytes32 sourceBlockchainID, address originSenderAddress, bytes calldata message)
        external
    {
        try processor.processMessage(sourceBlockchainID, originSenderAddress, message) returns (bytes result) {
            // Successful processing
            this._sendCallback(TeleporterPromiseOutcome.Success, result);
        } catch (bytes memory error) {
            // Failed processing
            this._sendCallback(TeleporterPromiseOutcome.Fail, error);
        }
    }

    function _sendCallback(TeleporterPromiseOutcome outcome, bytes memory resultOrError) internal {
        // Pack outcome and resultOrError
        // ...

        // Send feedback to the sender
        TeleporterMessageInput memory input;
        // Fill in input parameters for sending the callback
        // ...

        messenger.sendCrossChainMessage(input);
    }
}