[KMS-ETH]- Added calculating locked tokens in future period for findCumSum method

pull/195/head^2
szotov 2018-01-04 20:16:28 +03:00
parent de026cb04e
commit 4f7af1eb9d
6 changed files with 139 additions and 79 deletions

View File

@ -4,7 +4,7 @@ from nkms_eth import blockchain
from nkms_eth import token
ESCROW_NAME = 'Escrow'
MINING_COEFF = [10 ** 9, 50]
MINING_COEFF = [10 ** 9, 50, 30]
NULL_ADDR = '0x' + '0' * 40
@ -40,6 +40,7 @@ def sample(n: int = 10)-> List[str]:
escrow = get()
n_select = int(n * 1.7) # Select more ursulas
n_tokens = escrow.call().getAllLockedTokens()
duration = 10
for _ in range(5): # number of tries
points = [0] + sorted(random.randrange(n_tokens) for _ in
@ -50,7 +51,7 @@ def sample(n: int = 10)-> List[str]:
shift = 0
for delta in deltas:
addr, shift = escrow.call().findCumSum(addr, delta + shift)
addr, shift = escrow.call().findCumSum(addr, delta + shift, duration)
addrs.add(addr)
if len(addrs) >= n:

View File

@ -27,6 +27,7 @@ contract Escrow is Miner, Ownable {
uint256 decimals;
uint256 lockedValue;
uint256 releasePeriod;
uint256 releaseRate;
ConfirmedPeriodInfo[] confirmedPeriods;
uint256 numberConfirmedPeriods;
}
@ -37,6 +38,7 @@ contract Escrow is Miner, Ownable {
}
uint256 constant MAX_PERIODS = 100;
uint256 constant MAX_OWNERS = 50000;
NuCypherKMSToken token;
mapping (address => TokenInfo) public tokenInfo;
@ -44,23 +46,27 @@ contract Escrow is Miner, Ownable {
uint256 public blocksPerPeriod;
mapping (uint256 => PeriodInfo) public lockedPerPeriod;
uint256 public releasePeriods;
/**
* @notice The Escrow constructor sets address of token contract and coefficients for mining
* @param _token Token contract
* @param _miningCoefficient Mining coefficient
* @param _blocksPerPeriod Size of one period in blocks
* @param _releasePeriods Amount of periods during which tokens will be released
**/
function Escrow(
NuCypherKMSToken _token,
uint256 _miningCoefficient,
uint256 _blocksPerPeriod
uint256 _blocksPerPeriod,
uint256 _releasePeriods
)
Miner(_token, _miningCoefficient)
{
require(_blocksPerPeriod != 0);
token = _token;
blocksPerPeriod = _blocksPerPeriod;
releasePeriods = _releasePeriods;
}
/**
@ -71,6 +77,7 @@ contract Escrow is Miner, Ownable {
function deposit(uint256 _value, uint256 _periods) {
require(_value != 0);
if (!tokenOwners.valueExists(msg.sender)) {
require(tokenOwners.sizeOf() < MAX_OWNERS);
tokenOwners.push(msg.sender, true);
}
tokenInfo[msg.sender].value = tokenInfo[msg.sender].value.add(_value);
@ -185,7 +192,7 @@ contract Escrow is Miner, Ownable {
}
}
// checks if owner can mine more tokens (before or after release period)
if (calculateLockedTokens(_owner, period, lockedValue) == 0) {
if (calculateLockedTokens(_owner, period, lockedValue, 1) == 0) {
return 0;
} else {
return lockedValue;
@ -226,22 +233,24 @@ contract Escrow is Miner, Ownable {
* @notice Calculate locked tokens value for owner in next period
* @param _owner Tokens owner
* @param _period Current or future period
* @param _currentLockedToken Current locked tokens
* @param _lockedTokens Locked tokens in specified period
* @param _periods Number of periods after _period that need to calculate
* @return Calculated locked tokens in next period
**/
function calculateLockedTokens(
address _owner,
uint256 _period,
uint256 _currentLockedToken
uint256 _lockedTokens,
uint256 _periods
)
public constant returns (uint256)
{
var nextPeriod = _period + 1;
var nextPeriod = _period + _periods;
var info = tokenInfo[_owner];
if (info.releasePeriod <= nextPeriod) {
return 0;
} else {
return _currentLockedToken;
return _lockedTokens;
}
}
@ -254,7 +263,8 @@ contract Escrow is Miner, Ownable {
public constant returns (uint256)
{
var period = block.number.div(blocksPerPeriod);
return calculateLockedTokens(_owner, period, getLockedTokens(_owner));
return calculateLockedTokens(
_owner, period, getLockedTokens(_owner), 1);
}
/**
@ -350,46 +360,55 @@ contract Escrow is Miner, Ownable {
}
/**
* @notice Fixedstep in cumsum
* @notice Fixed-step in cumulative sum
* @param _start Starting point
* @param _delta How much to step
* @param _periods Amount of periods to get locked tokens
* @dev
_start
v
|-------->*--------------->*---->*------------->|
| ^
| o_stop
| stop
|
| _delta
|---------------------------->|
|
| o_shift
| shift
| |----->|
**/
// _blockNumber?
function findCumSum(address _start, uint256 _delta)
public constant returns (address o_stop, uint256 o_shift)
function findCumSum(address _start, uint256 _delta, uint256 _periods)
public constant returns (address stop, uint256 shift)
{
var currentPeriod = block.number / blocksPerPeriod;
require(_periods > 0);
var currentPeriod = block.number.div(blocksPerPeriod);
uint256 distance = 0;
uint256 lockedTokens = 0;
var current = _start;
if (current == 0x0)
if (current == 0x0) {
current = tokenOwners.step(current, true);
}
while (current != 0x0) {
var info = tokenInfo[current];
var numberConfirmedPeriods = info.numberConfirmedPeriods;
if (numberConfirmedPeriods == 0 ||
info.confirmedPeriods[numberConfirmedPeriods - 1].period != currentPeriod &&
(numberConfirmedPeriods == 1 ||
info.confirmedPeriods[numberConfirmedPeriods - 2].period != currentPeriod)) {
uint256 lockedTokens = 0;
if (numberConfirmedPeriods > 0 &&
info.confirmedPeriods[numberConfirmedPeriods - 1].period == currentPeriod) {
lockedTokens = info.confirmedPeriods[numberConfirmedPeriods - 1].lockedValue;
} else if (numberConfirmedPeriods > 1 &&
info.confirmedPeriods[numberConfirmedPeriods - 2].period == currentPeriod) {
lockedTokens = info.confirmedPeriods[numberConfirmedPeriods - 2].lockedValue;
}
if (lockedTokens == 0) {
current = tokenOwners.step(current, true);
continue;
}
lockedTokens = info.lockedValue;
lockedTokens = calculateLockedTokens(current, currentPeriod, lockedTokens, _periods);
if (_delta < distance + lockedTokens) {
o_stop = current;
o_shift = _delta - distance;
stop = current;
shift = _delta - distance;
break;
} else {
distance += lockedTokens;

View File

@ -50,20 +50,22 @@ contract Wallet is Ownable {
/**
* @notice Calculate locked tokens value in next period
* @param _period Current or future period
* @param _currentLockedToken Current locked tokens
* @param _lockedTokens Locked tokens in specified period
* @param _periods Number of periods after _period that need to calculate
* @return Calculated locked tokens in next period
**/
function calculateLockedTokens(
uint256 _period,
uint256 _currentLockedToken
uint256 _lockedTokens,
uint256 _periods
)
public constant returns (uint256)
{
var nextPeriod = _period + 1;
var nextPeriod = _period + _periods;
if (releasePeriod <= nextPeriod) {
return 0;
} else {
return _currentLockedToken;
return _lockedTokens;
}
}
@ -72,7 +74,7 @@ contract Wallet is Ownable {
**/
function calculateLockedTokens() public constant returns (uint256) {
var period = block.number.div(blocksPerPeriod);
return calculateLockedTokens(period, getLockedTokens());
return calculateLockedTokens(period, getLockedTokens(), 1);
}
/**
@ -127,7 +129,7 @@ contract Wallet is Ownable {
}
}
// checks if owner can mine more tokens (before or after release period)
if (calculateLockedTokens(period, lockedValueToCheck) == 0) {
if (calculateLockedTokens(period, lockedValueToCheck, 1) == 0) {
return 0;
} else {
return lockedValueToCheck;

View File

@ -13,6 +13,7 @@ import "./Wallet.sol";
/**
* @notice Contract creates wallets and manage mining for that wallets
**/
// FIXME bug with big file size. Can't deploy contract even with maximum gas if uncomment
contract WalletManager is Miner, Ownable {
using LinkedList for LinkedList.Data;
using SafeERC20 for NuCypherKMSToken;
@ -82,7 +83,6 @@ contract WalletManager is Miner, Ownable {
confirmActivity(wallet.lockedValue());
}
// FIXME bug with big file size. Can't deploy contract even with maximum gas if uncomment
/**
* @notice Terminate contract and refund to owners
* @dev The called token contracts could try to re-enter this contract.
@ -227,28 +227,29 @@ contract WalletManager is Miner, Ownable {
}
/**
* @notice Fixedstep in cumsum
* @notice Fixed-step in cumulative sum
* @param _start Starting point
* @param _delta How much to step
* @param _periods Amount of periods to get locked tokens
* @dev
_start
v
|-------->*--------------->*---->*------------->|
| ^
| o_stop
| stop
|
| _delta
|---------------------------->|
|
| o_shift
| shift
| |----->|
**/
// _blockNumber?
function findCumSum(address _start, uint256 _delta)
public constant returns (address o_stop, uint256 o_shift)
function findCumSum(address _start, uint256 _delta, uint256 _periods)
public constant returns (address stop, uint256 shift)
{
require(walletOwners.valueExists(_start));
var currentPeriod = block.number / blocksPerPeriod;
require(walletOwners.valueExists(_start) && _periods > 0);
var currentPeriod = block.number.div(blocksPerPeriod);
uint256 distance = 0;
uint256 lockedTokens = 0;
var current = _start;
if (current == 0x0) {
@ -258,17 +259,23 @@ contract WalletManager is Miner, Ownable {
while (current != 0x0) {
var wallet = wallets[current];
var numberConfirmedPeriods = wallet.numberConfirmedPeriods();
if (numberConfirmedPeriods == 0 ||
wallet.getConfirmedPeriod(numberConfirmedPeriods - 1) != currentPeriod &&
(numberConfirmedPeriods == 1 ||
wallet.getConfirmedPeriod(numberConfirmedPeriods - 2) != currentPeriod)) {
uint256 lockedTokens = 0;
if (numberConfirmedPeriods > 0 &&
wallet.getConfirmedPeriod(numberConfirmedPeriods - 1) == currentPeriod) {
lockedTokens = wallet.getConfirmedPeriodValue(numberConfirmedPeriods - 1);
} else if (numberConfirmedPeriods > 1 &&
wallet.getConfirmedPeriod(numberConfirmedPeriods - 2) == currentPeriod) {
lockedTokens = wallet.getConfirmedPeriodValue(numberConfirmedPeriods - 2);
}
if (lockedTokens == 0) {
current = walletOwners.step(current, true);
continue;
}
lockedTokens = wallet.lockedValue();
lockedTokens = wallet.calculateLockedTokens(currentPeriod, lockedTokens, _periods);
if (_delta < distance + lockedTokens) {
o_stop = current;
o_shift = _delta - distance;
stop = current;
shift = _delta - distance;
break;
} else {
distance += lockedTokens;

View File

@ -17,7 +17,7 @@ def escrow(web3, chain, token):
creator = web3.eth.accounts[0]
# Creator deploys the escrow
escrow, _ = chain.provider.get_or_deploy_contract(
'Escrow', deploy_args=[token.address, 10 ** 9, 50],
'Escrow', deploy_args=[token.address, 10 ** 9, 50, 2],
deploy_transaction={'from': creator})
return escrow
@ -175,40 +175,55 @@ def test_escrow(web3, chain, token, escrow):
def test_locked_distribution(web3, chain, token, escrow):
NULL_ADDR = '0x' + '0' * 40
creator = web3.eth.accounts[0]
miners = web3.eth.accounts[1:]
amount = token.call().balanceOf(creator) // 2
largest_locked = amount
# Airdrop
for miner in web3.eth.accounts[1:]:
for miner in miners:
tx = token.transact({'from': creator}).transfer(miner, amount)
chain.wait.for_receipt(tx)
amount = amount // 2
# Lock
for addr in web3.eth.accounts[1:][::-1]:
balance = token.call().balanceOf(addr)
tx = token.transact({'from': addr}).approve(escrow.address, balance)
for index, miner in enumerate(miners[::-1]):
balance = token.call().balanceOf(miner)
tx = token.transact({'from': miner}).approve(escrow.address, balance)
chain.wait.for_receipt(tx)
tx = escrow.transact({'from': addr}).deposit(balance, 100)
tx = escrow.transact({'from': miner}).deposit(balance, len(miners) - index + 1)
chain.wait.for_receipt(tx)
# Check current period
address_stop, shift = escrow.call().findCumSum(NULL_ADDR, 1, 1)
assert NULL_ADDR == address_stop.lower()
assert 0 == shift
# Wait next period
chain.wait.for_block(web3.eth.blockNumber + 50)
n_locked = escrow.call().getAllLockedTokens()
assert n_locked > 0
address_stop, shift = escrow.call().findCumSum(NULL_ADDR, n_locked // 3)
assert address_stop.lower() == web3.eth.accounts[1].lower()
assert shift == n_locked // 3
address_stop, shift = escrow.call().findCumSum(NULL_ADDR, n_locked // 3, 1)
assert miners[0].lower() == address_stop.lower()
assert n_locked // 3 == shift
address_stop, shift = escrow.call().findCumSum(NULL_ADDR, largest_locked)
assert address_stop.lower() == web3.eth.accounts[2].lower()
assert shift == 0
address_stop, shift = escrow.call().findCumSum(NULL_ADDR, largest_locked, 1)
assert miners[1].lower() == address_stop.lower()
assert 0 == shift
address_stop, shift = escrow.call().findCumSum(
web3.eth.accounts[2], largest_locked // 2 + 1)
assert address_stop.lower() == web3.eth.accounts[3].lower()
assert shift == 1
miners[1], largest_locked // 2 + 1, 1)
assert miners[2].lower() == address_stop.lower()
assert 1 == shift
address_stop, shift = escrow.call().findCumSum(NULL_ADDR, 1, 10)
assert NULL_ADDR == address_stop.lower()
assert 0 == shift
for index, _ in enumerate(miners[:-1]):
address_stop, shift = escrow.call().findCumSum(NULL_ADDR, 1, index + 2)
assert miners[index + 1].lower() == address_stop.lower()
assert 1 == shift
def test_mining(web3, chain, token, escrow):

View File

@ -205,42 +205,58 @@ def test_escrow(web3, chain, token, wallet_manager):
def test_locked_distribution(web3, chain, token, wallet_manager):
NULL_ADDR = '0x' + '0' * 40
creator = web3.eth.accounts[0]
miners = web3.eth.accounts[1:]
amount = token.call().balanceOf(creator) // 2
largest_locked = amount
# Airdrop
for miner in web3.eth.accounts[1:]:
for miner in miners:
tx = token.transact({'from': creator}).transfer(miner, amount)
chain.wait.for_receipt(tx)
amount = amount // 2
# Lock
for addr in web3.eth.accounts[1:][::-1]:
balance = token.call().balanceOf(addr)
tx = wallet_manager.transact({'from': addr}).createWallet()
for index, miner in enumerate(miners[::-1]):
balance = token.call().balanceOf(miner)
tx = wallet_manager.transact({'from': miner}).createWallet()
chain.wait.for_receipt(tx)
wallet = wallet_manager.call().wallets(addr)
tx = token.transact({'from': addr}).transfer(wallet, balance)
wallet = wallet_manager.call().wallets(miner)
tx = token.transact({'from': miner}).transfer(wallet, balance)
chain.wait.for_receipt(tx)
tx = wallet_manager.transact({'from': addr}).lock(balance, 100)
tx = wallet_manager.transact({'from': miner}).deposit(balance, len(miners) - index + 1)
chain.wait.for_receipt(tx)
# Check current period
address_stop, shift = wallet_manager.call().findCumSum(NULL_ADDR, 1, 1)
assert NULL_ADDR == address_stop.lower()
assert 0 == shift
# Wait next period
chain.wait.for_block(web3.eth.blockNumber + 50)
n_locked = wallet_manager.call().getAllLockedTokens()
assert n_locked > 0
address_stop, shift = wallet_manager.call().findCumSum(NULL_ADDR, n_locked // 3)
assert address_stop.lower() == web3.eth.accounts[1].lower()
assert shift == n_locked // 3
address_stop, shift = wallet_manager.call().findCumSum(NULL_ADDR, n_locked // 3, 1)
assert miners[0].lower() == address_stop.lower()
assert n_locked // 3 == shift
address_stop, shift = wallet_manager.call().findCumSum(NULL_ADDR, largest_locked)
assert address_stop.lower() == web3.eth.accounts[2].lower()
assert shift == 0
address_stop, shift = wallet_manager.call().findCumSum(NULL_ADDR, largest_locked, 1)
assert miners[1].lower() == address_stop.lower()
assert 0 == shift
address_stop, shift = wallet_manager.call().findCumSum(
web3.eth.accounts[2], largest_locked // 2 + 1)
assert address_stop.lower() == web3.eth.accounts[3].lower()
assert shift == 1
miners[1], largest_locked // 2 + 1, 1)
assert miners[2].lower() == address_stop.lower()
assert 1 == shift
address_stop, shift = wallet_manager.call().findCumSum(NULL_ADDR, 1, 10)
assert NULL_ADDR == address_stop.lower()
assert 0 == shift
for index, _ in enumerate(miners[:-1]):
address_stop, shift = wallet_manager.call().findCumSum(NULL_ADDR, 1, index + 2)
assert miners[index + 1].lower() == address_stop.lower()
assert 1 == shift
@pytest.mark.skip(reason="too big contract")