SIP-182: Wrappr Factory

Author
Discussions-ToSynthetix Discord
StatusApproved
Created2021-09-01

Implementors

Mark E. Barrasso (@barrasso), Daniel Beal (@KillerByte)

Simple Summary

Allows users to wrap any ERC20 token and mint its synthetic counterpart

Abstract

This SIP proposes to create a new WrapperFactory contract that can deploy new Wrapper contracts to support virtually any ERC20 token. The new Wrapper contract behaves just like its predecessor, the ETH Wrappr from SIP-112.

Motivation

With respect to adding support for more token wrappers, this SIP proposes a new abstract factory contract that can deploy new wrappers as needed rather than creating another distinct SIP and contract for each new token to be added in the future.

Specification

Overview

A new contract is created, WrapperFactory, which deploys Wrapper instances. Before a new Wrapper instance can be deployed, it must first be approved via SIP. A synthetic token is minted whenever a user deposits a supported ERC20 token into its respective Wrapper. The user can deposit any amount desired however, this is subject to not exceeding the maxTokenAmount, which is configurable via SCCP. There is no duration, interest rate or collateralization ratio, as any user can at any time buy back the ERC20 token available in the contract by burning the synthetic token. SNX stakers benefit as minting and burning are subject to a mintingFeeRate and burningFeeRate, both of which are paid to the fee pool after conversion into sUSD. The WrapperFactory contract supports wrapper deployment for virtually any ERC20 token, and Wrapper automatically handles wrapping/unwrapping of the token for the user.

Rationale

This SIP makes it easier to deploy Wrapper contracts that can support more assets in the future and will help expand the total supply of synths.

Technical Specification

WrapperFactory

A new Synthetix core contract is created, WrapperFactory, which by means of the factory pattern deploys Wrapper instances using createWrapper. The WrapperFactory assumes functionality previously exposed by EtherWrapper.

Specifically, WrapperFactory contains the following functions:

  • createWrapper(IERC20 token, bytes32 currencyKey, bytes32 synthContractName)
    • Allows owner to create Wrapper contracts. token and currencyKey indicate which token and synth should be wrapped, and synthContractName is included because concatenation of bytes32 strings within solidity is difficult, and should match the AddressResolver name of the contract for currencyKey.
  • isWrapper(address possibleWrapper) returns (bool)
    • Allows Synthetix system components to verify a valid Wrapper contract by passing contract address. Returns boolean, indicating if its a Wrapper contract or not
  • distributeFees()
    • Assumes functionality of EtherWrapper.distributeFees, sending fees to the fee pool and informing of status
    • note small change in functionality fees are returned from sUSD instead of source currency keys. This means there is no debt hedging, but much easier to develop code for

Wrapper

The Wrapper created by the factory should be functionally equivalent to SIP-112, with the following exceptions:

  • Constructor accepts IERC20 token address token of source token, and currency key currencyKey of target synth
  • sETHIssued renamed to synthIssued
  • maxETH renamed to maxTokenAmount
  • SystemSettings values have additional wrapperAddress argument to match
  • Upon _mint or _burn, equivalent value sUSD is minted into the WrapperFactory contract instead of withholding sETH. This is functionally different in that fees are held as sUSD instead of their matching synth, meaning there is no hedging for the period of time before synth distribution.
  • distributeFees removed

EtherWrapper and NativeEtherWrapper are deprecated.

DebtCache

In order to optimize for gas usage and improve gas calculation

The new functions are added:

  • recordExcludedDebtChange(bytes32 currencyKey, int256 delta) external
    • only callable by "Debt issuers" (which for now is just Wrapper instances)
    • updates the excludedDebt entry for
    • for this function to work properly it is critical that all debt changes for reporters be reported here
  • excludedIssuedDebts(bytes32[] memory currencyKeys) external view returns (uint256[])
    • returns excludedIssuedDebt values for supplied currencyKeys
  • takeDebtSnapshot()
    • modified to subtract excludedDebt entries from its total debt calculation

SystemSettings

External functions are added to match settings described in the Configurable Values section below.

Special Concerns

It is possible that the SystemSettings contract will not fit on L2.

Test Cases

Wrapper initialized with WETH -> sETH

  • Given that a user has e amount of ETH and ew WETH and the contract has c amount of ETH in spare capacity

    • Given that ew is larger than or equal to c
      • When the user attempts to deposit e ETH into the contract
        • ✅ Then it succeeds and the following take place:
          • c ETH is wrapped into WETH and is locked in the contract
          • c(1-mintFeeRate) is minted into the user's wallet in sETH
          • c*mintFeeRate worth of sETH is sent to the fee pool in the form of sUSD
          • u - c worth of ETH is refunded back to the user
      • When the user attempts to deposit ew WETH into the contract
        • ✅ Then it succeeds and the following take place:
        • c WETH is locked up in the contract
        • c(1-mintFeeRate) is minted into the user's wallet in sETH
          • c*mintFeeRate worth of sETH is sent to the fee pool in the form of sUSD
          • u - c worth of WETH is refunded back to the user
    • Given that u is strictly lower than c
      • When the user attempts to deposit e ETH into the contract
        • ✅ Then it succeeds and the following take place:
          • u ETH is wrapped into WETH and is locked in the contract
          • u(1-mintFeeRate) is minted into the user's wallet in sETH
          • u*mintFeeRate worth of sETH is sent to the fee pool in the form of sUSD
      • When the user attempts to deposit ew WETH into the contract
        • ✅ Then it succeeds and the following take place:
          • u WETH is locked up in the contract
          • u(1-mintFeeRate) is minted into the user's wallet in sETH
          • u*mintFeeRate worth of sETH is sent to the fee pool in the form of sUSD
  • Given that the contract's capacity is zero, as maxETH is locked in the contract

    • When the user attempts deposit ETH or WETH into the contract
      • ❌ Then the transaction reverts due to max capacity being reached
  • Given that a user has u amount of sETH and the contract holds c amount of WETH

    • Given that u is larger than or equal to c(1+burnFeeRate)
      • When the user attempts to draw out ETH from the contract by burning u sETH and flagging withdrawal in ETH
        • ✅ Then it succeeds and the following take place:
          • c WETH is unwrapped to ETH and is sent to the user
          • c sETH is burned
          • c * burnFeeRate worth of sETH is swapped to sUSD and sent to the fee pool
          • u - c(1+burnFeeRate) worth of sETH is refunded back to the user
      • When the user attempts to draw out WETH from the contract by burning u sETH and flagging withdrawal in WETH
        • ✅ Then it succeeds and the following take place:
          • c WETH is sent to the user
          • c sETH is burned
          • c * burnFeeRate worth of sETH is swapped to sUSD and sent to the fee pool
          • u - c(1+burnFeeRate) worth of sETH is refunded back to the user
    • Given that u is strictly lower than c(1+burnFeeRate)
      • When the user attempts to draw out ETH from the contract by burning u sETH and flagging withdrawal in ETH
        • ✅ Then it succeeds and the following take place:
          • u (1-burnFeeRate) worth of WETH is unwrapped to ETH and is sent to the user
          • u * burnFeeRate worth of sETH is swapped to sUSD and sent to the fee pool
          • u (1 - burnFeeRate) sETH is burned
      • When the user attempts to draw out WETH from the contract by burning u sETH and flagging withdrawal in WETH
        • ✅ Then it succeeds and the following take place:
          • u (1-burnFeeRate) worth of WETH is sent to the user
          • u * burnFeeRate worth of sETH is swapped to sUSD and sent to the fee pool
          • u (1 - burnFeeRate) sETH is burned
  • Given that the contract's holds no WETH

    • When the user attempts draw out ETH or WETH from the contract
      • ❌ Then the transaction reverts due to the contract being out of WETH
  • Given that a user attemps to mint with WETH and msg.value is greater than zero then the following takes place:

    • ❌ The transaction reverts due to the user minting with both WETH and ETH

Configurable Values (Via SCCP)

There are 3 new values for each Wrapper created by the WrapperFactory:

Name Setter Description
wrapperMaxTokenAmount setWrapperMaxTokenAmount Maximum number of tokens in custody of the wrapper
wrapperMintFeeRate setWrapperMintFeeRate Portion of synths after minting to be withheld for delivery to fee pool
wrapperBurnFeeRate setWrapperBurnFeeRate Portion of synths prior to burning to be withheld for delivery to fee pool

For all of the above parameters, the corresponding setter or getter function must be supplied with wrapperAddress to indicate which wrapper should be applied the setting.

In addition, etherWrapperMaxETH, etherWrapperMintFeeRate, and etherWrapperBurnFeeRate are deprecated.

Copyright and related rights waived via CC0.