Skip to content

About Call Proxies

Didi edited this page Oct 24, 2023 · 9 revisions

(This concept is not in any way related to the proxy pattern for upgradable smart contracts)

Overview

The design of the Superfluid protocol requires that the protocol logic is executed within a context. Such context is called "Superfluid Call Context". An inner transaction involves the superfluid protocol is also called a "superfluid transaction". To queue an operation in a superfluid transaction, one must use a type of "call proxy": if it is the initial call, a contextless call proxy must be used; or else, contextful call proxy must be used.

Key Concepts

Superfluid Call Context (Ctx)

A Superfluid transaction involves external calls between multiple contracts. At minimum, it involves the host, a token and an agreement. When Super app is involved, it involves the super app, more super apps (multi-level super apps) or contracts that are not super apps.

Such transaction is conducted within a "superfluid call context", where a verifiable chunk of shared memory is passed between external calls as bytes that is often dubbed as "ctx". The specifics of this technique can be found in the section of "Stamped Ctx" of this page.

The use cases of the "superfluid call context" are:

  • Provide the abstracted timestamp.
  • Provide the correct msgSender.
  • Provide userData attached by the original sender.
  • App credit system uses the call contexts to store the accounting information.
  • Tracking super app nesting levels. Note: in production, max-level being 1 conveniently provides a some re-entrance protection.
  • Provider callback context to the agreements.

Stamped Ctx

Context can be thought of as trusted data which can only be altered by the Superfluid host; if the data could be altered at will, then it would not be secure/trustworthy. It is important to note that this state is tracked within one transaction, at the end of the transaction, the context is cleared.

"Stamped Ctx" is a technique to provide such transactional, secure and efficient heap-like shared memory data in EVM called "stamped Ctx" for both agreements and super app actions.

Related EIP: EIP-1153: Transient storage opcodes - this EIP would introduce EVM opcodes for storing and reading data which exists only during the lifetime of the transaction. With this, it would nomore be necessary to explicitly pass around a ctx object as function argument. EIP-1153 may be included in the upcoming Dencun Ethereum upgrade.

Call Proxies

Contextless Call Proxies

These call proxies initiate a new call context.

callAgreement

It is a call proxy that uses the "duck-type polymorphic calling convention" (see below). All agreements supported by Superfluid framework must support this.

Note: it is in contrast to the token-centric approach, where "payment primitives" are exposed as "extended functions" to the ERC20 token.

callAppAction

Purpose: A way for the target contract to get msgSender (at the time this method is created, EIP-2771 was not popular yet).

How does it work:

  • The target function should have a function signature with the last parameter of type bytes ctx.
  • The ctx should initialized as placeHolderCtx (zero length bytes), and host contract will replace it with the actual ctx.
  • The target function should also follow the linear-type (use at least once, and always return the latest value) convention of using ctx.

Contextual Call Proxies

These are the counter part for the contextless version, so that a contract can interact with Superfluid framework within a call context:

  • callAgreementWithContext
  • callAppActionWithContext

Batch Call Proxies

Plain batchCall

Defined as BatchOperation in Definitions.sol.

EIP-2771 forwardBatchCall

The forwardBatchCall allows "trusted forwarders" (configured through the Superfluid governance) to forward a batch call with a different msgSender.

It is evident that being trusted forward must pass high security bar: audited code, verified contracts, etc.

EIP-712 Batch Call

EIP-712 improves the usability of off-chain message signing for use on-chain. Integrating it with batch call give users more visibility to the batched operations.

It is currently not implemented.

Duck-type Polymorphic Calling Convention

There are different approaches to support new functionalities:

No polymorphic approach

Always add new entry functions for new agreement in host contract.

  • Pros: avoid duck tapes (hence type-safe)
  • Cons: more boiler plate.

Duck-type "polymorphic" solution using bytes memory

  • Pros: extremely flexible; by-passing Solidity language limitation.
  • Cons: type-unsafe, may have security implications when handling calldata.

Note: Solidity limitation may favor the non-polymorphic solution. In the future the token-centric interface may also simply disable the public access of polymorphic callAgreement interface.

Placeholder Ctx

In order to be able to pass "ctx" to the agreement function, such as:

interface IConstantFlowAgreementV1 {
...
function createFlow(
        ISuperfluidToken token,
        address receiver,
        int96 flowRate,
        bytes calldata ctx
    )
...

from top-level call:

    function callAgreement(
        ISuperAgreement agreementClass,
        bytes memory callData,
        bytes memory userData
    )

_replacePlaceholderCtx was used, this requires that the user provided abi encoded function call ends with a "0x" zero bytes data, a.k.a "placeholder" ctx, then the superfluid framework will reflate it with the actual stamped ctx, so that no one else can fake the ctx.

The process looks like this:

DATA PACKING:

0  : subscriber (32B)
32 : token (32B)
64 : indexId (32B)
96 : &placeHodlerCtx :: data (32B)
128: *placeHodlerCtx: [0, 0x] (32B) <--- to be replaced _replacePlaceholderCtx

=> _replacePlaceholderCtx replaces the actual *placeHodlerCtx 0x with ctx

STEP 1.b => reduce the length of data by 32B

0  : subscriber (32B)
32 : token (32B)
64 : indexId (32B)
96 : &placeHodlerCtx :: data (32B)

STEP 1.c => append actualCtx

0  : subscriber (32B)
32 : token (32B)
64 : indexId (32B)
96 : &actualCtx :: data (32B)
128: actualCtx: [CTX_LENGTH, CTX_RAW_DATA]

Note that there is a replacement technique similar to what's used EIP-2771 in where the access of ctx will require assembly code. Due to the backward-compatibility challenge, Superfluid framework will continue to use the current "placeHolder ctx" method.

Clone this wiki locally