SIP-182: Wrappr Factory
| Author | |
|---|---|
| Status | Implemented |
| Type | Governance |
| Network | Ethereum |
| Implementor | TBD |
| Release | TBD |
| Proposal | Loading status... |
| Created | 2021-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
ownerto createWrappercontracts.tokenandcurrencyKeyindicate which token and synth should be wrapped, andsynthContractNameis included because concatenation ofbytes32strings within solidity is difficult, and should match theAddressResolvername of the contract forcurrencyKey.
- Allows
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
- Assumes functionality of
Wrapper
The Wrapper created by the factory should be functionally equivalent to SIP-112, with the following exceptions:
- Constructor accepts IERC20 token address
tokenof source token, and currency keycurrencyKeyof target synth sETHIssuedrenamed tosynthIssuedmaxETHrenamed tomaxTokenAmountSystemSettingsvalues have additionalwrapperAddressargument to match- Upon
_mintor_burn, equivalent value sUSD is minted into theWrapperFactorycontract instead of withholdingsETH. This is functionally different in that fees are held assUSDinstead of their matching synth, meaning there is no hedging for the period of time before synth distribution. distributeFeesremoved
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
Wrapperinstances) - updates the
excludedDebtentry for - for this function to work properly it is critical that all debt changes for reporters be reported here
- only callable by "Debt issuers" (which for now is just
excludedIssuedDebts(bytes32[] memory currencyKeys) external view returns (uint256[])- returns excludedIssuedDebt values for supplied currencyKeys
takeDebtSnapshot()- modified to subtract
excludedDebtentries from its total debt calculation
- modified to subtract
SystemSettings
External functions are added to match settings described in the Configurable Values section below. Of particular note, the mintRate and burnRate
configurabale params will now have the ability to specify a negative value (type will be int256 instead of uint256), effectively incentivising rapid burn or minting of debt if the situation
calls for the need. If mintRate is set to -0.5, for example, the user will receive 0.5% more sETH than the ETH they put in. The reverse is true for burnRate--
the user will receive 0.5% more ETH when they burn the corresponding amount of sETH.
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
eamount of ETH andewWETH and the contract hascamount of ETH in spare capacity- Given that
ewis larger than or equal toc- When the user attempts to deposit
eETH into the contract- ✅ Then it succeeds and the following take place:
cETH is wrapped into WETH and is locked in the contractc(1-mintFeeRate)is minted into the user's wallet in sETHc*mintFeeRateworth of sETH is sent to the fee pool in the form of sUSDu - cworth of ETH is refunded back to the user
- ✅ Then it succeeds and the following take place:
- When the user attempts to deposit
ewWETH into the contract- ✅ Then it succeeds and the following take place:
cWETH is locked up in the contractc(1-mintFeeRate)is minted into the user's wallet in sETHc*mintFeeRateworth of sETH is sent to the fee pool in the form of sUSDu - cworth of WETH is refunded back to the user
- ✅ Then it succeeds and the following take place:
- Given that
mintFeeRateis <0- When the user attempts to deposit
ewWETH into the contract- ✅ Then it succeeds and the following take place:
c(1-mintFeeRate)WETH is locked up in the contractcis minted into the user's wallet in sETH
- ✅ Then it succeeds and the following take place:
- When the user attempts to deposit
- When the user attempts to deposit
- Given that
uis strictly lower thanc- When the user attempts to deposit
eETH into the contract- ✅ Then it succeeds and the following take place:
uETH is wrapped into WETH and is locked in the contractu(1-mintFeeRate)is minted into the user's wallet in sETHu*mintFeeRateworth of sETH is sent to the fee pool in the form of sUSD
- ✅ Then it succeeds and the following take place:
- When the user attempts to deposit
ewWETH into the contract- ✅ Then it succeeds and the following take place:
uWETH is locked up in the contractu(1-mintFeeRate)is minted into the user's wallet in sETH
- ✅ Then it succeeds and the following take place:
- Given that
mintFeeRateis <0- When the user attempts to deposit
ewWETH into the contract- ✅ Then it succeeds and the following take place:
u(1-mintFeeRate)WETH is locked up in the contractuis minted into the user's wallet in sETH
- ✅ Then it succeeds and the following take place:
- When the user attempts to deposit
- When the user attempts to deposit
- Given that
-
Given that the contract's capacity is zero, as
maxETHis 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
- When the user attempts deposit ETH or WETH into the contract
-
Given that a user has
uamount of sETH and the contract holdscamount of WETH- Given that
uis larger than or equal toc(1+burnFeeRate)- When the user attempts to draw out ETH from the contract by burning
usETH and flagging withdrawal in ETH- ✅ Then it succeeds and the following take place:
cWETH is unwrapped to ETH and is sent to the usercsETH is burnedc * burnFeeRateworth of sETH is swapped to sUSD and sent to the fee poolu - c(1+burnFeeRate)worth of sETH is refunded back to the user
- ✅ Then it succeeds and the following take place:
- When the user attempts to draw out WETH from the contract by burning
usETH and flagging withdrawal in WETH- ✅ Then it succeeds and the following take place:
cWETH is sent to the usercsETH is burnedc * burnFeeRateworth of sETH is swapped to sUSD and sent to the fee poolu - c(1+burnFeeRate)worth of sETH is refunded back to the user
- ✅ Then it succeeds and the following take place:
- Given that
burnFeeRateis <0- When the user attempts to draw out WETH from the contract by burning
usETH and flagging withdrawal in WETH- ✅ Then it succeeds and the following take place:
cWETH is sent to the usercsETH is burnedu + c(1+burnFeeRate)worth of sETH is refunded back to the user
- ✅ Then it succeeds and the following take place:
- When the user attempts to draw out WETH from the contract by burning
- When the user attempts to draw out ETH from the contract by burning
- Given that
uis strictly lower thanc(1+burnFeeRate)- When the user attempts to draw out ETH from the contract by burning
usETH 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 useru * burnFeeRateworth of sETH is swapped to sUSD and sent to the fee poolu (1 - burnFeeRate)sETH is burned
- ✅ Then it succeeds and the following take place:
- When the user attempts to draw out WETH from the contract by burning
usETH and flagging withdrawal in WETH- ✅ Then it succeeds and the following take place:
u (1-burnFeeRate)worth of WETH is sent to the useru * burnFeeRateworth of sETH is swapped to sUSD and sent to the fee poolu (1 - burnFeeRate)sETH is burned
- ✅ Then it succeeds and the following take place:
- Given that
burnFeeRateis <0- When the user attempts to draw out WETH from the contract by burning
usETH and flagging withdrawal in WETH- ✅ Then it succeeds and the following take place:
u (1+burnFeeRate)worth of WETH is sent to the useru 1sETH is burned
- ✅ Then it succeeds and the following take place:
- When the user attempts to draw out WETH from the contract by burning
- When the user attempts to draw out ETH from the contract by burning
- Given that
-
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
- When the user attempts draw out ETH or WETH from the contract
-
Given that a user attemps to mint with
WETHandmsg.valueis greater than zero then the following takes place:- ❌ The transaction reverts due to the user minting with both
WETHandETH
- ❌ The transaction reverts due to the user minting with both
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. This value may be negative. |
wrapperBurnFeeRate |
setWrapperBurnFeeRate |
Portion of synths prior to burning to be withheld for delivery to fee pool. This value may be negative. |
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
Copyright and related rights waived via CC0.