Skip to content

Commit

Permalink
Merge add1a05 into 1b7f04d
Browse files Browse the repository at this point in the history
  • Loading branch information
jameshowlett977 committed May 24, 2021
2 parents 1b7f04d + add1a05 commit 9469f67
Show file tree
Hide file tree
Showing 21 changed files with 404 additions and 60 deletions.
16 changes: 16 additions & 0 deletions contracts/connectors/loantoken/LoanTokenLogicStandard.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ contract LoanTokenLogicStandard is LoanTokenSettingsLowerAdmin {
address internal constant arbitraryCaller = 0x000F400e6818158D541C3EBE45FE3AA0d47372FF;
bytes32 internal constant iToken_ProfitSoFar = 0x37aa2b7d583612f016e4a4de4292cb015139b3d7762663d06a53964912ea2fb6; // keccak256("iToken_ProfitSoFar")

uint256 public constant TINY_AMOUNT = 25 * 10**13;

/**
* @notice Fallback function is to react to receiving value (rBTC).
* */
Expand Down Expand Up @@ -357,6 +359,8 @@ contract LoanTokenLogicStandard is LoanTokenSettingsLowerAdmin {
sentAmounts[1] /// depositAmount
);

require(_getAmountInRbtc(loanTokenAddress, sentAmounts[1]) > TINY_AMOUNT, "principal too small");

return
_borrowOrTrade(
loanId,
Expand Down Expand Up @@ -918,6 +922,18 @@ contract LoanTokenLogicStandard is LoanTokenSettingsLowerAdmin {
}

/**
* @dev returns amount of the asset converted to RBTC
* @param asset the asset to be transferred
* @param amount the amount to be transferred
* @return amount in RBTC
* */
function _getAmountInRbtc(address asset, uint256 amount) internal returns (uint256) {
(uint256 rbtcRate, uint256 rbtcPrecision) =
FeedsLike(ProtocolLike(sovrynContractAddress).priceFeeds()).queryRate(asset, wrbtcTokenAddress);
return amount.mul(rbtcRate).div(rbtcPrecision);
}

/*
* @notice Compute interest rate and other loan parameters.
*
* @param borrowAmount The amount of tokens to borrow.
Expand Down
11 changes: 11 additions & 0 deletions contracts/events/LoanClosingsEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,16 @@ contract LoanClosingsEvents {
uint256 currentMargin
);

event Rollover(
address indexed user,
address indexed lender,
bytes32 indexed loanId,
uint256 principal,
uint256 collateral,
uint256 endTimestamp,
address rewardReceiver,
uint256 reward
);

event swapExcess(bool shouldRefund, uint256 amount, uint256 amountInRbtc, uint256 threshold);
}
40 changes: 38 additions & 2 deletions contracts/modules/LoanClosingsWith.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ contract LoanClosingsWith is
//because it's not shared state anyway and only used by this contract
uint256 public constant paySwapExcessToBorrowerThreshold = 10000000000000;

uint256 public constant TINY_AMOUNT = 25 * 10**13;

enum CloseTypes { Deposit, Swap, Liquidation }

constructor() public {}
Expand Down Expand Up @@ -155,11 +157,21 @@ contract LoanClosingsWith is

Loan storage loanLocal = loans[loanId];
LoanParams storage loanParamsLocal = loanParams[loanLocal.loanParamsId];
//TODO should we skip this check if invoked from rollover ?
_checkAuthorized(loanLocal, loanParamsLocal);

/// Can't close more than the full principal.
loanCloseAmount = depositAmount > loanLocal.principal ? loanLocal.principal : depositAmount;

//close whole loan if tiny position will remain
uint256 remainingAmount = loanLocal.principal - loanCloseAmount;
if (remainingAmount > 0) {
remainingAmount = _getAmountInRbtc(loanParamsLocal.loanToken, remainingAmount);
if (remainingAmount <= TINY_AMOUNT) {
loanCloseAmount = loanLocal.principal;
}
}

uint256 loanCloseAmountLessInterest = _settleInterestToPrincipal(loanLocal, loanParamsLocal, loanCloseAmount, receiver);

if (loanCloseAmountLessInterest != 0) {
Expand Down Expand Up @@ -232,6 +244,13 @@ contract LoanClosingsWith is
/// Can't swap more than collateral.
swapAmount = swapAmount > loanLocal.collateral ? loanLocal.collateral : swapAmount;

//close whole loan if tiny position will remain
if (loanLocal.collateral - swapAmount > 0) {
if (_getAmountInRbtc(loanParamsLocal.collateralToken, loanLocal.collateral - swapAmount) <= TINY_AMOUNT) {
swapAmount = loanLocal.collateral;
}
}

uint256 loanCloseAmountLessInterest;
if (swapAmount == loanLocal.collateral || returnTokenIsCollateral) {
/// loanCloseAmountLessInterest will be passed as required amount amount of destination tokens.
Expand Down Expand Up @@ -429,13 +448,30 @@ contract LoanClosingsWith is
* @return True if the amount is bigger than the threshold.
* */
function worthTheTransfer(address asset, uint256 amount) internal returns (bool) {
(uint256 rbtcRate, uint256 rbtcPrecision) = IPriceFeeds(priceFeeds).queryRate(asset, address(wrbtcToken));
uint256 amountInRbtc = amount.mul(rbtcRate).div(rbtcPrecision);
uint256 amountInRbtc = _getAmountInRbtc(asset, amount);
emit swapExcess(amountInRbtc > paySwapExcessToBorrowerThreshold, amount, amountInRbtc, paySwapExcessToBorrowerThreshold);
return amountInRbtc > paySwapExcessToBorrowerThreshold;
}

/**
* @dev returns amount of the asset converted to RBTC
* @param asset the asset to be transferred
* @param amount the amount to be transferred
* @return amount in RBTC
* */
function _getAmountInRbtc(address asset, uint256 amount) internal returns (uint256) {
(uint256 rbtcRate, uint256 rbtcPrecision) = IPriceFeeds(priceFeeds).queryRate(asset, address(wrbtcToken));
return amount.mul(rbtcRate).div(rbtcPrecision);
}

/**
* swaps a share of a loan's collateral or the complete collateral in order to cover the principle.
* @param loanLocal the loan
* @param loanParamsLocal the loan parameters
* @param swapAmount in case principalNeeded == 0 or !returnTokenIsCollateral, this is the amount which is going to be swapped.
* Else, swapAmount doesn't matter, because the amount of source tokens needed for the swap is estimated by the connector.
* @param principalNeeded the required amount of destination tokens in order to cover the principle (only used if returnTokenIsCollateral)
* @param returnTokenIsCollateral tells if the user wants to withdraw his remaining collateral + profit in collateral tokens
* @notice Swaps a share of a loan's collateral or the complete collateral
* in order to cover the principle.
*
Expand Down
3 changes: 2 additions & 1 deletion shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ def Constants():
"ZERO_ADDRESS": "0x0000000000000000000000000000000000000000",
"ONE_ADDRESS": "0x0000000000000000000000000000000000000001",
"MAX_UINT": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"ZERO_32": "0x0000000000000000000000000000000000000000000000000000000000000000"
"ZERO_32": "0x0000000000000000000000000000000000000000000000000000000000000000",
"TINY_AMOUNT": 25 * 10**13
})

def Addresses():
Expand Down
35 changes: 32 additions & 3 deletions tests-js/EscrowReward/anyone.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
// For this test, multisig wallet will be done by normal wallets.

const EscrowReward = artifacts.require("EscrowReward");
const LockedSOV = artifacts.require("LockedSOVMockup"); // Ideally should be using actual LockedSOV for testing.
const LockedSOV = artifacts.require("LockedSOV"); // Ideally should be using actual LockedSOV for testing.
const StakingLogic = artifacts.require("Staking");
const StakingProxy = artifacts.require("StakingProxy");
const SOV = artifacts.require("TestToken");
const FeeSharingProxy = artifacts.require("FeeSharingProxyMockup");
const VestingLogic = artifacts.require("VestingLogic");
const VestingFactory = artifacts.require("VestingFactory");
const VestingRegistry = artifacts.require("VestingRegistry3");

const {
BN, // Big Number support.
Expand All @@ -14,6 +20,8 @@ const { assert } = require("chai");

// Some constants we would be using in the contract.
let zero = new BN(0);
let cliff = 1; // This is in 4 weeks. i.e. 1 * 4 weeks.
let duration = 11; // This is in 4 weeks. i.e. 11 * 4 weeks.
const depositLimit = 75000000;

/**
Expand Down Expand Up @@ -48,8 +56,29 @@ contract("Escrow Rewards (Any User Functions)", (accounts) => {
// Creating the instance of SOV Token.
sov = await SOV.new("Sovryn", "SOV", 18, zero);

// Creating the instance of LockedSOV Contract.
lockedSOV = await LockedSOV.new(sov.address, [multisig]);
// Creating the Staking Instance.
stakingLogic = await StakingLogic.new(sov.address);
staking = await StakingProxy.new(sov.address);
await staking.setImplementation(stakingLogic.address);
staking = await StakingLogic.at(staking.address);

// Creating the FeeSharing Instance.
feeSharingProxy = await FeeSharingProxy.new(constants.ZERO_ADDRESS, staking.address);

// Creating the Vesting Instance.
vestingLogic = await VestingLogic.new();
vestingFactory = await VestingFactory.new(vestingLogic.address);
vestingRegistry = await VestingRegistry.new(
vestingFactory.address,
sov.address,
staking.address,
feeSharingProxy.address,
creator // This should be Governance Timelock Contract.
);
vestingFactory.transferOwnership(vestingRegistry.address);

// Creating the instance of newLockedSOV Contract.
lockedSOV = await LockedSOV.new(sov.address, vestingRegistry.address, cliff, duration, [multisig]);
});

beforeEach("Creating New Escrow Contract Instance.", async () => {
Expand Down
35 changes: 32 additions & 3 deletions tests-js/EscrowReward/creator.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
// For this test, multisig wallet will be done by normal wallets.

const EscrowReward = artifacts.require("EscrowReward");
const LockedSOV = artifacts.require("LockedSOVMockup"); // Ideally should be using actual LockedSOV for testing.
const LockedSOV = artifacts.require("LockedSOV"); // Ideally should be using actual LockedSOV for testing.
const VestingLogic = artifacts.require("VestingLogic");
const VestingFactory = artifacts.require("VestingFactory");
const VestingRegistry = artifacts.require("VestingRegistry3");
const StakingLogic = artifacts.require("Staking");
const StakingProxy = artifacts.require("StakingProxy");
const FeeSharingProxy = artifacts.require("FeeSharingProxyMockup");
const SOV = artifacts.require("TestToken");

const {
Expand All @@ -16,6 +22,8 @@ const { assert } = require("chai");
// Some constants we would be using in the contract.
let zero = new BN(0);
let zeroAddress = constants.ZERO_ADDRESS;
let cliff = 1; // This is in 4 weeks. i.e. 1 * 4 weeks.
let duration = 11; // This is in 4 weeks. i.e. 11 * 4 weeks.
const depositLimit = 75000000;

/**
Expand Down Expand Up @@ -50,8 +58,29 @@ contract("Escrow Rewards (Creator Functions)", (accounts) => {
// Creating the instance of SOV Token.
sov = await SOV.new("Sovryn", "SOV", 18, zero);

// Creating the instance of LockedSOV Contract.
lockedSOV = await LockedSOV.new(sov.address, [multisig]);
// Creating the Staking Instance.
stakingLogic = await StakingLogic.new(sov.address);
staking = await StakingProxy.new(sov.address);
await staking.setImplementation(stakingLogic.address);
staking = await StakingLogic.at(staking.address);

// Creating the FeeSharing Instance.
feeSharingProxy = await FeeSharingProxy.new(constants.ZERO_ADDRESS, staking.address);

// Creating the Vesting Instance.
vestingLogic = await VestingLogic.new();
vestingFactory = await VestingFactory.new(vestingLogic.address);
vestingRegistry = await VestingRegistry.new(
vestingFactory.address,
sov.address,
staking.address,
feeSharingProxy.address,
creator // This should be Governance Timelock Contract.
);
vestingFactory.transferOwnership(vestingRegistry.address);

// Creating the instance of newLockedSOV Contract.
lockedSOV = await LockedSOV.new(sov.address, vestingRegistry.address, cliff, duration, [multisig]);
});

beforeEach("Creating New Escrow Contract Instance.", async () => {
Expand Down
37 changes: 33 additions & 4 deletions tests-js/EscrowReward/event.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
// For this test, multisig wallet will be done by normal wallets.

const EscrowReward = artifacts.require("EscrowReward");
const LockedSOV = artifacts.require("LockedSOVMockup"); // Ideally should be using actual LockedSOV for testing.
const LockedSOV = artifacts.require("LockedSOV"); // Ideally should be using actual LockedSOV for testing.
const SOV = artifacts.require("TestToken");
const VestingLogic = artifacts.require("VestingLogic");
const VestingFactory = artifacts.require("VestingFactory");
const VestingRegistry = artifacts.require("VestingRegistry3");
const StakingLogic = artifacts.require("Staking");
const StakingProxy = artifacts.require("StakingProxy");
const FeeSharingProxy = artifacts.require("FeeSharingProxyMockup");

const {
BN, // Big Number support.
Expand All @@ -14,6 +20,8 @@ const { assert } = require("chai");

// Some constants we would be using in the contract.
let zero = new BN(0);
let cliff = 1; // This is in 4 weeks. i.e. 1 * 4 weeks.
let duration = 11; // This is in 4 weeks. i.e. 11 * 4 weeks.
const depositLimit = 75000000;

/**
Expand Down Expand Up @@ -48,8 +56,29 @@ contract("Escrow Rewards (Events)", (accounts) => {
// Creating the instance of SOV Token.
sov = await SOV.new("Sovryn", "SOV", 18, zero);

// Creating the instance of LockedSOV Contract.
lockedSOV = await LockedSOV.new(sov.address, [multisig]);
// Creating the Staking Instance.
stakingLogic = await StakingLogic.new(sov.address);
staking = await StakingProxy.new(sov.address);
await staking.setImplementation(stakingLogic.address);
staking = await StakingLogic.at(staking.address);

// Creating the FeeSharing Instance.
feeSharingProxy = await FeeSharingProxy.new(constants.ZERO_ADDRESS, staking.address);

// Creating the Vesting Instance.
vestingLogic = await VestingLogic.new();
vestingFactory = await VestingFactory.new(vestingLogic.address);
vestingRegistry = await VestingRegistry.new(
vestingFactory.address,
sov.address,
staking.address,
feeSharingProxy.address,
creator // This should be Governance Timelock Contract.
);
vestingFactory.transferOwnership(vestingRegistry.address);

// Creating the instance of newLockedSOV Contract.
lockedSOV = await LockedSOV.new(sov.address, vestingRegistry.address, cliff, duration, [multisig]);
});

beforeEach("Creating New Escrow Contract Instance.", async () => {
Expand Down Expand Up @@ -155,7 +184,7 @@ contract("Escrow Rewards (Events)", (accounts) => {
});

it("Updating the Locked SOV Contract Address should emit LockedSOVUpdated Event.", async () => {
let newLockedSOV = await LockedSOV.new(sov.address, [multisig]);
let newLockedSOV = await LockedSOV.new(sov.address, vestingRegistry.address, cliff, duration, [multisig]);
let txReceipt = await escrowReward.updateLockedSOV(newLockedSOV.address, { from: multisig });
expectEvent(txReceipt, "LockedSOVUpdated", {
_initiator: multisig,
Expand Down
37 changes: 33 additions & 4 deletions tests-js/EscrowReward/multisig.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
// For this test, multisig wallet will be done by normal wallets.

const EscrowReward = artifacts.require("EscrowReward");
const LockedSOV = artifacts.require("LockedSOVMockup"); // Ideally should be using actual LockedSOV for testing.
const LockedSOV = artifacts.require("LockedSOV"); // Ideally should be using actual LockedSOV for testing.
const VestingLogic = artifacts.require("VestingLogic");
const VestingFactory = artifacts.require("VestingFactory");
const VestingRegistry = artifacts.require("VestingRegistry3");
const StakingLogic = artifacts.require("Staking");
const StakingProxy = artifacts.require("StakingProxy");
const FeeSharingProxy = artifacts.require("FeeSharingProxyMockup");
const SOV = artifacts.require("TestToken");

const {
Expand All @@ -14,6 +20,8 @@ const { assert } = require("chai");

// Some constants we would be using in the contract.
let zero = new BN(0);
let cliff = 1; // This is in 4 weeks. i.e. 1 * 4 weeks.
let duration = 11; // This is in 4 weeks. i.e. 11 * 4 weeks.
let zeroAddress = constants.ZERO_ADDRESS;
const depositLimit = 75000000;

Expand Down Expand Up @@ -49,8 +57,29 @@ contract("Escrow Rewards (Multisig Functions)", (accounts) => {
// Creating the instance of SOV Token.
sov = await SOV.new("Sovryn", "SOV", 18, zero);

// Creating the instance of LockedSOV Contract.
lockedSOV = await LockedSOV.new(sov.address, [multisig]);
// Creating the Staking Instance.
stakingLogic = await StakingLogic.new(sov.address);
staking = await StakingProxy.new(sov.address);
await staking.setImplementation(stakingLogic.address);
staking = await StakingLogic.at(staking.address);

// Creating the FeeSharing Instance.
feeSharingProxy = await FeeSharingProxy.new(constants.ZERO_ADDRESS, staking.address);

// Creating the Vesting Instance.
vestingLogic = await VestingLogic.new();
vestingFactory = await VestingFactory.new(vestingLogic.address);
vestingRegistry = await VestingRegistry.new(
vestingFactory.address,
sov.address,
staking.address,
feeSharingProxy.address,
creator // This should be Governance Timelock Contract.
);
vestingFactory.transferOwnership(vestingRegistry.address);

// Creating the instance of newLockedSOV Contract.
lockedSOV = await LockedSOV.new(sov.address, vestingRegistry.address, cliff, duration, [multisig]);
});

beforeEach("Creating New Escrow Contract Instance.", async () => {
Expand Down Expand Up @@ -147,7 +176,7 @@ contract("Escrow Rewards (Multisig Functions)", (accounts) => {
});

it("Multisig should be able to update the Locked SOV Address.", async () => {
let newLockedSOV = await LockedSOV.new(sov.address, [multisig]);
let newLockedSOV = await LockedSOV.new(sov.address, vestingRegistry.address, cliff, duration, [multisig]);
await escrowReward.updateLockedSOV(newLockedSOV.address, { from: multisig });
});

Expand Down
Loading

0 comments on commit 9469f67

Please sign in to comment.