Asynchronous Debt: Enabling Cross-Chain Composability
Freshly launched layer 1s have a significant problem: there is nothing to do there.
It doesn’t matter whether your chain is faster, more secure, more energy efficient, decentralized, provides incredible features, or technically advanced. Ethereum is, and will always be, the king of everything we do on the blockchain.
Why is that? The platform dilemma.
If there are no users on your platform, there will be no developers to learn how to build apps for that platform and ship apps for end users. No developers mean no apps, and no apps mean even less incentives to bring new users onboard.
Recently there has been a surge in efforts trying to bridge this gap by building standardized transport and messaging protocols in between different blockchains. From centralized token transfer bots to IBC and XCMP, such solutions allow access to assets and features that only exist on one blockchain from another.
Even with such standardized transport protocols, however, a foreign chain cannot directly access logic that only exists on a single blockchain. For instance, while Binance Smart Chain has access to most major assets that exist on Ethereum over token transfer bridges, it still cannot directly call and use Uniswap or Compound that resides on the Ethereum blockchain; BSC will need to build such logic on its own chain (e.g. PancakeSwap) in order to enable AMMs or money markets.
This is because on-chain runtimes inherently cannot handle asynchronous operations.
Blockchains are, in essence, state machines that are extremely rigid with enforced consensus. State machines are a fancy term for computers. Under a standard server-to-client paradigm, asynchronous processing is fairly common — simply because waiting for every round-trip request to a server to complete doesn’t make sense. Such requests may fail at any time, and there is no guarantee that such a request will always succeed or complete within a set timeout period.
On blockchains, there is no concept of asynchronous processing. This extremely limits the ability for one blockchain to directly access logic residing on another. Consider the following scenario:
Alice is a user holding wrapped TrueUSD on the Near blockchain. She wants to deposit her TUSD to Compound on Ethereum; but as TUSD is not a supported asset on Compound she needs to swap them to DAI first then deposit them into Compound to receive cDAI, and finally send over those cDAI tokens over to her Near wallet. She then discovers PancakeSwap offers lower fees and higher liquidity than AMMs on Ethereum for swapping TrueUSD to DAI.
For Alice to perform the above, she will have to sign every single token transfer, swap and deposit transaction on each of the chains manually. Bots may be able to automate them, but this will also mean that Alice will have to build automated bots to handle every possible use case, and that for every step involved there will be separate transactions with no transaction atomicity at all. Atomicity is essential for composability; without it, protocols built on top of DeFi building blocks like yearn.finance simply would not have been possible.
If Alice was only interacting with protocols on the Ethereum blockchain with an Ethereum wallet, all of the required operations could have been done in a single transaction, and a separate wallet for every chain would not have been required. This effectively locks in users onto one blockchain, especially Ethereum, even when they have its own limitations.
Can we solve this problem, such that logic residing on different chains can be composed on-chain — similar to the way third-party contracts acting as building blocks for new protocols on Ethereum?
Enabling Asynchronous Processing On-Chain
Those familiar with programming languages supporting asynchronous processing should be used to the concept of Promises, or Futures. A Promise
, by definition, is an object that represents a value that will exist some time in the future. This means that Promises may be used as a placeholder object for an asynchronous task under a synchronous setting, of which further execution may be held until the Promise is fully resolved to a definite value.
Interestingly enough, Futures in asynchronous programming is conceptually similar to Futures contracts in finance, which “resolves” to a fixed trading price at its expiry date. This is perfect, as blockchain is the only industry in the world where one can blend both concepts into one.
Let’s assume that Alice is holding token APPLE
on CHAIN_A
, and wants to perform some action with the APPLE
token of which logic is only available on CHAIN_B
, that yields some output token PEAR
. Typically, such an operation would be done like this
CHAIN_A CHAIN_B
| |
SEND: APPLE -----RELAY-----> RECEIVE: APPLE
| |
| LOGIC:
| IN: APPLE
| OUT: PEAR
| |
RECEIVE: PEAR <----RELAY---- SEND: PEAR
This would result in a minimum of two transactions on CHAIN_A
(the “client”) for a round-trip, and a minimum of three transactions on CHAIN_B
(the “server”) for receiving the input token APPLE
from CHAIN_A
, processing logic that accepts input token APPLE
yielding output token PEAR
, and sending back output token PEAR
back to CHAIN_A
.
This is, obviously, non-atomic. If Alice wants to do something else with PEAR
, she cannot do so within a single transaction, and must manually invoke another transaction after waiting for the round-trip operation to settle.
But if we can enable Promise
objects on CHAIN_A
in the form of Futures tokens, Alice can effectively perform operations asynchronously on CHAIN_A
without having to wait for a previous round-trip operation to settle.
CHAIN_A CHAIN_B
| |
SEND: APPLE -----RELAY-----> RECEIVE: APPLE
MINT: fPEAR |
SWAP: fPEAR -> PEAR LOGIC:
| IN: APPLE
| OUT: PEAR
| |
RECEIVE: PEAR <----RELAY---- SEND: PEAR
(LOCK) |
| |
SWAP: PEAR -> fPEAR |
BURN: fPEAR |
CLAIM: PEAR |
(UNLOCK) |
In plaintext, this is what happened:
- Alice has initiated this action on
CHAIN_A
by sending overAPPLE
tokens toCHAIN_B
. However, this time she was minted a Futures derivative token that targets the value of its intended output token (i.e. representing a token that will be returned sometime in the future), called fTokens. In this case, Alice was immediately mintedfPEAR
within a single transaction. - Within the same transaction, Alice has swapped
fPEAR
back toPEAR
on an AMM available onCHAIN_A
. As this is effectively selling off futures for its intended output asset (i.e. devalued based on future consensus and transaction costs with failure risks), Alice faces a loss, but can preserve complete atomicity of the transaction. This means that she can use the resultingPEAR
tokens with logic that resides onCHAIN_A
or another blockchain, sayCHAIN_C
, all within the same transaction. - Meanwhile, the transaction has completed round-tripping, but this time the resulting
PEAR
tokens get locked in a contract onCHAIN_A
. A third-party arbitrager, Bob, may buy backfPEAR
from the on-chain AMM withPEAR
he already holds to buyfPEAR
at a lower price than its intended peg, and burnfPEAR
to claimPEAR
locked in a contract for profit.
Unlike Promises and Future contracts, fTokens have unique properties that differs from both of them:
- All fTokens are fungible per operation: if you are dealing with the same contract with the same input and output token, then fTokens for the specified output token are always fungible. Essentially fTokens are minted with its consecutive transaction on the foreign chain as collateral, and could be treated as asynchronous debt; liquidating this debt is equivalent to resolving
Promise
objects. Due to this property, fToken pools share common risk profiles — more on this later. - fTokens have no specified expiry date: they may be redeemed back to its underlying asset at any time, but is subject to additional risks the longer someone holds onto them.
- fTokens may be redeemed back to its underlying token, as long there is enough liquidity within the output pool: on Future expiry, fTokens target a 1:1 peg. However, due to potential frontrunning attacks or operation failure, there may not be enough liquidity within the output pool to redeem all fTokens one-to-one.
Configuration & Logic
Components are as follows:
- An oracle feed for transaction simulation: Periodically feed expected output amount in output token denomination. Should also specify maximum tolerated slippage for this fToken, as there may be loss involved with transaction processing delays on the foreign chain. May be replaced with on-chain logic that simulates transaction results on the foreign chain.
- Transaction relayers: Relay token transfers and arbitrary messages between
CHAIN_A
andCHAIN_B
. Any basic token transfer bridge should work, although arbitrary messaging support would be a plus. - Job queue & Jobkeepers: Maintain a queue of jobs due for processing on the origin chain; jobkeepers stake tokens to participate in an auction. If an operation fails, staked tokens are slashed to compensate for fToken pool loss; successful keepers are rewarded with jobkeeper fees with subsidized transaction fees. May be replaced with third-party decentralized DevOps such as keep3r.network, or temporarily a centralized bot. Ideally an IBC module could be built that tracks this job queue and maintains transaction ordering; this is set aside for now as future work.
- Arbitragers: External parties that manage the fToken — Token AMM pool, as well as to perform arbitrage on this pool to buy back fTokens below peg with Tokens, burn them, and receive back more Tokens. This is equivalent to resolving a
Promise
object combined with bond liquidation properties.
Full logic, roughly, could be put as follows:
- Alice calls an on-chain
entry contract
fromCHAIN_A
. The contract transfers specifiedAPPLE
balances from Alice’s wallet to the contract account, and stores them within aninput queue
present onCHAIN_A
. - The
entry contract
calculates how muchPEAR
Alice would have received (transaction simulation), and mints equivalent amounts offPEAR
. Actual transaction execution is sent over to ajob queue
, with unsigned transaction metadata including contract ABIs, input token amount, destinations, etc. - Jobkeepers run an auction to process jobs listed on the
job queue
. As transaction metadata already exist, jobkeepers first transfer requiredAPPLE
balances toCHAIN_B
over a transaction relayer, and run the transaction as specified. Resulting tokenPEAR
should be returned to anexit contract
present onCHAIN_A
. - The entity holding
fPEAR
now has the right to claim equivalent amounts ofPEAR
from theexit contract
by burning them. This entity may be Alice, or external arbitragers (if Alice sold off resultingfPEAR
in one transaction for receivingPEAR
).
Attack Factors & Future Work
There could be two primary attack vectors here: loss due to transaction failure on the foreign chain (including foreign chain failures, oracle failures, and transaction simulation failures), and sandwich frontrunning by jobkeepers or external parties.
Jobkeepers should retry
until Future
is fully resolved, but this will also incur additional gas costs; in cases where a particular job fails, this will result in a net loss for the output pool, resulting in fTokens permanently going off-peg. Promise
s in programming have (pending, fulfilled, rejected) states per object of which will solve this issue, but this will harm fungibility — unless each Promise
object is built as NFTs. Current design relies on slashing to cover potential failure losses, but economic implications and effectiveness of such a mechanism require further research.
Sandwich frontrunning is also an issue. This means that, a jobkeeper or an external party may inject transactions in front of and after the target transaction to slowly drain funds within the exit contract
. This is very similar to MEV attacks on the mempool or sandwiching bot attacks currently present on Ethereum-based DeFi protocols.
- Jobkeeper Cindy receives a job for trading
APPLE
toPEAR
on Uniswap. She first inserts aAPPLE -> PEAR
swap transaction in front of the job transaction, and performs Alice’sAPPLE -> PEAR
swap transaction next, resulting in lessPEAR
tokens being returned to theexit contract
. - Cindy can then insert a
PEAR -> APPLE
swap transaction immediately after Alice’s swap transaction, earning an instant profit.
Setting slippage tolerance to a lower value should solve this issue, but this also conflicts with operation rejection loss issues (of which slippage tolerance should be set higher). Calculating an optimal value for this should be done in addition to considering both attack scenarios.
Last but not least, there is the issue of capital inefficiency. There needs to be minimal liquidity for the fToken — Token trading pair on every “client” chain, but this means that some fToken debt will not be finalized forever. Any additional loss within the exit contract token
pool could be incurred to liquidity providers in addition to impermanent loss.
Any feedback is welcome — please leave a comment, or feel free to send an email at me@unifiedh.com.