Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

B.Protocol support #5

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions contracts/Comptroller.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import "./Governance/Comp.sol";
* @title Compound's Comptroller Contract
* @author Compound
*/
contract Comptroller is ComptrollerV5Storage, ComptrollerInterface, ComptrollerErrorReporter, ExponentialNoError {
contract Comptroller is ComptrollerV6Storage, ComptrollerInterface, ComptrollerErrorReporter, ExponentialNoError {
/// @notice Emitted when an admin supports a market
event MarketListed(CToken cToken);

Expand Down Expand Up @@ -64,6 +64,9 @@ contract Comptroller is ComptrollerV5Storage, ComptrollerInterface, ComptrollerE
/// @notice Emitted when COMP is granted by admin
event CompGranted(address recipient, uint amount);

/// @notice Emitted when B.Protocol is changed
event NewBProtocol(address indexed cToken, address oldBProtocol, address newBProtocol);

/// @notice The initial COMP index for a market
uint224 public constant compInitialIndex = 1e36;

Expand Down Expand Up @@ -466,8 +469,6 @@ contract Comptroller is ComptrollerV5Storage, ComptrollerInterface, ComptrollerE
address liquidator,
address borrower,
uint repayAmount) external returns (uint) {
// Shh - currently unused
liquidator;

if (!markets[cTokenBorrowed].isListed || !markets[cTokenCollateral].isListed) {
return uint(Error.MARKET_NOT_LISTED);
Expand Down Expand Up @@ -495,6 +496,13 @@ contract Comptroller is ComptrollerV5Storage, ComptrollerInterface, ComptrollerE
return uint(Error.TOO_MUCH_REPAY);
}
}

/* Only B.Protocol can liquidate */
address bLiquidator = bprotocol[address(cTokenBorrowed)];
if(bLiquidator != address(0) && IBProtocol(bLiquidator).canLiquidate(cTokenBorrowed, cTokenCollateral, repayAmount)) {
require(liquidator == bLiquidator, "only B.Protocol can liquidate");
}

return uint(Error.NO_ERROR);
}

Expand Down Expand Up @@ -1317,6 +1325,15 @@ contract Comptroller is ComptrollerV5Storage, ComptrollerInterface, ComptrollerE
emit ContributorCompSpeedUpdated(contributor, compSpeed);
}

function _setBProtocol(address cToken, address newBProtocol) public returns (uint) {
require(adminOrInitializing(), "only admin can set B.Protocol");

emit NewBProtocol(cToken, bprotocol[cToken], newBProtocol);
bprotocol[cToken] = newBProtocol;

return uint(Error.NO_ERROR);
}

/**
* @notice Return all of the markets
* @dev The automatic getter may be used to access an individual market.
Expand Down
6 changes: 6 additions & 0 deletions contracts/ComptrollerStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pragma solidity ^0.5.16;

import "./CToken.sol";
import "./PriceOracle.sol";
import "./IBProtocol.sol";

contract UnitrollerAdminStorage {
/**
Expand Down Expand Up @@ -143,3 +144,8 @@ contract ComptrollerV5Storage is ComptrollerV4Storage {
/// @notice Last block at which a contributor's COMP rewards have been allocated
mapping(address => uint) public lastContributorBlock;
}

contract ComptrollerV6Storage is ComptrollerV5Storage {
/// @notice CToken => IBProtocol (per CToken debt)
mapping(address => address) public bprotocol;
}
14 changes: 14 additions & 0 deletions contracts/IBProtocol.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pragma solidity ^0.5.16;

interface IBProtocol {
function canLiquidate(
address cTokenBorrowed,
address cTokenCollateral,
uint repayAmount
)
external
view
returns(bool);
}


21 changes: 21 additions & 0 deletions tests/Contracts/MockBProtocol.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
pragma solidity ^0.5.16;

contract MockBProtocol {
bool val;

function setVal(bool _val) external {
val = _val;
}

function canLiquidate(
address /* cTokenBorrowed */,
address /* cTokenCollateral */,
uint /* repayAmount */
)
external
view
returns(bool) {
return val;
}
}

78 changes: 78 additions & 0 deletions tests/Tokens/liquidateTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,3 +312,81 @@ describe('Comptroller', () => {
await expect(send(comptroller, 'liquidateBorrowAllowed', [cTokenBorrow._address, cTokenCollat._address, liquidator, borrower, borrowAmount * 2])).rejects.toRevert('revert Can not repay more than the total borrow');
});
})

describe('B.Protocol', () => {
it('set B.Protocol', async () => {
let [root, liquidator, borrower] = saddle.accounts;

const bprotocol1 = await deploy('MockBProtocol', []);
const bprotocol2 = await deploy('MockBProtocol', []);

const cTokenCollat = await makeCToken({supportMarket: true, underlyingPrice: 1, collateralFactor: .5});
const cTokenBorrow = await makeCToken({supportMarket: true, underlyingPrice: 1, comptroller: cTokenCollat.comptroller});
const comptroller = cTokenCollat.comptroller;

expect(await send(comptroller, '_setBProtocol', [cTokenBorrow._address, bprotocol1._address])).toSucceed();
await expect(send(comptroller, '_setBProtocol', [cTokenBorrow._address, bprotocol2._address], {from: borrower})).rejects.toRevert('revert only admin can set B.Protocol');
});

it('liquidateBorrowAllowed', async () => {
// setup liquidation state
let [root, liquidator, borrower] = saddle.accounts;
let collatAmount = 10;
let borrowAmount = 2;
const cTokenCollat = await makeCToken({supportMarket: true, underlyingPrice: 1, collateralFactor: .5});
const cTokenBorrow = await makeCToken({supportMarket: true, underlyingPrice: 1, comptroller: cTokenCollat.comptroller});
const comptroller = cTokenCollat.comptroller;

// borrow some tokens
await send(cTokenCollat.underlying, 'harnessSetBalance', [borrower, collatAmount]);
await send(cTokenCollat.underlying, 'approve', [cTokenCollat._address, collatAmount], {from: borrower});
await send(cTokenBorrow.underlying, 'harnessSetBalance', [cTokenBorrow._address, collatAmount]);
await send(cTokenBorrow, 'harnessSetTotalSupply', [collatAmount * 10]);
await send(cTokenBorrow, 'harnessSetExchangeRate', [etherExp(1)]);
expect(await enterMarkets([cTokenCollat], borrower)).toSucceed();
expect(await send(cTokenCollat, 'mint', [collatAmount], {from: borrower})).toSucceed();
expect(await send(cTokenBorrow, 'borrow', [borrowAmount], {from: borrower})).toSucceed();

// show the account is healthy
expect(await call(comptroller, 'isDeprecated', [cTokenBorrow._address])).toEqual(false);
expect(await call(comptroller, 'liquidateBorrowAllowed', [cTokenBorrow._address, cTokenCollat._address, liquidator, borrower, borrowAmount])).toHaveTrollError('INSUFFICIENT_SHORTFALL');

// show deprecating a market works
expect(await send(comptroller, '_setCollateralFactor', [cTokenBorrow._address, 0])).toSucceed();
expect(await send(comptroller, '_setBorrowPaused', [cTokenBorrow._address, true])).toSucceed();
expect(await send(cTokenBorrow, '_setReserveFactor', [etherMantissa(1)])).toSucceed();

expect(await call(comptroller, 'isDeprecated', [cTokenBorrow._address])).toEqual(true);

// show deprecated markets can be liquidated even if healthy
expect(await send(comptroller, 'liquidateBorrowAllowed', [cTokenBorrow._address, cTokenCollat._address, liquidator, borrower, borrowAmount])).toSucceed();

// with b.protocol
const bprotocol1 = await deploy('MockBProtocol', []);

// set b.protocol for that token
expect(await send(comptroller, '_setBProtocol', [cTokenBorrow._address, bprotocol1._address])).toSucceed();

// check that other users can liquidate, because can liquidate returns false
expect(await send(comptroller, 'liquidateBorrowAllowed', [cTokenBorrow._address, cTokenCollat._address, liquidator, borrower, borrowAmount])).toSucceed();

// set can liquidate to true
expect(await send(bprotocol1, 'setVal', [true])).toSucceed();

// check that other users cannot liquidate
await expect(send(comptroller, 'liquidateBorrowAllowed', [cTokenBorrow._address, cTokenCollat._address, liquidator, borrower, borrowAmount])).rejects.toRevert('revert only B.Protocol can liquidate');

// check that bprotocol can liquidate
expect(await send(comptroller, 'liquidateBorrowAllowed', [cTokenBorrow._address, cTokenCollat._address, bprotocol1._address, borrower, borrowAmount])).toSucceed();

// reset bprotocol and show now everyone can liquidate
expect(await send(comptroller, '_setBProtocol', [cTokenBorrow._address, "0x0000000000000000000000000000000000000000"])).toSucceed();
expect(await send(comptroller, 'liquidateBorrowAllowed', [cTokenBorrow._address, cTokenCollat._address, liquidator, borrower, borrowAmount])).toSucceed();

// set another b.protocol to a different token and show it does not have an affect on the other ctoken
const bprotocol2 = await deploy('MockBProtocol', []);
expect(await send(bprotocol2, 'setVal', [true])).toSucceed();
expect(await send(comptroller, '_setBProtocol', [cTokenCollat._address, bprotocol2._address])).toSucceed();
expect(await send(comptroller, 'liquidateBorrowAllowed', [cTokenBorrow._address, cTokenCollat._address, liquidator, borrower, borrowAmount])).toSucceed();
});
})