Skip to content

About Custom Super Token

Didi edited this page Jun 14, 2024 · 8 revisions

Background

A Custom SuperToken is a SuperToken with logic which deviates from the default implementation.

First familiarize yourself with the possible types of SuperTokens.
Once you decided you want a Custom SuperToken, there's a few more choices to be made regarding Upgradability and Governance.

SuperTokens are by default upgradable. While it's in principle possible to create non-upgradable SuperTokens, that's currently not recommended.
That's because SuperTokens are tightly integrated with the set of Superfluid framework contracts. Those are still evolving through upgrades, and occasionally those upgrades require changes to the SuperToken logic too. Keeping the logic of SuperTokens in sync with each other also helps keeping them fully compatible with the off-chain tooling.

When implementing a Custom SuperToken, the recommended technical design depends on a few choices, mainly regarding required Modifications, Upgradability and Governance.

Modifications

The Superfluid contracts include 2 types of canonical SuperToken implementations:

  • ERC20 wrappers: deploy a stock UUPSProxy contract pointing to the canonical SuperToken implementation
  • SETHProxy: extends UUPSProxy with functionality for upgrading/downgrading from/to the the native asset (ETH, MATIC, ...), points to the canonical SuperToken implementation

SETHProxy is an example for a custom SuperToken which adds functionality.

Example for added functionality in SETHProxy:

    function upgradeByETH() external override payable {
        ISuperToken(address(this)).selfMint(msg.sender, msg.value, new bytes(0));
        emit TokenUpgraded(msg.sender, msg.value);
    }

This does an external call to selfMint, which is an external method of SuperToken provided specifically for the use case of extending functionality in Custom SuperTokens.
It needs to be an external call (forced by the syntax ISuperToken(address(this)).<method>) in order to route the call through the fallback function of the proxy, which triggers a delegateCall to the implementation. The "self" methods (alongside selfMint, there's also selfBurn, selfTransferFrom and selfApprove) can be invoked only by the token contract itself (msg.sender == address(this) needs to be true). This is the case when invoking the method from a proxy via delegateCall, because in that context both msg.sender and address(this) point to the proxy's address.

It's also possible to implement changed behaviour using this pattern. You could for example add a transfer fee by implementing transfer and transferFrom in the proxy and use selfTransferFrom like this:

    function transferFrom(address holder, address recipient, uint256 amount) public returns (bool) {
        // transfer to the receiver
        ISuperToken(address(this)).selfTransferFrom(holder, msg.sender, recipient, amount);

        // transfer the fee
        ISuperToken(address(this)).selfTransferFrom(holder, msg.sender, feeRecipient, feePerTx);
        
        return true; // returns true if it didn't revert
    }
    
    function transfer(address recipient, uint256 amount) public returns (bool) {
        return transferFrom(msg.sender, recipient, amount);
    }

In this case, the implementation of transfer and transferFrom in the proxy contract shadows the one in the implementation contract. This basically intercepts their invocations (prevents them being handled by the fallback function which would route the calls to the implementation contract).

Many potential modifications can be implemented using the same pattern.

While it's in principle possible to implement arbitrary behaviour changes (because ultimately the Proxy contract can intercept any call and has full control over what logic it executes in respone), it's recommended to rely on the canonical SuperToken implementation as much as possible in order to avoid the maintenance cost of keeping Custom SuperTokens compatible with the Superfluid framework.

Upgradability

Upgradability of SuperToken contracts is usually implemented using the UUPSProxy contract. You can find a good explanation of the UUPSProxy pattern here.

The usage of a Proxy for SuperToken contracts not only makes them upgradable, but also helps reducing deployment costs, because the (rather large) logic contract can be shared by many (rather small) Proxy contracts.

A caveat to be considered in the context of Custom SuperTokens is this:
The custom logic placed in the Proxy contract will NOT be upgradable. That is, in the example above which adds a fee to transfers, it would not be possible to later change the behaviour to for example add another transfer to a second fee receiver. Such possible future behaviour changes need to be covered from the start e.g. by making it configurable by an admin.

If upgradability of the custom logic is a MUST, there's a few more involved options to achieve that:

  1. Instead of extending the Proxy, extend the SuperToken contract and deploy a custom version of it. This means the SuperToken will not be managed by Superfluid governance (upgraded to the latest canonical logic). There's also constraints to how much logic can be added (contract size limit) since the SuperToken implementation is already quite complex.
    Extending the SuperToken contract is facilitated by its public interface methods being virtual.
  2. Nested proxies: You could deploy a stock proxy which points to an intermediate proxy with your custom logic and routes everything else to the canonical SuperToken implementation contract.
  3. Custom routing: You could implement a Proxy contract with additional logic which intercepts calls to a few methods and routes them to a secondary implementation contract, where you put your custom, now upgradable logic. This would however not be standards compliant and thus lack ecosystem support (e.g. not be understood by Explorers)
  4. Use the Diamond Pattern. This is similar to the previous option, but following an opinionated terminology specified in an EIP.

Governance

In order to deploy a SuperToken with an account of your choice set as the sole upgrade admin, see Self Governed Super Token.

Clone this wiki locally