MinakoKojima

Yet another Xoogler, MtF, Developer from Taipei Ethereum Meetup.

联合铸币合约

背景知识

目前 DeFi Yeild Framing a.k.a. 流动性挖矿非常火热,不过由于作为一个普通用户,实在感觉现在以太坊的 gas 费和几个月前相比实在是贵太多了。为了帮助农夫们节约手续费我开发了这个联合铸币合约,可以众筹 USDT,印成目标代币并自动进行抵押生息。合约没有经过审计,不过已经跑了一个星期,目前锁仓 3.5m 左右,应该不会出太大的问题。

产品页面:https://y3d.finance/y3d/mint/

因为 UI 比较看起来像是 scam,所以用的人不是很多。。。。


目前我们提供了两种联合铸币产品,USDT 铸 yyCrv,和 USDT 铸 yswUSD,使用方法就是 Deposit USDT 然后等后面有人点 mint() 的时候顺便帮你也 mint() 就行。后者是新出的 Curve 分叉,目前不仅 2.5x 加速,而且头两周还有 bonus,所以年化按照官网给出的数据有 100% - 600% 左右。(但是 DeFi 的高年化普遍都不会持续很久)

这里着重介绍一下 yyCrv 的原理。yCrv 是 Curve 平台针对 YFI 的 yearn 系列稳定币之间的流动性 Token,相比于稳定币本身,yCrv 还可以获取各种被动收入。简单来说 yyCrv 的被动收益包含三个部分:

  • yearn 中的理财收益
  • Curve 的交易手续费
  • Curve 的流动性挖矿

无论之后的行情朝着什么方向发展,看起来她都是目前最为稳定的一个理财组合,这也是为什么 y3d 首发 yyCrv 之间的交易对的原因,而且看起来刚刚上线的 yETH 的理财路径现在最后也是通向这里,也难怪为什么有人说 yCrv 才是真稳定币了。

一般说来,要想获得 yyCrv,首先需要获得 yCrv。最简单的方法是直接从 Uniswap Pool 中购买,但是 yCrv 的溢价很高,非常不划算。另一种方法是从 Curve 合约中进行铸币,但是因为目前以太坊的手续费整体偏高,使得后者的手续费目前通常达到 50 美金甚至 100 美金,不利于向散户推广。


因此,我们设计了联合铸币(United Mint)合约,希望可以解决这一问题。

如何使用

  • 存钱 Deposit(uint):将 USDT 存入合约,等待有人来铸币。
  • 铸币 Mint():将合约中所有的 USDT 铸成 yyCrv,执行这个方法的人通常我们称之为雷锋。
  • 取币 Claim():根据当前价格,计算我的 USDT 余额对应的 yyCrv。如果合约中的 yyCrv 余额足够,则取回这些 yyCrv。
  • 还原 Restore(uint):根据当前价格,计算我的 yyCrv 对应的 USDT。如果此时合约中 USDT 的余额足够,则用这笔 yyCrv 替换出 USDT。
  • 存钱取币 DepositAndClaim(uint):将 USDT 存入合约,并且如果没有人铸币,则自己主动铸币。

其中 DepositAndClaim(uint) 方法将会被后续出品的 yUSDT 合约频繁调用,因此散户可以经常搭便车(free rider)。

技术细节

数据结构

  • balance[]: 每个用户的 USDT 数。
  • mintedUSDT: 已参与的铸币的 USDT 数,需要单独存一下。
  • unminted_USDT(): 尚未参与铸币的 USDT 数,就是合约里 USDT 的余额。
  • minted_yyCrv(): 已铸币的 yyCrv 数,就是合约里 yyCrv 的余额。

方法

核心的几个方法:

    /**
     * @dev Deposit usdt or claim yyCrv directly if balance of yyCrv is sufficient
     */function deposit(uint256 input) external {
        require(input != 0, "Empty usdt");
        IUSDT(USDT).transferFrom(msg.sender, address(this), input);
        if (input > mintedUSDT) {
            setBalance(msg.sender, balanceOf(msg.sender).add(input));
            emit Deposit(msg.sender, input);
        } else {
            uint256 output = get_yyCrvFromUsdt(input);
            mintedUSDT = mintedUSDT.sub(input);
            IERC20(yyCrv).transfer(msg.sender, output);
            emit Claim(msg.sender, input, output);
        }
    }

    /**
     * @dev Mint all unminted_USDT into yyCrv
     */function mint() public {
        require(unminted_USDT() > 0, "Empty usdt");
        mintedUSDT = mintedUSDT.add(unminted_USDT());
        IyDeposit(yDeposit).add_liquidity([0, 0, unminted_USDT(), 0], 0);
        IyyCrv(yyCrv).stake(minted_yCRV());
    }

    /**
     * @dev Claim yyCrv back, if the balance is sufficient, execute mint()
     */function claim() public {
        uint256 input = balanceOf(msg.sender);
        require(input != 0, "You don't have USDT balance to withdraw");
        uint256 r; // requirement yCrvif (mintedUSDT == 0) {
            mint();
            r = get_yyCrvFromUsdt(input);
        } else {
            r = get_yyCrvFromUsdt(input);
            if (r > minted_yyCRV()) mint();
            r = get_yyCrvFromUsdt(input);
        }
        mintedUSDT = mintedUSDT.sub(input);        
        IERC20(yyCrv).transfer(msg.sender, r);
        setBalance(msg.sender, 0);
        emit Claim(msg.sender, input, r);
    }

    /**
     * @dev Try to claim unminted usdt by yyCrv if the balance is sufficient
     */function restore(uint input) external {
        require(input != 0, "Empty yyCrv");
        require(minted_yyCRV() != 0, "No yyCrv price at this moment");
        uint output = get_yyCrvFromUsdt(unminted_USDT());
        if (output < input) input = output;
        output = get_usdtFromYycrv(input);
        mintedUSDT = mintedUSDT.add(output);
        IERC20(yyCrv).transferFrom(msg.sender, address(this), input);
        IUSDT(USDT).transfer(msg.sender, output);
        emit Restore(msg.sender, input, output);
    }    

    /**
     * @dev Deposit usdt and claim yyCrv in any case
     */function depositAndClaim(uint256 input) external {
        require(input != 0, "Empty usdt");
        IUSDT(USDT).transferFrom(msg.sender, address(this), input);
        if (input > mintedUSDT) {
            mint();
        }
        uint256 output = get_yyCrvFromUsdt(input);
        mintedUSDT = mintedUSDT.sub(input);
        IERC20(yyCrv).transfer(msg.sender, output);
        emit Claim(msg.sender, input, output);
    }

首先 Deposit(), Claim() 看起来都挺直接的。这里先说一下铸币 Mint() 方法,铸币主要是调用 Curve yCrv 的铸币合约 的添加流动性 add_liquidity() 方法,类似 Uniswap 的 add_liquidity(),但是看起来它里面还要跟 yEarn 系列交互,所以里面换来换去,会非常烧 gas。细节可参考这个 铸币过程

yCrv 交易对合约 中相关的代码如下:

@public
@nonreentrant('lock')
def add_liquidity(uamounts: uint256[N_COINS], min_mint_amount: uint256):
    tethered: bool[N_COINS] = TETHERED
    amounts: uint256[N_COINS] = ZEROS

    for i in range(N_COINS):
        uamount: uint256 = uamounts[i]

        if uamount > 0:
            # Transfer the underlying coin from ownerif tethered[i]:
                USDT(self.underlying_coins[i]).transferFrom(
                    msg.sender, self, uamount)
            else:
                assert_modifiable(ERC20(self.underlying_coins[i])\
                    .transferFrom(msg.sender, self, uamount))

            # Mint if needed
            ERC20(self.underlying_coins[i]).approve(self.coins[i], uamount)
            yERC20(self.coins[i]).deposit(uamount)
            amounts[i] = yERC20(self.coins[i]).balanceOf(self)
            ERC20(self.coins[i]).approve(self.curve, amounts[i])

    Curve(self.curve).add_liquidity(amounts, min_mint_amount)

    tokens: uint256 = ERC20(self.token).balanceOf(self)
    assert_modifiable(ERC20(self.token).transfer(msg.sender, tokens))

然后再重点说一下销毁 restore(uint) 方法,这个方法可以利用其他人想进来铸币的人的余额,用当前合约中的铸币比例,反向兑出自己手里的 yyCrv,这样甚至可以豁免 yyCrv 中的 3% P3D 手续费,并且没有任何人因此蒙受损失,是我觉得比较有趣的一个设计。

最后理论上说两次铸币之间,价格可能是有波动的,我们取平均数,如果这个合约你都能从中套利,那我愿称你为最强。

發佈評論

看不過癮?

一鍵登入,即可加入全球最優質中文創作社區