Skip to content

Commit

Permalink
StakingEscrow: function to wrap and topUp stake in threshold staking …
Browse files Browse the repository at this point in the history
…contract without withdrawing to staker's account
  • Loading branch information
vzotova committed Sep 26, 2023
1 parent 972ca1a commit 5034202
Show file tree
Hide file tree
Showing 8 changed files with 298 additions and 22 deletions.
64 changes: 62 additions & 2 deletions contracts/contracts/StakingEscrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@ interface WorkLockInterface {
}


/**
* @notice VendingMachine interface
*/
interface IVendingMachine {

function wrappedToken() external returns (IERC20);
function tToken() external returns (IERC20);
function wrap(uint256 amount) external;
function unwrap(uint256 amount) external;
function conversionToT(uint256 amount) external view returns (uint256 tAmount, uint256 wrappedRemainder);
function conversionFromT(uint256 amount) external view returns (uint256 wrappedAmount, uint256 tRemainder);

}


/**
* @title StakingEscrowStub
* @notice Stub is used to deploy main StakingEscrow after all other contract and make some variables immutable
Expand Down Expand Up @@ -110,6 +125,13 @@ contract StakingEscrow is Upgradeable, IERC900History {
* @param stakingProvider Staking provider address
*/
event MergeRequested(address indexed staker, address indexed stakingProvider);

/**
* @notice Signals that NU tokens were wrapped and topped up to the existing T stake
* @param staker Staker address
* @param value Amount wrapped (in NuNits)
*/
event WrappedAndTopedUp(address indexed staker, uint256 value);

struct StakerInfo {
uint256 value;
Expand Down Expand Up @@ -145,6 +167,8 @@ contract StakingEscrow is Upgradeable, IERC900History {
NuCypherToken public immutable token;
WorkLockInterface public immutable workLock;
IStaking public immutable tStaking;
IERC20 public immutable tToken;
IVendingMachine public immutable vendingMachine;

uint128 private stub1; // former slot for previousPeriodSupply
uint128 public currentPeriodSupply; // resulting token supply
Expand All @@ -168,21 +192,28 @@ contract StakingEscrow is Upgradeable, IERC900History {
* @param _token NuCypher token contract
* @param _workLock WorkLock contract. Zero address if there is no WorkLock
* @param _tStaking T token staking contract
* @param _vendingMachine Nu vending machine
*/
constructor(
NuCypherToken _token,
WorkLockInterface _workLock,
IStaking _tStaking
IStaking _tStaking,
IERC20 _tToken,
IVendingMachine _vendingMachine
) {
require(_token.totalSupply() > 0 &&
_tStaking.stakedNu(address(0)) == 0 &&
(address(_workLock) == address(0) || _workLock.token() == _token),
(address(_workLock) == address(0) || _workLock.token() == _token) &&
_vendingMachine.wrappedToken() == _token &&
_vendingMachine.tToken() == _tToken,
"Input addresses must be deployed contracts"
);

token = _token;
workLock = _workLock;
tStaking = _tStaking;
tToken = _tToken;
vendingMachine = _vendingMachine;
}

/**
Expand Down Expand Up @@ -273,6 +304,7 @@ contract StakingEscrow is Upgradeable, IERC900History {
function withdraw(uint256 _value) external onlyStaker {
require(_value > 0, "Value must be specified");
StakerInfo storage info = stakerInfo[msg.sender];
// TODO remove that line after 2 step of upgrading TokenStaking
require(
_value + tStaking.stakedNu(info.stakingProvider) <= info.value,
"Not enough tokens unstaked in T staking contract"
Expand All @@ -287,6 +319,34 @@ contract StakingEscrow is Upgradeable, IERC900History {
emit Withdrawn(msg.sender, _value);
}

/**
* @notice Wraps all tokens and top up stake in T staking contract
*/
function wrapAndTopUp() external onlyStaker {
StakerInfo storage info = stakerInfo[msg.sender];
require(info.stakingProvider != address(0), "There is no stake in T staking contract");
require(
tStaking.stakedNu(info.stakingProvider) == 0,
"Not all tokens unstaked in T staking contract"
);
require(
getUnvestedTokens(msg.sender) == 0,
"Not all tokens released during vesting"
);

(uint256 tTokenAmount, uint256 remainder) = vendingMachine.conversionToT(
info.value
);

uint256 wrappedTokenAmount = info.value - remainder;
token.approve(address(vendingMachine), wrappedTokenAmount);
vendingMachine.wrap(wrappedTokenAmount);
tToken.approve(address(tStaking), tTokenAmount);
tStaking.topUp(info.stakingProvider, uint96(tTokenAmount));
info.value = remainder;
emit WrappedAndTopedUp(msg.sender, wrappedTokenAmount);
}

/**
* @notice Returns amount of not released yet tokens for staker
*/
Expand Down
105 changes: 99 additions & 6 deletions contracts/test/StakingEscrowTestSet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ pragma solidity ^0.8.0;
import "../contracts/StakingEscrow.sol";
import "../contracts/NuCypherToken.sol";
import "@threshold/contracts/staking/IStaking.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";


/**
* @notice Contract for testing the application contract
*/
contract TToken is ERC20("T", "T") {
constructor(uint256 _totalSupplyOfTokens) {
_mint(msg.sender, _totalSupplyOfTokens);
}
}


/**
* @notice Enhanced version of StakingEscrow to use in tests
Expand All @@ -15,12 +27,16 @@ contract EnhancedStakingEscrow is StakingEscrow {
constructor(
NuCypherToken _token,
WorkLockInterface _workLock,
IStaking _tStaking
IStaking _tStaking,
IERC20 _tToken,
IVendingMachine _vendingMachine
)
StakingEscrow(
_token,
_workLock,
_tStaking
_tStaking,
_tToken,
_vendingMachine
)
{
}
Expand All @@ -43,12 +59,16 @@ contract StakingEscrowBad is StakingEscrow {
constructor(
NuCypherToken _token,
WorkLockInterface _workLock,
IStaking _tStaking
IStaking _tStaking,
IERC20 _tToken,
IVendingMachine _vendingMachine
)
StakingEscrow(
_token,
_workLock,
_tStaking
_tStaking,
_tToken,
_vendingMachine
)
{
}
Expand All @@ -68,12 +88,16 @@ contract StakingEscrowV2Mock is StakingEscrow {
constructor(
NuCypherToken _token,
WorkLockInterface _workLock,
IStaking _tStaking
IStaking _tStaking,
IERC20 _tToken,
IVendingMachine _vendingMachine
)
StakingEscrow(
_token,
_workLock,
_tStaking
_tStaking,
_tToken,
_vendingMachine
)
{
valueToCheck = 2;
Expand Down Expand Up @@ -128,6 +152,7 @@ contract WorkLockForStakingEscrowMock {
*/
contract ThresholdStakingForStakingEscrowMock {

IERC20 public immutable tToken;
StakingEscrow public escrow;

struct StakingProviderInfo {
Expand All @@ -137,6 +162,10 @@ contract ThresholdStakingForStakingEscrowMock {

mapping(address => StakingProviderInfo) public stakingProviders;

constructor(IERC20 _tToken) {
tToken = _tToken;
}

function setStakingEscrow(StakingEscrow _escrow) external {
escrow = _escrow;
}
Expand Down Expand Up @@ -185,4 +214,68 @@ contract ThresholdStakingForStakingEscrowMock {
require(_minStaked <= stakingProviders[_stakingProvider].staked);
stakingProviders[_stakingProvider].minStaked = _minStaked;
}

function topUp(address _stakingProvider, uint96 _amount) external {
stakingProviders[_stakingProvider].staked += _amount;
tToken.transferFrom(msg.sender, address(this), _amount);
}
}


contract VendingMachineForStakingEscrowMock {
using SafeERC20 for IERC20;

uint256 public constant WRAPPED_TOKEN_CONVERSION_PRECISION = 3;
uint256 public constant FLOATING_POINT_DIVISOR =
10**(18 - WRAPPED_TOKEN_CONVERSION_PRECISION);


IERC20 public immutable wrappedToken;
IERC20 public immutable tToken;


constructor(
IERC20 _wrappedToken,
IERC20 _tToken
) {
wrappedToken = _wrappedToken;
tToken = _tToken;
}

function wrap(uint256 amount) external {
(uint256 tTokenAmount, uint256 remainder) = conversionToT(
amount
);
amount -= remainder;

wrappedToken.safeTransferFrom(
msg.sender,
address(this),
amount
);
tToken.safeTransfer(msg.sender, tTokenAmount);
}

function unwrap(uint256 amount) external {
(uint256 wrappedTokenAmount, uint256 remainder) = conversionFromT(
amount
);
amount -= remainder;

tToken.safeTransferFrom(msg.sender, address(this), amount);
wrappedToken.safeTransfer(msg.sender, wrappedTokenAmount);
}

function conversionToT(uint256 amount) public view returns (uint256 tAmount, uint256 wrappedRemainder) {
wrappedRemainder = amount % FLOATING_POINT_DIVISOR;
uint256 convertibleAmount = amount - wrappedRemainder;
tAmount = convertibleAmount;
}

function conversionFromT(uint256 amount) public view returns (uint256 wrappedAmount, uint256 tRemainder) {
tRemainder = amount % FLOATING_POINT_DIVISOR;
uint256 convertibleAmount = amount - tRemainder;
wrappedAmount = convertibleAmount;
}

}
8 changes: 7 additions & 1 deletion scripts/deploy_staking_escrow.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,22 @@ def main(account_id=None):
deployer = get_account(account_id)
deployments_config = DEPLOYMENTS_CONFIG
if CURRENT_NETWORK in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
nucypher_token, t_staking, _, work_lock, _, _ = deploy_mocks(deployer)
nucypher_token, t_staking, _, work_lock, _, t_token, vending_machine = deploy_mocks(
deployer
)
else:
nucypher_token = deployments_config.get("nu_token")
t_staking = deployments_config.get("t_staking")
work_lock = deployments_config.get("work_lock")
t_token = deployments_config.get("t_token")
vending_machine = deployments_config.get("vending_machine")

staking_escrow = project.StakingEscrow.deploy(
nucypher_token,
work_lock,
t_staking,
t_token,
vending_machine,
sender=deployer,
publish=deployments_config.get("verify"),
)
Expand Down
2 changes: 1 addition & 1 deletion scripts/deploy_taco_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def main(account_id=None):
deployments_config = DEPLOYMENTS_CONFIG

if CURRENT_NETWORK in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
_, _, t_staking, _, _, t_token = deploy_mocks(deployer)
_, _, t_staking, _, _, t_token, _ = deploy_mocks(deployer)
else:
t_staking = deployments_config.get("t_staking")
t_token = deployments_config.get("t_token")
Expand Down
15 changes: 13 additions & 2 deletions scripts/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,23 @@ def deploy_mocks(deployer):
"""This function should deploy nucypher_token and t_staking and return the
corresponding contract addresses"""
nucypher_token = project.NuCypherToken.deploy(1_000_000_000, sender=deployer)
t_staking_for_escrow = project.ThresholdStakingForStakingEscrowMock.deploy(sender=deployer)
t_staking_for_taco = project.ThresholdStakingForTACoApplicationMock.deploy(sender=deployer)
total_supply = Web3.to_wei(10_000_000_000, "ether")
t_token = project.TToken.deploy(total_supply, sender=deployer)
work_lock = project.WorkLockForStakingEscrowMock.deploy(nucypher_token, sender=deployer)
vending_machine_for_escrow = project.VendingMachineForStakingEscrowMock.deploy(
nucypher_token.address, t_token.address, sender=deployer
)
t_staking_for_escrow = project.ThresholdStakingForStakingEscrowMock.deploy(
t_token.address, sender=deployer
)
staking_escrow = project.StakingEscrow.deploy(
nucypher_token, work_lock, t_staking_for_escrow, sender=deployer
nucypher_token,
work_lock,
t_staking_for_escrow,
t_token.address,
vending_machine_for_escrow.address,
sender=deployer,
)
return (
nucypher_token,
Expand All @@ -26,6 +36,7 @@ def deploy_mocks(deployer):
work_lock,
staking_escrow,
t_token,
vending_machine_for_escrow,
)


Expand Down
Loading

0 comments on commit 5034202

Please sign in to comment.