Added draft of the docs for contracts, fixed user escrow

pull/271/head
szotov 2018-05-13 12:44:27 +03:00
parent 01a1a6d7d7
commit 0b068d26e0
9 changed files with 171 additions and 337 deletions

View File

@ -1,27 +0,0 @@
# NuCypher Ethereum
Ethereum (solidity) code for NuCypher, consists contracts and python classes for miners and clients.
Part of contracts was copied from [OpenZeppelin](https://github.com/OpenZeppelin/zeppelin-solidity) repo.
The basis is built on the [Populus](https://github.com/ethereum/populus) framework.
# Periods structure
Most of the function in contracts works by periods. For example, stake in the contract `Escrow` is discretely unlocked by periods.
Period is calculating using block.timestamp in getCurrentPeriod() function (`Miner.sol`). Each period is 24 hours. So result of getting locked tokens in one day will be the same.
# Main contracts
* Token contract
`NuCypherToken` contract is ERC20 token with additional function - burn own tokens (only for owners)
* Miner contract
`MinersEscrow` contract holds stake from miners, store information about miners activity and assigns a reward for participating in NuCypher network
* Client contract
`PolicyManager` contract holds policies fee and distributes fee by periods
* User escrow contract
`UserEscrow` contract locks tokens for some time. In that period tokens are lineraly unlocked and all tokens can be used as stake in `MinersEscrow` contract
# Solidity libraries
* `LinkedList` library is structure of linked list for address data type
* `Dispatcher` contract is proxy which used for updating versions of any contract. See [README.MD](nucypher.blockchain.eth/project/contracts/proxy/README.MD)

114
doc/contracts.rst Normal file
View File

@ -0,0 +1,114 @@
NuCypher contracts
========================
* Token contract
`NuCypherToken` contract is the ERC20 token contract with additional function - burn own tokens (only for owners)
* Miners contract
`MinersEscrow` contract holds Ursula's stake, stores information about Ursulas activity and assigns a reward for participating in NuCypher network.
The `Issuer` contract is part of the `MinersEscrow` and uses only to split code
* Client contract
`PolicyManager` contract holds policies fee and distributes fee by periods
* Upgradeable abstract contract
`Upgradeable` is base contract for upgrading `(README.MD) <nucypher.blockchain.eth/project/contracts/proxy/README.MD>`
* Dispatcher
`Dispatcher` contract is used as proxy to other contracts. This provides upgrading of the `MinersEscrow`, `PolicyManager` and `Government` contracts
* Government
`Government` contract holds ownership for dispatchers and provides voting for upgrading of those contracts
* User escrow contract
`UserEscrow` contract locks tokens for some time. In that period tokens are lineraly unlocked and all tokens can be used as a stake in the `MinersEscrow` contract
Deployment
========================
* The first place is the contract `NuCypherToken` with all future supply tokens
* Next `MinersEscrow` should be deployed with its dispatcher
* Similarly `PolicyManager` is deployed with own dispatcher
* Transfer reward tokens to the `MinersEscrow` contract. This tokens is reward for mining. The remaining tokens are initial supply
* Run the `initialize()` method to initialize the `MinersEscrow` contract
* Set the address of the `PolicyManager` contract in the `MinersEscrow` by using the `setPolicyManager(address)`
* After this deploy `Government` contract with dispatcher
* Transfer ownership of the `MinersEscrow`, `PolicyManager` and `Government` dispatchers to the `Government` dispatcher address
* Pre-deposit tokens to the `MinersEscrow` if necessary:
* Approve the transfer tokens for the `MinersEscrow` contract using the `approve(address, uint)` method.
The parameters are the address of `MinersEscrow` and the amount of tokens for a miner or group of miner;
* Deposit tokens to the `MinersEscrow` contract using the `preDeposit(address[], uint[], uint[])` method.
The parameters are the addresses of token miner, the amount of token for each miner and the periods during which tokens will be locked for each miner
* Pre-deposit tokens to the `UserEscrow` if necessary:
* Create new instance of the `UserEscrow` contract
* Transfer ownership of the instance of the `UserEscrow` contract to the user
* Approve the transfer tokens for the `UserEscrow`
* Deposit tokens by the `initialDeposit(uint256, uint256)` method
Miner / Ursula
========================
In order to become a participant of the network, a miner should stake tokens in the `MinersEscrow` contract.
For that, the miner allows the (mining) contract to do a transaction using the `approve(address, uint256)` method in the token contract
(ERC20 contracts allow to delegrate access to another address).
After that, the miner transfers some quantity of tokens (method `deposit(uint256, uint256)`), locking them at the same time.
Another way to do it is using the `approveAndCall(address, uint256, bytes)` method in the token contract.
The parameters are the address of the `MinersEscrow` contract, the amount of staked tokens and the periods for locking which are serialized into an array of bytes.
When staking tokens, the miner sets the number of periods while tokens will be locked.
Each stake is the amount of tokens and the duration in periods.
The miner can add new stake by the `deposit(uint256, uint256)` or `lock(uint256, uint256)` methods.
Also the miner can split stake into two parts: one with the same duration and other with an extended duration.
For this purpose, the `divideStake(uint256, uint256, uint256, uint256)` method is used.
The first two parameters are used to identify the stake to divide and the others two for the extended part of the stake.
When calculating locked tokens (`getLockedTokens(address, uint256)` method), all stakes that are active during the specified period are summed up.
In order to confirm activity every period, the miner should call `confirmActivity()` in the process of which activity for the next period is registered.
Also the method `confirmActivity` is called every time when methods `deposit(uint256, uint256)` or `lock(uint256, uint256)` are called.
The miner gets a reward for every confirmed period.
After the period of activity has passed, the miner could call `mint()` method which computes and transfers tokens to the miner's account.
Also, the `lock(uint256, uint256)` and `confirmActivity()` methods include the `mint()` method.
The reward depends on the fraction of locked tokens for the period (only those who confirmed activity are accounted for)
Also, the reward depends on the number of periods during which the tokens will be locked: if the tokens will be locked in half a year, the coefficient is 1.5.
Minimal coefficient is 1 (when tokens will get unlocked in the next period), and maximum is 2 (when the time is 1 year or more).
The reward is calculated separately for each stakes that are active during the mining period and all rewards are summed up.
The order of calling `mint` by miners (e.g. who calls first, second etc) doesn't matter.
All reward the miner can get by using the `witdraw(uint256)` method. Only non-locked tokens can be withdrawn.
Also the miner gets rewards for policies deployed.
Computation of the reward happens every time `mint()` is called by the `updateReward(address, uint256)` method.
In order to take the reward, the miner needs to call method `withdraw()` of the contract `PolicyManager`.
The miner can set a minimum reward rate for a policy. For that, the miner should call the `setMinRewardRate(uint256)` method.
Some users will have locked but not staked tokens.
In that case, a instance of the `UserEscrow` contract will hold their tokens (method `initialDeposit(uint256, uint256)`).
All tokens will be linearly unlocked and the user can get them by method `withdraw(uint256)`.
When the user wants to become a miner - he uses the `UserEscrow` contract as a proxy for the `MinersEscrow` and `PolicyManager` contracts.
Alice
========================
Alice uses the net of miners to deploy policies.
In order to take advantage of network Alice should choose miners and deploy policies with fees for that miners.
Alice can choose miners by herself or by `findCumSum(uint256, uint256, uint256)` method of the contract `MinersEscrow`.
The parameters are the start index (if the method is not called the first time), delta of the step and minimum number of periods during which tokens are locked.
This method will return only active miners.
In order to place fee for policy Alice should call method `createPolicy(bytes20, uint256, uint256, address[])` of the contract `PolicyManager`
by specifying the miners addresses, the policy id (off-chain generation), duration in periods, first period reward.
Payment should be added in transaction in ETH and the amount is `firstReward * miners.length + rewardRate * periods * miners.length`.
Reward rate must be equal or more than minimum reward for each miner in the list. First period reward can not be refundable and it can be zero.
In case Alice wants to cancel policy then she calls the `revokePolicy(bytes20)` or `revokeArrangement(bytes20, address)` methods of the contract `PolicyManager`.
While executing those methods Alice get all fee for future periods and for periods when the miners were inactive.
Also Alice can refund ETH for inactive miners periods without revoking policy by using methods `refund(bytes20)` or `refund(bytes20, address)` of the contract `PolicyManager`.
Government
========================
Smart contracts in Ethereum are not really changeable.
So fixing bugs and upgrading logic is to change the contract (address) and save the previous storage values.
The `Dispatcher` contract is used for this purpose - the fallback function in contract will execute on any request,
redirect request to the target address (delegatecall) and return result value (using some opcodes).
A target contract should be inherited from the `Upgradeable` contract in addition to the use of the `Dispatcher`.
The `Upgradeable` contract include 2 abstract methods that need to be implemented:
`verifyState(address)` method which checks that new version has correct storage;
`finishUpgrade(address)` method which should copy initialization data from library storage to the dispatcher storage;
The `Government` contract is used when a new version of one of the contracts `MinersEscrow`, `PolicyManager` and `Government` is ready and deployed.
Any miner who stacked tokens in `MinersEscrow` can create voting for upgrade one of the contracts by using the `createVoting(VotingType, address)` method.
There are 6 types of voting: upgrade to the new address or rollback to the previous version for the `MinersEscrow`, `PolicyManager` or `Government` contracts.
Every miner can vote for or against, the weight of the vote is the amount of locked tokens in the current period.
Voting lasts for a predetermined amount of time.
After the end of voting any user can run the `commitUpgrade()` method if there were more votes for upgrading than against.
The upgrade will be canceled in case of errors.

View File

@ -5,6 +5,7 @@ import "zeppelin/token/ERC20/SafeERC20.sol";
import "./lib/AdditionalMath.sol";
import "contracts/Issuer.sol";
/**
* @notice PolicyManager interface
**/
@ -15,8 +16,8 @@ contract PolicyManagerInterface {
/**
* @notice Contract holds and locks nodes tokens.self._solidity_source_dir
Each node that lock its tokens will receive some compensation
* @notice Contract holds and locks miners tokens.
Each miner that lock its tokens will receive some compensation
**/
contract MinersEscrow is Issuer {
using SafeERC20 for NuCypherToken;

View File

@ -33,6 +33,7 @@ contract UserEscrow is Ownable {
event Mined(address indexed owner);
event RewardWithdrawnAsMiner(address indexed owner, uint256 value);
event RewardWithdrawn(address indexed owner, uint256 value);
event MinRewardRateSet(address indexed owner, uint256 value);
NuCypherToken public token;
MinersEscrow public escrow;
@ -184,4 +185,12 @@ contract UserEscrow is Ownable {
emit RewardWithdrawn(owner, balance);
}
/**
* @notice Set the minimum reward that the miner will take in the policy manager
**/
function setMinRewardRate(uint256 _minRewardRate) public onlyOwner {
policyManager.setMinRewardRate(_minRewardRate);
emit MinRewardRateSet(owner, _minRewardRate);
}
}

View File

@ -1,120 +0,0 @@
pragma solidity ^0.4.23;
/**
* @notice Doubly linked list for addresses
* @dev see https://github.com/o0ragman0o/LibCLL
* @dev see https://github.com/Majoolr/ethereum-libraries/blob/master/LinkedListLib/truffle/contracts/LinkedListLib.sol
**/
library LinkedList {
address constant NULL = 0x0;
address constant HEAD = NULL;
bool constant PREV = false;
bool constant NEXT = true;
struct Data {
mapping (address => mapping (bool => address)) data;
uint256 count;
}
/// @notice Return existential state of a list.
function exists(Data storage self)
internal view returns (bool)
{
if (self.data[HEAD][PREV] != HEAD ||
self.data[HEAD][NEXT] != HEAD)
return true;
}
/// @notice Returns the number of elements in the list
function sizeOf(Data storage self)
internal view returns (uint result)
{
return self.count;
}
/**
* @notice Check existence of a value
* @param value Value to search for
**/
function valueExists(Data storage self, address value)
internal view returns (bool)
{
if (self.data[value][PREV] == HEAD && self.data[value][NEXT] == HEAD) {
if (self.data[HEAD][NEXT] == value) {
return true;
} else {
return false;
}
} else {
return true;
}
}
/// @notice Returns the links of a value as an array
function getLinks(Data storage self, address value)
internal view returns (address[2])
{
return [self.data[value][PREV], self.data[value][NEXT]];
}
/// @notice Returns the link of a value in specified direction.
function step(Data storage self, address value, bool direction)
internal view returns (address)
{
return self.data[value][direction];
}
/// @notice Creates a bidirectional link between two nodes on specified direction
function createLinks(
Data storage self,
address from,
address to,
bool direction
)
internal
{
self.data[to][!direction] = from;
self.data[from][direction] = to;
}
/// @notice Insert value beside existing value `from` in specified direction.
function insert (
Data storage self,
address from,
address value,
bool direction
)
internal
{
address to = self.data[from][direction];
createLinks(self, from, value, direction);
createLinks(self, value, to, direction);
self.count++;
}
/// @notice Remove value from the list.
function remove(Data storage self, address value) internal returns (address) {
if (value == NULL ||
((self.data[value][NEXT] == HEAD) &&
(self.data[value][PREV] == HEAD) &&
(self.data[self.data[value][PREV]][NEXT] != value))) {
return NULL;
}
createLinks(self, self.data[value][PREV], self.data[value][NEXT], NEXT);
delete self.data[value][PREV];
delete self.data[value][NEXT];
self.count--;
return value;
}
/// @notice Put value to the top of the list in specified direction.
function push(Data storage self, address value, bool direction) internal {
insert(self, HEAD, value, direction);
}
/// @notice Get value from the top of the list in specified direction and remove it.
function pop(Data storage self, bool direction) internal returns (address) {
return remove(self, step(self, HEAD, direction));
}
}

View File

@ -6,10 +6,16 @@ pragma solidity ^0.4.23;
**/
contract PolicyManagerForUserEscrowMock {
uint256 public minRewardRate;
function withdraw() public {
require(address(this).balance > 0);
msg.sender.transfer(address(this).balance);
}
function setMinRewardRate(uint256 _minRewardRate) public {
minRewardRate = _minRewardRate;
}
function () public payable {}
}

View File

@ -1,56 +0,0 @@
pragma solidity ^0.4.0;
import "contracts/lib/LinkedList.sol";
/**
* @notice Contract for testing LinkedList library
* @dev see https://github.com/Majoolr/ethereum-libraries/blob/master/LinkedListLib/truffle/contracts/LinkedListTestContract.sol
**/
contract LinkedListMock {
using LinkedList for LinkedList.Data;
LinkedList.Data list;
function exists() public view returns (bool) {
return list.exists();
}
function valueExists(address _value) public view returns (bool) {
return list.valueExists(_value);
}
function sizeOf() public view returns (uint256 numElements) {
return list.sizeOf();
}
function getLinks(address _value) public view returns (address[2]) {
return list.getLinks(_value);
}
function step(address _value, bool _direction) public view returns (address) {
return list.step(_value, _direction);
}
function createLinks(address _node, address _link, bool _direction) public {
list.createLinks(_node,_link,_direction);
}
function insert(address _node, address _new, bool _direction) public {
list.insert(_node,_new,_direction);
}
function remove(address _node) public returns (address) {
return list.remove(_node);
}
function push(address _node, bool _direction) public {
list.push(_node,_direction);
}
function pop(bool _direction) public returns (address) {
return list.pop(_direction);
}
}

View File

@ -1,107 +0,0 @@
"""
tests taken from
https://github.com/Majoolr/ethereum-libraries/blob/master/LinkedListLib/truffle/test/TestLinkedListLib.sol
"""
import pytest
NULL = '0x0000000000000000000000000000000000000000'
HEAD = NULL
PREV = False
NEXT = True
@pytest.mark.slow
def test_linked_list(web3, chain):
address1 = web3.eth.accounts[0]
address2 = web3.eth.accounts[1]
address3 = web3.eth.accounts[2]
address4 = web3.eth.accounts[3]
# Deploy test contract
instance, _ = chain.provider.deploy_contract('LinkedListMock')
# Check that list is empty
assert not instance.functions.exists().call()
assert instance.functions.sizeOf().call() == 0
# assert instance.functions.seek(HEAD, address2, NEXT).call() == NULL
# Insert new value
tx = instance.functions.insert(HEAD, address2, NEXT).transact({'from': address1})
chain.wait_for_receipt(tx)
assert instance.functions.exists().call()
assert instance.functions.sizeOf().call() == 1
assert instance.functions.valueExists(address2).call()
# Insert more values
tx = instance.functions.insert(address2, address1, PREV).transact({'from': address1})
chain.wait_for_receipt(tx)
tx = instance.functions.insert(address2, address3, NEXT).transact({'from': address1})
chain.wait_for_receipt(tx)
assert instance.functions.sizeOf().call() == 3
# Try to remove non-existent value
assert instance.functions.remove(address4).call() == NULL
# Remove middle value
assert instance.functions.remove(address2).call() == address2
tx = instance.functions.remove(address2).transact({'from': address1})
chain.wait_for_receipt(tx)
assert instance.functions.sizeOf().call() == 2
# Check node
node = instance.functions.getLinks(address1).call()
assert node[0] == HEAD
assert node[1] == address3
# Remove another value
assert instance.functions.remove(address3).call() == address3
tx = instance.functions.remove(address3).transact({'from': address1})
chain.wait_for_receipt(tx)
assert instance.functions.sizeOf().call() == 1
# Check node
node = instance.functions.getLinks(address1).call()
assert node[0] == HEAD
assert node[1] == HEAD
# Remove last value
assert instance.functions.remove(address1).call() == address1
tx = instance.functions.remove(address1).transact({'from': address1})
chain.wait_for_receipt(tx)
assert instance.functions.sizeOf().call() == 0
# Check head node
node = instance.functions.getLinks(HEAD).call()
assert node[0] == HEAD
assert node[1] == HEAD
# Push values
tx = instance.functions.push(address2, NEXT).transact({'from': address1})
chain.wait_for_receipt(tx)
tx = instance.functions.push(address3, PREV).transact({'from': address1})
chain.wait_for_receipt(tx)
tx = instance.functions.push(address1, NEXT).transact({'from': address1})
chain.wait_for_receipt(tx)
assert instance.functions.sizeOf().call() == 3
# Check nodes
node = instance.functions.getLinks(address3).call()
assert node[0] == address2
assert node[1] == HEAD
node = instance.functions.getLinks(address1).call()
assert node[0] == HEAD
assert node[1] == address2
# Pop values
assert instance.functions.pop(NEXT).call() == address1
assert instance.functions.pop(PREV).call() == address3
tx = instance.functions.pop(NEXT).transact({'from': address1})
chain.wait_for_receipt(tx)
tx = instance.functions.pop(PREV).transact({'from': address1})
chain.wait_for_receipt(tx)
assert instance.functions.sizeOf().call() == 1
# Check last node
node = instance.functions.getLinks(address2).call()
assert node[0] == HEAD
assert node[1] == HEAD

View File

@ -4,7 +4,6 @@ from eth_tester.exceptions import TransactionFailed
@pytest.fixture()
def token(web3, chain):
creator = web3.eth.accounts[0]
# Create an ERC20 token
token, _ = chain.provider.deploy_contract('NuCypherToken', int(2e9))
return token
@ -38,7 +37,7 @@ def user_escrow(web3, chain, token, escrow, policy_manager):
contract, _ = chain.provider.deploy_contract('UserEscrow', token.address, escrow.address, policy_manager.address)
# Transfer ownership
tx = contract.functions.transferOwnership(user).transact({'from': creator})
tx = contract.functions.transferOwnership(user).transact({'from': creator})
chain.wait_for_receipt(tx)
return contract
@ -50,9 +49,9 @@ def test_escrow(web3, chain, token, user_escrow):
deposits = user_escrow.events.Deposited.createFilter(fromBlock='latest')
# Deposit some tokens to the user escrow and lock them
tx = token.functions.approve(user_escrow.address, 2000).transact({'from': creator})
tx = token.functions.approve(user_escrow.address, 2000).transact({'from': creator})
chain.wait_for_receipt(tx)
tx = user_escrow.functions.initialDeposit(1000, 1000).transact({'from': creator})
tx = user_escrow.functions.initialDeposit(1000, 1000).transact({'from': creator})
chain.wait_for_receipt(tx)
assert 1000 == token.functions.balanceOf(user_escrow.address).call()
assert user == user_escrow.functions.owner().call()
@ -68,16 +67,16 @@ def test_escrow(web3, chain, token, user_escrow):
# Can't deposit tokens again
with pytest.raises((TransactionFailed, ValueError)):
tx = user_escrow.functions.initialDeposit(1000, 1000).transact({'from': creator})
tx = user_escrow.functions.initialDeposit(1000, 1000).transact({'from': creator})
chain.wait_for_receipt(tx)
# Can't withdraw before unlocking
with pytest.raises((TransactionFailed, ValueError)):
tx = user_escrow.functions.withdraw(100).transact({'from': user})
tx = user_escrow.functions.withdraw(100).transact({'from': user})
chain.wait_for_receipt(tx)
# Can transfer more tokens
tx = token.functions.transfer(user_escrow.address, 1000).transact({'from': creator})
tx = token.functions.transfer(user_escrow.address, 1000).transact({'from': creator})
chain.wait_for_receipt(tx)
assert 2000 == token.functions.balanceOf(user_escrow.address).call()
@ -85,9 +84,9 @@ def test_escrow(web3, chain, token, user_escrow):
# Only user can withdraw available tokens
with pytest.raises((TransactionFailed, ValueError)):
tx = user_escrow.functions.withdraw(100).transact({'from': creator})
tx = user_escrow.functions.withdraw(100).transact({'from': creator})
chain.wait_for_receipt(tx)
tx = user_escrow.functions.withdraw(1000).transact({'from': user})
tx = user_escrow.functions.withdraw(1000).transact({'from': user})
chain.wait_for_receipt(tx)
assert 1000 == token.functions.balanceOf(user).call()
assert 1000 == token.functions.balanceOf(user_escrow.address).call()
@ -105,7 +104,7 @@ def test_escrow(web3, chain, token, user_escrow):
assert 450 <= user_escrow.functions.getLockedTokens().call()
# User can withdraw some unlocked tokens
tx = user_escrow.functions.withdraw(500).transact({'from': user})
tx = user_escrow.functions.withdraw(500).transact({'from': user})
chain.wait_for_receipt(tx)
assert 1500 == token.functions.balanceOf(user).call()
@ -120,7 +119,7 @@ def test_escrow(web3, chain, token, user_escrow):
# Wait more time and withdraw all
chain.time_travel(seconds=500)
assert 0 == user_escrow.functions.getLockedTokens().call()
tx = user_escrow.functions.withdraw(500).transact({'from': user})
tx = user_escrow.functions.withdraw(500).transact({'from': user})
chain.wait_for_receipt(tx)
assert 0 == token.functions.balanceOf(user_escrow.address).call()
assert 2000 == token.functions.balanceOf(user).call()
@ -142,11 +141,11 @@ def test_miner(web3, chain, token, escrow, user_escrow):
deposits = user_escrow.events.Deposited.createFilter(fromBlock='latest')
# Deposit some tokens to the user escrow and lock them
tx = token.functions.approve(user_escrow.address, 1000).transact({'from': creator})
tx = token.functions.approve(user_escrow.address, 1000).transact({'from': creator})
chain.wait_for_receipt(tx)
tx = user_escrow.functions.initialDeposit(1000, 1000).transact({'from': creator})
tx = user_escrow.functions.initialDeposit(1000, 1000).transact({'from': creator})
chain.wait_for_receipt(tx)
tx = token.functions.transfer(user_escrow.address, 1000).transact({'from': creator})
tx = token.functions.transfer(user_escrow.address, 1000).transact({'from': creator})
chain.wait_for_receipt(tx)
assert 2000 == token.functions.balanceOf(user_escrow.address).call()
@ -155,17 +154,17 @@ def test_miner(web3, chain, token, escrow, user_escrow):
# Only user can deposit tokens to the miner escrow
with pytest.raises((TransactionFailed, ValueError)):
tx = user_escrow.functions.minerDeposit(1000, 5).transact({'from': creator})
tx = user_escrow.functions.minerDeposit(1000, 5).transact({'from': creator})
chain.wait_for_receipt(tx)
# Can't deposit more than amount in the user escrow
with pytest.raises((TransactionFailed, ValueError)):
tx = user_escrow.functions.minerDeposit(10000, 5).transact({'from': user})
tx = user_escrow.functions.minerDeposit(10000, 5).transact({'from': user})
chain.wait_for_receipt(tx)
miner_deposits = user_escrow.events.DepositedAsMiner.createFilter(fromBlock='latest')
# Deposit some tokens to the miners escrow
tx = user_escrow.functions.minerDeposit(1500, 5).transact({'from': user})
tx = user_escrow.functions.minerDeposit(1500, 5).transact({'from': user})
chain.wait_for_receipt(tx)
assert user_escrow.address == escrow.functions.node().call()
assert 1500 == escrow.functions.value().call()
@ -184,12 +183,12 @@ def test_miner(web3, chain, token, escrow, user_escrow):
# Can't withdraw because of locking
with pytest.raises((TransactionFailed, ValueError)):
tx = user_escrow.functions.withdraw(100).transact({'from': user})
tx = user_escrow.functions.withdraw(100).transact({'from': user})
chain.wait_for_receipt(tx)
# Can't use the miners escrow directly
with pytest.raises((TransactionFailed, ValueError)):
tx = escrow.functions.lock(100, 1).transact({'from': user})
tx = escrow.functions.lock(100, 1).transact({'from': user})
chain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = escrow.functions.divideStake(1500, 5, 100, 1).transact({'from': user})
@ -201,7 +200,7 @@ def test_miner(web3, chain, token, escrow, user_escrow):
tx = escrow.functions.mint().transact({'from': user})
chain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = escrow.functions.withdraw(100).transact({'from': user})
tx = escrow.functions.withdraw(100).transact({'from': user})
chain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = escrow.functions.withdrawAll().transact({'from': user})
@ -215,7 +214,7 @@ def test_miner(web3, chain, token, escrow, user_escrow):
withdraws = user_escrow.events.Withdrawn.createFilter(fromBlock='latest')
# Use methods through the user escrow
tx = user_escrow.functions.lock(100, 1).transact({'from': user})
tx = user_escrow.functions.lock(100, 1).transact({'from': user})
chain.wait_for_receipt(tx)
assert 1500 == escrow.functions.value().call()
assert 1600 == escrow.functions.lockedValue().call()
@ -231,12 +230,12 @@ def test_miner(web3, chain, token, escrow, user_escrow):
tx = user_escrow.functions.mint().transact({'from': user})
chain.wait_for_receipt(tx)
assert 2500 == escrow.functions.value().call()
tx = user_escrow.functions.minerWithdraw(1500).transact({'from': user})
tx = user_escrow.functions.minerWithdraw(1500).transact({'from': user})
chain.wait_for_receipt(tx)
assert 1000 == escrow.functions.value().call()
assert 10000 == token.functions.balanceOf(escrow.address).call()
assert 2000 == token.functions.balanceOf(user_escrow.address).call()
tx = user_escrow.functions.minerWithdraw(1000).transact({'from': user})
tx = user_escrow.functions.minerWithdraw(1000).transact({'from': user})
chain.wait_for_receipt(tx)
assert 0 == escrow.functions.value().call()
assert 9000 == token.functions.balanceOf(escrow.address).call()
@ -279,9 +278,9 @@ def test_miner(web3, chain, token, escrow, user_escrow):
# User can withdraw reward for mining but no more than locked
with pytest.raises((TransactionFailed, ValueError)):
tx = user_escrow.functions.withdraw(2500).transact({'from': user})
tx = user_escrow.functions.withdraw(2500).transact({'from': user})
chain.wait_for_receipt(tx)
tx = user_escrow.functions.withdraw(1000).transact({'from': user})
tx = user_escrow.functions.withdraw(1000).transact({'from': user})
chain.wait_for_receipt(tx)
assert 2000 == token.functions.balanceOf(user_escrow.address).call()
assert 1000 == token.functions.balanceOf(user).call()
@ -346,3 +345,18 @@ def test_policy(web3, chain, policy_manager, user_escrow):
event_args = events[0]['args']
assert user == event_args['owner']
assert 10000 == event_args['value']
# Set min reward rate
min_reward_sets = user_escrow.events.MinRewardRateSet.createFilter(fromBlock='latest')
with pytest.raises((TransactionFailed, ValueError)):
tx = user_escrow.functions.setMinRewardRate(333).transact({'from': creator})
chain.wait_for_receipt(tx)
tx = user_escrow.functions.setMinRewardRate(222).transact({'from': user})
chain.wait_for_receipt(tx)
assert 222 == policy_manager.functions.minRewardRate().call()
events = min_reward_sets.get_all_entries()
assert 1 == len(events)
event_args = events[0]['args']
assert user == event_args['owner']
assert 222 == event_args['value']