-
Notifications
You must be signed in to change notification settings - Fork 241
About Custom Super Token
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.
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 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:
- 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 beingvirtual
. - 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.
- 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)
- Use the Diamond Pattern. This is similar to the previous option, but following an opinionated terminology specified in an EIP.
In order to deploy a SuperToken with an account of your choice set as the sole upgrade admin, see Self Governed Super Token.
- Governance Overview
- For Contributors
- Development Process
- Protocol EVMv1 Operations
- Protocol EVMv1 Technical Notes
- Protocol EVMv1 Core Subgraph