SODA合约漏洞分析

soda是一个DEFI借贷的合约,其中的逻辑是这样的:用户超额抵押WETH然后贷出SOETH,抵押的WETH可以挖矿,SOETH则可以通过uniswap交易或者参与别的活动。

当用户抵押`WETH...

[soda][soda-git]是一个DEFI借贷的合约,其中的逻辑是这样的:用户超额抵押 `WETH`然后贷出 `SOETH`,抵押的 `WETH`可以挖矿,`SOETH`则可以通过uniswap交易或者参与别的活动。 当用户抵押 `WETH`后每日会产生一定的贷款利息,当贷出的 `SOETH`与总利息超过一个阈值时,别人就可以进行清算。清算就是别人把贷和利息还上,然后获得抵押人的抵押物,即 `WETH`。 比如,小明抵押了 `100 WETH`,贷出 `70 SOETH`,每日利息是0.05%,则每日需要付 `70 * 0.05% = 0.035 SOETH`. 假设平仓位是抵押额的0.9,则平仓位为 `100 * 0.9 = 90 SOETH`。 那么当n天后,`70 + n * 0.035 >= 90`时(计算可得 `n >= 572`),别人就可以清算,支付 `90 SOETH`以及5%抵押量的手续费,就可以拿走 `100 WETH`。最终需要支付 `95 SOETH`,获得 `100 WETH`。 我们整理一下思路: 对于借贷: 1. 用户抵押 `WETH`后,可以贷出70%的 `SOETH` 2. 用户的日息为贷出额度的0.05% 3. 用户的平仓位为90%的抵押量 4. 当贷出额度+总利息超过平仓位时,不再产生利息,并且可以被别人清算。 对于清算: 1. 需要还被清算者的贷款额度,即70%的 `SOETH` 2. 需要还清被清算者的利息 3. 需要出抵押值5%的手续费 4. 因此清算发起人,需要支付95%抵押量的 `SOETH`,并获取100%的抵押物 `WETH`,利润率为5% 而soda的问题就在于,清算时,平仓仓位由抵押量的90%,变成了贷出量的90%。也就是说贷出即爆仓。 还是以小明为例: 小明抵押了 `100 WETH`,贷出 `70 SOETH`,理论上小明的平仓位为 `100 * 0.9 = 90 SOETH`,但是由于合约的BUG,清算时,平仓价位变成了 `70 * 0.9 = 63 SOETH`。也就是说,下单后,别人就可以对他的贷进行平仓清算。而清算发起者需要支出的费用由三部分组成:70%的贷、利息、5%的手续费。而在短时间内,利息几乎为0,那么清算者一共只需要出 `75 SOETH`,而获取小明的 `100 WETH`。利润率为25%。 问题代码: ```js function collectDebt(uint256 _loanId) external override { ... // should be 'loanInfo[_loanId].lockedAmount' uint256 maximumLoan = loanInfo[_loanId].amount.mul(loanInfo[_loanId].maximumLTV).div(LTV_BASE); // You can collect only if the user defaults. require(loanTotal >= maximumLoan, "collectDebt: >="); ... } ``` 截止目前为止,依然有4310枚 `WETH`锁在合约内,分别是: - LoanId 16: `480 weth` - LoanId 92: `1100 weth` - LoanId 93: `270 weth` - LoanId 119: `1230 weth` 这些仓位暂时是安全的,因为 `SOETH`的挖出的总量才2156(被销毁了一部分?),流通在uniswap市场上的 `SOETH`只有500多枚。假如要清算 `270 WETH`,则至少要购入 `202 SOETH`,按照uniswap的计价规则,这样会导致 `SOETH`的价格剧烈攀升,而利用bug清算的利润率才25%左右。 目前soda已经更新了合约,但是合约生效需要48小时,所以还是存在一定的风险。 [soda-git]: https://github.com/soda-finance/soda-contracts

soda是一个DEFI借贷的合约,其中的逻辑是这样的:用户超额抵押 WETH然后贷出 SOETH,抵押的 WETH可以挖矿,SOETH则可以通过uniswap交易或者参与别的活动。

当用户抵押 WETH后每日会产生一定的贷款利息,当贷出的 SOETH与总利息超过一个阈值时,别人就可以进行清算。清算就是别人把贷和利息还上,然后获得抵押人的抵押物,即 WETH

比如,小明抵押了 100 WETH,贷出 70 SOETH,每日利息是0.05%,则每日需要付 70 * 0.05% = 0.035 SOETH. 假设平仓位是抵押额的0.9,则平仓位为 100 * 0.9 = 90 SOETH。 那么当n天后,70 + n * 0.035 >= 90时(计算可得 n >= 572),别人就可以清算,支付 90 SOETH以及5%抵押量的手续费,就可以拿走 100 WETH。最终需要支付 95 SOETH,获得 100 WETH

我们整理一下思路: 对于借贷:

  1. 用户抵押 WETH后,可以贷出70%的 SOETH
  2. 用户的日息为贷出额度的0.05%
  3. 用户的平仓位为90%的抵押量
  4. 当贷出额度+总利息超过平仓位时,不再产生利息,并且可以被别人清算。

对于清算:

  1. 需要还被清算者的贷款额度,即70%的 SOETH
  2. 需要还清被清算者的利息
  3. 需要出抵押值5%的手续费
  4. 因此清算发起人,需要支付95%抵押量的 SOETH,并获取100%的抵押物 WETH,利润率为5%

而soda的问题就在于,清算时,平仓仓位由抵押量的90%,变成了贷出量的90%。也就是说贷出即爆仓。

还是以小明为例: 小明抵押了 100 WETH,贷出 70 SOETH,理论上小明的平仓位为 100 * 0.9 = 90 SOETH,但是由于合约的BUG,清算时,平仓价位变成了 70 * 0.9 = 63 SOETH。也就是说,下单后,别人就可以对他的贷进行平仓清算。而清算发起者需要支出的费用由三部分组成:70%的贷、利息、5%的手续费。而在短时间内,利息几乎为0,那么清算者一共只需要出 75 SOETH,而获取小明的 100 WETH。利润率为25%。

问题代码:


function collectDebt(uint256 _loanId) external override {
    ...
    // should be 'loanInfo[_loanId].lockedAmount'
    uint256 maximumLoan = loanInfo[_loanId].amount.mul(loanInfo[_loanId].maximumLTV).div(LTV_BASE);

    // You can collect only if the user defaults.
    require(loanTotal >= maximumLoan, "collectDebt: >=");
    ...
}

截止目前为止,依然有4310枚 WETH锁在合约内,分别是:

  • LoanId 16: 480 weth
  • LoanId 92: 1100 weth
  • LoanId 93: 270 weth
  • LoanId 119: 1230 weth

这些仓位暂时是安全的,因为 SOETH的挖出的总量才2156(被销毁了一部分?),流通在uniswap市场上的 SOETH只有500多枚。假如要清算 270 WETH,则至少要购入 202 SOETH,按照uniswap的计价规则,这样会导致 SOETH的价格剧烈攀升,而利用bug清算的利润率才25%左右。

目前soda已经更新了合约,但是合约生效需要48小时,所以还是存在一定的风险。

区块链技术网。

  • 发表于 2020-09-22 00:11
  • 阅读 ( 779 )
  • 学分 ( 16 )
  • 分类:DApp

评论