mirror of https://github.com/nucypher/nucypher.git
Added draft of the docs for contracts, fixed user escrow
parent
01a1a6d7d7
commit
0b068d26e0
|
@ -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)
|
|
@ -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.
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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 {}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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']
|
||||
|
|
Loading…
Reference in New Issue