Skip to content

Commit

Permalink
Merge pull request #81 from liquity/trove_ERC721
Browse files Browse the repository at this point in the history
contracts: Convert Troves to ERC721 NFTs
  • Loading branch information
bingen committed Apr 4, 2024
2 parents 6a755d0 + 58e8334 commit 5c9f2f6
Show file tree
Hide file tree
Showing 39 changed files with 3,793 additions and 3,240 deletions.
219 changes: 152 additions & 67 deletions contracts/src/BorrowerOperations.sol

Large diffs are not rendered by default.

24 changes: 12 additions & 12 deletions contracts/src/CollSurplusPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ contract CollSurplusPool is Ownable, CheckContract, ICollSurplusPool {
// deposited ether tracker
uint256 internal ETHBalance;
// Collateral surplus claimable by trove owners
mapping (address => uint) internal balances;
mapping (uint256 => uint) internal balances;

// --- Events ---

event BorrowerOperationsAddressChanged(address _newBorrowerOperationsAddress);
event TroveManagerAddressChanged(address _newTroveManagerAddress);
event ActivePoolAddressChanged(address _newActivePoolAddress);

event CollBalanceUpdated(address indexed _account, uint _newBalance);
event CollBalanceUpdated(uint256 indexed _troveId, uint _newBalance);
event EtherSent(address _to, uint _amount);

constructor(address _ETHAddress) {
Expand Down Expand Up @@ -70,29 +70,29 @@ contract CollSurplusPool is Ownable, CheckContract, ICollSurplusPool {
return ETHBalance;
}

function getCollateral(address _account) external view override returns (uint) {
return balances[_account];
function getCollateral(uint256 _troveId) external view override returns (uint) {
return balances[_troveId];
}

// --- Pool functionality ---

function accountSurplus(address _account, uint _amount) external override {
function accountSurplus(uint256 _troveId, uint _amount) external override {
_requireCallerIsTroveManager();

uint newAmount = balances[_account] + _amount;
balances[_account] = newAmount;
uint newAmount = balances[_troveId] + _amount;
balances[_troveId] = newAmount;
ETHBalance = ETHBalance + _amount;

emit CollBalanceUpdated(_account, newAmount);
emit CollBalanceUpdated(_troveId, newAmount);
}

function claimColl(address _account) external override {
function claimColl(address _account, uint256 _troveId) external override {
_requireCallerIsBorrowerOperations();
uint claimableColl = balances[_account];
uint claimableColl = balances[_troveId];
require(claimableColl > 0, "CollSurplusPool: No collateral available to claim");

balances[_account] = 0;
emit CollBalanceUpdated(_account, 0);
balances[_troveId] = 0;
emit CollBalanceUpdated(_troveId, 0);

ETHBalance = ETHBalance - claimableColl;
emit EtherSent(_account, claimableColl);
Expand Down
21 changes: 0 additions & 21 deletions contracts/src/Dependencies/LiquityMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,6 @@ pragma solidity 0.8.18;
library LiquityMath {
uint internal constant DECIMAL_PRECISION = 1e18;

/* Precision for Nominal ICR (independent of price). Rationale for the value:
*
* - Making it “too high” could lead to overflows.
* - Making it “too low” could lead to an ICR equal to zero, due to truncation from Solidity floor division.
*
* This value of 1e20 is chosen for safety: the NICR will only overflow for numerator > ~1e39 ETH,
* and will only truncate to 0 if the denominator is at least 1e20 times greater than the numerator.
*
*/
uint internal constant NICR_PRECISION = 1e20;

function _min(uint _a, uint _b) internal pure returns (uint) {
return (_a < _b) ? _a : _b;
}
Expand Down Expand Up @@ -85,16 +74,6 @@ library LiquityMath {
return (_a >= _b) ? _a - _b : _b - _a;
}

function _computeNominalCR(uint _coll, uint _debt) internal pure returns (uint) {
if (_debt > 0) {
return _coll * NICR_PRECISION / _debt;
}
// Return the maximal value for uint256 if the Trove has a debt of 0. Represents "infinite" CR.
else { // if (_debt == 0)
return 2**256 - 1;
}
}

function _computeCR(uint _coll, uint _debt, uint _price) internal pure returns (uint) {
if (_debt > 0) {
uint newCollRatio = _coll * _price / _debt;
Expand Down
112 changes: 15 additions & 97 deletions contracts/src/HintHelpers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "./Dependencies/LiquityBase.sol";
import "./Dependencies/Ownable.sol";
import "./Dependencies/CheckContract.sol";


contract HintHelpers is LiquityBase, Ownable, CheckContract {
string constant public NAME = "HintHelpers";

Expand Down Expand Up @@ -42,103 +43,28 @@ contract HintHelpers is LiquityBase, Ownable, CheckContract {

// --- Functions ---

/* getRedemptionHints() - Helper function for finding the right hints to pass to redeemCollateral().
*
* It simulates a redemption of `_boldamount` to figure out where the redemption sequence will start and what state the final Trove
* of the sequence will end up in.
*
* Returns three hints:
* - `firstRedemptionHint` is the address of the first Trove with ICR >= MCR (i.e. the first Trove that will be redeemed).
* - `partialRedemptionHintNICR` is the final nominal ICR of the last Trove of the sequence after being hit by partial redemption,
* or zero in case of no partial redemption.
* - `truncatedBoldamount` is the maximum amount that can be redeemed out of the the provided `_boldamount`. This can be lower than
* `_boldamount` when redeeming the full amount would leave the last Trove of the redemption sequence with less net debt than the
* minimum allowed value (i.e. MIN_NET_DEBT).
*
* The number of Troves to consider for redemption can be capped by passing a non-zero value as `_maxIterations`, while passing zero
* will leave it uncapped.
*/

function getRedemptionHints(
uint _boldamount,
uint _price,
uint _maxIterations
)
external
view
returns (
address firstRedemptionHint,
uint partialRedemptionHintNICR,
uint truncatedBoldamount
)
{
ISortedTroves sortedTrovesCached = sortedTroves;

uint remainingBold = _boldamount;
address currentTroveuser = sortedTrovesCached.getLast();

while (currentTroveuser != address(0) && troveManager.getCurrentICR(currentTroveuser, _price) < MCR) {
currentTroveuser = sortedTrovesCached.getPrev(currentTroveuser);
}

firstRedemptionHint = currentTroveuser;

if (_maxIterations == 0) {
_maxIterations = type(uint256).max;
}

while (currentTroveuser != address(0) && remainingBold > 0 && _maxIterations-- > 0) {
uint netBoldDebt = _getNetDebt(troveManager.getTroveDebt(currentTroveuser))
+ troveManager.getPendingBoldDebtReward(currentTroveuser);

if (netBoldDebt > remainingBold) {
if (netBoldDebt > MIN_NET_DEBT) {
uint maxRedeemableBold = LiquityMath._min(remainingBold, netBoldDebt - MIN_NET_DEBT);

uint ETH = troveManager.getTroveColl(currentTroveuser)
+ troveManager.getPendingETHReward(currentTroveuser);

uint newColl = ETH - maxRedeemableBold * DECIMAL_PRECISION / _price;
uint newDebt = netBoldDebt - maxRedeemableBold;

uint compositeDebt = _getCompositeDebt(newDebt);
partialRedemptionHintNICR = LiquityMath._computeNominalCR(newColl, compositeDebt);

remainingBold = remainingBold - maxRedeemableBold;
}
break;
} else {
remainingBold = remainingBold - netBoldDebt;
}

currentTroveuser = sortedTrovesCached.getPrev(currentTroveuser);
}

truncatedBoldamount = _boldamount - remainingBold;
}

/* getApproxHint() - return address of a Trove that is, on average, (length / numTrials) positions away in the
/* getApproxHint() - return id of a Trove that is, on average, (length / numTrials) positions away in the
sortedTroves list from the correct insert position of the Trove to be inserted.
Note: The output address is worst-case O(n) positions away from the correct insert position, however, the function
Note: The output id is worst-case O(n) positions away from the correct insert position, however, the function
is probabilistic. Input can be tuned to guarantee results to a high degree of confidence, e.g:
Submitting numTrials = k * sqrt(length), with k = 15 makes it very, very likely that the ouput address will
Submitting numTrials = k * sqrt(length), with k = 15 makes it very, very likely that the ouput id will
be <= sqrt(length) positions away from the correct insert position.
*/
function getApproxHint(uint _CR, uint _numTrials, uint _inputRandomSeed)
function getApproxHint(uint _interestRate, uint _numTrials, uint _inputRandomSeed)
external
view
returns (address hintAddress, uint diff, uint latestRandomSeed)
returns (uint256 hintId, uint diff, uint latestRandomSeed)
{
uint arrayLength = troveManager.getTroveOwnersCount();
uint arrayLength = troveManager.getTroveIdsCount();

if (arrayLength == 0) {
return (address(0), 0, _inputRandomSeed);
return (0, 0, _inputRandomSeed);
}

hintAddress = sortedTroves.getLast();
diff = LiquityMath._getAbsoluteDifference(_CR, troveManager.getNominalICR(hintAddress));
hintId = sortedTroves.getLast();
diff = LiquityMath._getAbsoluteDifference(_interestRate, troveManager.getTroveAnnualInterestRate(hintId));
latestRandomSeed = _inputRandomSeed;

uint i = 1;
Expand All @@ -147,25 +73,17 @@ contract HintHelpers is LiquityBase, Ownable, CheckContract {
latestRandomSeed = uint(keccak256(abi.encodePacked(latestRandomSeed)));

uint arrayIndex = latestRandomSeed % arrayLength;
address currentAddress = troveManager.getTroveFromTroveOwnersArray(arrayIndex);
uint currentNICR = troveManager.getNominalICR(currentAddress);
uint256 currentId = troveManager.getTroveFromTroveIdsArray(arrayIndex);
uint currentInterestRate = troveManager.getTroveAnnualInterestRate(currentId);

// check if abs(current - CR) > abs(closest - CR), and update closest if current is closer
uint currentDiff = LiquityMath._getAbsoluteDifference(currentNICR, _CR);
// check if abs(current - IR) > abs(closest - IR), and update closest if current is closer
uint currentDiff = LiquityMath._getAbsoluteDifference(currentInterestRate, _interestRate);

if (currentDiff < diff) {
diff = currentDiff;
hintAddress = currentAddress;
hintId = currentId;
}
i++;
}
}

function computeNominalCR(uint _coll, uint _debt) external pure returns (uint) {
return LiquityMath._computeNominalCR(_coll, _debt);
}

function computeCR(uint _coll, uint _debt, uint _price) external pure returns (uint) {
return LiquityMath._computeCR(_coll, _debt, _price);
}
}
25 changes: 15 additions & 10 deletions contracts/src/Interfaces/IBorrowerOperations.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,30 @@ interface IBorrowerOperations is ILiquityBase {
address _boldTokenAddress
) external;

function openTrove(uint _maxFee, uint256 _ETHAmount, uint _boldAmount, address _upperHint, address _lowerHint, uint256 _annualInterestRate) external;
function openTrove(address _owner, uint256 _ownerIndex, uint _maxFee, uint256 _ETHAmount, uint _boldAmount, uint256 _upperHint, uint256 _lowerHint, uint256 _annualInterestRate) external returns (uint256);

function addColl(uint256 _ETHAmount) external;
function addColl(uint256 _troveId, uint256 _ETHAmount) external;

function moveETHGainToTrove(address _user, uint256 _ETHAmount) external;
function moveETHGainToTrove(address _sender, uint256 _troveId, uint256 _ETHAmount) external;

function withdrawColl(uint _amount) external;
function withdrawColl(uint256 _troveId, uint _amount) external;

function withdrawBold(uint _maxFee, uint _amount) external;
function withdrawBold(uint256 _troveId, uint _maxFee, uint _amount) external;

function repayBold(uint _amount) external;
function repayBold(uint256 _troveId, uint _amount) external;

function closeTrove() external;
function closeTrove(uint256 _troveId) external;

function adjustTrove(uint _maxFee, uint _collChange, bool _isCollIncrease, uint _debtChange, bool isDebtIncrease) external;
function adjustTrove(uint256 _troveId, uint _maxFee, uint _collChange, bool _isCollIncrease, uint _debtChange, bool isDebtIncrease) external;

function claimCollateral() external;
function claimCollateral(uint256 _troveId) external;

function setAddManager(uint256 _troveId, address _manager) external;
function setRemoveManager(uint256 _troveId, address _manager) external;

// TODO: addRepayWhitelistedAddress?(see github issue #64)

function getCompositeDebt(uint _debt) external pure returns (uint);

function adjustTroveInterestRate(uint _newAnnualInterestRate, address _upperHint, address _lowerHint) external;
function adjustTroveInterestRate(uint256 _troveId, uint _newAnnualInterestRate, uint256 _upperHint, uint256 _lowerHint) external;
}
6 changes: 3 additions & 3 deletions contracts/src/Interfaces/ICollSurplusPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ interface ICollSurplusPool {

function getETHBalance() external view returns (uint);

function getCollateral(address _account) external view returns (uint);
function getCollateral(uint256 _troveId) external view returns (uint);

function accountSurplus(address _account, uint _amount) external;
function accountSurplus(uint256 _troveId, uint _amount) external;

function claimColl(address _account) external;
function claimColl(address _account, uint256 _troveId) external;
}
25 changes: 15 additions & 10 deletions contracts/src/Interfaces/ISortedTroves.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,25 @@ pragma solidity 0.8.18;

import "./ITroveManager.sol";

// TODO
//type Id is uint256;
//type Value is uint256;


// Common interface for the SortedTroves Doubly Linked List.
interface ISortedTroves {
function borrowerOperationsAddress() external view returns (address);
function troveManager() external view returns (ITroveManager);

function setParams(uint256 _size, address _TroveManagerAddress, address _borrowerOperationsAddress) external;

function insert(address _id, uint256 _ICR, address _prevId, address _nextId) external;
function insert(uint256 _id, uint256 _value, uint256 _prevId, uint256 _nextId) external;

function remove(address _id) external;
function remove(uint256 _id) external;

function reInsert(address _id, uint256 _newICR, address _prevId, address _nextId) external;
function reInsert(uint256 _id, uint256 _newValue, uint256 _prevId, uint256 _nextId) external;

function contains(address _id) external view returns (bool);
function contains(uint256 _id) external view returns (bool);

function isFull() external view returns (bool);

Expand All @@ -27,15 +32,15 @@ interface ISortedTroves {

function getMaxSize() external view returns (uint256);

function getFirst() external view returns (address);
function getFirst() external view returns (uint256);

function getLast() external view returns (address);
function getLast() external view returns (uint256);

function getNext(address _id) external view returns (address);
function getNext(uint256 _id) external view returns (uint256);

function getPrev(address _id) external view returns (address);
function getPrev(uint256 _id) external view returns (uint256);

function validInsertPosition(uint256 _ICR, address _prevId, address _nextId) external view returns (bool);
function validInsertPosition(uint256 _value, uint256 _prevId, uint256 _nextId) external view returns (bool);

function findInsertPosition(uint256 _ICR, address _prevId, address _nextId) external view returns (address, address);
function findInsertPosition(uint256 _value, uint256 _prevId, uint256 _nextId) external view returns (uint256, uint256);
}
4 changes: 2 additions & 2 deletions contracts/src/Interfaces/IStabilityPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ interface IStabilityPool is ILiquityBase {
*/
function provideToSP(uint _amount) external;


/* withdrawFromSP():
* - Calculates depositor's ETH gain
* - Calculates the compounded deposit
Expand All @@ -69,7 +69,7 @@ interface IStabilityPool is ILiquityBase {
* - Leaves their compounded deposit in the Stability Pool
* - Takes new snapshots of accumulators P and S
*/
function withdrawETHGainToTrove() external;
function withdrawETHGainToTrove(uint256 _troveId) external;

/*
* Initial checks:
Expand Down
Loading

0 comments on commit 5c9f2f6

Please sign in to comment.