[KMS-ETH]- Added tests for policy manager

pull/195/head^2
szotov 2018-01-31 20:04:17 +03:00
parent 8beffcbaa7
commit 65073ee473
6 changed files with 453 additions and 25 deletions

View File

@ -399,7 +399,7 @@ contract Escrow is Miner, Ownable {
decimals);
// TODO remove
if (address(policyManager) != 0x0) {
policyManager.updateReward(msg.sender, previousPeriod);
policyManager.updateReward(msg.sender, period);
}
}
info.decimals = decimals;

View File

@ -31,16 +31,14 @@ contract PolicyManager {
}
struct NodeInfo {
mapping (uint256 => uint256) rewardByPeriod;
uint256 reward;
mapping (uint256 => uint256) rewardByPeriod;
}
NuCypherKMSToken public token;
Escrow public escrow;
mapping (bytes20 => Policy) public policies;
mapping (address => NodeInfo) public nodes;
// mapping (address => byte20[]) nodePolicies;
// mapping (address => byte20[]) clientPolicies;
/**
* @notice The PolicyManager constructor sets addresses of token and escrow contracts
@ -51,7 +49,8 @@ contract PolicyManager {
NuCypherKMSToken _token,
Escrow _escrow
) {
require(address(_token) != 0x0);
require(address(_token) != 0x0 &&
address(_escrow) != 0x0);
token = _token;
escrow = _escrow;
}
@ -60,14 +59,14 @@ contract PolicyManager {
* @notice Create policy by client
* @dev Generate policy id before creation
* @param _policyId Policy id
* @param _feeByPeriod Amount of node reward by period
* @param _node Node that will handle policy
* @param _feeByPeriod Amount of node reward by period
* @param _numberOfPeriods Duration of the policy in periods
**/
function createPolicy(
bytes20 _policyId,
uint256 _feeByPeriod,
address _node,
uint256 _feeByPeriod,
uint256 _numberOfPeriods
)
public
@ -137,7 +136,7 @@ contract PolicyManager {
function revokePolicy(bytes20 _policyId) public {
var policy = policies[_policyId];
require(policy.client == msg.sender);
var refund = calculateRefund(policy);
var refund = calculateRefund(_policyId);
var node = nodes[policy.node];
for (var i = policy.startPeriod; i <= policy.lastPeriod; i++) {
node.rewardByPeriod[i] = node.rewardByPeriod[i].sub(policy.rate);
@ -157,27 +156,27 @@ contract PolicyManager {
// msg.sender == policy.client);
require(msg.sender == policy.client);
var refund = calculateRefund(policy);
var refund = calculateRefund(_policyId);
var client = policy.client;
if (policy.startPeriod > policy.lastPeriod) {
delete policies[_policyId];
}
if (refund > 0) {
token.safeTransfer(policy.client, refund);
token.safeTransfer(client, refund);
}
}
/**
* @notice Calculate amount of refund
* @param policy Policy
* @param _policyId Policy id
**/
function calculateRefund(Policy policy) internal returns (uint256) {
function calculateRefund(bytes20 _policyId) internal returns (uint256) {
var policy = policies[_policyId];
var currentPeriod = escrow.getCurrentPeriod();
var maxPeriod = Math.min256(currentPeriod, policy.lastPeriod);
var minPeriod = policy.startPeriod;
var max = maxPeriod;
var activePeriods = maxPeriod.add(1).sub(minPeriod);
uint256 downtimePeriods = 0;
var length = escrow.getDowntimePeriodsLength(policy.node);
// TODO complete
for (var i = policy.indexOfDowntimePeriods; i < length; i++) {
var (startPeriod, endPeriod) = escrow.getDowntimePeriods(policy.node, i);
if (startPeriod > maxPeriod) {
@ -185,10 +184,9 @@ contract PolicyManager {
} else if (endPeriod < minPeriod) {
continue;
}
max = Math.min256(maxPeriod, endPeriod);
var max = Math.min256(maxPeriod, endPeriod);
var min = Math.max256(minPeriod, startPeriod);
// TODO safe math
activePeriods -= max - min + 1;
downtimePeriods = downtimePeriods.add(max.sub(min).add(1));
if (maxPeriod <= endPeriod) {
break;
}
@ -196,13 +194,12 @@ contract PolicyManager {
policy.indexOfDowntimePeriods = i;
var lastActivePeriod = escrow.getLastActivePeriod(policy.node);
if (i == length && lastActivePeriod < maxPeriod) {
min = Math.max256(minPeriod, lastActivePeriod);
// TODO safe math
activePeriods -= max - min + 1;
min = Math.max256(minPeriod.sub(1), lastActivePeriod);
downtimePeriods = downtimePeriods.add(maxPeriod.sub(min));
}
policy.startPeriod = max.add(1);
policy.startPeriod = maxPeriod.add(1);
return policy.rate.mul(activePeriods);
return policy.rate.mul(downtimePeriods);
}
}

View File

@ -0,0 +1,108 @@
pragma solidity ^0.4.8;
import "contracts/PolicyManager.sol";
/**
* @notice Contract for testing PolicyManager contract
**/
contract EscrowTest {
struct Downtime {
uint256 startPeriod;
uint256 endPeriod;
}
PolicyManager policyManager;
uint256 public secondsPerPeriod;
address node;
uint256 lastActivePeriod;
Downtime[] downtime;
/**
* @param _node Address of node that allow to use policy manager
* @param _minutesPerPeriod Size of period in minutes
**/
function EscrowTest(address _node, uint256 _minutesPerPeriod) {
node = _node;
secondsPerPeriod = _minutesPerPeriod * 1 minutes;
}
/**
* @notice Return non zero value for node
**/
function getLockedTokens(address _owner)
public constant returns (uint256)
{
if (_owner == node) {
return 1;
}
return 0;
}
/**
* @return Number of current period
**/
function getCurrentPeriod() public constant returns (uint256) {
return block.timestamp / secondsPerPeriod;
}
/**
* @notice Set last active period
**/
function setLastActivePeriod(uint256 _lastActivePeriod) {
lastActivePeriod = _lastActivePeriod;
}
/**
* @notice Add downtime period
**/
function pushDowntimePeriod(uint256 _startPeriod, uint256 _endPeriod) {
downtime.push(Downtime(_startPeriod, _endPeriod));
}
/**
* @notice Emulate mint method
* @param _period Period for minting
**/
function mint(uint256 _period) external {
policyManager.updateReward(node, _period);
}
/**
* @notice Set policy manager address
**/
function setPolicyManager(PolicyManager _policyManager) {
policyManager = _policyManager;
}
/**
* @dev Get info about downtime periods
**/
function getDowntimePeriods(address _owner, uint256 _index)
public constant returns (uint256 startPeriod, uint256 endPeriod)
{
var period = downtime[_index];
startPeriod = period.startPeriod;
endPeriod = period.endPeriod;
}
/**
* @dev Get size of downtime periods array
**/
function getDowntimePeriodsLength(address _owner)
public constant returns (uint256)
{
return downtime.length;
}
/**
* @dev Get last active period
**/
function getLastActivePeriod(address _owner)
public constant returns (uint256)
{
return lastActivePeriod;
}
}

View File

@ -0,0 +1,40 @@
pragma solidity ^0.4.0;
import "contracts/Escrow.sol";
/**
* @notice Contract for testing Escrow contract
**/
contract PolicyManagerTest {
Escrow public escrow;
mapping (address => uint256[]) public nodes;
function PolicyManagerTest(address _token, Escrow _escrow) {
escrow = _escrow;
}
/**
* @notice Update node info
**/
function updateReward(address _node, uint256 _period) external {
nodes[_node].push(_period);
}
/**
* @notice Get length of array
**/
function getPeriodsLength(address _node) public constant returns (uint256) {
return nodes[_node].length;
}
/**
* @notice Get period info
**/
function getPeriod(address _node, uint256 _index) public constant returns (uint256) {
return nodes[_node][_index];
}
}

View File

@ -22,6 +22,7 @@ def escrow(web3, chain, token):
return escrow
# TODO extract method
def wait_time(chain, wait_hours):
web3 = chain.web3
step = 50
@ -283,9 +284,8 @@ def test_mining(web3, chain, token, escrow):
ursula = web3.eth.accounts[1]
alice = web3.eth.accounts[2]
# TODO test setPolicyManager
policy_manager, _ = chain.provider.get_or_deploy_contract(
'PolicyManager', deploy_args=[token.address, escrow.address],
'PolicyManagerTest', deploy_args=[token.address, escrow.address],
deploy_transaction={'from': creator})
tx = escrow.transact({'from': creator}).setPolicyManager(policy_manager.address)
chain.wait.for_receipt(tx)
@ -349,6 +349,12 @@ def test_mining(web3, chain, token, escrow):
assert 9050 == token.call().balanceOf(ursula)
assert 9521 == token.call().balanceOf(alice)
assert 1 == policy_manager.call().getPeriodsLength(ursula)
assert 1 == policy_manager.call().getPeriodsLength(alice)
period = escrow.call().getCurrentPeriod() - 1
assert period == policy_manager.call().getPeriod(ursula, 0)
assert period == policy_manager.call().getPeriod(alice, 0)
# Only Ursula confirm activity for next period
tx = escrow.transact({'from': ursula}).switchLock()
chain.wait.for_receipt(tx)
@ -378,6 +384,11 @@ def test_mining(web3, chain, token, escrow):
assert 9163 == token.call().balanceOf(ursula)
assert 9521 == token.call().balanceOf(alice)
assert 3 == policy_manager.call().getPeriodsLength(ursula)
assert 1 == policy_manager.call().getPeriodsLength(alice)
assert period + 1 == policy_manager.call().getPeriod(ursula, 1)
assert period + 2 == policy_manager.call().getPeriod(ursula, 2)
# Alice confirm next period and mint tokens
tx = escrow.transact({'from': alice}).switchLock()
chain.wait.for_receipt(tx)
@ -390,6 +401,11 @@ def test_mining(web3, chain, token, escrow):
assert 9163 == token.call().balanceOf(ursula)
assert 9634 == token.call().balanceOf(alice)
assert 3 == policy_manager.call().getPeriodsLength(ursula)
assert 3 == policy_manager.call().getPeriodsLength(alice)
assert period + 3 == policy_manager.call().getPeriod(alice, 1)
assert period + 4 == policy_manager.call().getPeriod(alice, 2)
# Ursula can't confirm and mint because no locked tokens
with pytest.raises(TransactionFailed):
tx = escrow.transact({'from': ursula}).mint()

View File

@ -0,0 +1,267 @@
import pytest
from ethereum.tester import TransactionFailed
@pytest.fixture()
def token(web3, chain):
creator = web3.eth.accounts[0]
# Create an ERC20 token
token, _ = chain.provider.get_or_deploy_contract(
'NuCypherKMSToken', deploy_args=[10 ** 9, 2 * 10 ** 9],
deploy_transaction={'from': creator})
return token
@pytest.fixture()
def escrow(web3, chain):
creator = web3.eth.accounts[0]
node = web3.eth.accounts[1]
# Creator deploys the escrow
escrow, _ = chain.provider.get_or_deploy_contract(
'EscrowTest', deploy_args=[node, MINUTES_IN_PERIOD],
deploy_transaction={'from': creator})
return escrow
@pytest.fixture()
def policy_manager(web3, chain, token, escrow):
creator = web3.eth.accounts[0]
client = web3.eth.accounts[2]
# Creator deploys the policy manager
policy_manager, _ = chain.provider.get_or_deploy_contract(
'PolicyManager', deploy_args=[token.address, escrow.address],
deploy_transaction={'from': creator})
tx = escrow.transact({'from': creator}).setPolicyManager(policy_manager.address)
chain.wait.for_receipt(tx)
# Give client some coins
tx = token.transact({'from': creator}).transfer(client, 10000)
chain.wait.for_receipt(tx)
# Client give rights for policy manager to transfer coins
tx = token.transact({'from': client}).approve(policy_manager.address, 1000)
chain.wait.for_receipt(tx)
return policy_manager
def wait_time(chain, wait_periods):
web3 = chain.web3
step = 1
end_timestamp = web3.eth.getBlock(web3.eth.blockNumber).timestamp + wait_periods * 60 * MINUTES_IN_PERIOD
while web3.eth.getBlock(web3.eth.blockNumber).timestamp < end_timestamp:
chain.wait.for_block(web3.eth.blockNumber + step)
MINUTES_IN_PERIOD = 10
policy_id = bytes([1])
policy_id_2 = bytes([2])
rate = 20
number_of_periods = 10
def test_create_revoke(web3, chain, token, escrow, policy_manager):
creator = web3.eth.accounts[0]
node = web3.eth.accounts[1]
client = web3.eth.accounts[2]
bad_node = web3.eth.accounts[3]
# Try create policy for bad node
with pytest.raises(TransactionFailed):
tx = policy_manager.transact({'from': client}).createPolicy(policy_id, bad_node, 1, 1)
chain.wait.for_receipt(tx)
# Create policy
period = escrow.call().getCurrentPeriod()
tx = policy_manager.transact({'from': client}).createPolicy(policy_id, node, rate, number_of_periods)
chain.wait.for_receipt(tx)
policy = policy_manager.call().policies(policy_id)
assert 200 == token.call().balanceOf(policy_manager.address)
assert 9800 == token.call().balanceOf(client)
assert client == policy[0]
assert node == policy[1]
assert rate == policy[2]
assert period + 1 == policy[3]
assert period + 10 == policy[4]
# Try to create policy again
with pytest.raises(TransactionFailed):
tx = policy_manager.transact({'from': client}).createPolicy(policy_id, node, rate, number_of_periods)
chain.wait.for_receipt(tx)
# Not client try to revoke policy
with pytest.raises(TransactionFailed):
tx = policy_manager.transact({'from': creator}).revokePolicy(policy_id)
chain.wait.for_receipt(tx)
# Client try to revoke policy
tx = policy_manager.transact({'from': client}).revokePolicy(policy_id)
chain.wait.for_receipt(tx)
policy = policy_manager.call().policies(policy_id)
assert '0x' + '0' * 40 == policy[0]
# Create another policy
period = escrow.call().getCurrentPeriod()
tx = policy_manager.transact({'from': client}).createPolicy(policy_id_2, node, rate, number_of_periods)
chain.wait.for_receipt(tx)
policy = policy_manager.call().policies(policy_id_2)
assert 200 == token.call().balanceOf(policy_manager.address)
assert 9800 == token.call().balanceOf(client)
assert client == policy[0]
assert node == policy[1]
assert rate == policy[2]
assert period + 1 == policy[3]
assert period + 10 == policy[4]
def test_reward(web3, chain, token, escrow, policy_manager):
node = web3.eth.accounts[1]
client = web3.eth.accounts[2]
bad_node = web3.eth.accounts[3]
# Create policy
period = escrow.call().getCurrentPeriod()
tx = policy_manager.transact({'from': client}).createPolicy(policy_id, node, rate, number_of_periods)
chain.wait.for_receipt(tx)
# Nothing to withdraw
with pytest.raises(TransactionFailed):
tx = policy_manager.transact({'from': node}).withdraw()
chain.wait.for_receipt(tx)
# Can't update reward directly
with pytest.raises(TransactionFailed):
tx = policy_manager.transact({'from': node}).updateReward(node, period + 1)
chain.wait.for_receipt(tx)
# Mint some periods
for x in range(5):
tx = escrow.transact({'from': node}).mint(period)
chain.wait.for_receipt(tx)
period += 1
assert 80 == policy_manager.call().nodes(node)
# Withdraw
tx = policy_manager.transact({'from': node}).withdraw()
chain.wait.for_receipt(tx)
assert 80 == token.call().balanceOf(node)
assert 120 == token.call().balanceOf(policy_manager.address)
# Mint more periods
for x in range(20):
tx = escrow.transact({'from': node}).mint(period)
chain.wait.for_receipt(tx)
period += 1
assert 120 == policy_manager.call().nodes(node)
# Withdraw
tx = policy_manager.transact({'from': node}).withdraw()
chain.wait.for_receipt(tx)
assert 200 == token.call().balanceOf(node)
assert 0 == token.call().balanceOf(policy_manager.address)
def test_refund(web3, chain, token, escrow, policy_manager):
node = web3.eth.accounts[1]
client = web3.eth.accounts[2]
# Create policy
tx = policy_manager.transact({'from': client}).createPolicy(policy_id, node, rate, number_of_periods)
chain.wait.for_receipt(tx)
tx = escrow.transact().setLastActivePeriod(escrow.call().getCurrentPeriod())
chain.wait.for_receipt(tx)
# Wait and refund all
wait_time(chain, 9)
tx = policy_manager.transact({'from': client}).refund(policy_id)
chain.wait.for_receipt(tx)
assert 20 == token.call().balanceOf(policy_manager.address)
assert 9980 == token.call().balanceOf(client)
assert client == policy_manager.call().policies(policy_id)[0]
wait_time(chain, 1)
tx = policy_manager.transact({'from': client}).refund(policy_id)
chain.wait.for_receipt(tx)
assert 0 == token.call().balanceOf(policy_manager.address)
assert 10000 == token.call().balanceOf(client)
assert '0x' + '0' * 40 == policy_manager.call().policies(policy_id)[0]
# Create policy again
period = escrow.call().getCurrentPeriod()
tx = policy_manager.transact({'from': client}).createPolicy(policy_id, node, rate, number_of_periods)
chain.wait.for_receipt(tx)
# Nothing to refund
tx = policy_manager.transact({'from': client}).refund(policy_id)
chain.wait.for_receipt(tx)
assert 200 == token.call().balanceOf(policy_manager.address)
assert 9800 == token.call().balanceOf(client)
# Try to refund nonexistent policy
with pytest.raises(TransactionFailed):
tx = policy_manager.transact({'from': client}).refund(policy_id_2)
chain.wait.for_receipt(tx)
# Node try to refund by node
with pytest.raises(TransactionFailed):
tx = policy_manager.transact({'from': node}).refund(policy_id)
chain.wait.for_receipt(tx)
# Mint some periods and mark others as downtime periods
period += 1
tx = escrow.transact().mint(period)
chain.wait.for_receipt(tx)
tx = escrow.transact().mint(period + 1)
chain.wait.for_receipt(tx)
tx = escrow.transact().pushDowntimePeriod(period + 2, period + 3)
chain.wait.for_receipt(tx)
tx = escrow.transact().mint(period + 4)
chain.wait.for_receipt(tx)
tx = escrow.transact().pushDowntimePeriod(period + 5, period + 7)
chain.wait.for_receipt(tx)
tx = escrow.transact().mint(period + 8)
chain.wait.for_receipt(tx)
tx = escrow.transact().setLastActivePeriod(period + 8)
chain.wait.for_receipt(tx)
assert 80 == policy_manager.call().nodes(node)
# Wait and refund
wait_time(chain, 10)
tx = policy_manager.transact({'from': client}).refund(policy_id)
chain.wait.for_receipt(tx)
assert 80 == token.call().balanceOf(policy_manager.address)
assert 9920 == token.call().balanceOf(client)
assert '0x' + '0' * 40 == policy_manager.call().policies(policy_id)[0]
# Create policy again
period = escrow.call().getCurrentPeriod()
tx = policy_manager.transact({'from': client}).createPolicy(policy_id, node, rate, number_of_periods)
chain.wait.for_receipt(tx)
# Mint some periods
period += 1
tx = escrow.transact().pushDowntimePeriod(period, period)
chain.wait.for_receipt(tx)
for x in range(3):
period += 1
tx = escrow.transact({'from': node}).mint(period)
chain.wait.for_receipt(tx)
tx = escrow.transact().setLastActivePeriod(period)
chain.wait.for_receipt(tx)
assert 140 == policy_manager.call().nodes(node)
# Client revokes policy
wait_time(chain, 4)
tx = policy_manager.transact({'from': client}).revokePolicy(policy_id)
chain.wait.for_receipt(tx)
policy = policy_manager.call().policies(policy_id)
assert 140 == token.call().balanceOf(policy_manager.address)
assert 9860 == token.call().balanceOf(client)
assert '0x' + '0' * 40 == policy[0]
# Minting is useless after revoke
for x in range(20):
period += 1
tx = escrow.transact({'from': node}).mint(period)
chain.wait.for_receipt(tx)
assert 140 == policy_manager.call().nodes(node)