From 7889dd659cfb5cb373f38fb7a1b5ed27a401ed91 Mon Sep 17 00:00:00 2001 From: fnanni-0 Date: Tue, 7 Mar 2023 11:24:53 -0300 Subject: [PATCH 1/2] refactor: add transfer() of stake commitments --- contracts/dao/DXDInfluence.sol | 37 ++++++++++++++++++++++++++++++++++ contracts/dao/DXDStake.sol | 33 ++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/contracts/dao/DXDInfluence.sol b/contracts/dao/DXDInfluence.sol index fdf22ac6..8a352465 100644 --- a/contracts/dao/DXDInfluence.sol +++ b/contracts/dao/DXDInfluence.sol @@ -166,6 +166,43 @@ contract DXDInfluence is OwnableUpgradeable, AccountSnapshot { votingPower.callback(); } + /** + * @dev Mints influence tokens according to the amount staked and takes a snapshot. The influence value + * is not stored, only the linear and exponential terms of the formula are updated, which are then used + * to compute the influence on the fly in the balance getter. + * @param _account Account that has staked the tokens. + * @param _to Account that has staked the tokens. + * @param _amount Amount of tokens to have been staked. + * @param _timeCommitment Time that the user commits to lock the tokens. + */ + function transfer( + address _account, + address _to, + uint256 _amount, + uint256 _timeCommitment + ) external onlyOwner { + CumulativeStake storage lastCumulativeStake = cumulativeStakesSnapshots[_account][_lastSnapshotId(_account)]; + CumulativeStake storage lastToCumulativeStake = cumulativeStakesSnapshots[_to][_lastSnapshotId(_account)]; + uint256 currentSnapshotId = _snapshot(_account); + + (uint256 linearTerm, uint256 exponentialTerm) = getFormulaTerms(_amount, _timeCommitment); + + // Update account's stake data + CumulativeStake storage cumulativeStake = cumulativeStakesSnapshots[_account][currentSnapshotId]; + cumulativeStake.linearTerm = lastCumulativeStake.linearTerm - linearTerm; + cumulativeStake.exponentialTerm = lastCumulativeStake.exponentialTerm - exponentialTerm; + // Update recipients stake data + CumulativeStake storage toCumulativeStake = cumulativeStakesSnapshots[_to][currentSnapshotId]; + toCumulativeStake.linearTerm = lastToCumulativeStake.linearTerm + linearTerm; + toCumulativeStake.exponentialTerm = lastToCumulativeStake.exponentialTerm + exponentialTerm; + + // Update global stake data + _totalInfluenceSnapshots[currentSnapshotId] = _totalInfluenceSnapshots[currentSnapshotId - 1]; + + // Notify Voting Power contract. + votingPower.callback(); + } + /** * @dev Updates the time a given amount of DXD was staked for and takes a snapshot. The influence value * is not stored, only the linear and exponential terms of the formula are updated, which are then used diff --git a/contracts/dao/DXDStake.sol b/contracts/dao/DXDStake.sol index 43198dc4..aabaa24b 100644 --- a/contracts/dao/DXDStake.sol +++ b/contracts/dao/DXDStake.sol @@ -153,6 +153,39 @@ contract DXDStake is OwnableUpgradeable, OptimizedERC20SnapshotUpgradeable { stakeCommitment.commitmentEnd = uint40(block.timestamp) + _newTimeCommitment; } + /** + * @dev Transfers a commitment. The recipient gets the voting power rights that come from DXDInfluence + * and can claim the locked DXD once the stakes completes. + * @param _commitmentId Id of the commitment. The Id is an incremental variable for each account. + * @param _to Address that the stake commitment will be transferred to. + */ + function transferCommitment(uint256 _commitmentId, address _to) external { + StakeCommitment storage stakeCommitment = stakeCommitments[msg.sender][_commitmentId]; + require(stakeCommitment.commitmentEnd != 0, "DXDStake: commitment inactive"); + + // Set new commitment + StakeCommitment storage newStakeCommitment = stakeCommitments[_to].push(); + newStakeCommitment.stake = stakeCommitment.stake; + newStakeCommitment.timeCommitment = stakeCommitment.timeCommitment; + newStakeCommitment.commitmentEnd = stakeCommitment.commitmentEnd; + + // Transfer influence. + dxdInfluence.transfer(msg.sender, _to, stakeCommitment.stake, stakeCommitment.timeCommitment); + + // Transfer staked DXD tokens. + _burn(msg.sender, stakeCommitment.stake); + _snapshot(); + _mint(_to, stakeCommitment.stake); + _snapshot(); + + // Clean old commitment. + stakeCommitment.stake = 0; + stakeCommitment.timeCommitment = 0; + stakeCommitment.commitmentEnd = 0; + userWithdrawals[msg.sender] += 1; + totalWithdrawals += 1; + } + /** * @dev Withdraws the tokens to the user. * @param _account Account that has staked. From 0f1de2484db92d70e9ce01656aff79dbc2c9d567 Mon Sep 17 00:00:00 2001 From: fnanni-0 Date: Wed, 8 Mar 2023 09:21:44 -0300 Subject: [PATCH 2/2] refactor: allow partial transfers --- contracts/dao/DXDStake.sol | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/contracts/dao/DXDStake.sol b/contracts/dao/DXDStake.sol index aabaa24b..cec3e2bc 100644 --- a/contracts/dao/DXDStake.sol +++ b/contracts/dao/DXDStake.sol @@ -157,33 +157,35 @@ contract DXDStake is OwnableUpgradeable, OptimizedERC20SnapshotUpgradeable { * @dev Transfers a commitment. The recipient gets the voting power rights that come from DXDInfluence * and can claim the locked DXD once the stakes completes. * @param _commitmentId Id of the commitment. The Id is an incremental variable for each account. + * @param _amount How much of the stake to transfer. * @param _to Address that the stake commitment will be transferred to. */ - function transferCommitment(uint256 _commitmentId, address _to) external { + function transferCommitment(uint256 _commitmentId, uint176 _amount, address _to) external { StakeCommitment storage stakeCommitment = stakeCommitments[msg.sender][_commitmentId]; require(stakeCommitment.commitmentEnd != 0, "DXDStake: commitment inactive"); // Set new commitment StakeCommitment storage newStakeCommitment = stakeCommitments[_to].push(); - newStakeCommitment.stake = stakeCommitment.stake; + newStakeCommitment.stake = _amount; newStakeCommitment.timeCommitment = stakeCommitment.timeCommitment; newStakeCommitment.commitmentEnd = stakeCommitment.commitmentEnd; // Transfer influence. - dxdInfluence.transfer(msg.sender, _to, stakeCommitment.stake, stakeCommitment.timeCommitment); + dxdInfluence.transfer(msg.sender, _to, _amount, stakeCommitment.timeCommitment); // Transfer staked DXD tokens. - _burn(msg.sender, stakeCommitment.stake); - _snapshot(); - _mint(_to, stakeCommitment.stake); + _burn(msg.sender, _amount); + _mint(_to, _amount); _snapshot(); - // Clean old commitment. - stakeCommitment.stake = 0; - stakeCommitment.timeCommitment = 0; - stakeCommitment.commitmentEnd = 0; - userWithdrawals[msg.sender] += 1; - totalWithdrawals += 1; + stakeCommitment.stake -= _amount; + if (stakeCommitment.stake == 0) { + // Clean old commitment. + stakeCommitment.timeCommitment = 0; + stakeCommitment.commitmentEnd = 0; + userWithdrawals[msg.sender] += 1; + totalWithdrawals += 1; + } } /**