Skip to content

Commit

Permalink
Fix allocation strategy (#39)
Browse files Browse the repository at this point in the history
* bug fix #38: add IAS.redeemAll function

In order to switch allocatraion strategy and drain all funds from
the old startegy, IAS.redeemAll is a more clean way of achieving it.

- test case improved wrt allocation strategy switching

also deployed new allocation strategy

- deployed new allocation strategy
- switched to the new allocation strategy
- deployed the new logic contract
  • Loading branch information
hellwolf authored Dec 22, 2019
1 parent ddb6e58 commit 811bbc7
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 58 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,8 +408,8 @@ Testing on rinkeby has been deprecated.
| Contract | Info | Address |
|---------------------|-------------------------------|-----------------------------------------------------------------------------------------------------------------------|
| rDAI proxy | Use this address in your dapp | [0x261b45D85cCFeAbb11F022eBa346ee8D1cd488c0](https://etherscan.io/address/0x261b45d85ccfeabb11f022eba346ee8d1cd488c0) |
| rToken logic | Version 1.0.1-rc2 | [0x806a196872Beff0CDE5663cbc1d8962309444932](https://etherscan.io/address/0x806a196872beff0cde5663cbc1d8962309444932) |
| Allocation Strategy | Compound | [0xd0810DeE68dD9aEAfc2dBC2A6f53C3809A2E6578](https://etherscan.io/address/0xd0810dee68dd9aeafc2dbc2a6f53c3809a2e6578) |
| rToken logic | Version 1.0.1-rc2 | [0x56b481bA9f338144Fa40C84fb0F4C87B9f4d6dFE](https://etherscan.io/address/0x56b481bA9f338144Fa40C84fb0F4C87B9f4d6dFE) |
| Allocation Strategy | Compound | [0xbB16307aaed1e070B3C4465d4FDa5E518bDc2433](https://etherscan.io/address/0xbB16307aaed1e070B3C4465d4FDa5E518bDc2433) |
| Underlying token | DAI | [0x6B175474E89094C44Da98b954EedeAC495271d0F](https://etherscan.io/address/0x6B175474E89094C44Da98b954EedeAC495271d0F) |
| Allocation token | cDAI | [0x5d3a536e4d6dbd6114cc1ead35777bab948e3643](https://etherscan.io/address/0x5d3a536e4d6dbd6114cc1ead35777bab948e3643) |

Expand Down
11 changes: 10 additions & 1 deletion contracts/CompoundAllocationStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ contract CompoundAllocationStrategy is IAllocationStrategy, Ownable {
function redeemUnderlying(uint256 redeemAmount) external onlyOwner returns (uint256) {
uint256 cTotalBefore = cToken.totalSupply();
// TODO should we handle redeem failure?
require(cToken.redeemUnderlying(redeemAmount) == 0, "redeemUnderlying failed");
require(cToken.redeemUnderlying(redeemAmount) == 0, "cToken.redeemUnderlying failed");
uint256 cTotalAfter = cToken.totalSupply();
uint256 cBurnedAmount;
require(cTotalAfter <= cTotalBefore, "Compound redeemed negative amount!?");
Expand All @@ -57,4 +57,13 @@ contract CompoundAllocationStrategy is IAllocationStrategy, Ownable {
return cBurnedAmount;
}

/// @dev ISavingStrategy.redeemAll implementation
function redeemAll() external onlyOwner
returns (uint256 savingsAmount, uint256 underlyingAmount) {
savingsAmount = cToken.balanceOf(address(this));
require(cToken.redeem(savingsAmount) == 0, "cToken.redeem failed");
underlyingAmount = token.balanceOf(address(this));
token.transfer(msg.sender, underlyingAmount);
}

}
10 changes: 9 additions & 1 deletion contracts/IAllocationStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.5.8;
* @notice Allocation strategy for assets.
* - It invests the underlying assets into some yield generating contracts,
* usually lending contracts, in return it gets new assets aka. saving assets.
* - Sainv assets can be redeemed back to the underlying assets plus interest any time.
* - Savings assets can be redeemed back to the underlying assets plus interest any time.
*/
interface IAllocationStrategy {

Expand Down Expand Up @@ -48,4 +48,12 @@ interface IAllocationStrategy {
*/
function redeemUnderlying(uint256 redeemAmount) external returns (uint256);

/**
* @notice Owner redeems all saving assets
* @dev Interst shall be accrued
* @return uint256 savingsAmount Amount of savings redeemed
* @return uint256 underlyingAmount Amount of underlying redeemed
*/
function redeemAll() external returns (uint256 savingsAmount, uint256 underlyingAmount);

}
18 changes: 15 additions & 3 deletions contracts/RToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -426,10 +426,22 @@ contract RToken is
IAllocationStrategy oldIas = ias;
ias = allocationStrategy;
// redeem everything from the old strategy
uint256 sOriginalBurned = oldIas.redeemUnderlying(totalSupply);
(uint256 sOriginalBurned, ) = oldIas.redeemAll();
uint256 totalAmount = token.balanceOf(address(this));
// invest everything into the new strategy
require(token.approve(address(ias), totalSupply), "token approve failed");
uint256 sOriginalCreated = ias.investUnderlying(totalSupply);
require(token.approve(address(ias), totalAmount), "token approve failed");
uint256 sOriginalCreated = ias.investUnderlying(totalAmount);

// give back the ownership of the old allocation strategy to the admin
// unless we are simply switching to the same allocaiton Strategy
//
// - But why would we switch to the same allocation strategy?
// - This is a special case where one could pick up the unsoliciated
// savings from the allocation srategy contract as extra "interest"
// for all rToken holders.
if (address(ias) != address(oldIas)) {
Ownable(address(oldIas)).transferOwnership(address(owner()));
}

// calculate new saving asset conversion rate
//
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rtoken/contracts",
"version": "1.0.1-rc3",
"version": "1.0.1-rc4",
"description": "RToken Ethereum contracts",
"license": "MIT",
"dependencies": {
Expand Down
6 changes: 1 addition & 5 deletions scripts/deploy-rdai-logic.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ module.exports = async function (callback) {

const { web3tx } = require("@decentral.ee/web3-test-helpers");
const RToken = artifacts.require("rDAI");
const rDaiLogic = await web3tx(RToken.new, "RToken.new")(
{
gas: 5000000,
}
);
const rDaiLogic = await web3tx(RToken.new, "RToken.new")();
console.log("rDaiLogic deployed at: ", rDaiLogic.address);

callback();
Expand Down
16 changes: 4 additions & 12 deletions scripts/deploy-rdai.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,15 @@ module.exports = async function (callback) {
const RDAI = artifacts.require("rDAI");
const Proxy = artifacts.require("Proxy");

const addresses = await require("./addresses")[network];
const addresses = require("./addresses")[network];

let compoundASAddress = await promisify(rl.question)("Specify a deployed CompoundAllocationStrategy (deploy a new one if blank): ");
let compoundAS;
if (!compoundASAddress) {
compoundAS = await web3tx(
CompoundAllocationStrategy.new,
`CompoundAllocationStrategy.new cDAI ${addresses.cDAI}`)(
addresses.cDAI, {
gas: 1000000,
}
addresses.cDAI
);
console.log("compoundAllocationStrategy deployed at: ", compoundAS.address);
} else {
Expand All @@ -46,11 +44,7 @@ module.exports = async function (callback) {
let rDAIAddress = await promisify(rl.question)("Specify a deployed rDAI (deploy a new one if blank): ");
let rDAI;
if (!rDAIAddress) {
rDAI = await web3tx(RDAI.new, "rDAI.new")(
{
gas: 5000000,
}
);
rDAI = await web3tx(RDAI.new, "rDAI.new")();
console.log("rDAI deployed at: ", rDAI.address);
} else {
rDAI = await RDAI.at(rDAIAddress);
Expand All @@ -59,9 +53,7 @@ module.exports = async function (callback) {
const rDaiConstructCode = rDAI.contract.methods.initialize(compoundAS.address).encodeABI();
console.log(`rDaiConstructCode rDAI.initialize(${rDaiConstructCode})`);
const proxy = await web3tx(Proxy.new, "Proxy.new")(
rDaiConstructCode, rDAI.address, {
gas: 1000000,
}
rDaiConstructCode, rDAI.address
);
console.log("proxy deployed at: ", proxy.address);

Expand Down
6 changes: 1 addition & 5 deletions scripts/deploy-rsai-logic.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ module.exports = async function (callback) {

const { web3tx } = require("@decentral.ee/web3-test-helpers");
const rSAI = artifacts.require("rSAI");
const rSAILogic = await web3tx(rSAI.new, "rSAI.new")(
{
gas: 5000000,
}
);
const rSAILogic = await web3tx(rSAI.new, "rSAI.new")();
console.log("rSAILogic deployed at: ", rSAILogic.address);

callback();
Expand Down
155 changes: 127 additions & 28 deletions test/RToken.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1747,84 +1747,183 @@ contract("RToken", accounts => {
it("#22 Change allocation strategy multiple times", async () => {
let cToken2, compoundAS2, cToken3, compoundAS3;
{
// from 0.1 to 0.01
const result = await createCompoundAllocationStrategy(toWad(.01));
// from 0.1 (AS1) to 0.01 (AS2)
const result = await createCompoundAllocationStrategy(toWad(".01"));
cToken2 = result.cToken;
compoundAS2 = result.compoundAS;
}
{
// from 0.01 to 10
const result = await createCompoundAllocationStrategy(toWad(10));
// from 0.01 (AS2) to 10 (AS3)
const result = await createCompoundAllocationStrategy(toWad("10"));
cToken3 = result.cToken;
compoundAS3 = result.compoundAS;
}
await web3tx(compoundAS2.transferOwnership, "compoundAS2.transferOwnership")(rToken.address);
await web3tx(compoundAS3.transferOwnership, "compoundAS3.transferOwnership")(rToken.address);

// create some reserve in the cToken pool for lending
await web3tx(token.transfer, "token.transfer 100 from customer 1 to customer 2")(
customer2, toWad(100), {
from: customer1
}
);
await web3tx(token.approve, "token.approve 100 by customer2")(cToken.address, toWad(100), {
from: customer2
});
await web3tx(cToken.mint, "cToken.mint 100 to customer2")(toWad(100), {
from: customer2
});
assert.equal(wad4human(await token.balanceOf.call(customer1)), "900.00000");
assert.equal(wad4human(await token.balanceOf.call(cToken.address)), "100.00000");
assert.equal(wad4human(await cToken.balanceOfUnderlying.call(compoundAS.address)), "0.00000");
assert.equal(wad4human(await cToken.balanceOfUnderlying.call(customer2)), "100.00000");
assert.equal(toWad(0)
.add(await token.balanceOf.call(customer1))
.add(await token.balanceOf.call(cToken.address))
.toString(), toWad(1000));
assert.equal(wad4human(await rToken.balanceOf.call(customer1)), "0.00000");

// mint 100 rToken
await web3tx(token.approve, "token.approve 100 by customer1")(rToken.address, toWad(100), {
from: customer1
});
await web3tx(rToken.mint, "rToken.mint 100 to customer1")(
toWad(100), {
toWad("100"), {
from: customer1
});
assert.equal(wad4human(await token.balanceOf.call(customer1)), "900.00000");
assert.equal(wad4human(await token.balanceOf.call(cToken.address)), "100.00000");
assert.equal(wad4human(await cToken.balanceOf.call(compoundAS.address)), "1000.00000");
assert.equal(wad4human(await token.balanceOf.call(customer1)), "800.00000");
assert.equal(wad4human(await token.balanceOf.call(cToken.address)), "200.00000");
assert.equal(wad4human(await cToken.balanceOfUnderlying.call(compoundAS.address)), "100.00000");
assert.equal(wad4human(await cToken.balanceOfUnderlying.call(customer2)), "100.00000");
assert.equal(toWad(0)
.add(await token.balanceOf.call(customer1))
.add(await token.balanceOf.call(cToken.address))
.toString(), toWad(1000));
assert.equal(wad4human(await rToken.balanceOf.call(customer1)), "100.00000");
assert.equal(wad4human(await rToken.receivedSavingsOf.call(customer1)), "100.00000");

await doBingeBorrowing();

// trivial case: transfer the the same allocation strategy
assert.equal((await rToken.savingAssetConversionRate.call()).toString(), toWad(1).toString());
await web3tx(rToken.changeAllocationStrategy,
"change to the same allocation strategy", {
inLogs: [{
name: "AllocationStrategyChanged",
args: {
strategy: compoundAS.address,
conversionRate: toWad(1)
}
}]
})(
compoundAS.address, {
from: admin
});
assert.equal((await rToken.savingAssetConversionRate.call()).toString(), toWad(1).toString());
assert.equal(wad4human(await token.balanceOf.call(bingeBorrower)), "10.00000");
assert.equal(wad4human(await token.balanceOf.call(customer1)), "800.00000");
assert.equal(wad4human(await token.balanceOf.call(cToken.address)), "190.00000");
assert.equal(wad4human(await cToken.balanceOfUnderlying.call(compoundAS.address)), "100.00051");
assert.equal(wad4human(await cToken.balanceOfUnderlying.call(customer2)), "100.00051");
assert.equal(toWad(0)
.add(await token.balanceOf.call(bingeBorrower))
.add(await token.balanceOf.call(customer1))
.add(await token.balanceOf.call(cToken.address))
.toString(), toWad(1000));
assert.equal(wad4human(await rToken.balanceOf.call(customer1)), "100.00000");
assert.equal(wad4human(await rToken.receivedSavingsOf.call(customer1)), "100.00051");

await web3tx(rToken.changeAllocationStrategy,
"change allocation strategy 1st time", {
inLogs: [{
name: "AllocationStrategyChanged",
args: {
strategy: compoundAS2.address,
conversionRate: "99999999999999995"
conversionRate: toWad(".099999490001595991")
}
}]
})(
compoundAS2.address, {
from: admin
});
assert.equal(wad4human(await token.balanceOf.call(cToken.address)), "0.00000");
assert.equal(wad4human(await cToken.balanceOf.call(compoundAS.address)), "0.00000");
assert.equal(wad4human(await token.balanceOf.call(cToken2.address)), "100.00000");
assert.equal(wad4human(await cToken2.balanceOf.call(compoundAS2.address)), "10000.00000");
assert.equal(wad4human(await token.balanceOf.call(customer1)), "900.00000");
assert.equal(wad4human(await token.balanceOf.call(bingeBorrower)), "10.00000");
assert.equal(wad4human(await token.balanceOf.call(customer1)), "800.00000");
assert.equal(wad4human(await token.balanceOf.call(cToken.address)), "89.99949");
assert.equal((await cToken.balanceOfUnderlying.call(compoundAS.address)).toString(), "0");
assert.equal(wad4human(await cToken.balanceOfUnderlying.call(customer2)), "100.00051");
assert.equal(wad4human(await token.balanceOf.call(cToken2.address)), "100.00051");
assert.equal(wad4human(await cToken2.balanceOfUnderlying.call(compoundAS2.address)), "100.00051");
assert.equal(toWad(0)
.add(await token.balanceOf.call(bingeBorrower))
.add(await token.balanceOf.call(customer1))
.add(await token.balanceOf.call(cToken.address))
.add(await token.balanceOf.call(cToken2.address))
.toString(), toWad(1000));
assert.equal(wad4human(await rToken.balanceOf.call(customer1)), "100.00000");
assert.equal(wad4human(await rToken.receivedSavingsOf.call(customer1)), "100.00000");
assert.equal(wad4human(await rToken.receivedSavingsOf.call(customer1)), "100.00051");

await web3tx(rToken.changeAllocationStrategy,
"change allocation strategy 2nd time", {
inLogs: [{
name: "AllocationStrategyChanged",
args: {
strategy: compoundAS3.address,
conversionRate: "99999999999999995000"
conversionRate: toWad("99.999490001595991007")
}
}]
})(
compoundAS3.address, {
from: admin
});
assert.equal(wad4human(await token.balanceOf.call(cToken2.address)), "0.00000");
assert.equal(wad4human(await cToken2.balanceOf.call(compoundAS2.address)), "0.00000");
assert.equal(wad4human(await token.balanceOf.call(cToken3.address)), "100.00000");
assert.equal(wad4human(await cToken3.balanceOf.call(compoundAS3.address)), "10.00000");
assert.equal(wad4human(await token.balanceOf.call(customer1)), "900.00000");
assert.equal(wad4human(await token.balanceOf.call(bingeBorrower)), "10.00000");
assert.equal(wad4human(await token.balanceOf.call(customer1)), "800.00000");
assert.equal(wad4human(await token.balanceOf.call(cToken.address)), "89.99949");
assert.equal(wad4human(await token.balanceOf.call(cToken3.address)), "100.00051");
assert.equal((await cToken.balanceOfUnderlying.call(compoundAS.address)).toString(), "0");
assert.equal((await cToken2.balanceOfUnderlying.call(compoundAS2.address)).toString(), "0");
assert.equal(wad4human(await cToken3.balanceOfUnderlying.call(compoundAS3.address)), "100.00051");
assert.equal(toWad(0)
.add(await token.balanceOf.call(bingeBorrower))
.add(await token.balanceOf.call(customer1))
.add(await token.balanceOf.call(cToken.address))
.add(await token.balanceOf.call(cToken3.address))
.toString(), toWad(1000));
assert.equal(wad4human(await rToken.balanceOf.call(customer1)), "100.00000");
assert.equal(wad4human(await rToken.receivedSavingsOf.call(customer1)), "100.00000");
assert.equal(wad4human(await rToken.receivedSavingsOf.call(customer1)), "100.00051");

await web3tx(rToken.redeem, "rToken.redeem 10 to customer1")(
toWad(100), {

// create some reserve in the cToken3 pool for lending in order to make redeemAll solvent
await web3tx(token.transfer, "token.transfer 100 from customer 1 to customer 2")(
customer2, toWad(100), {
from: customer1
});
assert.equal(wad4human(await token.balanceOf.call(cToken3.address)), "0.00000");
assert.equal(wad4human(await cToken3.balanceOf.call(compoundAS3.address)), "0.00000");
assert.equal(wad4human(await token.balanceOf.call(customer1)), "1000.00000");
}
);
await web3tx(token.approve, "token.approve 100 by customer2")(rToken.address, toWad(100), {
from: customer2
});
await web3tx(rToken.mint, "rToken.mint 100 to customer2")(toWad(100), {
from: customer2
});
await web3tx(rToken.redeemAll, "rToken.redeemAll to customer1")({
from: customer1
});
assert.equal(wad4human(await token.balanceOf.call(bingeBorrower)), "10.00000");
assert.equal(wad4human(await token.balanceOf.call(cToken.address)), "89.99949");
assert.equal(wad4human(await token.balanceOf.call(cToken3.address)), "100.00000");
assert.equal(wad4human(await token.balanceOf.call(customer1)), "800.00051");
assert.equal((await cToken.balanceOfUnderlying.call(compoundAS.address)).toString(), "0");
assert.equal((await cToken2.balanceOfUnderlying.call(compoundAS2.address)).toString(), "0");
assert.equal(wad4human(await cToken3.balanceOfUnderlying.call(compoundAS3.address)), "100.00000");
assert.equal(toWad(0)
.add(await token.balanceOf.call(bingeBorrower))
.add(await token.balanceOf.call(customer1))
.add(await token.balanceOf.call(cToken.address))
.add(await token.balanceOf.call(cToken3.address))
.toString(), toWad(1000));
assert.equal(wad4human(await rToken.balanceOf.call(customer1)), "0.00000");
assert.equal(wad4human(await rToken.balanceOf.call(customer2)), "100.00000");
assert.equal(wad4human(await rToken.receivedSavingsOf.call(customer1)), "0.00000");
assert.equal(wad4human(await rToken.receivedSavingsOf.call(customer2)), "100.00000");
});

it("#23 rToken change hat test", async () => {
Expand Down

0 comments on commit 811bbc7

Please sign in to comment.