Skip to content
Merged
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
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ Token for Yelay.
New staking contract, which inherits from `YelayStakingBase` (`SpoolStaking` before).
`YelayStakingBase` is a simple Synthetix reward contract, with added functionality to mint `sYLAY` (`voSPOOL` before). `YelayStaking` inherits the same properties.
- `migrateUser`: migrate user stake from `SpoolStaking` to `YelayStaking`. Only callable by the `YelayMigrator` contract. _Note: This contract should also migrate SPOOL rewards from SpoolStaking and voSPOOL, as after SPOOL is paused they are not claimable. Currently, it gets SPOOL rewards from SpoolStaking only, as getting latest voSPOOL rewards earned is not possible via a view function. However, SpoolStaking and voSPOOLRewards are upgradeable, so it would be possible to add a function on SpoolStaking to get them, and upgrade SpoolStaking._

- `transferUser`: Allows a user to migrate their stake to another wallet (e.g. if they want to transfer from a hot wallet to a cold wallet). The new wallet must NOT be an existing staker.
All other `SpoolStaking` functions stay in place.

---
Expand Down Expand Up @@ -80,8 +78,6 @@ Functions in sYLAY:
- `migrateInitial`: prerequisite to start migration
- `migrateGlobalTranches`: allows the migrator contract to migrate global tranches from VoSPOOL to sYLAY. Performs conversion as above.
- `migrateUser`: allows the migrator contract to migrate user tranches and user global from VoSPOOL to sYLAY. Performs conversion as above.
- `transferUser`: allows the user, via the migrator contract, to transfer sYLAY state from one address to another.


# The Migration Flow

Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions deployment/mainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
"proxy": "0x3E246beb1A0daF94FB376C44cAc2D1C8B979C0d0"
},
"YelayStaking": {
"implementation": "0x7fc74Dc0b8755f17BaFCaF92DfeD7Fd2cC5Cabf5",
"implementation": "0x15fF5CBC5F1f37279F60509f8e5532C79ccF5318",
"proxy": "0x8e933387AFc6F0F67588e5Dac33EBa97eF988C69"
},
"sYLAY": {
"implementation": "0xEA8e63346e8a6ae1a15aa41c14004D030622B67B",
"implementation": "0x276a0f45051dC2530E8B7354df3fD31673C6e8Bd",
"proxy": "0xC0F7B477e05B29097546dAae2E3dF2decBeB405d"
},
"sYLAYRewards": {
Expand Down
35 changes: 35 additions & 0 deletions script/TransferUserRemovalUpgrade.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import {Script} from "forge-std/Script.sol";

import {JsonReadWriter, Environment} from "./helpers.sol";

import {YelayStaking} from "src/YelayStaking.sol";
import {sYLAY} from "src/sYLAY.sol";
import {IYelayOwner} from "src/interfaces/IYelayOwner.sol";

/**
* source .env && FOUNDRY_PROFILE=mainnet forge script script/TransferUserRemovalUpgrade.s.sol:TransferUserRemovalUpgrade --slow --broadcast --legacy --etherscan-api-key $ETHERSCAN_API_KEY --verify
*/
contract TransferUserRemovalUpgrade is Script {
function run() external {
Environment.setRpc(vm);

JsonReadWriter json = new JsonReadWriter(vm, Environment.getContractsPath(vm));

vm.startBroadcast(Environment.getPrivateKey(vm));
YelayStaking yelayStaking = new YelayStaking(
json.getAddress(".YLAY.proxy"),
json.getAddress(".sYLAY.proxy"),
json.getAddress(".sYLAYRewards.proxy"),
json.getAddress(".YelayRewardsDistributor.proxy"),
json.getAddress(".YelayOwner")
);
sYLAY sylay = new sYLAY(IYelayOwner(json.getAddress(".YelayOwner")));
vm.stopBroadcast();

json.addProxy("YelayStaking", address(yelayStaking), json.getAddress(".YelayStaking.proxy"));
json.addProxy("sYLAY", address(sylay), json.getAddress(".sYLAY.proxy"));
}
}
63 changes: 2 additions & 61 deletions src/YelayStaking.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import {ECDSA} from "openzeppelin-contracts/utils/cryptography/ECDSA.sol";
import {EIP712} from "openzeppelin-contracts/utils/cryptography/draft-EIP712.sol";
import "openzeppelin-contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";

Expand All @@ -25,7 +23,7 @@ import "./interfaces/IRewardDistributor.sol";
* At stake, gradual sYLAY (Yelay Voting Token) is minted and accumulated every week.
* At unstake all sYLAY is burned. The maturing process of sYLAY restarts.
*/
contract YelayStaking is ReentrancyGuardUpgradeable, YelayOwnable, IYelayStaking, EIP712 {
contract YelayStaking is ReentrancyGuardUpgradeable, YelayOwnable, IYelayStaking {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since EIP712 does not define any storage variables or storage gaps, it can be safely removed from the inheritance chain.

using SafeERC20 for IERC20;

/* ========== STRUCTS ========== */
Expand Down Expand Up @@ -91,8 +89,6 @@ contract YelayStaking is ReentrancyGuardUpgradeable, YelayOwnable, IYelayStaking
/// @notice Account YLAY locked balance. subset of balances
mapping(address => uint256) public locked;

bytes32 private constant _TRANSFER_USER_TYPEHASH = keccak256("TransferUser(address from, uint256 deadline)");
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constant, not a storage - can be removed.


/* ========== CONSTRUCTOR ========== */

/**
Expand All @@ -110,7 +106,7 @@ contract YelayStaking is ReentrancyGuardUpgradeable, YelayOwnable, IYelayStaking
address _sYlayRewards,
address _rewardDistributor,
address _yelayOwner
) YelayOwnable(IYelayOwner(_yelayOwner)) EIP712("YelayStaking", "1.0.1") {
) YelayOwnable(IYelayOwner(_yelayOwner)) {
stakingToken = IERC20(_stakingToken);
sYlay = IsYLAY(_sYlay);
sYlayRewards = IsYLAYRewards(_sYlayRewards);
Expand Down Expand Up @@ -158,63 +154,8 @@ contract YelayStaking is ReentrancyGuardUpgradeable, YelayOwnable, IYelayStaking
return rewardTokens.length;
}

/**
* @dev Returns the domain separator for the current chain.
*/
function domainSeparatorV4() external view returns (bytes32) {
return _domainSeparatorV4();
}

/**
* @dev Returns the struct hash for hashTypedDataV4
*/
function structHash(address from, uint256 deadline) public pure returns (bytes32) {
return keccak256(abi.encode(_TRANSFER_USER_TYPEHASH, from, deadline));
}

/* ========== MUTATIVE FUNCTIONS ========== */

/**
* @notice Transfers the staking balance and rewards of one user to another.
* @dev This function is non-reentrant and updates rewards before transferring.
* @param to The address of the recipient to whom the staking data is transferred.
*/
function transferUser(address to, uint256 deadline, bytes memory signature)
external
nonReentrant
updateRewards(msg.sender)
{
require(deadline > block.timestamp, "YelayStaking::transferUser: deadline has passed");

bytes32 hash_ = _hashTypedDataV4(structHash(msg.sender, deadline));
address signer = ECDSA.recover(hash_, signature);

require(signer == to, "YelayStaking::transferUser: invalid signature");

balances[to] = balances[msg.sender];
stakedBy[to] = stakedBy[msg.sender];
locked[to] = locked[msg.sender];
canStakeFor[to] = false;

delete balances[msg.sender];
delete stakedBy[msg.sender];
delete locked[msg.sender];
delete canStakeFor[msg.sender];

uint256 _rewardTokensCount = rewardTokens.length;
for (uint256 i; i < _rewardTokensCount; i++) {
RewardConfiguration storage config = rewardConfiguration[rewardTokens[i]];

config.rewards[to] = config.rewards[msg.sender];
config.userRewardPerTokenPaid[to] = config.userRewardPerTokenPaid[msg.sender];

delete config.rewards[msg.sender];
delete config.userRewardPerTokenPaid[msg.sender];
}

sYlay.transferUser(msg.sender, to);
}

/**
* @notice Stake YLAY tokens and start earning sYLAY gradually.
* @param amount The amount of YLAY to stake.
Expand Down
4 changes: 0 additions & 4 deletions src/interfaces/IsYLAY.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,11 @@ interface IsYLAY is IsYLAYBase {

function burnLockups(address to) external returns (uint256 amount);

function transferUser(address from, address to) external;

event TrancheMigration(address indexed user, uint256 amount, uint256 index, uint256 rawUnmaturedVotingPower);

event LockupMinted(address indexed to, uint256 amount, uint256 power, uint256 startTranche, uint256 endTranche);

event LockupBurned(address indexed to, uint256 lockTranche);

event LockupContinued(address indexed to, uint256 lockTranche, uint256 addedPower, uint256 endTranche);

event UserTransferred(address indexed from, address indexed to);
}
59 changes: 0 additions & 59 deletions src/sYLAY.sol
Original file line number Diff line number Diff line change
Expand Up @@ -731,54 +731,6 @@ contract sYLAY is YelayOwnable, IsYLAY, IERC20MetadataUpgradeable {
}
}

/* ========== GRADUAL POWER: TRANSFER FUNCTIONS ========== */

/**
* @notice Transfers user data (staking and graduals) from one address to another.
* @param from The address of the user from whom data is being transferred.
* @param to The address of the recipient user.
*/
function transferUser(address from, address to) external onlyGradualMinter {
require(_userExists(from), "sYLAY::migrate: User does not exist");
require(!_userExists(to), "sYLAY::migrate: User already exists");

UserGradual memory _userGradual = _userGraduals[from];

// Migrate user tranches
if (_hasTranches(_userGradual)) {
uint256 fromIndex = _userGradual.oldestTranchePosition.arrayIndex;
uint256 toIndex = _userGradual.latestTranchePosition.arrayIndex;

for (uint256 i = fromIndex; i <= toIndex; i++) {
userTranches[to][i] = userTranches[from][i];
delete userTranches[from][i];
}
}

// migrate user lockups
uint16[] memory userLockupIndexesFrom = userLockupIndexes[from];
for (uint256 i = 0; i < userLockupIndexesFrom.length; i++) {
uint16 index = userLockupIndexesFrom[i];
userToTrancheIndexToLockup[to][index] = userToTrancheIndexToLockup[from][index];
delete userToTrancheIndexToLockup[from][index];
}
userLockupIndexes[to] = userLockupIndexesFrom;
delete userLockupIndexes[from];

// Migrate user gradual
_userGraduals[to] = _userGraduals[from];
delete _userGraduals[from];

// migrate user powers
userInstantPower[to] = userInstantPower[from];
delete userInstantPower[from];

userLockupPower[to] = userLockupPower[from];
delete userLockupPower[from];

emit UserTransferred(from, to);
}

/* ---------- GRADUAL POWER: BURN FUNCTIONS ---------- */

/**
Expand Down Expand Up @@ -1320,17 +1272,6 @@ contract sYLAY is YelayOwnable, IsYLAY, IERC20MetadataUpgradeable {
}
}

/**
* @notice check if user exists in the system.
*
* @param account user address to check
* @return userExists true if user exists
*/
function _userExists(address account) internal view returns (bool) {
return _userGraduals[account].lastUpdatedTrancheIndex != 0 || userLockupPower[account] != 0
|| userInstantPower[account] != 0;
}

/* ---------- GRADUAL POWER: HELPER FUNCTIONS ---------- */

/**
Expand Down
63 changes: 0 additions & 63 deletions test/SYLAYNew.t.sol

This file was deleted.

Loading