Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

StakingEscrow: function to wrap and topUp stake in threshold staking … #122

Merged
merged 2 commits into from
Oct 27, 2023
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
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 WrappedAndToppedUp(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
vzotova marked this conversation as resolved.
Show resolved Hide resolved
*/
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,
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible that stakedNu() will be removed at some point?

Copy link
Member Author

Choose a reason for hiding this comment

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

yep, it will be in second upgrade for threshold staking contract for removing legacy

"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));
Comment on lines +343 to +345
Copy link
Member

Choose a reason for hiding this comment

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

😍

info.value = remainder;
emit WrappedAndToppedUp(msg.sender, wrappedTokenAmount);
}

/**
* @notice Returns amount of not released yet tokens for staker
*/
Expand Down
1 change: 0 additions & 1 deletion contracts/contracts/testnet/OpenAccessAuthorizer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ interface IEncryptionAuthorizer {
) external view returns (bool);
}


contract OpenAccessAuthorizer is IEncryptionAuthorizer {
function isAuthorized(
uint32,
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;
}

}
35 changes: 30 additions & 5 deletions tests/staking_escrow/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"""

import pytest
import ape
from ape import project
from web3 import Web3

TOTAL_SUPPLY = Web3.to_wei(1_000_000_000, "ether") # TODO NU(1_000_000_000, 'NU').to_units()
Expand All @@ -29,23 +29,48 @@ def token(project, accounts):
return token


@pytest.fixture()
def t_token(project, accounts):
# Create an ERC20 token
token = accounts[0].deploy(project.TToken, TOTAL_SUPPLY)
return token


@pytest.fixture()
def worklock(project, token, accounts):
worklock = accounts[0].deploy(project.WorkLockForStakingEscrowMock, token.address)
return worklock


@pytest.fixture()
def threshold_staking(project, accounts):
threshold_staking = accounts[0].deploy(project.ThresholdStakingForStakingEscrowMock)
def threshold_staking(project, t_token, accounts):
threshold_staking = accounts[0].deploy(
project.ThresholdStakingForStakingEscrowMock, t_token.address
)
return threshold_staking


@pytest.fixture()
def vending_machine(token, t_token, accounts):
contract = accounts[0].deploy(
project.VendingMachineForStakingEscrowMock, token.address, t_token.address
)
t_token.transfer(contract.address, TOTAL_SUPPLY, sender=accounts[0])
return contract


@pytest.fixture(params=[False, True])
def escrow(project, token, worklock, threshold_staking, request, accounts):
def escrow(
project, token, worklock, threshold_staking, vending_machine, t_token, request, accounts
):
creator = accounts[0]
contract = creator.deploy(
project.EnhancedStakingEscrow, token.address, worklock.address, threshold_staking.address
project.EnhancedStakingEscrow,
token.address,
worklock.address,
threshold_staking.address,
t_token.address,
vending_machine.address,
)

if request.param:
Expand Down
Loading
Loading