Skip to content

Commit

Permalink
SOV-3613 Electron release
Browse files Browse the repository at this point in the history
* compile all electron sipArgs into 1

* update the sipArgs for permit2 integration

* update mainnet deployment

* update testnet deployment files

* ran prettier


* SOV-3613: adding LoanOpenings and SwapsImplSovrynSwapLib deployment objects for integrated QA tests

* SOV-3613: adding VestingFactory and VestingLogic deployment objects for integretaed QA tests

* SOV-3161 Increase borrowing debt fix

* fix increase borrowing bug & refactor (stack too deep)

* SOV-625 remove governance withdraw tokens

* remove governanceWithdrawTokens from vestingLogic

* using vesting start date to start withdrawal

* introduce partial withdraw tokens in vesting contract

* add validation for loanOpenings in onchain test

* redeploy VestingFactory and VestingLogic due to matadata issue
  • Loading branch information
tjcloa authored Feb 8, 2024
1 parent fb6acd8 commit 06d8ea8
Show file tree
Hide file tree
Showing 65 changed files with 41,643 additions and 158 deletions.
40 changes: 34 additions & 6 deletions contracts/governance/Staking/modules/StakingWithdrawModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,15 @@ contract StakingWithdrawModule is IFunctionsList, StakingShared, CheckpointsShar
* @param _receiver The receiving address.
* @param _startFrom The start value for the iterations.
* or just unlocked tokens (false).
*
* @return nextStartFrom is a timestamp to be used for next withdrawal.
* @return notCompleted flag that indicates that the cancel team vesting is not completely done.
* */
function _cancelTeamVesting(address _vesting, address _receiver, uint256 _startFrom) private {
function _cancelTeamVesting(
address _vesting,
address _receiver,
uint256 _startFrom
) private returns (uint256 nextStartFrom, bool notCompleted) {
require(_receiver != address(0), "receiver address invalid");

ITeamVesting teamVesting = ITeamVesting(_vesting);
Expand Down Expand Up @@ -130,9 +137,12 @@ contract StakingWithdrawModule is IFunctionsList, StakingShared, CheckpointsShar
}

if (adjustedEnd < end) {
emit TeamVestingPartiallyCancelled(msg.sender, _receiver, adjustedEnd);
nextStartFrom = adjustedEnd + TWO_WEEKS;
emit TeamVestingPartiallyCancelled(msg.sender, _receiver, nextStartFrom);
return (nextStartFrom, true);
} else {
emit TeamVestingCancelled(msg.sender, _receiver);
return (end, false);
}
}

Expand Down Expand Up @@ -322,7 +332,6 @@ contract StakingWithdrawModule is IFunctionsList, StakingShared, CheckpointsShar
* @notice Withdraw tokens for vesting contract.
* @param vesting The address of Vesting contract.
* @param receiver The receiver of the tokens. If not specified, send to the msg.sender
* @dev Can be invoked only by whitelisted contract passed to governanceWithdrawVesting.
* @dev This function is dedicated only to support backward compatibility for sovryn ecosystem that has been implementing this staking contract.
* @dev Sovryn protocol will use the cancelTeamVesting function for the withdrawal moving forward.
* https://github.com/DistributedCollective/Sovryn-smart-contracts/blob/4bbfe5bd0311ca71e4ef0e3af810d3791d8e4061/contracts/governance/Staking/modules/StakingWithdrawModule.sol#L78
Expand All @@ -331,9 +340,28 @@ contract StakingWithdrawModule is IFunctionsList, StakingShared, CheckpointsShar
address vesting,
address receiver
) public onlyAuthorized whenNotFrozen {
vestingWhitelist[vesting] = true;
ITeamVesting(vesting).governanceWithdrawTokens(receiver);
vestingWhitelist[vesting] = false;
require(vestingRegistryLogic.isTeamVesting(vesting), "Only team vesting allowed");

ITeamVesting teamVesting = ITeamVesting(vesting);
uint256 teamVestingStartDate = teamVesting.startDate();
uint256 teamVestingCliff = teamVesting.cliff();

uint256 nextStartFrom = teamVestingStartDate + teamVestingCliff;
bool withdrawFlag = true;

bool notCompleted;

/**
* The withdrawal is limited to certain iterations (set in maxVestingWithdrawIterations), so in order to withdraw all, we need to iterate until it is fully withdrawn.
*/
while (withdrawFlag) {
/**
* notCompleted is the flag whether the withdrawal is fully withdrawn or not.
* As long as the notCompleted is true, we will keep the iteration using the nextStartFrom.
*/
(nextStartFrom, notCompleted) = _cancelTeamVesting(vesting, receiver, nextStartFrom);
withdrawFlag = notCompleted ? true : false;
}

emit VestingTokensWithdrawn(vesting, receiver);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ contract CheckpointsShared is StakingStorageShared, SafeMath96 {
event TeamVestingPartiallyCancelled(
address indexed caller,
address receiver,
uint256 lastProcessedDate
uint256 nextStartFrom
);

constructor() internal {
Expand Down
2 changes: 1 addition & 1 deletion contracts/governance/Vesting/ITeamVesting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity ^0.5.17;
/**
* @title Interface for TeamVesting contract.
* @dev Interfaces are used to cast a contract address into a callable instance.
* This interface is used by Staking contract to call governanceWithdrawTokens
* This interface is used by Staking contract to cancel the team vesting
* function having the vesting contract instance address.
*/
interface ITeamVesting {
Expand Down
1 change: 0 additions & 1 deletion contracts/governance/Vesting/Vesting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import "./TeamVesting.sol";
* @notice Team tokens and investor tokens are vested. Therefore, a smart
* contract needs to be developed to enforce the vesting schedule.
*
* @dev TODO add tests for governanceWithdrawTokens.
* */
contract Vesting is TeamVesting {
/**
Expand Down
1 change: 1 addition & 0 deletions contracts/governance/Vesting/VestingFactory.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pragma solidity ^0.5.17;
pragma experimental ABIEncoderV2;

import "../../openzeppelin/Ownable.sol";
import "./Vesting.sol";
Expand Down
78 changes: 52 additions & 26 deletions contracts/governance/Vesting/VestingLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,25 @@ import "../IFeeSharingCollector.sol";
import "./IVesting.sol";
import "../ApprovalReceiver.sol";
import "./VestingStorage.sol";
import "../../openzeppelin/SafeMath.sol";

/**
* @title Vesting Logic contract.
* @notice Staking, delegating and withdrawal functionality.
* @dev Deployed by a VestingFactory contract.
* */
contract VestingLogic is IVesting, VestingStorage, ApprovalReceiver {
using SafeMath for uint256;
/* Events */

event TokensStaked(address indexed caller, uint256 amount);
event VotesDelegated(address indexed caller, address delegatee);
event TokensWithdrawn(address indexed caller, address receiver);
event TokensWithdrawn(
address indexed caller,
address receiver,
uint256 startFrom,
uint256 end
);
event DividendsCollected(
address indexed caller,
address loanPoolToken,
Expand Down Expand Up @@ -110,70 +117,76 @@ contract VestingLogic is IVesting, VestingStorage, ApprovalReceiver {
}

/**
* @notice Withdraws all tokens from the staking contract and
* @notice Withdraws unlocked tokens from the staking contract and
* forwards them to an address specified by the token owner.
* @param receiver The receiving address.
* @dev Can be called only by owner.
* @dev **WARNING** This function should not be no longer used by Sovryn Protocol.
* Sovryn protocol will use the cancelTeamVesting function for the withdrawal moving forward.
* */
function governanceWithdrawTokens(address receiver) public {
require(msg.sender == address(staking), "unauthorized");

_withdrawTokens(receiver, true);
function withdrawTokens(address receiver) public onlyOwners {
uint256 startFrom = startDate + cliff;
_withdrawTokens(receiver, startFrom, block.timestamp);
}

/**
* @notice Withdraws unlocked tokens from the staking contract and
* @notice Withdraws unlocked tokens partially (based on the max withdraw iteration that has been set) from the staking contract and
* forwards them to an address specified by the token owner.
* @param receiver The receiving address.
* @param startFrom The start value for the iterations.
* @param maxWithdrawIterations max withdrawal iteration to work around block gas limit issue.
* */
function withdrawTokens(address receiver) public onlyOwners {
_withdrawTokens(receiver, false);
function withdrawTokensStartingFrom(
address receiver,
uint256 startFrom,
uint256 maxWithdrawIterations
) public onlyOwners {
uint256 defaultStartFrom = startDate + cliff;

startFrom = _timestampToLockDate(startFrom);
startFrom = startFrom < defaultStartFrom ? defaultStartFrom : startFrom;

// @dev max iterations need to be decreased by 1, otherwise the iteration will always be surplus by 1
uint256 maxWithdrawDate = (startFrom + (FOUR_WEEKS * (maxWithdrawIterations.sub(1))));
uint256 endAt = endDate < maxWithdrawDate ? endDate : maxWithdrawDate;
_withdrawTokens(receiver, startFrom, endAt);
}

/**
* @notice Withdraws tokens from the staking contract and forwards them
* to an address specified by the token owner. Low level function.
* @dev Once here the caller permission is taken for granted.
* @param receiver The receiving address.
* @param isGovernance Whether all tokens (true)
* @param startFrom start withdrawal from date.
* @param endAt end time for regular withdrawal
* or just unlocked tokens (false).
* */
function _withdrawTokens(address receiver, bool isGovernance) internal {
function _withdrawTokens(address receiver, uint256 startFrom, uint256 endAt) internal {
require(receiver != address(0), "receiver address invalid");

uint96 stake;

/// @dev Usually we just need to iterate over the possible dates until now.
uint256 end;

/// @dev In the unlikely case that all tokens have been unlocked early,
/// allow to withdraw all of them.
if (staking.allUnlocked() || isGovernance) {
end = endDate;
if (staking.allUnlocked()) {
end = endAt < endDate ? endAt : endDate;
} else {
end = block.timestamp;
end = endAt < block.timestamp ? endAt : block.timestamp;
if (end > endDate) end = endDate;
}

/// @dev Withdraw for each unlocked position.
/// @dev Don't change FOUR_WEEKS to TWO_WEEKS, a lot of vestings already deployed with FOUR_WEEKS
/// workaround found, but it doesn't work with TWO_WEEKS
for (uint256 i = startDate + cliff; i <= end; i += FOUR_WEEKS) {
for (uint256 i = startFrom; i <= end; i += FOUR_WEEKS) {
/// @dev Read amount to withdraw.
stake = staking.getPriorUserStakeByDate(address(this), i, block.number - 1);

/// @dev Withdraw if > 0
if (stake > 0) {
if (isGovernance) {
staking.governanceWithdraw(stake, i, receiver);
} else {
staking.withdraw(stake, i, receiver);
}
staking.withdraw(stake, i, receiver);
}
}

emit TokensWithdrawn(msg.sender, receiver);
emit TokensWithdrawn(msg.sender, receiver, startFrom, end);
}

/**
Expand Down Expand Up @@ -224,4 +237,17 @@ contract VestingLogic is IVesting, VestingStorage, ApprovalReceiver {
selectors[0] = this.stakeTokensWithApproval.selector;
return selectors;
}

function _timestampToLockDate(uint256 timestamp) internal view returns (uint256 lockDate) {
// Optimize gas costs by reading kickoffTS from storage only once.
uint256 start = startDate + cliff;
require(timestamp >= start, "timestamp < contract creation"); // WS23
/**
* @dev If staking timestamp does not match any of the unstaking dates
* , set the lockDate to the closest one before the timestamp.
* E.g. Passed timestamps lies 7 weeks after kickoff -> only stake for 6 weeks.
* */
uint256 periodFromKickoff = (timestamp - start) / FOUR_WEEKS;
lockDate = periodFromKickoff * FOUR_WEEKS + start;
}
}
62 changes: 43 additions & 19 deletions contracts/modules/LoanOpenings.sol
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,9 @@ contract LoanOpenings is
"invalid interest"
);

// @note this fix is for borrowing only
uint256 sentNewPrincipal = isTorqueLoan ? sentValues.newPrincipal : 0;

/// Initialize loan.
Loan storage loanLocal = loans[
_initializeLoan(
Expand Down Expand Up @@ -393,25 +396,13 @@ contract LoanOpenings is
}
} else {
/// Update collateral after trade.
uint256 receivedAmount;
(receivedAmount, , sentValues.loanToCollateralSwapRate) = _loanSwap(
sentValues = _updateCollateralAfterTrade(
loanId,
loanParamsLocal.loanToken,
loanParamsLocal.collateralToken,
sentAddresses.borrower, /// borrower
sentValues.loanTokenSent, /// loanTokenUsable (minSourceTokenAmount)
0, /// maxSourceTokenAmount (0 means minSourceTokenAmount)
0, /// requiredDestTokenAmount (enforces that all of loanTokenUsable is swapped)
false, /// bypassFee
loanParamsLocal,
sentAddresses,
sentValues,
loanDataBytes
);
sentValues.collateralTokenSent = sentValues.collateralTokenSent.add(receivedAmount);

/// Check the minEntryPrice with the rate
require(
sentValues.loanToCollateralSwapRate >= sentValues.minEntryPrice,
"entry price above the minimum"
);
}

/// Settle collateral.
Expand All @@ -421,7 +412,8 @@ contract LoanOpenings is
loanLocal,
initialMargin,
sentValues.collateralTokenSent,
collateralAmountRequired
collateralAmountRequired,
sentNewPrincipal
),
"collateral insufficient"
);
Expand All @@ -441,6 +433,36 @@ contract LoanOpenings is
return (sentValues.newPrincipal, sentValues.collateralTokenSent); /// newPrincipal, newCollateral
}

function _updateCollateralAfterTrade(
bytes32 loanId,
LoanParams memory loanParamsLocal,
MarginTradeStructHelpers.SentAddresses memory sentAddresses,
MarginTradeStructHelpers.SentAmounts memory sentValues,
bytes memory loanDataBytes
) internal returns (MarginTradeStructHelpers.SentAmounts memory) {
uint256 receivedAmount;
(receivedAmount, , sentValues.loanToCollateralSwapRate) = _loanSwap(
loanId,
loanParamsLocal.loanToken,
loanParamsLocal.collateralToken,
sentAddresses.borrower, /// borrower
sentValues.loanTokenSent, /// loanTokenUsable (minSourceTokenAmount)
0, /// maxSourceTokenAmount (0 means minSourceTokenAmount)
0, /// requiredDestTokenAmount (enforces that all of loanTokenUsable is swapped)
false, /// bypassFee
loanDataBytes
);
sentValues.collateralTokenSent = sentValues.collateralTokenSent.add(receivedAmount);

/// Check the minEntryPrice with the rate
require(
sentValues.loanToCollateralSwapRate >= sentValues.minEntryPrice,
"entry price above the minimum"
);

return sentValues;
}

/**
* @notice Finalize an open loan.
*
Expand Down Expand Up @@ -602,6 +624,7 @@ contract LoanOpenings is
* @param initialMargin The initial amount of margin.
* @param newCollateral The amount of new collateral.
* @param collateralAmountRequired The amount of required collateral.
* @param newPrincipal The amount to borrow.
*
* @return Whether the collateral is satisfied.
* */
Expand All @@ -610,7 +633,8 @@ contract LoanOpenings is
Loan memory loanLocal,
uint256 initialMargin,
uint256 newCollateral,
uint256 collateralAmountRequired
uint256 collateralAmountRequired,
uint256 newPrincipal
) internal view returns (bool) {
/// Allow at most 2% under-collateralized.
collateralAmountRequired = collateralAmountRequired.mul(98 ether).div(100 ether);
Expand All @@ -621,7 +645,7 @@ contract LoanOpenings is
uint256 maxDrawdown = IPriceFeeds(priceFeeds).getMaxDrawdown(
loanParamsLocal.loanToken,
loanParamsLocal.collateralToken,
loanLocal.principal,
loanLocal.principal.sub(newPrincipal), // sub(newPrincipal) to exclude the new borrowed amount from the total principal to calculate maxDrawdown for existing loan
loanLocal.collateral,
initialMargin
);
Expand Down
2 changes: 1 addition & 1 deletion deployment/deploy/1020-deploy-FeeSharingCollector.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const func = async function (hre) {
"FeeSharingCollector_Proxy",
true,
[],
[(await get("ISovryn")).address, (await get("StakingProxy")).address]
[(await get("SovrynProtocol")).address, (await get("StakingProxy")).address]
);
};
func.tags = ["FeeSharingCollector"];
Expand Down
Loading

0 comments on commit 06d8ea8

Please sign in to comment.