WUSD Presto

The Presto verticalization of the Covenant WUSD protocol allows users to mint / burn WUSD directly from / to ETH or any other token.

Mint

Imagine a user with ETH who wants to mint WUSD tokens. Without Presto, they would need to manually swap their ETH for a pair of stablecoins that can then be collateralized in a liquidity pool to mint WUSD. Using Presto, they can simply input the amount of ETH they would like to swap, and Presto performs the transactions automatically. Let's assume, for example, that 1 ETH is worth 2178 WUSD. The user inputs one ETH, and Presto automatically swaps it for 1089 DAI and 1088 USDC, collateralizes these in the DAI/USDC Uniswap liquidity pool, mints the equivalent amount of WUSD and transfers it to the user.

To mint WUSD tokens directly from ETH through Presto in this way, use the addLiquidity function.

 function addLiquidity (
     address prestoAddress,
     PrestoOperation[] memory operations,
     address wusdExtensionControllerAddress,
     uint256 ammPosition,
     uint256 liquidityPoolPosition
 ) public payable returns(uint256 minted) {

The inputs of this function are as follows:

First of all, the _transferToMeAndCheckAllowance method is called by addLiquidity, passing as input the _operations parameter (which represents all PrestoOperation initially passed as input into the PrestoOperation [] array) and the Presto contract address. This takes care of transferring tokens from the msg.sender to the contract.

uint256 eth = _transferToMeAndCheckAllowance(operations, prestoAddress);

To calculate the amount of tokens to be transferred, the _transferToMeAndCheckAllowance method internally calls the _collectTokensAndCheckAllowance and then the _collectTokenData methods. These stores the parameters _tokensToTransfer and _tokenAmounts in the contract's storage (see the Presto section about the _collectTokens and _collectTokenData functions).

If the input token passed into PrestoOperation is ETH / if the enterInEth value is true, the amount of ETH to be transferred is expressed by the eth variable, otherwise the variable eth is equal to zero.

Then, through the Presto contract's interface, the execute function is called to perform the requested Operation (as explained in the Presto section), passing the _operations parameter and the previously calculated eth value (which as mentioned above can be populated or equal to zero depending on whether ETH is being used or not).

IPresto(prestoAddress).execute{value : eth}(_operations);

The WUSD Extension Controller contract interface is retrieved from its address by the addLiquidity. Then, the _calculateAmountsAndApprove method is called to retrieve the amounts of and approve the tokens needed to mint the new WUSD token amount.

IWUSDExtensionController wusdExtensionController = IWUSDExtensionController(wusdExtensionControllerAddress);
(uint256 liquidityPoolAmount, address[] memory tokenAddresses) = _calculateAmountsAndApprove(wusdExtensionController, ammPosition, liquidityPoolPosition);

The _calculateAmountsAndApprove method retrieves the AMM address, the LP token address and the pair token addresses you are using to mint WUSD.

AllowedAMM memory allowedAMM = wusdExtensionController.allowedAMMs()[ammPosition];
address liquidityPoolAddress = allowedAMM.liquidityPools[liquidityPoolPosition];
(,,tokenAddresses) = IAMM(allowedAMM.ammAddress).byLiquidityPool(liquidityPoolAddress);

The byTokenAmount method of the AMM Aggregator is used by the _calculateAmountsAndApprove to retrieve, passing as values the liquidityPoolTokenAddress, the token address and the token amount of one of the pair tokens obtained by the Presto Operation execution to retrieve the equivalent LP token amount and the pair tokens amount.

uint256[] memory tokenAmounts;
(liquidityPoolAmount, tokenAmounts,) = IAMM(allowedAMM.ammAddress).byTokenAmount(liquidityPoolAddress, tokenAddresses[0], _balanceOf(tokenAddresses[0]));
uint256 balance = _balanceOf(tokenAddresses[1]);

Then, if you entered a token amount in Presto Operation that turned out to be greater than the token amount actually received at the end of the Operation (e.g. due to slippage), the token amount you entered is changed to the token amount you actually swapped.

if(tokenAmounts[1] > balance) {
    (liquidityPoolAmount, tokenAmounts,) = IAMM(allowedAMM.ammAddress).byTokenAmount(liquidityPoolAddress, tokenAddresses[1], balance);
}

Then the _safeApprove is called to allow the wusdExtensionController contract to spend the sent tokens amount to mint WUSD.

At this point, the WUSD amount is minted by calling the addLiquidity method of the wusdExtensionController contract, passing the ammPosition, the LP position, the LP amount and false (representing byLiquidityPool as false. Look here for more details). At this point the minted amount of the operation is defined.

minted = wusdExtensionController.addLiquidity(ammPosition, liquidityPoolPosition, liquidityPoolAmount, false);

After performing the required operations, the execute function calls the _flushAndClear method internally.

_flushAndClear(wusdExtensionController, tokenAddresses, msg.sender);

This method takes care of three things, first it sends to the msg.sender the amount of WUSD minted and sent to this contract from the WUSDExtension contract.

(,,address wusdInteroperableAddress) = wusdExtensionController.wusdInfo();
_safeTransfer(wusdInteroperableAddress, receiver, _balanceOf(wusdInteroperableAddress));

Then, as described in the Presto section, it checks if there are any remaining tokens in the contract from Operations and sends them back to the msg.sender.

and at the end, it deletes from the contract storage the _tokenAmounts and _tokenToTransfer parameters previously used to efficiently manage transfer Operations from the user to the contract and vice-versa.

Burn

To burn WUSD tokens, use the safeTransferFrom or safeBatchTransferFrom method directly from the WUSD token collection itself to this WUSD Presto verticalization contract. Look at this section to read more about WUSD Burn

This allows for example to burn WUSD giving back to the user directly ETH, Presto in fact burns the amount of WUSD into the pair tokens and swap them in ETH. For example: 2178 WUSD equals to 1 ETH. A user wants to burn WUSD, using Uniswap's DAI/USDC pair and taking back ETH. Presto automatically burns 2178 WUSD in 1089 DAI and 1088 USDC and swaps them in 1 ETH that sends to the user.

Use the safeTransferFrom or safeBatchTransferFrom method with the following inputs:

  • from -> sender address

  • objectId/s -> WUSD object Id

  • amount/s -> WUSD amount to burn, or an array containing the amounts in case of safeBatchTransferFrom

  • data

Regarding the data input:

The payload must contain three parameters:

1) Burn data according to WUSD Burn operation

2) Presto contract address

3) Array containing the Presto Operations to perform

Regarding point one, as explained in the WUSD Burn section, the data must contain a value other than "1" to burn the WUSD token amount and in addition, a series of parameters useful for recovering tokens from the burn action. In particular, the payload must contain:

  • ammPosition -> position of the AMM you want to use (see here for more information)

  • liquidityPoolPosition -> position of the LP you want to use (see here for more information)

  • liquidityPoolAmount -> amount of tokens corresponding to the WUSD amount to be burned for that chosen pool. If byLiquidityPool is true, this parameter represents an amount of LP tokens; if false, this parameter represents an amount of pair tokens

  • keepLiquidityPool -> a boolean value representing whether to get back LP tokens (true) or the corresponding amount of pair tokens inside the LP token (false)

The onERC1155Received/onERC1155BatchReceived function takes care of burning the WUSD amount that arrives at the contract, as described below.

The WUSD Extension Controller contract interface is retrieved from its address

IWUSDExtensionController wusdExtensionController = IWUSDExtensionController(wusdExtensionControllerAddress);

The onERC1155Received/onERC1155BatchReceivedfunction decodes the payload passed by _safeTransferFrom retrieving the data containing the burn information, Presto's contract address and the array of Operations to execute (see above)

(bytes memory transferData, address prestoAddress, PrestoOperation[] memory operations) = abi.decode(data, (bytes, address, PrestoOperation[]));

Subsequently, to perform the burn action, the safeTransferFrom method is used which transfers the WUSD amount from this contract (WUSD Presto verticalization contract) to the WUSDExtensionController contract.

INativeV1(msg.sender).safeTransferFrom(address(this), address(wusdExtensionController), id, value, transferData);

After sending and burning the amount of WUSD, this contract gets back the corresponding amount of collateralized stablecoins sent from the wusdExtensionController contract. Consequently, the onERC1155Received/onERC1155BatchReceived function calls the _afterBurn method to perform Presto's operations

_afterBurn(prestoAddress, operations, wusdExtensionController, from);

The presto operation is performed by calling the execute function from Presto's contract interface

IPresto(prestoAddress).execute{value : _collectTokensAndCheckAllowance(operations, prestoAddress)}(operations);

After performing the required operations, the _afterBurn function calls the _flushAndClear method internally.

_flushAndClear(wusdExtensionController, new address[](0), from);

This method takes care of three things, first it sends to the msg.sender the amount of WUSD minted and sent to this contract from the WUSDExtension contract.

Then, as described in the Presto section, it checks if there are any remaining tokens in the contract from Operations and sends them back to the msg.sender.

And at the end, it deletes from the contract storage the _tokenAmounts and _tokenToTransfer parameters previously used to efficiently manage transfer Operations from the user to the contract and vice-versa.

Last updated