* ADDS basic boilerplate for PSM bridge WIP * ADDS integrate the MakerPSM mixin and fix incorrect naming * fix: take into account PSM fee when buying USDC from PSM * feat: intial stab at a PSM sampler WIP * feat: integrate MakerPsm into AS WIP * refactor: get VAT contract address from PSM instead of passing it in * fix: hardcode PSM Gemtoken to USDC * fix: remove passing in authGem, get from PSM contract instead * fix: use constant modified to avoid using storage variables * fix: incorrect num decimals after multiplication in sampler * fix: PSM buy sampling * fix: use fillData to estimate gas schedule * Rebased on latest development * Guard and use latest Curve LiquidityProvider * `@0x/contract-addresses`: Redeploy FQT on mainnet and ropsten Co-authored-by: Jacob Evans <jacob@dekz.net> Co-authored-by: Lawrence Forman <lawrence@0xproject.com>
268 lines
9.3 KiB
Solidity
268 lines
9.3 KiB
Solidity
// SPDX-License-Identifier: Apache-2.0
|
|
/*
|
|
|
|
Copyright 2021 ZeroEx Intl.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
pragma solidity ^0.6;
|
|
pragma experimental ABIEncoderV2;
|
|
|
|
import "./SamplerUtils.sol";
|
|
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
|
|
|
interface IPSM {
|
|
// @dev Get the fee for selling USDC to DAI in PSM
|
|
// @return tin toll in [wad]
|
|
function tin() external view returns (uint256);
|
|
// @dev Get the fee for selling DAI to USDC in PSM
|
|
// @return tout toll out [wad]
|
|
function tout() external view returns (uint256);
|
|
|
|
// @dev Get the address of the PSM state Vat
|
|
// @return address of the Vat
|
|
function vat() external view returns (address);
|
|
|
|
// @dev Get the address of the underlying vault powering PSM
|
|
// @return address of gemJoin contract
|
|
function gemJoin() external view returns (address);
|
|
|
|
// @dev Get the address of DAI
|
|
// @return address of DAI contract
|
|
function dai() external view returns (address);
|
|
|
|
// @dev Sell USDC for DAI
|
|
// @param usr The address of the account trading USDC for DAI.
|
|
// @param gemAmt The amount of USDC to sell in USDC base units
|
|
function sellGem(
|
|
address usr,
|
|
uint256 gemAmt
|
|
) external;
|
|
// @dev Buy USDC for DAI
|
|
// @param usr The address of the account trading DAI for USDC
|
|
// @param gemAmt The amount of USDC to buy in USDC base units
|
|
function buyGem(
|
|
address usr,
|
|
uint256 gemAmt
|
|
) external;
|
|
}
|
|
|
|
interface IVAT {
|
|
// @dev Get a collateral type by identifier
|
|
// @param ilkIdentifier bytes32 identifier. Example: ethers.utils.formatBytes32String("PSM-USDC-A")
|
|
// @return ilk
|
|
// @return ilk.Art Total Normalised Debt in wad
|
|
// @return ilk.rate Accumulated Rates in ray
|
|
// @return ilk.spot Price with Safety Margin in ray
|
|
// @return ilk.line Debt Ceiling in rad
|
|
// @return ilk.dust Urn Debt Floor in rad
|
|
function ilks(
|
|
bytes32 ilkIdentifier
|
|
) external view returns (
|
|
uint256 Art,
|
|
uint256 rate,
|
|
uint256 spot,
|
|
uint256 line,
|
|
uint256 dust
|
|
);
|
|
}
|
|
|
|
contract MakerPSMSampler is
|
|
SamplerUtils
|
|
{
|
|
using LibSafeMathV06 for uint256;
|
|
|
|
/// @dev Information about which PSM module to use
|
|
struct MakerPsmInfo {
|
|
address psmAddress;
|
|
bytes32 ilkIdentifier;
|
|
address gemTokenAddress;
|
|
}
|
|
|
|
/// @dev Gas limit for MakerPsm calls.
|
|
uint256 constant private MAKER_PSM_CALL_GAS = 300e3; // 300k
|
|
|
|
|
|
// Maker units
|
|
// wad: fixed point decimal with 18 decimals (for basic quantities, e.g. balances)
|
|
uint256 constant private WAD = 10 ** 18;
|
|
// ray: fixed point decimal with 27 decimals (for precise quantites, e.g. ratios)
|
|
uint256 constant private RAY = 10 ** 27;
|
|
// rad: fixed point decimal with 45 decimals (result of integer multiplication with a wad and a ray)
|
|
uint256 constant private RAD = 10 ** 45;
|
|
// See https://github.com/makerdao/dss/blob/master/DEVELOPING.m
|
|
|
|
/// @dev Sample sell quotes from Maker PSM
|
|
function sampleSellsFromMakerPsm(
|
|
MakerPsmInfo memory psmInfo,
|
|
address takerToken,
|
|
address makerToken,
|
|
uint256[] memory takerTokenAmounts
|
|
)
|
|
public
|
|
view
|
|
returns (uint256[] memory makerTokenAmounts)
|
|
{
|
|
_assertValidPair(makerToken, takerToken);
|
|
IPSM psm = IPSM(psmInfo.psmAddress);
|
|
IVAT vat = IVAT(psm.vat());
|
|
|
|
|
|
uint256 numSamples = takerTokenAmounts.length;
|
|
makerTokenAmounts = new uint256[](numSamples);
|
|
|
|
if (makerToken != psm.dai() && takerToken != psm.dai()) {
|
|
return makerTokenAmounts;
|
|
}
|
|
|
|
for (uint256 i = 0; i < numSamples; i++) {
|
|
uint256 buyAmount = _samplePSMSell(psmInfo, makerToken, takerToken, takerTokenAmounts[i], psm, vat);
|
|
|
|
if (buyAmount == 0) {
|
|
break;
|
|
}
|
|
makerTokenAmounts[i] = buyAmount;
|
|
}
|
|
}
|
|
|
|
function sampleBuysFromMakerPsm(
|
|
MakerPsmInfo memory psmInfo,
|
|
address takerToken,
|
|
address makerToken,
|
|
uint256[] memory makerTokenAmounts
|
|
)
|
|
public
|
|
view
|
|
returns (uint256[] memory takerTokenAmounts)
|
|
{
|
|
_assertValidPair(makerToken, takerToken);
|
|
IPSM psm = IPSM(psmInfo.psmAddress);
|
|
IVAT vat = IVAT(psm.vat());
|
|
|
|
uint256 numSamples = makerTokenAmounts.length;
|
|
takerTokenAmounts = new uint256[](numSamples);
|
|
if (makerToken != psm.dai() && takerToken != psm.dai()) {
|
|
return takerTokenAmounts;
|
|
}
|
|
|
|
for (uint256 i = 0; i < numSamples; i++) {
|
|
uint256 sellAmount = _samplePSMBuy(psmInfo, makerToken, takerToken, makerTokenAmounts[i], psm, vat);
|
|
|
|
if (sellAmount == 0) {
|
|
break;
|
|
}
|
|
|
|
takerTokenAmounts[i] = sellAmount;
|
|
}
|
|
|
|
}
|
|
|
|
function _samplePSMSell(MakerPsmInfo memory psmInfo, address makerToken, address takerToken, uint256 takerTokenAmount, IPSM psm, IVAT vat)
|
|
private
|
|
view
|
|
returns (uint256)
|
|
{
|
|
(uint256 totalDebtInWad,,, uint256 debtCeilingInRad, uint256 debtFloorInRad) = vat.ilks(psmInfo.ilkIdentifier);
|
|
uint256 gemTokenBaseUnit = uint256(1e6);
|
|
|
|
if (takerToken == psmInfo.gemTokenAddress) {
|
|
// Simulate sellGem
|
|
// Selling USDC to the PSM, increasing the total debt
|
|
// Convert USDC 6 decimals to 18 decimals [wad]
|
|
uint256 takerTokenAmountInWad = takerTokenAmount.safeMul(1e12);
|
|
|
|
uint256 newTotalDebtInRad = totalDebtInWad.safeAdd(takerTokenAmountInWad).safeMul(RAY);
|
|
|
|
// PSM is too full to fit
|
|
if (newTotalDebtInRad >= debtCeilingInRad) {
|
|
return 0;
|
|
}
|
|
|
|
uint256 feeInWad = takerTokenAmountInWad.safeMul(psm.tin()).safeDiv(WAD);
|
|
uint256 makerTokenAmountInWad = takerTokenAmountInWad.safeSub(feeInWad);
|
|
|
|
return makerTokenAmountInWad;
|
|
} else if (makerToken == psmInfo.gemTokenAddress) {
|
|
// Simulate buyGem
|
|
// Buying USDC from the PSM, decreasing the total debt
|
|
// Selling DAI for USDC, already in 18 decimals [wad]
|
|
uint256 takerTokenAmountInWad = takerTokenAmount;
|
|
if (takerTokenAmountInWad > totalDebtInWad) {
|
|
return 0;
|
|
}
|
|
uint256 newTotalDebtInRad = totalDebtInWad.safeSub(takerTokenAmountInWad).safeMul(RAY);
|
|
|
|
// PSM is empty, not enough USDC to buy from it
|
|
if (newTotalDebtInRad <= debtFloorInRad) {
|
|
return 0;
|
|
}
|
|
|
|
uint256 feeDivisorInWad = WAD.safeAdd(psm.tout()); // eg. 1.001 * 10 ** 18 with 0.1% tout;
|
|
uint256 makerTokenAmountInGemTokenBaseUnits = takerTokenAmountInWad.safeMul(gemTokenBaseUnit).safeDiv(feeDivisorInWad);
|
|
|
|
return makerTokenAmountInGemTokenBaseUnits;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
function _samplePSMBuy(MakerPsmInfo memory psmInfo, address makerToken, address takerToken, uint256 makerTokenAmount, IPSM psm, IVAT vat)
|
|
private
|
|
view
|
|
returns (uint256)
|
|
{
|
|
(uint256 totalDebtInWad,,, uint256 debtCeilingInRad, uint256 debtFloorInRad) = vat.ilks(psmInfo.ilkIdentifier);
|
|
|
|
if (takerToken == psmInfo.gemTokenAddress) {
|
|
// Simulate sellGem
|
|
// Selling USDC to the PSM, increasing the total debt
|
|
uint256 makerTokenAmountInWad = makerTokenAmount;
|
|
uint256 feeDivisorInWad = WAD.safeSub(psm.tin()); // eg. 0.999 * 10 ** 18 with 0.1% tin;
|
|
uint256 takerTokenAmountInWad = makerTokenAmountInWad.safeMul(WAD).safeDiv(feeDivisorInWad);
|
|
uint256 newTotalDebtInRad = totalDebtInWad.safeAdd(takerTokenAmountInWad).safeMul(RAY);
|
|
|
|
// PSM is too full to fit
|
|
if (newTotalDebtInRad >= debtCeilingInRad) {
|
|
return 0;
|
|
}
|
|
|
|
uint256 takerTokenAmountInGemInGemBaseUnits = (takerTokenAmountInWad.safeDiv(1e12)).safeAdd(1); // Add 1 to deal with cut off decimals converting to lower decimals
|
|
|
|
return takerTokenAmountInGemInGemBaseUnits;
|
|
} else if (makerToken == psmInfo.gemTokenAddress) {
|
|
// Simulate buyGem
|
|
// Buying USDC from the PSM, decreasing the total debt
|
|
uint256 makerTokenAmountInWad = makerTokenAmount.safeMul(1e12);
|
|
uint256 feeMultiplierInWad = WAD.safeAdd(psm.tout()); // eg. 1.001 * 10 ** 18 with 0.1% tout;
|
|
uint256 takerTokenAmountInWad = makerTokenAmountInWad.safeMul(feeMultiplierInWad).safeDiv(WAD);
|
|
if (takerTokenAmountInWad > totalDebtInWad) {
|
|
return 0;
|
|
}
|
|
uint256 newTotalDebtInRad = totalDebtInWad.safeSub(takerTokenAmountInWad).safeMul(RAY);
|
|
|
|
// PSM is empty, not enough USDC to buy
|
|
if (newTotalDebtInRad <= debtFloorInRad) {
|
|
return 0;
|
|
}
|
|
|
|
|
|
return takerTokenAmountInWad;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
}
|