[KMS-ETH]- Changed period in blocks to period in hours, small changes in mining reward

pull/195/head^2
szotov 2018-01-19 17:27:09 +03:00
parent 2c8263f941
commit 7d64ec8f79
11 changed files with 147 additions and 1308 deletions

View File

@ -4,15 +4,15 @@ from nkms_eth import blockchain
from nkms_eth import token
ESCROW_NAME = 'Escrow'
BLOCKS_PER_PERIOD = 50 # 6100
HOURS_PER_PERIOD = 1 # 24
MIN_RELEASE_PERIODS = 1 # 30
MAX_AWARDED_PERIODS = 365
MINING_COEFF = [
BLOCKS_PER_PERIOD * MAX_AWARDED_PERIODS * 10 ** 9,
BLOCKS_PER_PERIOD,
MIN_RELEASE_PERIODS,
BLOCKS_PER_PERIOD * MAX_AWARDED_PERIODS,
MAX_AWARDED_PERIODS
HOURS_PER_PERIOD,
2 * 10 ** 7,
MAX_AWARDED_PERIODS,
MAX_AWARDED_PERIODS,
MIN_RELEASE_PERIODS
]
NULL_ADDR = '0x' + '0' * 40

View File

@ -43,36 +43,38 @@ contract Escrow is Miner, Ownable {
uint256 constant MAX_PERIODS = 100;
uint256 constant MAX_OWNERS = 50000;
NuCypherKMSToken token;
mapping (address => TokenInfo) public tokenInfo;
LinkedList.Data tokenOwners;
uint256 public blocksPerPeriod;
mapping (uint256 => PeriodInfo) public lockedPerPeriod;
uint256 public minReleasePeriods;
/**
* @notice The Escrow constructor sets address of token contract and coefficients for mining
* @param _token Token contract
* @param _hoursPerPeriod Size of period in hours
* @param _miningCoefficient Mining coefficient
* @param _blocksPerPeriod Size of one period in blocks
* @param _minReleasePeriods Min amount of periods during which tokens will be released
* @param _lockedBlocksCoefficient Locked blocks coefficient
* @param _lockedPeriodsCoefficient Locked blocks coefficient
* @param _awardedPeriods Max periods that will be additionally awarded
**/
function Escrow(
NuCypherKMSToken _token,
uint256 _hoursPerPeriod,
uint256 _miningCoefficient,
uint256 _blocksPerPeriod,
uint256 _minReleasePeriods,
uint256 _lockedBlocksCoefficient,
uint256 _awardedPeriods
uint256 _lockedPeriodsCoefficient,
uint256 _awardedPeriods,
uint256 _minReleasePeriods
)
Miner(_token, _miningCoefficient, _lockedBlocksCoefficient, _awardedPeriods * _blocksPerPeriod)
Miner(
_token,
_hoursPerPeriod,
_miningCoefficient,
_lockedPeriodsCoefficient,
_awardedPeriods
)
{
require(_blocksPerPeriod != 0 && _minReleasePeriods != 0);
token = _token;
blocksPerPeriod = _blocksPerPeriod;
require(_minReleasePeriods != 0);
minReleasePeriods = _minReleasePeriods;
}
@ -83,7 +85,7 @@ contract Escrow is Miner, Ownable {
function getLockedTokens(address _owner)
public constant returns (uint256)
{
var currentPeriod = block.number.div(blocksPerPeriod);
var currentPeriod = getCurrentPeriod();
var info = tokenInfo[_owner];
var numberConfirmedPeriods = info.numberConfirmedPeriods;
@ -121,8 +123,7 @@ contract Escrow is Miner, Ownable {
function getAllLockedTokens()
public constant returns (uint256)
{
var period = block.number.div(blocksPerPeriod);
return lockedPerPeriod[period].totalLockedValue;
return lockedPerPeriod[getCurrentPeriod()].totalLockedValue;
}
/**
@ -160,7 +161,7 @@ contract Escrow is Miner, Ownable {
public constant returns (uint256)
{
require(_periods > 0);
var currentPeriod = block.number.div(blocksPerPeriod);
var currentPeriod = getCurrentPeriod();
var nextPeriod = currentPeriod.add(_periods);
var info = tokenInfo[_owner];
@ -225,7 +226,7 @@ contract Escrow is Miner, Ownable {
require(_value <= token.balanceOf(address(this)) &&
_value <= info.value.sub(lockedTokens));
var currentPeriod = block.number.div(blocksPerPeriod);
var currentPeriod = getCurrentPeriod();
if (lockedTokens == 0) {
info.lockedValue = _value;
info.maxReleasePeriods = Math.max256(_periods, minReleasePeriods);
@ -305,7 +306,7 @@ contract Escrow is Miner, Ownable {
function confirmActivity(uint256 _lockedValue) internal {
require(_lockedValue > 0);
var info = tokenInfo[msg.sender];
var nextPeriod = block.number.div(blocksPerPeriod) + 1;
var nextPeriod = getCurrentPeriod() + 1;
var numberConfirmedPeriods = info.numberConfirmedPeriods;
if (numberConfirmedPeriods > 0 &&
@ -335,14 +336,14 @@ contract Escrow is Miner, Ownable {
**/
function confirmActivity() external {
var info = tokenInfo[msg.sender];
var nextPeriod = block.number.div(blocksPerPeriod) + 1;
var currentPeriod = getCurrentPeriod();
var nextPeriod = currentPeriod + 1;
if (info.numberConfirmedPeriods > 0 &&
info.confirmedPeriods[info.numberConfirmedPeriods - 1].period >= nextPeriod) {
return;
}
var currentPeriod = nextPeriod - 1;
var lockedTokens = calculateLockedTokens(
msg.sender, false, getLockedTokens(msg.sender), 1);
confirmActivity(lockedTokens);
@ -352,18 +353,17 @@ contract Escrow is Miner, Ownable {
* @notice Mint tokens for sender for previous periods if he locked his tokens and confirmed activity
**/
function mint() external {
var previousPeriod = block.number.div(blocksPerPeriod).sub(1);
var previousPeriod = getCurrentPeriod().sub(1);
var info = tokenInfo[msg.sender];
var numberPeriodsForMinting = info.numberConfirmedPeriods;
require(numberPeriodsForMinting > 0 &&
info.confirmedPeriods[0].period <= previousPeriod);
var currentLockedValue = getLockedTokens(msg.sender);
var allLockedBlocks = calculateLockedPeriods(
var allLockedPeriods = calculateLockedPeriods(
msg.sender,
info.confirmedPeriods[numberPeriodsForMinting - 1].lockedValue)
.add(numberPeriodsForMinting)
.mul(blocksPerPeriod);
.add(numberPeriodsForMinting);
var decimals = info.decimals;
if (info.confirmedPeriods[numberPeriodsForMinting - 1].period > previousPeriod) {
@ -376,13 +376,13 @@ contract Escrow is Miner, Ownable {
for(uint i = 0; i < numberPeriodsForMinting; ++i) {
var period = info.confirmedPeriods[i].period;
var lockedValue = info.confirmedPeriods[i].lockedValue;
allLockedBlocks = allLockedBlocks.sub(blocksPerPeriod);
allLockedPeriods--;
(, decimals) = mint(
msg.sender,
previousPeriod,
lockedValue,
lockedPerPeriod[period].totalLockedValue,
blocksPerPeriod,
allLockedBlocks,
allLockedPeriods,
decimals);
if (lockedPerPeriod[period].numberOwnersToBeRewarded > 1) {
lockedPerPeriod[period].numberOwnersToBeRewarded--;
@ -424,7 +424,7 @@ contract Escrow is Miner, Ownable {
public constant returns (address stop, uint256 shift)
{
require(_periods > 0);
var currentPeriod = block.number.div(blocksPerPeriod);
var currentPeriod = getCurrentPeriod();
uint256 distance = 0;
var current = _start;

View File

@ -1,7 +1,7 @@
pragma solidity ^0.4.0;
import "./MineableToken.sol";
import "./NuCypherKMSToken.sol";
import "./zeppelin/math/SafeMath.sol";
@ -11,71 +11,90 @@ import "./zeppelin/math/SafeMath.sol";
contract Miner {
using SafeMath for uint256;
MineableToken token;
NuCypherKMSToken token;
uint256 public miningCoefficient;
uint256 public lockedBlocksCoefficient;
uint256 public awardedLockedBlocks;
uint256 public secondsPerPeriod;
uint256 public lockedPeriodsCoefficient;
uint256 public awardedPeriods;
uint256 public lastMintedPeriod;
uint256 public lastTotalSupply;
/**
* @notice The Miner constructor sets address of token contract and coefficients for mining
* @dev Formula for mining
(futureSupply - currentSupply) * (lockedBlocks * lockedValue / totalLockedValue) * (k1 + allLockedBlocks) / k2
if allLockedBlocks > awardedLockedBlocks then allLockedBlocks = awardedLockedBlocks
* @dev Formula for mining in one period
(futureSupply - currentSupply) * (lockedValue / totalLockedValue) * (k1 + allLockedPeriods) / k2
if allLockedPeriods > awardedPeriods then allLockedPeriods = awardedPeriods
* @param _token Token contract
* @param _hoursPerPeriod Size of period in hours
* @param _miningCoefficient Mining coefficient (k2)
* @param _lockedBlocksCoefficient Locked blocks coefficient (k1)
* @param _awardedLockedBlocks Max blocks that will be additionally awarded
* @param _lockedPeriodsCoefficient Locked blocks coefficient (k1)
* @param _awardedPeriods Max periods that will be additionally awarded
**/
function Miner(
MineableToken _token,
NuCypherKMSToken _token,
uint256 _hoursPerPeriod,
uint256 _miningCoefficient,
uint256 _lockedBlocksCoefficient,
uint256 _awardedLockedBlocks
uint256 _lockedPeriodsCoefficient,
uint256 _awardedPeriods
) {
require(address(_token) != 0x0 &&
_miningCoefficient != 0 &&
_lockedBlocksCoefficient != 0 &&
_awardedLockedBlocks != 0);
_hoursPerPeriod != 0 &&
_lockedPeriodsCoefficient != 0 &&
_awardedPeriods != 0);
token = _token;
miningCoefficient = _miningCoefficient;
lockedBlocksCoefficient = _lockedBlocksCoefficient;
awardedLockedBlocks = _awardedLockedBlocks;
secondsPerPeriod = _hoursPerPeriod.mul(1 hours);
lockedPeriodsCoefficient = _lockedPeriodsCoefficient;
awardedPeriods = _awardedPeriods;
}
/**
* @notice Function to mint tokens for sender
* @return Number of current period
**/
function getCurrentPeriod() public constant returns (uint256) {
return block.timestamp / secondsPerPeriod;
}
/**
* @notice Function to mint tokens for sender for one period.
* @param _to The address that will receive the minted tokens.
* @param _lockedValue The amount of tokens that were locked by user.
* @param _totalLockedValue The amount of tokens that were locked by all users.
* @param _currentLockedBlocks The current amount of blocks during which tokens were locked.
* @param _allLockedBlocks The max amount of blocks during which tokens were locked.
* @param _period Period number.
* @param _lockedValue The amount of tokens that were locked by user in specified period.
* @param _totalLockedValue The amount of tokens that were locked by all users in specified period.
* @param _allLockedPeriods The max amount of periods during which tokens will be locked after specified period.
* @param _decimals The amount of locked tokens and blocks in decimals.
* @return Amount of minted tokens.
*/
function mint(
address _to,
uint256 _period,
uint256 _lockedValue,
uint256 _totalLockedValue,
uint256 _currentLockedBlocks,
uint256 _allLockedBlocks,
uint256 _allLockedPeriods,
uint256 _decimals
)
internal returns (uint256 amount, uint256 decimals)
{
//futureSupply * currentLockedBlocks * lockedValue * (k1 + allLockedBlocks) / (totalLockedValue * k2) -
//currentSupply * currentLockedBlocks * lockedValue * (k1 + allLockedBlocks) / (totalLockedValue * k2)
var allLockedBlocks = (_allLockedBlocks <= awardedLockedBlocks ?
_allLockedBlocks : awardedLockedBlocks).add(lockedBlocksCoefficient);
if (_period > lastMintedPeriod) {
lastTotalSupply = token.totalSupply();
lastMintedPeriod = _period;
}
//futureSupply * lockedValue * (k1 + allLockedPeriods) / (totalLockedValue * k2) -
//currentSupply * lockedValue * (k1 + allLockedPeriods) / (totalLockedValue * k2)
var allLockedPeriods = (_allLockedPeriods <= awardedPeriods ?
_allLockedPeriods : awardedPeriods)
.add(lockedPeriodsCoefficient);
var denominator = _totalLockedValue.mul(miningCoefficient);
var maxValue = token.futureSupply()
.mul(_currentLockedBlocks)
.mul(_lockedValue)
.mul(allLockedBlocks)
.mul(allLockedPeriods)
.div(denominator);
var value = token.totalSupply()
.mul(_currentLockedBlocks)
var value = lastTotalSupply
.mul(_lockedValue)
.mul(allLockedBlocks)
.mul(allLockedPeriods)
.div(denominator);
amount = maxValue.sub(value);
token.mint(_to, amount);

View File

@ -1,277 +0,0 @@
pragma solidity ^0.4.8;
import "./NuCypherKMSToken.sol";
import "./zeppelin/token/SafeERC20.sol";
import "./zeppelin/math/SafeMath.sol";
import "./zeppelin/math/Math.sol";
import "./zeppelin/ownership/Ownable.sol";
import "./lib/AdditionalMath.sol";
/**
* @notice Contract holds and locks client tokens.
**/
contract Wallet is Ownable {
using SafeERC20 for NuCypherKMSToken;
using SafeMath for uint256;
using AdditionalMath for uint256;
struct PeriodInfo {
uint256 period;
uint256 lockedValue;
}
address public manager;
// TODO maybe get from manager
NuCypherKMSToken public token;
uint256 public blocksPerPeriod;
uint256 public minReleasePeriods;
uint256 public lockedValue;
uint256 public decimals;
bool public release;
uint256 public maxReleasePeriods;
uint256 public releaseRate;
PeriodInfo[] public confirmedPeriods;
uint256 public numberConfirmedPeriods;
/**
* @dev Throws if called by any account other than the manager.
**/
modifier onlyManager() {
require(msg.sender == manager);
_;
}
/**
* @notice Wallet constructor set token contract
* @param _token Token contract
* @param _blocksPerPeriod Size of one period in blocks
* @param _minReleasePeriods Min amount of periods during which tokens will be released
**/
function Wallet(NuCypherKMSToken _token,
uint256 _blocksPerPeriod,
uint256 _minReleasePeriods
) {
token = _token;
manager = msg.sender;
blocksPerPeriod = _blocksPerPeriod;
minReleasePeriods = _minReleasePeriods;
}
/**
* @notice Get locked tokens value in current period
**/
function getLockedTokens()
public constant returns (uint256)
{
var period = block.number.div(blocksPerPeriod);
// no confirmed periods, so current period may be release period
if (numberConfirmedPeriods == 0) {
var lockedValueToCheck = lockedValue;
} else {
var i = numberConfirmedPeriods - 1;
var confirmedPeriod = confirmedPeriods[i].period;
// last confirmed period is current period
if (confirmedPeriod == period) {
return confirmedPeriods[i].lockedValue;
// last confirmed period is previous periods, so current period may be release period
} else if (confirmedPeriod < period) {
lockedValueToCheck = confirmedPeriods[i].lockedValue;
// penultimate confirmed period is previous or current period, so get its lockedValue
} else if (numberConfirmedPeriods > 1) {
return confirmedPeriods[numberConfirmedPeriods - 2].lockedValue;
// no previous periods, so return saved lockedValue
} else {
return lockedValue;
}
}
// checks if owner can mine more tokens (before or after release period)
if (calculateLockedTokens(false, lockedValueToCheck, 1) == 0) {
return 0;
} else {
return lockedValueToCheck;
}
}
/**
* @notice Calculate locked tokens value for owner in next period
* @param _forceRelease Force unlocking period calculation
* @param _lockedTokens Locked tokens in specified period
* @param _periods Number of periods that need to calculate
* @return Calculated locked tokens in next period
**/
function calculateLockedTokens(
bool _forceRelease,
uint256 _lockedTokens,
uint256 _periods
)
public constant returns (uint256)
{
if ((_forceRelease || release) && _periods != 0) {
var unlockedTokens = _periods.mul(releaseRate);
return unlockedTokens <= _lockedTokens ? _lockedTokens.sub(unlockedTokens) : 0;
} else {
return _lockedTokens;
}
}
/**
* @notice Calculate locked tokens value in next period
* @param _periods Number of periods after current that need to calculate
* @return Calculated locked tokens in next period
**/
function calculateLockedTokens(uint256 _periods)
public constant returns (uint256)
{
require(_periods > 0);
var currentPeriod = block.number.div(blocksPerPeriod);
var nextPeriod = currentPeriod.add(_periods);
if (numberConfirmedPeriods > 0 &&
confirmedPeriods[numberConfirmedPeriods - 1].period >= currentPeriod) {
var lockedTokens = confirmedPeriods[numberConfirmedPeriods - 1].lockedValue;
var period = confirmedPeriods[numberConfirmedPeriods - 1].period;
} else {
lockedTokens = getLockedTokens();
period = currentPeriod;
}
var periods = nextPeriod.sub(period);
return calculateLockedTokens(false, lockedTokens, periods);
}
/**
* @notice Calculate locked periods from start period
* @param _lockedTokens Locked tokens in start period
* @return Calculated locked periods
**/
function calculateLockedPeriods(uint256 _lockedTokens)
public constant returns (uint256)
{
return _lockedTokens.divCeil(releaseRate).sub(1);
}
/**
* @notice Lock some tokens
* @param _value Amount of tokens which should lock
* @param _periods Amount of periods during which tokens will be locked
**/
function lock(uint256 _value, uint256 _periods) onlyManager public {
require(_value != 0 || _periods != 0);
var lockedTokens = calculateLockedTokens(1);
require(_value <= token.balanceOf(address(this)).sub(lockedTokens));
var currentPeriod = block.number.div(blocksPerPeriod);
if (lockedTokens == 0) {
lockedValue = _value;
maxReleasePeriods = Math.max256(_periods, minReleasePeriods);
releaseRate = Math.max256(_value.divCeil(maxReleasePeriods), 1);
release = false;
} else {
lockedValue = lockedTokens.add(_value);
maxReleasePeriods = maxReleasePeriods.add(_periods);
releaseRate = Math.max256(
lockedValue.divCeil(maxReleasePeriods), releaseRate);
}
}
/**
* @notice Sets locked tokens
* @param _value Amount of tokens which should lock
**/
function updateLockedTokens(uint256 _value) onlyManager public {
lockedValue = _value;
}
/**
* @notice Switch lock
**/
function switchLock() onlyOwner public {
release = !release;
}
/**
* @notice Withdraw available amount of tokens back to owner
* @param _value Amount of token to withdraw
**/
function withdraw(uint256 _value) onlyOwner public {
// TODO optimize
var lockedTokens = Math.max256(calculateLockedTokens(1),
getLockedTokens());
require(_value <= token.balanceOf(address(this)).sub(lockedTokens));
token.safeTransfer(msg.sender, _value);
}
/**
* @notice Terminate contract and refund to owner
* @dev The called token contracts could try to re-enter this contract.
Only supply token contracts you trust.
**/
function destroy() onlyManager public {
token.safeTransfer(owner, token.balanceOf(address(this)));
selfdestruct(owner);
}
/**
* @notice Set minted decimals
**/
function setDecimals(uint256 _decimals) onlyManager {
decimals = _decimals;
}
/**
* @notice Get confirmed period
* @param _index Index of period
**/
function getConfirmedPeriod(uint256 _index) public constant returns (uint256) {
return confirmedPeriods[_index].period;
}
/**
* @notice Get locked value for confirmed period
* @param _index Index of period
**/
function getConfirmedPeriodValue(uint256 _index) public constant returns (uint256) {
return confirmedPeriods[_index].lockedValue;
}
/**
* @notice Set locked value for confirmed period
* @param _index Index of period
* @param _value Locked tokens
**/
function setConfirmedPeriodValue(uint256 _index, uint256 _value) onlyManager {
confirmedPeriods[_index].lockedValue = _value;
}
/**
* @notice Add period as confirmed
* @param _period Period to add
* @param _lockedValue Locked tokens for period
**/
function addConfirmedPeriod(uint256 _period, uint256 _lockedValue) onlyManager {
if (numberConfirmedPeriods < confirmedPeriods.length) {
confirmedPeriods[numberConfirmedPeriods].period = _period;
confirmedPeriods[numberConfirmedPeriods].lockedValue = _lockedValue;
} else {
confirmedPeriods.push(PeriodInfo(_period, _lockedValue));
}
numberConfirmedPeriods++;
}
/**
* @notice Clear periods array
* @param _number Number of periods to delete
**/
function deleteConfirmedPeriods(uint256 _number) onlyManager {
var newNumberConfirmedPeriods = numberConfirmedPeriods - _number;
for (uint256 i = 0; i < newNumberConfirmedPeriods; i++) {
confirmedPeriods[i] = confirmedPeriods[_number + i];
}
numberConfirmedPeriods = newNumberConfirmedPeriods;
}
}

View File

@ -1,274 +0,0 @@
pragma solidity ^0.4.8;
import "./zeppelin/token/SafeERC20.sol";
import "./zeppelin/math/Math.sol";
import "./zeppelin/ownership/Ownable.sol";
import "./NuCypherKMSToken.sol";
import "./lib/LinkedList.sol";
import "./Miner.sol";
import "./Wallet.sol";
/**
* @notice Contract creates wallets and manage mining for that wallets
**/
contract WalletManager is Miner, Ownable {
using LinkedList for LinkedList.Data;
using SafeERC20 for NuCypherKMSToken;
struct PeriodInfo {
uint256 totalLockedValue;
uint256 numberOwnersToBeRewarded;
}
uint256 constant MAX_PERIODS = 100;
uint256 constant MAX_OWNERS = 50000;
NuCypherKMSToken token;
mapping (address => Wallet) public wallets;
LinkedList.Data walletOwners;
uint256 public blocksPerPeriod;
mapping (uint256 => PeriodInfo) public lockedPerPeriod;
uint256 public minReleasePeriods;
/**
* @notice The WalletManager 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 _minReleasePeriods Min amount of periods during which tokens will be released
* @param _lockedBlocksCoefficient Locked blocks coefficient
* @param _awardedPeriods Max periods that will be additionally awarded
**/
function WalletManager(
NuCypherKMSToken _token,
uint256 _miningCoefficient,
uint256 _blocksPerPeriod,
uint256 _minReleasePeriods,
uint256 _lockedBlocksCoefficient,
uint256 _awardedPeriods
)
Miner(_token, _miningCoefficient, _lockedBlocksCoefficient, _awardedPeriods * _blocksPerPeriod)
{
require(_blocksPerPeriod != 0 && _minReleasePeriods != 0);
token = _token;
blocksPerPeriod = _blocksPerPeriod;
minReleasePeriods = _minReleasePeriods;
}
/**
* @dev Throws if called by account that have not wallet.
*/
modifier walletExists() {
require(walletOwners.valueExists(msg.sender));
_;
}
/**
* @notice Create wallet for user
* @return Address of created wallet
**/
function createWallet() public returns (address) {
require(!walletOwners.valueExists(msg.sender) &&
walletOwners.sizeOf() < MAX_OWNERS);
Wallet wallet = new Wallet(token, blocksPerPeriod, minReleasePeriods);
wallet.transferOwnership(msg.sender);
wallets[msg.sender] = wallet;
walletOwners.push(msg.sender, true);
return wallet;
}
/**
* @notice Get locked tokens value for all owners in current period
**/
function getAllLockedTokens()
public constant returns (uint256)
{
var period = block.number.div(blocksPerPeriod);
return lockedPerPeriod[period].totalLockedValue;
}
/**
* @notice Lock some tokens
* @param _value Amount of tokens which should lock
* @param _periods Amount of periods during which tokens will be locked
**/
function lock(uint256 _value, uint256 _periods) walletExists external {
// TODO add checking min _value
Wallet wallet = wallets[msg.sender];
wallet.lock(_value, _periods);
confirmActivity(wallet.lockedValue());
}
/**
* @notice Terminate contract and refund to owners
* @dev The called token contracts could try to re-enter this contract.
Only supply token contracts you trust.
**/
function destroy() onlyOwner external {
// Transfer tokens to owners
var current = walletOwners.step(0x0, true);
while (current != 0x0) {
wallets[current].destroy();
current = walletOwners.step(current, true);
}
token.safeTransfer(owner, token.balanceOf(address(this)));
// Transfer Eth to owner and terminate contract
selfdestruct(owner);
}
/**
* @notice Confirm activity for future period
* @param _lockedValue Locked tokens in future period
**/
function confirmActivity(uint256 _lockedValue) internal {
require(_lockedValue > 0);
var wallet = wallets[msg.sender];
var nextPeriod = block.number.div(blocksPerPeriod) + 1;
var numberConfirmedPeriods = wallet.numberConfirmedPeriods();
if (numberConfirmedPeriods > 0 &&
wallet.getConfirmedPeriod(numberConfirmedPeriods - 1) == nextPeriod) {
var confirmedPeriodValue = wallet.getConfirmedPeriodValue(numberConfirmedPeriods - 1);
lockedPerPeriod[nextPeriod].totalLockedValue = lockedPerPeriod[nextPeriod].totalLockedValue
.add(_lockedValue.sub(confirmedPeriodValue));
wallet.setConfirmedPeriodValue(numberConfirmedPeriods - 1, _lockedValue);
return;
}
require(numberConfirmedPeriods < MAX_PERIODS);
lockedPerPeriod[nextPeriod].totalLockedValue += _lockedValue;
lockedPerPeriod[nextPeriod].numberOwnersToBeRewarded++;
wallet.addConfirmedPeriod(nextPeriod, _lockedValue);
}
/**
* @notice Confirm activity for future period
**/
function confirmActivity() walletExists external {
var wallet = wallets[msg.sender];
var nextPeriod = block.number.div(blocksPerPeriod) + 1;
var numberConfirmedPeriods = wallet.numberConfirmedPeriods();
if (numberConfirmedPeriods > 0 &&
wallet.getConfirmedPeriod(numberConfirmedPeriods - 1) >= nextPeriod) {
return;
}
var currentPeriod = nextPeriod - 1;
var lockedTokens = wallet.calculateLockedTokens(
false, wallet.getLockedTokens(), 1);
confirmActivity(lockedTokens);
}
/**
* @notice Mint tokens for sender for previous periods if he locked his tokens and confirmed activity
**/
function mint() walletExists external {
var previousPeriod = block.number.div(blocksPerPeriod).sub(1);
var wallet = wallets[msg.sender];
var numberPeriodsForMinting = wallet.numberConfirmedPeriods();
require(numberPeriodsForMinting > 0 &&
wallet.getConfirmedPeriod(0) <= previousPeriod);
var currentLockedValue = wallet.getLockedTokens();
var allLockedBlocks = wallet.calculateLockedPeriods(
wallet.getConfirmedPeriodValue(numberPeriodsForMinting - 1))
.add(numberPeriodsForMinting)
.mul(blocksPerPeriod);
var decimals = wallet.decimals();
if (wallet.getConfirmedPeriod(numberPeriodsForMinting - 1) > previousPeriod) {
numberPeriodsForMinting--;
}
if (wallet.getConfirmedPeriod(numberPeriodsForMinting - 1) > previousPeriod) {
numberPeriodsForMinting--;
}
for(uint i = 0; i < numberPeriodsForMinting; ++i) {
var (period, lockedValue) = wallet.confirmedPeriods(i);
allLockedBlocks = allLockedBlocks.sub(blocksPerPeriod);
(, decimals) = mint(
wallet,
lockedValue,
lockedPerPeriod[period].totalLockedValue,
blocksPerPeriod,
allLockedBlocks,
decimals);
if (lockedPerPeriod[period].numberOwnersToBeRewarded > 1) {
lockedPerPeriod[period].numberOwnersToBeRewarded--;
} else {
delete lockedPerPeriod[period];
}
}
wallet.setDecimals(decimals);
wallet.deleteConfirmedPeriods(numberPeriodsForMinting);
// Update lockedValue for current period
wallet.updateLockedTokens(currentLockedValue);
}
/**
* @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
|-------->*--------------->*---->*------------->|
| ^
| stop
|
| _delta
|---------------------------->|
|
| shift
| |----->|
**/
function findCumSum(address _start, uint256 _delta, uint256 _periods)
public constant returns (address stop, uint256 shift)
{
require(walletOwners.valueExists(_start) && _periods > 0);
var currentPeriod = block.number.div(blocksPerPeriod);
uint256 distance = 0;
var current = _start;
if (current == 0x0) {
current = walletOwners.step(current, true);
}
while (current != 0x0) {
var wallet = wallets[current];
var numberConfirmedPeriods = wallet.numberConfirmedPeriods();
var period = currentPeriod;
if (numberConfirmedPeriods > 0 &&
wallet.getConfirmedPeriod(numberConfirmedPeriods - 1) == currentPeriod) {
var lockedTokens = wallet.calculateLockedTokens(
true,
wallet.getConfirmedPeriodValue(numberConfirmedPeriods - 1),
_periods);
} else if (numberConfirmedPeriods > 1 &&
wallet.getConfirmedPeriod(numberConfirmedPeriods - 2) == currentPeriod) {
lockedTokens = wallet.calculateLockedTokens(
true,
wallet.getConfirmedPeriodValue(numberConfirmedPeriods - 1),
_periods - 1);
} else {
current = walletOwners.step(current, true);
continue;
}
if (_delta < distance + lockedTokens) {
stop = current;
shift = _delta - distance;
break;
} else {
distance += lockedTokens;
current = walletOwners.step(current, true);
}
}
}
}

View File

@ -1,159 +0,0 @@
"""Deploy contracts in tester.
A simple Python script to deploy contracts and then estimate gas for different methods.
"""
from populus import Project
from populus.utils.wait import wait_for_transaction_receipt
from web3 import Web3
def check_succesful_tx(web3: Web3, txid: str, timeout=180) -> dict:
"""See if transaction went through (Solidity code did not throw).
:return: Transaction receipt
"""
# http://ethereum.stackexchange.com/q/6007/620
receipt = wait_for_transaction_receipt(web3, txid, timeout=timeout)
txinfo = web3.eth.getTransaction(txid)
# EVM has only one error mode and it's consume all gas
assert txinfo["gas"] != receipt["gasUsed"]
return receipt
def main():
project = Project()
chain_name = "tester"
print("Make sure {} chain is running, you can connect to it, or you'll get timeout".format(chain_name))
with project.get_chain(chain_name) as chain:
web3 = chain.web3
print("Web3 providers are", web3.providers)
creator = web3.eth.accounts[0]
ursula = web3.eth.accounts[1]
alice = web3.eth.accounts[2]
# Create an ERC20 token
token, txhash = chain.provider.get_or_deploy_contract(
'HumanStandardToken', deploy_args=[
10 ** 9, 10 ** 10, 'NuCypher KMS', 6, 'KMS'],
deploy_transaction={
'from': creator})
check_succesful_tx(web3, txhash)
print("Deploying HumanStandardToken, tx hash is", txhash)
# Creator deploys the escrow
escrow, txhash = chain.provider.get_or_deploy_contract(
'Escrow', deploy_args=[token.address, 10 ** 5, 10 ** 7],
deploy_transaction={'from': creator})
check_succesful_tx(web3, txhash)
print("Deploying Escrow, tx hash is", txhash)
# Give Ursula and Alice some coins
tx = token.transact({'from': creator}).transfer(ursula, 10000)
chain.wait.for_receipt(tx)
tx = token.transact({'from': creator}).transfer(alice, 10000)
chain.wait.for_receipt(tx)
print("Estimate gas for checking balance = " + str(token.estimateGas().balanceOf(alice)))
# Ursula and Alice give Escrow rights to transfer
print("Estimate gas for approving = " +
str(token.estimateGas({'from': ursula}).approve(escrow.address, 1000)))
tx = token.transact({'from': ursula}).approve(escrow.address, 1000)
chain.wait.for_receipt(tx)
tx = token.transact({'from': alice}).approve(escrow.address, 500)
chain.wait.for_receipt(tx)
# Ursula and Alice transfer some tokens to the escrow and lock them
print("Estimate gas for deposit = " +
str(escrow.estimateGas({'from': ursula}).deposit(1000, 100)))
tx = escrow.transact({'from': ursula}).deposit(1000, 100)
chain.wait.for_receipt(tx)
tx = escrow.transact({'from': alice}).deposit(500, 200)
chain.wait.for_receipt(tx)
# Give rights for mining
print("Estimate gas for giving rights for mining = " +
str(token.estimateGas({'from': creator}).addMiner(escrow.address)))
tx = token.transact({'from': creator}).addMiner(escrow.address)
chain.wait.for_receipt(tx)
# Wait 150 blocks and mint tokens
chain.wait.for_block(web3.eth.blockNumber + 150)
print("Estimate gas for Ursula mining = " +
str(escrow.estimateGas({'from': ursula}).mint()))
tx = escrow.transact({'from': ursula}).mint()
chain.wait.for_receipt(tx)
# Wait 100 blocks and mint tokens
chain.wait.for_block(web3.eth.blockNumber + 100)
print("Estimate gas for Alice mining = " +
str(escrow.estimateGas({'from': alice}).mint()))
tx = escrow.transact({'from': alice}).mint()
chain.wait.for_receipt(tx)
# Creator deploys the wallet manager
wallet_manager, txhash = chain.provider.get_or_deploy_contract(
'WalletManager', deploy_args=[token.address, 10 ** 5, 10 ** 7],
deploy_transaction={'from': creator})
check_succesful_tx(web3, txhash)
print("Deploying WalletManager, tx hash is", txhash)
print("Estimate gas for creating wallet = " +
str(wallet_manager.estimateGas({'from': ursula}).createWallet()))
contract_factory = chain.provider.get_contract_factory("Wallet")
tx = wallet_manager.transact({'from': ursula}).createWallet()
chain.wait.for_receipt(tx)
ursula_wallet = contract_factory(address=wallet_manager.call().wallets(ursula))
tx = wallet_manager.transact({'from': alice}).createWallet()
chain.wait.for_receipt(tx)
alice_wallet = contract_factory(address=wallet_manager.call().wallets(alice))
# Give Ursula and Alice some coins
tx = token.transact({'from': creator}).transfer(ursula, 10000)
chain.wait.for_receipt(tx)
tx = token.transact({'from': creator}).transfer(alice, 10000)
chain.wait.for_receipt(tx)
# Ursula and Alice transfer some money to wallets
print("Estimate gas for deposit = " +
str(token.estimateGas({'from': ursula}).transfer(ursula_wallet.address, 1000)))
tx = token.transact({'from': ursula}).transfer(ursula_wallet.address, 1000)
chain.wait.for_receipt(tx)
tx = token.transact({'from': alice}).transfer(alice_wallet.address, 500)
chain.wait.for_receipt(tx)
# Ursula and Alice lock some tokens for 100 and 200 blocks
print("Estimate gas for locking = " +
str(wallet_manager.estimateGas({'from': ursula}).lock(1000, 100)))
tx = wallet_manager.transact({'from': ursula}).lock(1000, 100)
chain.wait.for_receipt(tx)
tx = wallet_manager.transact({'from': alice}).lock(500, 200)
chain.wait.for_receipt(tx)
# Give rights for mining
tx = token.transact({'from': creator}).addMiner(wallet_manager.address)
chain.wait.for_receipt(tx)
# Wait 150 blocks and mint tokens
chain.wait.for_block(web3.eth.blockNumber + 150)
print("Estimate gas for Ursula mining = " +
str(wallet_manager.estimateGas({'from': ursula}).mint()))
tx = wallet_manager.transact({'from': ursula}).mint()
chain.wait.for_receipt(tx)
# Wait 100 blocks and mint tokens
chain.wait.for_block(web3.eth.blockNumber + 100)
print("Estimate gas for Alice mining = " +
str(wallet_manager.estimateGas({'from': alice}).mint()))
tx = wallet_manager.transact({'from': alice}).mint()
chain.wait.for_receipt(tx)
print("All done!")
if __name__ == "__main__":
main()

View File

@ -2,7 +2,7 @@ pragma solidity ^0.4.11;
import "contracts/Miner.sol";
import "contracts/MineableToken.sol";
import "contracts/NuCypherKMSToken.sol";
/**
@ -11,31 +11,38 @@ import "contracts/MineableToken.sol";
contract MinerTest is Miner {
function MinerTest(
MineableToken _token,
NuCypherKMSToken _token,
uint256 _hoursPerPeriod,
uint256 _miningCoefficient,
uint256 _lockedBlocksCoefficient,
uint256 _awardedLockedBlocks
uint256 _lockedPeriodsCoefficient,
uint256 _awardedPeriods
)
Miner(_token, _miningCoefficient, _lockedBlocksCoefficient, _awardedLockedBlocks)
Miner(
_token,
_hoursPerPeriod,
_miningCoefficient,
_lockedPeriodsCoefficient,
_awardedPeriods
)
{
}
function testMint(
address _to,
uint256 _period,
uint256 _lockedValue,
uint256 _totalLockedValue,
uint256 _lockedBlocks,
uint256 _allLockedBlocks,
uint256 _allLockedPeriods,
uint256 _decimals
)
public returns (uint256 amount, uint256 decimals)
{
return mint(
_to,
_period,
_lockedValue,
_totalLockedValue,
_lockedBlocks,
_allLockedBlocks,
_allLockedPeriods,
_decimals);
}

View File

@ -17,11 +17,19 @@ 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, 4 * 50 * 10 ** 9, 50, 2, 4 * 50, 4],
'Escrow', deploy_args=[token.address, 1, 4 * 2 * 10 ** 7, 4, 4, 2],
deploy_transaction={'from': creator})
return escrow
def wait_time(chain, wait_hours):
web3 = chain.web3
step = 50
end_timestamp = web3.eth.getBlock(web3.eth.blockNumber).timestamp + wait_hours * 60 * 60
while web3.eth.getBlock(web3.eth.blockNumber).timestamp < end_timestamp:
chain.wait.for_block(web3.eth.blockNumber + step)
def test_escrow(web3, chain, token, escrow):
creator = web3.eth.accounts[0]
ursula = web3.eth.accounts[1]
@ -85,7 +93,7 @@ def test_escrow(web3, chain, token, escrow):
assert 500 == escrow.call().calculateLockedTokens(alice, 1)
# Checks locked tokens in next period
chain.wait.for_block(web3.eth.blockNumber + 50)
wait_time(chain, 1)
assert 1000 == escrow.call().getLockedTokens(ursula)
assert 500 == escrow.call().getLockedTokens(alice)
assert 1500 == escrow.call().getAllLockedTokens()
@ -110,14 +118,14 @@ def test_escrow(web3, chain, token, escrow):
chain.wait.for_receipt(tx)
assert 750 == escrow.call().calculateLockedTokens(ursula, 2)
# Wait 50 blocks and checks locking
chain.wait.for_block(web3.eth.blockNumber + 50)
# Wait 1 period and checks locking
wait_time(chain, 1)
assert 1500 == escrow.call().getLockedTokens(ursula)
# Confirm activity and wait 50 blocks
# Confirm activity and wait 1 period
tx = escrow.transact({'from': ursula}).confirmActivity()
chain.wait.for_receipt(tx)
chain.wait.for_block(web3.eth.blockNumber + 50)
wait_time(chain, 1)
assert 750 == escrow.call().getLockedTokens(ursula)
assert 0 == escrow.call().calculateLockedTokens(ursula, 1)
@ -147,7 +155,7 @@ def test_escrow(web3, chain, token, escrow):
chain.wait.for_receipt(tx)
assert 300 == escrow.call().calculateLockedTokens(ursula, 2)
assert 0 == escrow.call().calculateLockedTokens(ursula, 3)
chain.wait.for_block(web3.eth.blockNumber + 50)
wait_time(chain, 1)
assert 600 == escrow.call().getLockedTokens(ursula)
assert 300 == escrow.call().calculateLockedTokens(ursula, 1)
assert 0 == escrow.call().calculateLockedTokens(ursula, 2)
@ -160,7 +168,7 @@ def test_escrow(web3, chain, token, escrow):
assert 500 == escrow.call().calculateLockedTokens(ursula, 2)
assert 200 == escrow.call().calculateLockedTokens(ursula, 3)
assert 0 == escrow.call().calculateLockedTokens(ursula, 4)
chain.wait.for_block(web3.eth.blockNumber + 50)
wait_time(chain, 1)
assert 800 == escrow.call().getLockedTokens(ursula)
# Alice can't deposit too low value (less then rate)
@ -178,10 +186,10 @@ def test_escrow(web3, chain, token, escrow):
assert 1000 == escrow.call().calculateLockedTokens(alice, 1)
assert 500 == escrow.call().calculateLockedTokens(alice, 2)
assert 0 == escrow.call().calculateLockedTokens(alice, 3)
chain.wait.for_block(web3.eth.blockNumber + 50)
wait_time(chain, 1)
assert 1000 == escrow.call().getLockedTokens(alice)
# And increases locked blocks
# And increases locked time
tx = escrow.transact({'from': alice}).lock(0, 2)
chain.wait.for_receipt(tx)
assert 1000 == escrow.call().getLockedTokens(alice)
@ -235,7 +243,7 @@ def test_locked_distribution(web3, chain, token, escrow):
assert 0 == shift
# Wait next period
chain.wait.for_block(web3.eth.blockNumber + 50)
wait_time(chain, 1)
n_locked = escrow.call().getAllLockedTokens()
assert n_locked > 0
@ -315,7 +323,7 @@ def test_mining(web3, chain, token, escrow):
chain.wait.for_receipt(tx)
# Only Ursula confirm next period
chain.wait.for_block(web3.eth.blockNumber + 50)
wait_time(chain, 1)
assert 1500 == escrow.call().getAllLockedTokens()
tx = escrow.transact({'from': ursula}).confirmActivity()
chain.wait.for_receipt(tx)
@ -325,7 +333,7 @@ def test_mining(web3, chain, token, escrow):
chain.wait.for_receipt(tx)
# Ursula and Alice mint tokens for last periods
chain.wait.for_block(web3.eth.blockNumber + 50)
wait_time(chain, 1)
assert 1000 == escrow.call().getAllLockedTokens()
tx = escrow.transact({'from': ursula}).mint()
chain.wait.for_receipt(tx)
@ -341,7 +349,7 @@ def test_mining(web3, chain, token, escrow):
chain.wait.for_receipt(tx)
# Ursula can't confirm next period because end of locking
chain.wait.for_block(web3.eth.blockNumber + 50)
wait_time(chain, 1)
assert 500 == escrow.call().getAllLockedTokens()
with pytest.raises(TransactionFailed):
tx = escrow.transact({'from': ursula}).confirmActivity()
@ -352,7 +360,7 @@ def test_mining(web3, chain, token, escrow):
chain.wait.for_receipt(tx)
# Ursula mint tokens for next period
chain.wait.for_block(web3.eth.blockNumber + 50)
wait_time(chain, 1)
assert 500 == escrow.call().getAllLockedTokens()
tx = escrow.transact({'from': ursula}).mint()
chain.wait.for_receipt(tx)
@ -368,7 +376,7 @@ def test_mining(web3, chain, token, escrow):
chain.wait.for_receipt(tx)
tx = escrow.transact({'from': alice}).confirmActivity()
chain.wait.for_receipt(tx)
chain.wait.for_block(web3.eth.blockNumber + 100)
wait_time(chain, 2)
assert 0 == escrow.call().getAllLockedTokens()
tx = escrow.transact({'from': alice}).mint()
chain.wait.for_receipt(tx)
@ -419,4 +427,4 @@ def test_mining(web3, chain, token, escrow):
chain.wait.for_receipt(tx)
assert 10134 == token.call().balanceOf(alice)
# TODO test max confirmed periods
# TODO test max confirmed periods and miners

View File

@ -1,478 +0,0 @@
import pytest
from ethereum.tester import TransactionFailed
@pytest.fixture()
def token(web3, chain):
creator = web3.eth.accounts[0]
# Create an ERC20 token
token_contract, _ = chain.provider.get_or_deploy_contract(
'NuCypherKMSToken', deploy_args=[10 ** 9, 2 * 10 ** 9],
deploy_transaction={'from': creator})
return token_contract
@pytest.fixture()
def wallet_manager(web3, chain, token):
creator = web3.eth.accounts[0]
# Creator deploys the wallet manager
wallet_manager_contract, _ = chain.provider.get_or_deploy_contract(
'WalletManager', deploy_args=[token.address, 4 * 50 * 10 ** 9, 50, 2, 4 * 50, 4],
deploy_transaction={'from': creator, 'gas': 4000000})
return wallet_manager_contract
def test_escrow(web3, chain, token, wallet_manager):
creator = web3.eth.accounts[0]
ursula = web3.eth.accounts[1]
alice = web3.eth.accounts[2]
# Give Ursula and Alice some coins
tx = token.transact({'from': creator}).transfer(ursula, 10000)
chain.wait.for_receipt(tx)
tx = token.transact({'from': creator}).transfer(alice, 10000)
chain.wait.for_receipt(tx)
assert 10000 == token.call().balanceOf(ursula)
assert 10000 == token.call().balanceOf(alice)
# Ursula and Alice create wallets
contract_factory = chain.provider.get_contract_factory("Wallet")
tx = wallet_manager.transact({'from': ursula}).createWallet()
chain.wait.for_receipt(tx)
ursula_wallet = contract_factory(address=wallet_manager.call().wallets(ursula))
tx = wallet_manager.transact({'from': alice}).createWallet()
chain.wait.for_receipt(tx)
alice_wallet = contract_factory(address=wallet_manager.call().wallets(alice))
# Ursula's withdrawal attempt won't succeed because nothing to withdraw
with pytest.raises(TransactionFailed):
tx = ursula_wallet.transact({'from': ursula}).withdraw(100)
chain.wait.for_receipt(tx)
# And can't lock because nothing to lock
with pytest.raises(TransactionFailed):
tx = wallet_manager.transact({'from': ursula}).lock(500, 2)
chain.wait.for_receipt(tx)
# And can't set lock using wallet
with pytest.raises(TransactionFailed):
tx = ursula_wallet.transact({'from': ursula}).updateLockedTokens(1)
chain.wait.for_receipt(tx)
# Ursula and Alice transfer some money to wallets
tx = token.transact({'from': ursula}).transfer(ursula_wallet.address, 1500)
chain.wait.for_receipt(tx)
assert 1500 == token.call().balanceOf(ursula_wallet.address)
tx = token.transact({'from': alice}).transfer(alice_wallet.address, 500)
chain.wait.for_receipt(tx)
assert 500 == token.call().balanceOf(alice_wallet.address)
# Ursula asks for refund
tx = ursula_wallet.transact({'from': ursula}).withdraw(500)
chain.wait.for_receipt(tx)
# and it works
assert 1000 == token.call().balanceOf(ursula_wallet.address)
assert 9000 == token.call().balanceOf(ursula)
# Alice can't withdraw from Ursula's wallet
with pytest.raises(TransactionFailed):
tx = ursula_wallet.transact({'from': alice}).withdraw(1)
chain.wait.for_receipt(tx)
# Check that nothing is locked
assert 0 == ursula_wallet.call().getLockedTokens()
assert 0 == alice_wallet.call().getLockedTokens()
# Ursula can't lock too low value (less then rate)
# TODO uncomment
# with pytest.raises(TransactionFailed):
# tx = wallet_manager.transact({'from': ursula}).lock(1000, 10)
# chain.wait.for_receipt(tx)
# Ursula and Alice lock some tokens for 100 and 200 blocks
tx = wallet_manager.transact({'from': ursula}).lock(1000, 1)
chain.wait.for_receipt(tx)
tx = wallet_manager.transact({'from': alice}).lock(500, 2)
chain.wait.for_receipt(tx)
assert 1000 == ursula_wallet.call().getLockedTokens()
assert 1000 == ursula_wallet.call().calculateLockedTokens(1)
assert 1000 == ursula_wallet.call().calculateLockedTokens(2)
tx = ursula_wallet.transact({'from': ursula}).switchLock()
chain.wait.for_receipt(tx)
assert 500 == ursula_wallet.call().calculateLockedTokens(2)
tx = ursula_wallet.transact({'from': ursula}).switchLock()
chain.wait.for_receipt(tx)
assert 1000 == ursula_wallet.call().calculateLockedTokens(2)
assert 500 == alice_wallet.call().getLockedTokens()
assert 500 == alice_wallet.call().calculateLockedTokens(1)
# Checks locked tokens in next period
chain.wait.for_block(web3.eth.blockNumber + 50)
assert 1000 == ursula_wallet.call().getLockedTokens()
assert 500 == alice_wallet.call().getLockedTokens()
assert 1500 == wallet_manager.call().getAllLockedTokens()
# Ursula's withdrawal attempt won't succeed
with pytest.raises(TransactionFailed):
tx = ursula_wallet.transact({'from': ursula}).withdraw(100)
chain.wait.for_receipt(tx)
assert 1000 == token.call().balanceOf(ursula_wallet.address)
assert 9000 == token.call().balanceOf(ursula)
# Ursula can deposit more tokens
tx = wallet_manager.transact({'from': ursula}).confirmActivity()
chain.wait.for_receipt(tx)
tx = token.transact({'from': ursula}).transfer(ursula_wallet.address, 500)
chain.wait.for_receipt(tx)
tx = wallet_manager.transact({'from': ursula}).lock(500, 0)
chain.wait.for_receipt(tx)
assert 1500 == token.call().balanceOf(ursula_wallet.address)
assert 8500 == token.call().balanceOf(ursula)
# Wait 50 blocks and checks locking
chain.wait.for_block(web3.eth.blockNumber + 50)
assert 1500 == ursula_wallet.call().getLockedTokens()
# Ursula starts unlocking
tx = ursula_wallet.transact({'from': ursula}).switchLock()
chain.wait.for_receipt(tx)
assert 750 == ursula_wallet.call().calculateLockedTokens(1)
assert 0 == ursula_wallet.call().calculateLockedTokens(2)
# Confirm activity and wait 50 blocks
tx = wallet_manager.transact({'from': ursula}).confirmActivity()
chain.wait.for_receipt(tx)
assert 1500 == ursula_wallet.call().getLockedTokens()
assert 750 == ursula_wallet.call().calculateLockedTokens(1)
assert 0 == ursula_wallet.call().calculateLockedTokens(2)
chain.wait.for_block(web3.eth.blockNumber + 50)
assert 750 == ursula_wallet.call().getLockedTokens()
assert 0 == ursula_wallet.call().calculateLockedTokens(1)
# And Ursula can withdraw some tokens
tx = ursula_wallet.transact({'from': ursula}).withdraw(100)
chain.wait.for_receipt(tx)
assert 1400 == token.call().balanceOf(ursula_wallet.address)
assert 8600 == token.call().balanceOf(ursula)
# But Ursula can't withdraw all without mining for locked value
# TODO complete method
# with pytest.raises(TransactionFailed):
# tx = escrow.transact({'from': ursula}).withdrawAll()
# chain.wait.for_receipt(tx)
# Ursula can deposit more tokens
tx = token.transact({'from': ursula}).transfer(ursula_wallet.address, 500)
chain.wait.for_receipt(tx)
assert 1900 == token.call().balanceOf(ursula_wallet.address)
tx = wallet_manager.transact({'from': ursula}).lock(500, 0)
chain.wait.for_receipt(tx)
tx = wallet_manager.transact({'from': ursula}).lock(100, 0)
chain.wait.for_receipt(tx)
# Locked tokens will be updated in next period
assert 750 == ursula_wallet.call().getLockedTokens()
assert 600 == ursula_wallet.call().calculateLockedTokens(1)
assert 600 == ursula_wallet.call().calculateLockedTokens(2)
tx = ursula_wallet.transact({'from': ursula}).switchLock()
chain.wait.for_receipt(tx)
assert 300 == ursula_wallet.call().calculateLockedTokens(2)
assert 0 == ursula_wallet.call().calculateLockedTokens(3)
chain.wait.for_block(web3.eth.blockNumber + 50)
assert 600 == ursula_wallet.call().getLockedTokens()
assert 300 == ursula_wallet.call().calculateLockedTokens(1)
assert 0 == ursula_wallet.call().calculateLockedTokens(2)
# Ursula can increase lock
tx = wallet_manager.transact({'from': ursula}).lock(500, 2)
chain.wait.for_receipt(tx)
assert 600 == ursula_wallet.call().getLockedTokens()
assert 800 == ursula_wallet.call().calculateLockedTokens(1)
assert 500 == ursula_wallet.call().calculateLockedTokens(2)
assert 200 == ursula_wallet.call().calculateLockedTokens(3)
assert 0 == ursula_wallet.call().calculateLockedTokens(4)
chain.wait.for_block(web3.eth.blockNumber + 50)
assert 800 == ursula_wallet.call().getLockedTokens()
# Alice can't deposit too low value (less then rate)
# TODO uncomment after completing logic
# with pytest.raises(TransactionFailed):
# tx = escrow.transact({'from': ursula}).deposit(100, 100)
# chain.wait.for_receipt(tx)
# Alice increases lock by deposit more tokens
tx = token.transact({'from': alice}).transfer(alice_wallet.address, 500)
chain.wait.for_receipt(tx)
tx = wallet_manager.transact({'from': alice}).lock(500, 0)
chain.wait.for_receipt(tx)
tx = alice_wallet.transact({'from': alice}).switchLock()
chain.wait.for_receipt(tx)
assert 500 == alice_wallet.call().getLockedTokens()
assert 1000 == alice_wallet.call().calculateLockedTokens(1)
assert 500 == alice_wallet.call().calculateLockedTokens(2)
assert 0 == alice_wallet.call().calculateLockedTokens(3)
chain.wait.for_block(web3.eth.blockNumber + 50)
assert 1000 == alice_wallet.call().getLockedTokens()
# And increases locked blocks
tx = wallet_manager.transact({'from': alice}).lock(0, 2)
chain.wait.for_receipt(tx)
assert 1000 == alice_wallet.call().getLockedTokens()
assert 500 == alice_wallet.call().calculateLockedTokens(1)
assert 0 == alice_wallet.call().calculateLockedTokens(2)
# Alice increases lock by small amount of tokens
tx = wallet_manager.transact({'from': alice}).lock(100, 0)
chain.wait.for_receipt(tx)
assert 600 == alice_wallet.call().calculateLockedTokens(1)
assert 100 == alice_wallet.call().calculateLockedTokens(2)
assert 0 == alice_wallet.call().calculateLockedTokens(3)
# Ursula can't destroy contract
with pytest.raises(TransactionFailed):
tx = wallet_manager.transact({'from': ursula}).destroy()
chain.wait.for_receipt(tx)
# # Destroy contract from creator and refund all to Ursula and Alice
tx = wallet_manager.transact({'from': creator}).destroy()
chain.wait.for_receipt(tx)
assert 0 == token.call().balanceOf(ursula_wallet.address)
assert 0 == token.call().balanceOf(alice_wallet.address)
assert 10000 == token.call().balanceOf(ursula)
assert 10000 == token.call().balanceOf(alice)
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 miners:
tx = token.transact({'from': creator}).transfer(miner, amount)
chain.wait.for_receipt(tx)
amount = amount // 2
# Lock
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(miner)
tx = token.transact({'from': miner}).transfer(wallet, balance)
chain.wait.for_receipt(tx)
tx = wallet_manager.transact({'from': miner}).lock(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
# And confirm activity
for miner in miners:
tx = wallet_manager.transact({'from': miner}).confirmActivity()
chain.wait.for_receipt(tx)
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, 1)
assert miners[1].lower() == address_stop.lower()
assert 0 == shift
address_stop, shift = wallet_manager.call().findCumSum(
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
address_stop, shift = wallet_manager.call().findCumSum(NULL_ADDR, 1, 11)
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 + 3)
assert miners[index + 1].lower() == address_stop.lower()
assert 1 == shift
def test_mining(web3, chain, token, wallet_manager):
creator = web3.eth.accounts[0]
ursula = web3.eth.accounts[1]
alice = web3.eth.accounts[2]
# Ursula and Alice create wallets
contract_factory = chain.provider.get_contract_factory("Wallet")
tx = wallet_manager.transact({'from': ursula}).createWallet()
chain.wait.for_receipt(tx)
ursula_wallet = contract_factory(address=wallet_manager.call().wallets(ursula))
tx = wallet_manager.transact({'from': alice}).createWallet()
chain.wait.for_receipt(tx)
alice_wallet = contract_factory(address=wallet_manager.call().wallets(alice))
# Give Ursula and Alice some coins
tx = token.transact({'from': creator}).transfer(ursula, 10000)
chain.wait.for_receipt(tx)
tx = token.transact({'from': creator}).transfer(alice, 10000)
chain.wait.for_receipt(tx)
# Ursula and Alice transfer some money to wallets
tx = token.transact({'from': ursula}).transfer(ursula_wallet.address, 1000)
chain.wait.for_receipt(tx)
tx = token.transact({'from': alice}).transfer(alice_wallet.address, 500)
chain.wait.for_receipt(tx)
# Give rights for mining
tx = token.transact({'from': creator}).addMiner(wallet_manager.address)
chain.wait.for_receipt(tx)
assert token.call().isMiner(wallet_manager.address)
# Ursula can't mint because no locked tokens
with pytest.raises(TransactionFailed):
tx = wallet_manager.transact({'from': ursula}).mint()
chain.wait.for_receipt(tx)
# Ursula and Alice lock some tokens
tx = wallet_manager.transact({'from': ursula}).lock(1000, 1)
chain.wait.for_receipt(tx)
tx = wallet_manager.transact({'from': alice}).lock(500, 2)
chain.wait.for_receipt(tx)
# Using locked tokens starts from next period
assert 0 == wallet_manager.call().getAllLockedTokens()
# Ursula can't use method from Miner contract
with pytest.raises(TypeError):
tx = wallet_manager.transact({'from': ursula}).mint(ursula, 1, 1, 1, 1, 1)
chain.wait.for_receipt(tx)
# Only Ursula confirm next period
chain.wait.for_block(web3.eth.blockNumber + 50)
assert 1500 == wallet_manager.call().getAllLockedTokens()
tx = wallet_manager.transact({'from': ursula}).confirmActivity()
chain.wait.for_receipt(tx)
# Checks that no error
tx = wallet_manager.transact({'from': ursula}).confirmActivity()
chain.wait.for_receipt(tx)
# Ursula and Alice mint tokens for last periods
chain.wait.for_block(web3.eth.blockNumber + 50)
assert 1000 == wallet_manager.call().getAllLockedTokens()
tx = wallet_manager.transact({'from': ursula}).mint()
chain.wait.for_receipt(tx)
tx = wallet_manager.transact({'from': alice}).mint()
chain.wait.for_receipt(tx)
assert 1050 == token.call().balanceOf(ursula_wallet.address)
assert 521 == token.call().balanceOf(alice_wallet.address)
# Only Ursula confirm activity for next period
tx = ursula_wallet.transact({'from': ursula}).switchLock()
chain.wait.for_receipt(tx)
tx = wallet_manager.transact({'from': ursula}).confirmActivity()
chain.wait.for_receipt(tx)
# Ursula can't confirm next period because end of locking
chain.wait.for_block(web3.eth.blockNumber + 50)
assert 500 == wallet_manager.call().getAllLockedTokens()
with pytest.raises(TransactionFailed):
tx = wallet_manager.transact({'from': ursula}).confirmActivity()
chain.wait.for_receipt(tx)
# But Alice can
tx = wallet_manager.transact({'from': alice}).confirmActivity()
chain.wait.for_receipt(tx)
# Ursula mint tokens for next period
chain.wait.for_block(web3.eth.blockNumber + 50)
assert 500 == wallet_manager.call().getAllLockedTokens()
tx = wallet_manager.transact({'from': ursula}).mint()
chain.wait.for_receipt(tx)
# But Alice can't mining because she did not confirmed activity
with pytest.raises(TransactionFailed):
tx = wallet_manager.transact({'from': alice}).mint()
chain.wait.for_receipt(tx)
assert 1163 == token.call().balanceOf(ursula_wallet.address)
assert 521 == token.call().balanceOf(alice_wallet.address)
# Alice confirm next period and mint tokens
tx = alice_wallet.transact({'from': alice}).switchLock()
chain.wait.for_receipt(tx)
tx = wallet_manager.transact({'from': alice}).confirmActivity()
chain.wait.for_receipt(tx)
chain.wait.for_block(web3.eth.blockNumber + 100)
assert 0 == wallet_manager.call().getAllLockedTokens()
tx = wallet_manager.transact({'from': alice}).mint()
chain.wait.for_receipt(tx)
assert 1163 == token.call().balanceOf(ursula_wallet.address)
assert 634 == token.call().balanceOf(alice_wallet.address)
# Ursula can't confirm and mint because no locked tokens
with pytest.raises(TransactionFailed):
tx = wallet_manager.transact({'from': ursula}).mint()
chain.wait.for_receipt(tx)
with pytest.raises(TransactionFailed):
tx = wallet_manager.transact({'from': ursula}).confirmActivity()
chain.wait.for_receipt(tx)
# Ursula can't confirm and mint because no locked tokens
with pytest.raises(TransactionFailed):
tx = wallet_manager.transact({'from': ursula}).mint()
chain.wait.for_receipt(tx)
with pytest.raises(TransactionFailed):
tx = wallet_manager.transact({'from': ursula}).confirmActivity()
chain.wait.for_receipt(tx)
# Ursula can lock some tokens again
tx = wallet_manager.transact({'from': ursula}).lock(500, 4)
chain.wait.for_receipt(tx)
tx = ursula_wallet.transact({'from': ursula}).switchLock()
chain.wait.for_receipt(tx)
assert 500 == ursula_wallet.call().getLockedTokens()
assert 500 == ursula_wallet.call().calculateLockedTokens(1)
assert 375 == ursula_wallet.call().calculateLockedTokens(2)
assert 250 == ursula_wallet.call().calculateLockedTokens(3)
assert 0 == ursula_wallet.call().calculateLockedTokens(5)
# And can increase lock
tx = wallet_manager.transact({'from': ursula}).lock(100, 0)
chain.wait.for_receipt(tx)
assert 600 == ursula_wallet.call().getLockedTokens()
assert 600 == ursula_wallet.call().calculateLockedTokens(1)
assert 450 == ursula_wallet.call().calculateLockedTokens(2)
assert 0 == ursula_wallet.call().calculateLockedTokens(5)
tx = wallet_manager.transact({'from': ursula}).lock(0, 2)
chain.wait.for_receipt(tx)
assert 600 == ursula_wallet.call().getLockedTokens()
assert 600 == ursula_wallet.call().calculateLockedTokens(1)
assert 450 == ursula_wallet.call().calculateLockedTokens(2)
assert 0 == ursula_wallet.call().calculateLockedTokens(5)
tx = token.transact({'from': ursula}).transfer(ursula_wallet.address, 800)
chain.wait.for_receipt(tx)
tx = wallet_manager.transact({'from': ursula}).lock(800, 1)
chain.wait.for_receipt(tx)
assert 1400 == ursula_wallet.call().getLockedTokens()
assert 1400 == ursula_wallet.call().calculateLockedTokens(1)
assert 1000 == ursula_wallet.call().calculateLockedTokens(3)
assert 400 == ursula_wallet.call().calculateLockedTokens(6)
assert 0 == ursula_wallet.call().calculateLockedTokens(8)
# Alice can withdraw all
# TODO complete method
# tx = wallet_manager.transact({'from': alice}).withdrawAll()
# chain.wait.for_receipt(tx)
tx = alice_wallet.transact({'from': alice}).withdraw(634)
chain.wait.for_receipt(tx)
assert 10134 == token.call().balanceOf(alice)
# TODO test max confirmed periods

View File

@ -18,45 +18,31 @@ def test_miner(web3, chain, token):
# Creator deploys the miner
miner, _ = chain.provider.get_or_deploy_contract(
'MinerTest', deploy_args=[token.address, 10 ** 48, 10 ** 7, 10 ** 7],
'MinerTest', deploy_args=[token.address, 1, 10 ** 46, 10 ** 7, 10 ** 7],
deploy_transaction={'from': creator})
# Give rights for mining
tx = token.transact({'from': creator}).addMiner(miner.address)
chain.wait.for_receipt(tx)
# Check reward
# TODO uncomment or delete
# assert miner.call().isEmptyReward(100, 100)
# assert not miner.call().isEmptyReward(1000, 100)
# # Try to mint using low value
# TODO uncomment or delete
# assert miner.call().testMint(ursula, 100, 200, 100, 0) == [0, 0]
# tx = miner.transact().testMint(ursula, 100, 200, 100, 0)
# chain.wait.for_receipt(tx)
# assert token.call().totalSupply() == 10 ** 9
# assert token.call().balanceOf(ursula) == 0
# assert miner.call().lastMintedPoint() == 0
# Mint some tokens
tx = miner.transact().testMint(ursula, 1000, 2000, 100, 0, 0)
tx = miner.transact().testMint(ursula, 0, 1000, 2000, 0, 0)
chain.wait.for_receipt(tx)
assert 10 == token.call().balanceOf(ursula)
assert 10 ** 30 + 10 == token.call().totalSupply()
# Mint more tokens
tx = miner.transact().testMint(ursula, 500, 500, 200, 0, 0)
tx = miner.transact().testMint(ursula, 0, 500, 500, 0, 0)
chain.wait.for_receipt(tx)
assert 50 == token.call().balanceOf(ursula)
assert 10 ** 30 + 50 == token.call().totalSupply()
assert 30 == token.call().balanceOf(ursula)
assert 10 ** 30 + 30 == token.call().totalSupply()
tx = miner.transact().testMint(ursula, 500, 500, 200, 10 ** 7, 0)
tx = miner.transact().testMint(ursula, 0, 500, 500, 10 ** 7, 0)
chain.wait.for_receipt(tx)
assert 130 == token.call().balanceOf(ursula)
assert 10 ** 30 + 130 == token.call().totalSupply()
assert 70 == token.call().balanceOf(ursula)
assert 10 ** 30 + 70 == token.call().totalSupply()
tx = miner.transact().testMint(ursula, 500, 500, 200, 2 * 10 ** 7, 0)
tx = miner.transact().testMint(ursula, 0, 500, 500, 2 * 10 ** 7, 0)
chain.wait.for_receipt(tx)
assert 210 == token.call().balanceOf(ursula)
assert 10 ** 30 + 210 == token.call().totalSupply()
assert 110 == token.call().balanceOf(ursula)
assert 10 ** 30 + 110 == token.call().totalSupply()

View File

@ -20,6 +20,14 @@ def airdrop(chain):
chain.wait.for_receipt(tx, timeout=10)
def wait_time(chain, wait_hours):
web3 = chain.web3
step = 50
end_timestamp = web3.eth.getBlock(web3.eth.blockNumber).timestamp + wait_hours * 60 * 60
while web3.eth.getBlock(web3.eth.blockNumber).timestamp < end_timestamp:
chain.wait.for_block(web3.eth.blockNumber + step)
def test_deposit(chain):
token.create()
escrow.create()
@ -35,7 +43,7 @@ def test_select_ursulas(chain):
# Create a random set of miners (we have 9 in total)
for u in chain.web3.eth.accounts[1:]:
ursula.lock((10 + random.randrange(9000)) * M, 100, u)
chain.wait.for_block(chain.web3.eth.blockNumber + escrow.BLOCKS_PER_PERIOD)
wait_time(chain, escrow.HOURS_PER_PERIOD)
miners = escrow.sample(3)
assert len(miners) == 3
@ -57,8 +65,7 @@ def test_mine_withdraw(chain):
for u in chain.web3.eth.accounts[1:]:
ursula.lock((10 + random.randrange(9000)) * M, 1, u)
chain.wait.for_block(chain.web3.eth.blockNumber + 2 * escrow.BLOCKS_PER_PERIOD)
wait_time(chain, 2 * escrow.HOURS_PER_PERIOD)
ursula.mine(addr)
ursula.withdraw(addr)
final_balance = token.balance(addr)