diff --git a/.gitignore b/.gitignore index f2b4363586..4c591b1ddc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ /**/__pycache__/* test_settings.py +deploy_settings.py *.pyc - diff --git a/README.md b/README.md index bf444dc8be..df276ea37c 100644 --- a/README.md +++ b/README.md @@ -62,16 +62,15 @@ come at the expense of clarity or simplicity. * `deploy.py` for deploying Havven contracts to the blockchain. * `run_tests.py` runs the test suite. * `contracts/` contains smart contract code to be deployed. -* `contracts/Court.sol` a court of arbitration to enable the balance of malicious contracts to be democratically confiscated and frozen. -* `contracts/ExternStateProxyToken.sol` a foundation for generic ERC20 tokens with external state, and which exist behind a proxy. -* `contracts/TokenState.sol` The balances of the ExternStateProxyToken contract. -* `contracts/ExternStateProxyFeeToken.sol` a foundation for generic ERC20 tokens which also charge fees on transfers, with external state, and which exist behind a proxy. -* `contracts/EtherNomin.sol` ether-backed nomin contract, with liquidation and confiscation logic. -* `contracts/Havven.sol` havven collateral token, including calculations involving entitlements to fees being generated by nomins. -* `contracts/HavvenEscrow.sol` vesting schedule manager, allows vested havvens to be freed up after certain dates. * `contracts/Owned.sol` a contract with an owner. -* `contracts/Proxy.sol` a proxy contract that can pass function calls through to an underlying contract which implements the "proxyable" interface. * `contracts/SafeDecimalMath.sol` a math library for unsigned fixed point decimal arithmetic, with built-in safety checking. +* `contracts/TokenState.sol` The balances of the DestructibleExternStateToken contract. +* `contracts/DestructibleExternStateToken.sol` a foundation for generic ERC20 tokens with external state. +* `contracts/ExternStateFeeToken.sol` a foundation for generic ERC20 tokens which also charge fees on transfers, with external state. +* `contracts/Nomin.sol` ether-backed nomin contract, with liquidation and confiscation logic. +* `contracts/Havven.sol` havven collateral token, including calculations involving entitlements to fees being generated by nomins. +* `contracts/HavvenEscrow.sol` vesting schedule manager, allows vested havvens to be freed up after certain dates. +* `contracts/Court.sol` a court of arbitration to enable the balance of malicious contracts to be democratically confiscated and frozen. * `tests/` test cases. * `tests/contracts` contracts used by the test suite. * `utils/` helper functions for testing and deployment. diff --git a/contracts/Court.sol b/contracts/Court.sol index 43bb0a6936..48882c36d5 100644 --- a/contracts/Court.sol +++ b/contracts/Court.sol @@ -2,15 +2,14 @@ ----------------------------------------------------------------- FILE INFORMATION ----------------------------------------------------------------- + file: Court.sol -version: 1.0 +version: 1.2 author: Anton Jurisevic Mike Spain + Dominic Romanowski -date: 2018-2-6 - -checked: Mike Spain -approved: Samuel Brooks +date: 2018-05-29 ----------------------------------------------------------------- MODULE DESCRIPTION @@ -32,25 +31,28 @@ confiscation motions are only approved if the havven foundation approves the result. This latter requirement may be lifted in future versions. -The foundation, or any user with a sufficient havven balance may bring a -confiscation motion. -A motion lasts for a default period of one week, with a further confirmation -period in which the foundation approves the result. -The latter period may conclude early upon the foundation's decision to either -veto or approve the mooted confiscation motion. -If the confirmation period elapses without the foundation making a decision, -the motion fails. - -The weight of a havven holder's vote is determined by examining their -average balance over the last completed fee period prior to the -beginning of a given motion. -Thus, since a fee period can roll over in the middle of a motion, we must -also track a user's average balance of the last two periods. -This system is designed such that it cannot be attacked by users transferring -funds between themselves, while also not requiring them to lock their havvens -for the duration of the vote. This is possible since any transfer that increases -the average balance in one account will be reflected by an equivalent reduction -in the voting weight in the other. +The foundation, or any user with a sufficient havven balance may +bring a confiscation motion. +A motion lasts for a default period of one week, with a further +confirmation period in which the foundation approves the result. +The latter period may conclude early upon the foundation's decision +to either veto or approve the mooted confiscation motion. +If the confirmation period elapses without the foundation making +a decision, the motion fails. + +The weight of a havven holder's vote is determined by examining +their average balance over the last completed fee period prior to +the beginning of a given motion. + +Thus, since a fee period can roll over in the middle of a motion, +we must also track a user's average balance of the last two periods. +This system is designed such that it cannot be attacked by users +transferring funds between themselves, while also not requiring them +to lock their havvens for the duration of the vote. This is possible +since any transfer that increases the average balance in one account +will be reflected by an equivalent reduction in the voting weight in +the other. + At present a user may cast a vote only for one motion at a time, but may cancel their vote at any time except during the confirmation period, when the vote tallies must remain static until the matter has been settled. @@ -58,7 +60,6 @@ when the vote tallies must remain static until the matter has been settled. A motion to confiscate the balance of a given address composes a state machine built of the following states: - Waiting: - A user with standing brings a motion: If the target address is not frozen; @@ -94,15 +95,14 @@ Confirmation: - The confirmation period elapses: transition to the Waiting state. - User votes are not automatically cancelled upon the conclusion of a motion. -Therefore, after a motion comes to a conclusion, if a user wishes to vote +Therefore, after a motion comes to a conclusion, if a user wishes to vote in another motion, they must manually cancel their vote in order to do so. This procedure is designed to be relatively simple. There are some things that can be added to enhance the functionality at the expense of simplicity and efficiency: - + - Democratic unfreezing of nomin accounts (induces multiple categories of vote) - Configurable per-vote durations; - Vote standing denominated in a fiat quantity rather than a quantity of havvens; @@ -113,28 +113,28 @@ We might consider updating the contract with any of these features at a later da ----------------------------------------------------------------- */ -pragma solidity ^0.4.21; +pragma solidity 0.4.24; import "contracts/Owned.sol"; import "contracts/SafeDecimalMath.sol"; -import "contracts/EtherNomin.sol"; +import "contracts/Nomin.sol"; import "contracts/Havven.sol"; /** * @title A court contract allowing a democratic mechanism to dissuade token wrappers. */ -contract Court is Owned, SafeDecimalMath { +contract Court is SafeDecimalMath, Owned { /* ========== STATE VARIABLES ========== */ /* The addresses of the token contracts this confiscation court interacts with. */ Havven public havven; - EtherNomin public nomin; + Nomin public nomin; - /* The minimum havven balance required to be considered to have standing - * to begin confiscation proceedings. */ + /* The minimum issued nomin balance required to be considered to have + * standing to begin confiscation proceedings. */ uint public minStandingBalance = 100 * UNIT; /* The voting period lasts for this duration, @@ -150,19 +150,23 @@ contract Court is Owned, SafeDecimalMath { uint constant MIN_CONFIRMATION_PERIOD = 1 days; uint constant MAX_CONFIRMATION_PERIOD = 2 weeks; - /* No fewer than this fraction of havvens must participate in a motion - * in order for a quorum to be reached. - * The participation fraction required may be set no lower than 10%. */ + /* No fewer than this fraction of total available voting power must + * participate in a motion in order for a quorum to be reached. + * The participation fraction required may be set no lower than 10%. + * As a fraction, it is expressed in terms of UNIT, not as an absolute quantity. */ uint public requiredParticipation = 3 * UNIT / 10; uint constant MIN_REQUIRED_PARTICIPATION = UNIT / 10; /* At least this fraction of participating votes must be in favour of * confiscation for the motion to pass. - * The required majority may be no lower than 50%. */ + * The required majority may be no lower than 50%. + * As a fraction, it is expressed in terms of UNIT, not as an absolute quantity. */ uint public requiredMajority = (2 * UNIT) / 3; uint constant MIN_REQUIRED_MAJORITY = UNIT / 2; - /* The next ID to use for opening a motion. */ + /* The next ID to use for opening a motion. + * The 0 motion ID corresponds to no motion, + * and is used as a null value for later comparison. */ uint nextMotionID = 1; /* Mapping from motion IDs to target addresses. */ @@ -186,12 +190,14 @@ contract Court is Owned, SafeDecimalMath { mapping(uint => uint) public votesFor; mapping(uint => uint) public votesAgainst; - /* The last/penultimate average balance of a user at the time they voted + /* The last average balance of a user at the time they voted * in a particular motion. * If we did not save this information then we would have to * disallow transfers into an account lest it cancel a vote * with greater weight than that with which it originally voted, * and the fee period rolled over in between. */ + // TODO: This may be unnecessary now that votes are forced to be + // within a fee period. Likely possible to delete this. mapping(address => mapping(uint => uint)) voteWeight; /* The possible vote types. @@ -208,9 +214,9 @@ contract Court is Owned, SafeDecimalMath { /* ========== CONSTRUCTOR ========== */ /** - * @dev Constructor. + * @dev Court Constructor. */ - function Court(Havven _havven, EtherNomin _nomin, address _owner) + constructor(Havven _havven, Nomin _nomin, address _owner) Owned(_owner) public { @@ -238,7 +244,7 @@ contract Court is Owned, SafeDecimalMath { /** * @notice Set the length of time a vote runs for. * @dev Only the contract owner may call this. The proposed duration must fall - * within sensible bounds (1 to 4 weeks), and must be no longer than a single fee period. + * within sensible bounds (3 days to 4 weeks), and must be no longer than a single fee period. */ function setVotingPeriod(uint duration) external @@ -248,10 +254,15 @@ contract Court is Owned, SafeDecimalMath { duration <= MAX_VOTING_PERIOD); /* Require that the voting period is no longer than a single fee period, * So that a single vote can span at most two fee periods. */ - require(duration <= havven.targetFeePeriodDurationSeconds()); + require(duration <= havven.feePeriodDuration()); votingPeriod = duration; } + /** + * @notice Set the confirmation period after a vote has concluded. + * @dev Only the contract owner may call this. The proposed duration must fall + * within sensible bounds (1 day to 2 weeks). + */ function setConfirmationPeriod(uint duration) external onlyOwner @@ -261,6 +272,10 @@ contract Court is Owned, SafeDecimalMath { confirmationPeriod = duration; } + /** + * @notice Set the required fraction of all Havvens that need to be part of + * a vote for it to pass. + */ function setRequiredParticipation(uint fraction) external onlyOwner @@ -269,6 +284,10 @@ contract Court is Owned, SafeDecimalMath { requiredParticipation = fraction; } + /** + * @notice Set what portion of voting havvens need to be in the affirmative + * to allow it to pass. + */ function setRequiredMajority(uint fraction) external onlyOwner @@ -280,21 +299,20 @@ contract Court is Owned, SafeDecimalMath { /* ========== VIEW FUNCTIONS ========== */ - /* There is a motion in progress on the specified - * account, and votes are being accepted in that motion. */ + /** + * @notice There is a motion in progress on the specified + * account, and votes are being accepted in that motion. + */ function motionVoting(uint motionID) public view returns (bool) { - /* No need to check (startTime < now) as there is no way - * to set future start times for votes. - * These values are timestamps, they will not overflow - * as they can only ever be initialised to relatively small values. */ - return now < motionStartTime[motionID] + votingPeriod; + return motionStartTime[motionID] < now && now < motionStartTime[motionID] + votingPeriod; } - /* A vote on the target account has concluded, but the motion + /** + * @notice A vote on the target account has concluded, but the motion * has not yet been approved, vetoed, or closed. */ function motionConfirming(uint motionID) public @@ -302,13 +320,16 @@ contract Court is Owned, SafeDecimalMath { returns (bool) { /* These values are timestamps, they will not overflow - * as they can only ever be initialised to relatively small values. */ + * as they can only ever be initialised to relatively small values. + */ uint startTime = motionStartTime[motionID]; return startTime + votingPeriod <= now && now < startTime + votingPeriod + confirmationPeriod; } - /* A vote motion either not begun, or it has completely terminated. */ + /** + * @notice A vote motion either not begun, or it has completely terminated. + */ function motionWaiting(uint motionID) public view @@ -319,8 +340,10 @@ contract Court is Owned, SafeDecimalMath { return motionStartTime[motionID] + votingPeriod + confirmationPeriod <= now; } - /* If the motion was to terminate at this instant, it would pass. - * That is: there was sufficient participation and a sizeable enough majority. */ + /** + * @notice If the motion was to terminate at this instant, it would pass. + * That is: there was sufficient participation and a sizeable enough majority. + */ function motionPasses(uint motionID) public view @@ -334,7 +357,7 @@ contract Court is Owned, SafeDecimalMath { return false; } - uint participation = safeDiv_dec(totalVotes, havven.totalSupply()); + uint participation = safeDiv_dec(totalVotes, havven.totalIssuanceLastAverageBalance()); uint fractionInFavour = safeDiv_dec(yeas, totalVotes); /* We require the result to be strictly greater than the requirement @@ -343,6 +366,9 @@ contract Court is Owned, SafeDecimalMath { fractionInFavour > requiredMajority; } + /** + * @notice Return if the specified account has voted on the specified motion + */ function hasVoted(address account, uint motionID) public view @@ -354,40 +380,49 @@ contract Court is Owned, SafeDecimalMath { /* ========== MUTATIVE FUNCTIONS ========== */ - /* Begin a motion to confiscate the funds in a given nomin account. - * Only the foundation, or accounts with sufficient havven balances + /** + * @notice Begin a motion to confiscate the funds in a given nomin account. + * @dev Only the foundation, or accounts with sufficient havven balances * may elect to start such a motion. - * Returns the ID of the motion that was begun. */ + * @return Returns the ID of the motion that was begun. + */ function beginMotion(address target) external returns (uint) { /* A confiscation motion must be mooted by someone with standing. */ - require((havven.balanceOf(msg.sender) >= minStandingBalance) || + require((havven.issuanceLastAverageBalance(msg.sender) >= minStandingBalance) || msg.sender == owner); /* Require that the voting period is longer than a single fee period, * So that a single vote can span at most two fee periods. */ - require(votingPeriod <= havven.targetFeePeriodDurationSeconds()); + require(votingPeriod <= havven.feePeriodDuration()); /* There must be no confiscation motion already running for this account. */ require(targetMotionID[target] == 0); - /* Disallow votes on accounts that have previously been frozen. */ + /* Disallow votes on accounts that are currently frozen. */ require(!nomin.frozen(target)); + /* It is necessary to roll over the fee period if it has elapsed, or else + * the vote might be initialised having begun in the past. */ + havven.rolloverFeePeriodIfElapsed(); + uint motionID = nextMotionID++; motionTarget[motionID] = target; targetMotionID[target] = motionID; - motionStartTime[motionID] = now; - emit MotionBegun(msg.sender, msg.sender, target, target, motionID, motionID); + /* Start the vote at the start of the next fee period */ + uint startTime = havven.feePeriodStartTime() + havven.feePeriodDuration(); + motionStartTime[motionID] = startTime; + emit MotionBegun(msg.sender, target, motionID, startTime); return motionID; } - /* Shared vote setup function between voteFor and voteAgainst. - * Returns the voter's vote weight. */ + /** + * @notice Shared vote setup function between voteFor and voteAgainst. + * @return Returns the voter's vote weight. */ function setupVote(uint motionID) internal returns (uint) @@ -402,18 +437,7 @@ contract Court is Owned, SafeDecimalMath { /* The voter may not cast votes on themselves. */ require(msg.sender != motionTarget[motionID]); - /* Ensure the voter's vote weight is current. */ - havven.recomputeAccountLastAverageBalance(msg.sender); - - uint weight; - /* We use a fee period guaranteed to have terminated before - * the start of the vote. Select the right period if - * a fee period rolls over in the middle of the vote. */ - if (motionStartTime[motionID] < havven.feePeriodStartTime()) { - weight = havven.penultimateAverageBalance(msg.sender); - } else { - weight = havven.lastAverageBalance(msg.sender); - } + uint weight = havven.recomputeLastAverageBalance(msg.sender); /* Users must have a nonzero voting weight to vote. */ require(weight > 0); @@ -423,30 +447,36 @@ contract Court is Owned, SafeDecimalMath { return weight; } - /* The sender casts a vote in favour of confiscation of the - * target account's nomin balance. */ + /** + * @notice The sender casts a vote in favour of confiscation of the + * target account's nomin balance. + */ function voteFor(uint motionID) external { uint weight = setupVote(motionID); vote[msg.sender][motionID] = Vote.Yea; votesFor[motionID] = safeAdd(votesFor[motionID], weight); - emit VotedFor(msg.sender, msg.sender, motionID, motionID, weight); + emit VotedFor(msg.sender, motionID, weight); } - /* The sender casts a vote against confiscation of the - * target account's nomin balance. */ + /** + * @notice The sender casts a vote against confiscation of the + * target account's nomin balance. + */ function voteAgainst(uint motionID) external { uint weight = setupVote(motionID); vote[msg.sender][motionID] = Vote.Nay; votesAgainst[motionID] = safeAdd(votesAgainst[motionID], weight); - emit VotedAgainst(msg.sender, msg.sender, motionID, motionID, weight); + emit VotedAgainst(msg.sender, motionID, weight); } - /* Cancel an existing vote by the sender on a motion - * to confiscate the target balance. */ + /** + * @notice Cancel an existing vote by the sender on a motion + * to confiscate the target balance. + */ function cancelVote(uint motionID) external { @@ -471,13 +501,16 @@ contract Court is Owned, SafeDecimalMath { votesAgainst[motionID] = safeSub(votesAgainst[motionID], voteWeight[msg.sender][motionID]); } /* A cancelled vote is only meaningful if a vote is running. */ - emit VoteCancelled(msg.sender, msg.sender, motionID, motionID); + emit VoteCancelled(msg.sender, motionID); } delete voteWeight[msg.sender][motionID]; delete vote[msg.sender][motionID]; } + /** + * @notice clear all data associated with a motionID for hygiene purposes. + */ function _closeMotion(uint motionID) internal { @@ -486,11 +519,13 @@ contract Court is Owned, SafeDecimalMath { delete motionStartTime[motionID]; delete votesFor[motionID]; delete votesAgainst[motionID]; - emit MotionClosed(motionID, motionID); + emit MotionClosed(motionID); } - /* If a motion has concluded, or if it lasted its full duration but not passed, - * then anyone may close it. */ + /** + * @notice If a motion has concluded, or if it lasted its full duration but not passed, + * then anyone may close it. + */ function closeMotion(uint motionID) external { @@ -498,43 +533,45 @@ contract Court is Owned, SafeDecimalMath { _closeMotion(motionID); } - /* The foundation may only confiscate a balance during the confirmation - * period after a motion has passed. */ + /** + * @notice The foundation may only confiscate a balance during the confirmation + * period after a motion has passed. + */ function approveMotion(uint motionID) external onlyOwner { require(motionConfirming(motionID) && motionPasses(motionID)); address target = motionTarget[motionID]; - nomin.confiscateBalance(target); + nomin.freezeAndConfiscate(target); _closeMotion(motionID); - emit MotionApproved(motionID, motionID); + emit MotionApproved(motionID); } - /* The foundation may veto a motion at any time. */ + /* @notice The foundation may veto a motion at any time. */ function vetoMotion(uint motionID) external onlyOwner { require(!motionWaiting(motionID)); _closeMotion(motionID); - emit MotionVetoed(motionID, motionID); + emit MotionVetoed(motionID); } /* ========== EVENTS ========== */ - event MotionBegun(address initiator, address indexed initiatorIndex, address target, address indexed targetIndex, uint motionID, uint indexed motionIDIndex); + event MotionBegun(address indexed initiator, address indexed target, uint indexed motionID, uint startTime); - event VotedFor(address voter, address indexed voterIndex, uint motionID, uint indexed motionIDIndex, uint weight); + event VotedFor(address indexed voter, uint indexed motionID, uint weight); - event VotedAgainst(address voter, address indexed voterIndex, uint motionID, uint indexed motionIDIndex, uint weight); + event VotedAgainst(address indexed voter, uint indexed motionID, uint weight); - event VoteCancelled(address voter, address indexed voterIndex, uint motionID, uint indexed motionIDIndex); + event VoteCancelled(address indexed voter, uint indexed motionID); - event MotionClosed(uint motionID, uint indexed motionIDIndex); + event MotionClosed(uint indexed motionID); - event MotionVetoed(uint motionID, uint indexed motionIDIndex); + event MotionVetoed(uint indexed motionID); - event MotionApproved(uint motionID, uint indexed motionIDIndex); + event MotionApproved(uint indexed motionID); } diff --git a/contracts/EtherNomin.sol b/contracts/EtherNomin.sol deleted file mode 100644 index 59dd7eda02..0000000000 --- a/contracts/EtherNomin.sol +++ /dev/null @@ -1,694 +0,0 @@ -/* ------------------------------------------------------------------ -FILE INFORMATION ------------------------------------------------------------------ -file: EtherNomin.sol -version: 1.0 -author: Anton Jurisevic - Mike Spain - -date: 2018-2-28 - -checked: Mike Spain -approved: Samuel Brooks - ------------------------------------------------------------------ -MODULE DESCRIPTION ------------------------------------------------------------------ - -Ether-backed nomin stablecoin contract. - -This contract issues nomins, which are tokens worth 1 USD each. They are backed -by a pool of ether collateral, so that if a user has nomins, they may -redeem them for ether from the pool, or if they want to obtain nomins, -they may pay ether into the pool in order to do so. - -The supply of nomins that may be in circulation at any time is limited. -The contract owner may increase this quantity, but only if they provide -ether to back it. The backing the owner provides at issuance must -keep each nomin at least twice overcollateralised. -The owner may also destroy nomins in the pool, which is potential avenue -by which to maintain healthy collateralisation levels, as it reduces -supply without withdrawing ether collateral. - -A configurable fee is charged on nomin transfers and deposited -into a common pot, which havven holders may withdraw from once per -fee period. - -Ether price is continually updated by an external oracle, and the value -of the backing is computed on this basis. To ensure the integrity of -this system, if the contract's price has not been updated recently enough, -it will temporarily disable itself until it receives more price information. - -The contract owner may at any time initiate contract liquidation. -During the liquidation period, most contract functions will be deactivated. -No new nomins may be issued or bought, but users may sell nomins back -to the system. -If the system's collateral falls below a specified level, then anyone -may initiate liquidation. - -After the liquidation period has elapsed, which is initially 90 days, -the owner may destroy the contract, transferring any remaining collateral -to a nominated beneficiary address. -This liquidation period may be extended up to a maximum of 180 days. -If the contract is recollateralised, the owner may terminate liquidation. - ------------------------------------------------------------------ -*/ - -pragma solidity ^0.4.21; - - -import "contracts/ExternStateProxyFeeToken.sol"; -import "contracts/TokenState.sol"; -import "contracts/Court.sol"; - - -contract EtherNomin is ExternStateProxyFeeToken { - - /* ========== STATE VARIABLES ========== */ - - /* The oracle provides price information to this contract. - * It may only call the updatePrice() function. */ - address public oracle; - - /* The address of the contract which manages confiscation votes. */ - Court public court; - - /* Foundation wallet for funds to go to post liquidation. */ - address public beneficiary; - - /* Nomins in the pool ready to be sold. */ - uint public nominPool; - - /* Impose a 50 basis-point fee for buying from and selling to the nomin pool. */ - uint public poolFeeRate = UNIT / 200; - - /* The minimum purchasable quantity of nomins is 1 cent. */ - uint constant MINIMUM_PURCHASE = UNIT / 100; - - /* When issuing, nomins must be overcollateralised by this ratio. */ - uint constant MINIMUM_ISSUANCE_RATIO = 2 * UNIT; - - /* If the collateralisation ratio of the contract falls below this level, - * immediately begin liquidation. */ - uint constant AUTO_LIQUIDATION_RATIO = UNIT; - - /* The liquidation period is the duration that must pass before the liquidation period is complete. - * It can be extended up to a given duration. */ - uint constant DEFAULT_LIQUIDATION_PERIOD = 14 days; - uint constant MAX_LIQUIDATION_PERIOD = 180 days; - uint public liquidationPeriod = DEFAULT_LIQUIDATION_PERIOD; - - /* The timestamp when liquidation was activated. We initialise this to - * uint max, so that we know that we are under liquidation if the - * liquidation timestamp is in the past. */ - uint public liquidationTimestamp = ~uint(0); - - /* Ether price from oracle (fiat per ether). */ - uint public etherPrice; - - /* Last time the price was updated. */ - uint public lastPriceUpdateTime; - - /* The period it takes for the price to be considered stale. - * If the price is stale, functions that require the price are disabled. */ - uint public stalePeriod = 60 minutes; - - /* Accounts which have lost the privilege to transact in nomins. */ - mapping(address => bool) public frozen; - - - /* ========== CONSTRUCTOR ========== */ - - /** - * @dev Constructor. - * @param _havven The address of the associated havven contract to become the fee authority. - * @param _oracle The address of the oracle. - * @param _beneficiary The self-destruction beneficiary. - * @param _initialEtherPrice The ether price to seed the contract with. - * @param _owner The owner of this contract. - * @param _initialState The address of a contract containing ERC20 balances. Constructs a fresh one if 0x0 is provided. - */ - function EtherNomin(address _havven, address _oracle, - address _beneficiary, - uint _initialEtherPrice, - address _owner, TokenState _initialState) - ExternStateProxyFeeToken("Ether-Backed USD Nomins", "eUSD", - 15 * UNIT / 10000, /* Nomin transfers incur a 15 bp fee. */ - _havven, /* The havven contract is the fee authority. */ - _initialState, - _owner) - public - { - oracle = _oracle; - beneficiary = _beneficiary; - - etherPrice = _initialEtherPrice; - lastPriceUpdateTime = now; - emit PriceUpdated(_initialEtherPrice); - - /* It should not be possible to transfer to the nomin contract itself. */ - frozen[this] = true; - } - - - /* ========== FALLBACK FUNCTION ========== */ - - /** - * @notice Fallback function allows convenient collateralisation of the contract, - * including by non-foundation parties. - */ - function() public payable {} - - - /* ========== SETTERS ========== */ - - function setOracle(address _oracle) - external - optionalProxy_onlyOwner - { - oracle = _oracle; - emit OracleUpdated(_oracle); - } - - function setCourt(Court _court) - external - optionalProxy_onlyOwner - { - court = _court; - emit CourtUpdated(_court); - } - - function setBeneficiary(address _beneficiary) - external - optionalProxy_onlyOwner - { - beneficiary = _beneficiary; - emit BeneficiaryUpdated(_beneficiary); - } - - function setPoolFeeRate(uint _poolFeeRate) - external - optionalProxy_onlyOwner - { - require(_poolFeeRate <= UNIT); - poolFeeRate = _poolFeeRate; - emit PoolFeeRateUpdated(_poolFeeRate); - } - - function setStalePeriod(uint _stalePeriod) - external - optionalProxy_onlyOwner - { - stalePeriod = _stalePeriod; - emit StalePeriodUpdated(_stalePeriod); - } - - - /* ========== VIEW FUNCTIONS ========== */ - - /* Return the equivalent fiat value of the given quantity - * of ether at the current price. - * Reverts if the price is stale. */ - function fiatValue(uint etherWei) - public - view - priceNotStale - returns (uint) - { - return safeMul_dec(etherWei, etherPrice); - } - - /* Return the current fiat value of the contract's balance. - * Reverts if the price is stale. */ - function fiatBalance() - public - view - returns (uint) - { - // Price staleness check occurs inside the call to fiatValue. - return fiatValue(address(this).balance); - } - - /* Return the equivalent ether value of the given quantity - * of fiat at the current price. - * Reverts if the price is stale. */ - function etherValue(uint fiat) - public - view - priceNotStale - returns (uint) - { - return safeDiv_dec(fiat, etherPrice); - } - - /* The same as etherValue(), but without the stale price check. */ - function etherValueAllowStale(uint fiat) - internal - view - returns (uint) - { - return safeDiv_dec(fiat, etherPrice); - } - - /* Return the units of fiat per nomin in the supply. - * Reverts if the price is stale. */ - function collateralisationRatio() - public - view - returns (uint) - { - return safeDiv_dec(fiatBalance(), _nominCap()); - } - - /* Return the maximum number of extant nomins, - * equal to the nomin pool plus total (circulating) supply. */ - function _nominCap() - internal - view - returns (uint) - { - return safeAdd(nominPool, totalSupply); - } - - /* Return the fee charged on a purchase or sale of n nomins. */ - function poolFeeIncurred(uint n) - public - view - returns (uint) - { - return safeMul_dec(n, poolFeeRate); - } - - /* Return the fiat cost (including fee) of purchasing n nomins. - * Nomins are purchased for $1, plus the fee. */ - function purchaseCostFiat(uint n) - public - view - returns (uint) - { - return safeAdd(n, poolFeeIncurred(n)); - } - - /* Return the ether cost (including fee) of purchasing n nomins. - * Reverts if the price is stale. */ - function purchaseCostEther(uint n) - public - view - returns (uint) - { - /* Price staleness check occurs inside the call to etherValue. */ - return etherValue(purchaseCostFiat(n)); - } - - /* Return the fiat proceeds (less the fee) of selling n nomins. - * Nomins are sold for $1, minus the fee. */ - function saleProceedsFiat(uint n) - public - view - returns (uint) - { - return safeSub(n, poolFeeIncurred(n)); - } - - /* Return the ether proceeds (less the fee) of selling n - * nomins. - * Reverts if the price is stale. */ - function saleProceedsEther(uint n) - public - view - returns (uint) - { - /* Price staleness check occurs inside the call to etherValue. */ - return etherValue(saleProceedsFiat(n)); - } - - /* The same as saleProceedsEther(), but without the stale price check. */ - function saleProceedsEtherAllowStale(uint n) - internal - view - returns (uint) - { - return etherValueAllowStale(saleProceedsFiat(n)); - } - - /* True iff the current block timestamp is later than the time - * the price was last updated, plus the stale period. */ - function priceIsStale() - public - view - returns (bool) - { - return safeAdd(lastPriceUpdateTime, stalePeriod) < now; - } - - function isLiquidating() - public - view - returns (bool) - { - return liquidationTimestamp <= now; - } - - /* True if the contract is self-destructible. - * This is true if either the complete liquidation period has elapsed, - * or if all tokens have been returned to the contract and it has been - * in liquidation for at least a week. - * Since the contract is only destructible after the liquidationTimestamp, - * a fortiori canSelfDestruct() implies isLiquidating(). */ - function canSelfDestruct() - public - view - returns (bool) - { - /* Not being in liquidation implies the timestamp is uint max, so it would roll over. - * We need to check whether we're in liquidation first. */ - if (isLiquidating()) { - /* These timestamps and durations have values clamped within reasonable values and - * cannot overflow. */ - bool totalPeriodElapsed = liquidationTimestamp + liquidationPeriod < now; - /* Total supply of 0 means all tokens have returned to the pool. */ - bool allTokensReturned = (liquidationTimestamp + 1 weeks < now) && (totalSupply == 0); - return totalPeriodElapsed || allTokensReturned; - } - return false; - } - - - /* ========== MUTATIVE FUNCTIONS ========== */ - - /* Override ERC20 transfer function in order to check - * whether the recipient account is frozen. Note that there is - * no need to check whether the sender has a frozen account, - * since their funds have already been confiscated, - * and no new funds can be transferred to it.*/ - function transfer(address to, uint value) - public - optionalProxy - returns (bool) - { - require(!frozen[to]); - return _transfer_byProxy(messageSender, to, value); - } - - /* Override ERC20 transferFrom function in order to check - * whether the recipient account is frozen. */ - function transferFrom(address from, address to, uint value) - public - optionalProxy - returns (bool) - { - require(!frozen[to]); - return _transferFrom_byProxy(messageSender, from, to, value); - } - - /* Update the current ether price and update the last updated time, - * refreshing the price staleness. - * Also checks whether the contract's collateral levels have fallen to low, - * and initiates liquidation if that is the case. - * Exceptional conditions: - * Not called by the oracle. - * Not the most recently sent price. */ - function updatePrice(uint price, uint timeSent) - external - postCheckAutoLiquidate - { - /* Should be callable only by the oracle. */ - require(msg.sender == oracle); - /* Must be the most recently sent price, but not too far in the future. - * (so we can't lock ourselves out of updating the oracle for longer than this) */ - require(lastPriceUpdateTime < timeSent && timeSent < now + 10 minutes); - - etherPrice = price; - lastPriceUpdateTime = timeSent; - emit PriceUpdated(price); - } - - /* Issues n nomins into the pool available to be bought by users. - * Must be accompanied by $n worth of ether. - * Exceptional conditions: - * Not called by contract owner. - * Insufficient backing funds provided (post-issuance collateralisation below minimum requirement). - * Price is stale. */ - function replenishPool(uint n) - external - payable - notLiquidating - optionalProxy_onlyOwner - { - /* Price staleness check occurs inside the call to fiatBalance. - * Safe additions are unnecessary here, as either the addition is checked on the following line - * or the overflow would cause the requirement not to be satisfied. */ - require(fiatBalance() >= safeMul_dec(safeAdd(_nominCap(), n), MINIMUM_ISSUANCE_RATIO)); - nominPool = safeAdd(nominPool, n); - emit PoolReplenished(n, msg.value); - } - - /* Burns n nomins from the pool. - * Exceptional conditions: - * Not called by contract owner. - * There are fewer than n nomins in the pool. */ - function diminishPool(uint n) - external - optionalProxy_onlyOwner - { - /* Require that there are enough nomins in the accessible pool to burn. */ - require(nominPool >= n); - nominPool = safeSub(nominPool, n); - emit PoolDiminished(n); - } - - /* Sends n nomins to the sender from the pool, in exchange for - * $n plus the fee worth of ether. - * Exceptional conditions: - * Insufficient or too many funds provided. - * More nomins requested than are in the pool. - * n below the purchase minimum (1 cent). - * contract in liquidation. - * Price is stale. */ - function buy(uint n) - external - payable - notLiquidating - optionalProxy - { - /* Price staleness check occurs inside the call to purchaseEtherCost. */ - require(n >= MINIMUM_PURCHASE && - msg.value == purchaseCostEther(n)); - address sender = messageSender; - // sub requires that nominPool >= n - nominPool = safeSub(nominPool, n); - state.setBalanceOf(sender, safeAdd(state.balanceOf(sender), n)); - emit Purchased(sender, sender, n, msg.value); - emit Transfer(0, sender, n); - totalSupply = safeAdd(totalSupply, n); - } - - /* Sends n nomins to the pool from the sender, in exchange for - * $n minus the fee worth of ether. - * Exceptional conditions: - * Insufficient nomins in sender's wallet. - * Insufficient funds in the pool to pay sender. - * Price is stale if not in liquidation. */ - function sell(uint n) - external - optionalProxy - { - - // Price staleness check occurs inside the call to saleProceedsEther, - // but we allow people to sell their nomins back to the system - // if we're in liquidation, regardless. - uint proceeds; - if (isLiquidating()) { - proceeds = saleProceedsEtherAllowStale(n); - } else { - proceeds = saleProceedsEther(n); - } - - require(address(this).balance >= proceeds); - - address sender = messageSender; - // sub requires that the balance is greater than n - state.setBalanceOf(sender, safeSub(state.balanceOf(sender), n)); - nominPool = safeAdd(nominPool, n); - emit Sold(sender, sender, n, proceeds); - emit Transfer(sender, 0, n); - totalSupply = safeSub(totalSupply, n); - sender.transfer(proceeds); - } - - /* Lock nomin purchase function in preparation for destroying the contract. - * While the contract is under liquidation, users may sell nomins back to the system. - * After liquidation period has terminated, the contract may be self-destructed, - * returning all remaining ether to the beneficiary address. - * Exceptional cases: - * Not called by contract owner; - * contract already in liquidation; */ - function forceLiquidation() - external - notLiquidating - optionalProxy_onlyOwner - { - beginLiquidation(); - } - - function beginLiquidation() - internal - { - liquidationTimestamp = now; - emit LiquidationBegun(liquidationPeriod); - } - - /* If the contract is liquidating, the owner may extend the liquidation period. - * It may only get longer, not shorter, and it may not be extended past - * the liquidation max. */ - function extendLiquidationPeriod(uint extension) - external - optionalProxy_onlyOwner - { - require(isLiquidating()); - uint sum = safeAdd(liquidationPeriod, extension); - require(sum <= MAX_LIQUIDATION_PERIOD); - liquidationPeriod = sum; - emit LiquidationExtended(extension); - } - - /* Liquidation can only be stopped if the collateralisation ratio - * of this contract has recovered above the automatic liquidation - * threshold, for example if the ether price has increased, - * or by including enough ether in this transaction. */ - function terminateLiquidation() - external - payable - priceNotStale - optionalProxy_onlyOwner - { - require(isLiquidating()); - require(_nominCap() == 0 || collateralisationRatio() >= AUTO_LIQUIDATION_RATIO); - liquidationTimestamp = ~uint(0); - liquidationPeriod = DEFAULT_LIQUIDATION_PERIOD; - emit LiquidationTerminated(); - } - - /* The owner may destroy this contract, returning all funds back to the beneficiary - * wallet, may only be called after the contract has been in - * liquidation for at least liquidationPeriod, or all circulating - * nomins have been sold back into the pool. */ - function selfDestruct() - external - optionalProxy_onlyOwner - { - require(canSelfDestruct()); - emit SelfDestructed(beneficiary); - selfdestruct(beneficiary); - } - - /* If a confiscation court motion has passed and reached the confirmation - * state, the court may transfer the target account's balance to the fee pool - * and freeze its participation in further transactions. */ - function confiscateBalance(address target) - external - { - // Should be callable only by the confiscation court. - require(Court(msg.sender) == court); - - // A motion must actually be underway. - uint motionID = court.targetMotionID(target); - require(motionID != 0); - - // These checks are strictly unnecessary, - // since they are already checked in the court contract itself. - // I leave them in out of paranoia. - require(court.motionConfirming(motionID)); - require(court.motionPasses(motionID)); - require(!frozen[target]); - - // Confiscate the balance in the account and freeze it. - uint balance = state.balanceOf(target); - state.setBalanceOf(address(this), safeAdd(state.balanceOf(address(this)), balance)); - state.setBalanceOf(target, 0); - frozen[target] = true; - emit AccountFrozen(target, target, balance); - emit Transfer(target, address(this), balance); - } - - /* The owner may allow a previously-frozen contract to once - * again accept and transfer nomins. */ - function unfreezeAccount(address target) - external - optionalProxy_onlyOwner - { - if (frozen[target] && EtherNomin(target) != this) { - frozen[target] = false; - emit AccountUnfrozen(target, target); - } - } - - - /* ========== MODIFIERS ========== */ - - modifier notLiquidating - { - require(!isLiquidating()); - _; - } - - modifier priceNotStale - { - require(!priceIsStale()); - _; - } - - /* Any function modified by this will automatically liquidate - * the system if the collateral levels are too low. - * This is called on collateral-value/nomin-supply modifying functions that can - * actually move the contract into liquidation. This is really only - * the price update, since issuance requires that the contract is overcollateralised, - * burning can only destroy tokens without withdrawing backing, buying from the pool can only - * asymptote to a collateralisation level of unity, while selling into the pool can only - * increase the collateralisation ratio. - * Additionally, price update checks should/will occur frequently. */ - modifier postCheckAutoLiquidate - { - _; - if (!isLiquidating() && _nominCap() != 0 && collateralisationRatio() < AUTO_LIQUIDATION_RATIO) { - beginLiquidation(); - } - } - - - /* ========== EVENTS ========== */ - - event PoolReplenished(uint nominsCreated, uint collateralDeposited); - - event PoolDiminished(uint nominsDestroyed); - - event Purchased(address buyer, address indexed buyerIndex, uint nomins, uint etherWei); - - event Sold(address seller, address indexed sellerIndex, uint nomins, uint etherWei); - - event PriceUpdated(uint newPrice); - - event StalePeriodUpdated(uint newPeriod); - - event OracleUpdated(address newOracle); - - event CourtUpdated(address newCourt); - - event BeneficiaryUpdated(address newBeneficiary); - - event LiquidationBegun(uint duration); - - event LiquidationTerminated(); - - event LiquidationExtended(uint extension); - - event PoolFeeRateUpdated(uint newFeeRate); - - event SelfDestructed(address beneficiary); - - event AccountFrozen(address target, address indexed targetIndex, uint balance); - - event AccountUnfrozen(address target, address indexed targetIndex); -} diff --git a/contracts/ExternStateProxyFeeToken.sol b/contracts/ExternStateProxyFeeToken.sol deleted file mode 100644 index ba9d323230..0000000000 --- a/contracts/ExternStateProxyFeeToken.sol +++ /dev/null @@ -1,322 +0,0 @@ -/* ------------------------------------------------------------------ -FILE INFORMATION ------------------------------------------------------------------ -file: ExternStateProxyFeeToken.sol -version: 1.0 -author: Anton Jurisevic - Dominic Romanowski - -date: 2018-2-28 - -checked: Mike Spain -approved: Samuel Brooks - ------------------------------------------------------------------ -MODULE DESCRIPTION ------------------------------------------------------------------ - -A token which also has a configurable fee rate -charged on its transfers. This is designed to be overridden in -order to produce an ERC20-compliant token. - -These fees accrue into a pool, from which a nominated authority -may withdraw. - -This contract utilises a state for upgradability purposes. -It relies on being called underneath a proxy contract, as -included in Proxy.sol. - ------------------------------------------------------------------ -*/ - -pragma solidity ^0.4.21; - - -import "contracts/SafeDecimalMath.sol"; -import "contracts/Owned.sol"; -import "contracts/TokenState.sol"; -import "contracts/Proxy.sol"; - - -/** - * @title ERC20 Token contract, with detached state and designed to operate behind a proxy. - * Additionally charges fees on each transfer. - */ -contract ExternStateProxyFeeToken is Proxyable, SafeDecimalMath { - - /* ========== STATE VARIABLES ========== */ - - /* Stores balances and allowances. */ - TokenState public state; - - /* Other ERC20 fields. */ - string public name; - string public symbol; - uint public totalSupply; - - /* A percentage fee charged on each transfer. */ - uint public transferFeeRate; - /* Fee may not exceed 10%. */ - uint constant MAX_TRANSFER_FEE_RATE = UNIT / 10; - /* The address with the authority to distribute fees. */ - address public feeAuthority; - - - /* ========== CONSTRUCTOR ========== */ - - /** - * @dev Constructor. - * @param _name Token's ERC20 name. - * @param _symbol Token's ERC20 symbol. - * @param _transferFeeRate The fee rate to charge on transfers. - * @param _feeAuthority The address which has the authority to withdraw fees from the accumulated pool. - * @param _state The state contract address. A fresh one is constructed if 0x0 is provided. - * @param _owner The owner of this contract. - */ - function ExternStateProxyFeeToken(string _name, string _symbol, - uint _transferFeeRate, address _feeAuthority, - TokenState _state, address _owner) - Proxyable(_owner) - public - { - if (_state == TokenState(0)) { - state = new TokenState(_owner, address(this)); - } else { - state = _state; - } - - name = _name; - symbol = _symbol; - feeAuthority = _feeAuthority; - - /* Constructed transfer fee rate should respect the maximum fee rate. */ - require(_transferFeeRate <= MAX_TRANSFER_FEE_RATE); - transferFeeRate = _transferFeeRate; - } - - /* ========== SETTERS ========== */ - - function setTransferFeeRate(uint _transferFeeRate) - external - optionalProxy_onlyOwner - { - require(_transferFeeRate <= MAX_TRANSFER_FEE_RATE); - transferFeeRate = _transferFeeRate; - emit TransferFeeRateUpdated(_transferFeeRate); - } - - function setFeeAuthority(address _feeAuthority) - external - optionalProxy_onlyOwner - { - feeAuthority = _feeAuthority; - emit FeeAuthorityUpdated(_feeAuthority); - } - - function setState(TokenState _state) - external - optionalProxy_onlyOwner - { - state = _state; - emit StateUpdated(_state); - } - - /* ========== VIEWS ========== */ - - function balanceOf(address account) - public - view - returns (uint) - { - return state.balanceOf(account); - } - - function allowance(address from, address to) - public - view - returns (uint) - { - return state.allowance(from, to); - } - - // Return the fee charged on top in order to transfer _value worth of tokens. - function transferFeeIncurred(uint value) - public - view - returns (uint) - { - return safeMul_dec(value, transferFeeRate); - /* Transfers less than the reciprocal of transferFeeRate should be completely eaten up by fees. - * This is on the basis that transfers less than this value will result in a nil fee. - * Probably too insignificant to worry about, but the following code will achieve it. - * if (fee == 0 && transferFeeRate != 0) { - * return _value; - * } - * return fee; - */ - } - - // The value that you would need to send so that the recipient receives - // a specified value. - function transferPlusFee(uint value) - external - view - returns (uint) - { - return safeAdd(value, transferFeeIncurred(value)); - } - - // The quantity to send in order that the sender spends a certain value of tokens. - function priceToSpend(uint value) - external - view - returns (uint) - { - return safeDiv_dec(value, safeAdd(UNIT, transferFeeRate)); - } - - // The balance of the nomin contract itself is the fee pool. - // Collected fees sit here until they are distributed. - function feePool() - external - view - returns (uint) - { - return state.balanceOf(address(this)); - } - - - /* ========== MUTATIVE FUNCTIONS ========== */ - - /* Whatever calls this should have either the optionalProxy or onlyProxy modifier, - * and pass in messageSender. */ - function _transfer_byProxy(address sender, address to, uint value) - internal - returns (bool) - { - require(to != address(0)); - - // The fee is deducted from the sender's balance, in addition to - // the transferred quantity. - uint fee = transferFeeIncurred(value); - uint totalCharge = safeAdd(value, fee); - - // Insufficient balance will be handled by the safe subtraction. - state.setBalanceOf(sender, safeSub(state.balanceOf(sender), totalCharge)); - state.setBalanceOf(to, safeAdd(state.balanceOf(to), value)); - state.setBalanceOf(address(this), safeAdd(state.balanceOf(address(this)), fee)); - - emit Transfer(sender, to, value); - emit TransferFeePaid(sender, fee); - emit Transfer(sender, address(this), fee); - - return true; - } - - /* Whatever calls this should have either the optionalProxy or onlyProxy modifier, - * and pass in messageSender. */ - function _transferFrom_byProxy(address sender, address from, address to, uint value) - internal - returns (bool) - { - require(to != address(0)); - - // The fee is deducted from the sender's balance, in addition to - // the transferred quantity. - uint fee = transferFeeIncurred(value); - uint totalCharge = safeAdd(value, fee); - - // Insufficient balance will be handled by the safe subtraction. - state.setBalanceOf(from, safeSub(state.balanceOf(from), totalCharge)); - state.setAllowance(from, sender, safeSub(state.allowance(from, sender), totalCharge)); - state.setBalanceOf(to, safeAdd(state.balanceOf(to), value)); - state.setBalanceOf(address(this), safeAdd(state.balanceOf(address(this)), fee)); - - emit Transfer(from, to, value); - emit TransferFeePaid(from, fee); - emit Transfer(from, address(this), fee); - - return true; - } - - function approve(address spender, uint value) - external - optionalProxy - returns (bool) - { - address sender = messageSender; - state.setAllowance(sender, spender, value); - - emit Approval(sender, spender, value); - - return true; - } - - /** - * @notice Withdraw tokens from the fee pool into a given account. - * @dev Only the fee authority may call this. - */ - function withdrawFee(address account, uint value) - external - returns (bool) - { - require(msg.sender == feeAuthority && account != address(0)); - - // 0-value withdrawals do nothing. - if (value == 0) { - return false; - } - - // Safe subtraction ensures an exception is thrown if the balance is insufficient. - state.setBalanceOf(address(this), safeSub(state.balanceOf(address(this)), value)); - state.setBalanceOf(account, safeAdd(state.balanceOf(account), value)); - - emit FeesWithdrawn(account, account, value); - emit Transfer(address(this), account, value); - - return true; - } - - /** - * @notice Donate tokens from the sender's balance into the fee pool. - */ - function donateToFeePool(uint n) - external - optionalProxy - returns (bool) - { - address sender = messageSender; - - /* Empty donations are disallowed. */ - uint balance = state.balanceOf(sender); - require(balance != 0); - - /* safeSub ensures the donor has sufficient balance. */ - state.setBalanceOf(sender, safeSub(balance, n)); - state.setBalanceOf(address(this), safeAdd(state.balanceOf(address(this)), n)); - - emit FeesDonated(sender, sender, n); - emit Transfer(sender, address(this), n); - - return true; - } - - /* ========== EVENTS ========== */ - - event Transfer(address indexed from, address indexed to, uint value); - - event TransferFeePaid(address indexed account, uint value); - - event Approval(address indexed owner, address indexed spender, uint value); - - event TransferFeeRateUpdated(uint newFeeRate); - - event FeeAuthorityUpdated(address feeAuthority); - - event StateUpdated(address newState); - - event FeesWithdrawn(address account, address indexed accountIndex, uint value); - - event FeesDonated(address donor, address indexed donorIndex, uint value); -} diff --git a/contracts/ExternStateProxyToken.sol b/contracts/ExternStateProxyToken.sol deleted file mode 100644 index 017de032ba..0000000000 --- a/contracts/ExternStateProxyToken.sol +++ /dev/null @@ -1,184 +0,0 @@ -/* ------------------------------------------------------------------ -FILE INFORMATION ------------------------------------------------------------------ -file: ExternStateProxyToken.sol -version: 1.0 -author: Anton Jurisevic - Dominic Romanowski - -date: 2018-2-28 - -checked: Mike Spain -approved: Samuel Brooks - ------------------------------------------------------------------ -MODULE DESCRIPTION ------------------------------------------------------------------ - -A token interface to be overridden to produce an ERC20-compliant -token contract. It relies on being called underneath a proxy, -as described in Proxy.sol. - -This contract utilises a state for upgradability purposes. - ------------------------------------------------------------------ -*/ - -pragma solidity ^0.4.21; - - -import "contracts/SafeDecimalMath.sol"; -import "contracts/Owned.sol"; -import "contracts/TokenState.sol"; -import "contracts/Proxy.sol"; - - -/** - * @title ERC20 Token contract, with detached state and designed to operate behind a proxy. - */ -contract ExternStateProxyToken is SafeDecimalMath, Proxyable { - - /* ========== STATE VARIABLES ========== */ - - /* Stores balances and allowances. */ - TokenState public state; - - /* Other ERC20 fields. */ - string public name; - string public symbol; - uint public totalSupply; - - - /* ========== CONSTRUCTOR ========== */ - - /** - * @dev Constructor. - * @param _name Token's ERC20 name. - * @param _symbol Token's ERC20 symbol. - * @param _initialSupply The initial supply of the token. - * @param _initialBeneficiary The recipient of the initial token supply if _state is 0. - * @param _state The state contract address. A fresh one is constructed if 0x0 is provided. - * @param _owner The owner of this contract. - */ - function ExternStateProxyToken(string _name, string _symbol, - uint _initialSupply, address _initialBeneficiary, - TokenState _state, address _owner) - Proxyable(_owner) - public - { - name = _name; - symbol = _symbol; - totalSupply = _initialSupply; - - // if the state isn't set, create a new one - if (_state == TokenState(0)) { - state = new TokenState(_owner, address(this)); - state.setBalanceOf(_initialBeneficiary, totalSupply); - emit Transfer(address(0), _initialBeneficiary, _initialSupply); - } else { - state = _state; - } - } - - /* ========== VIEWS ========== */ - - /** - * @notice Returns the ERC20 allowance of one party to spend on behalf of another. - * @param tokenOwner The party authorising spending of their funds. - * @param spender The party spending tokenOwner's funds. - */ - function allowance(address tokenOwner, address spender) - public - view - returns (uint) - { - return state.allowance(tokenOwner, spender); - } - - /** - * @notice Returns the ERC20 token balance of a given account. - */ - function balanceOf(address account) - public - view - returns (uint) - { - return state.balanceOf(account); - } - - /* ========== MUTATIVE FUNCTIONS ========== */ - - /** - * @notice Change the address of the state contract. - * @dev Only the contract owner may operate this function. - */ - function setState(TokenState _state) - external - optionalProxy_onlyOwner - { - state = _state; - emit StateUpdated(_state); - } - - /** - * @dev Perform an ERC20 token transfer. Designed to be called by transfer functions possessing - * the onlyProxy or optionalProxy modifiers. - */ - function _transfer_byProxy(address sender, address to, uint value) - internal - returns (bool) - { - require(to != address(0)); - - /* Insufficient balance will be handled by the safe subtraction. */ - state.setBalanceOf(sender, safeSub(state.balanceOf(sender), value)); - state.setBalanceOf(to, safeAdd(state.balanceOf(to), value)); - - emit Transfer(sender, to, value); - - return true; - } - - /** - * @dev Perform an ERC20 token transferFrom. Designed to be called by transferFrom functions - * possessing the onlyProxy or optionalProxy modifiers. - */ - function _transferFrom_byProxy(address sender, address from, address to, uint value) - internal - returns (bool) - { - require(to != address(0)); - - /* Insufficient balance will be handled by the safe subtraction. */ - state.setBalanceOf(from, safeSub(state.balanceOf(from), value)); - state.setAllowance(from, sender, safeSub(state.allowance(from, sender), value)); - state.setBalanceOf(to, safeAdd(state.balanceOf(to), value)); - - emit Transfer(from, to, value); - - return true; - } - - /** - * @notice Approves spender to transfer on the message sender's behalf. - */ - function approve(address spender, uint value) - external - optionalProxy - returns (bool) - { - address sender = messageSender; - state.setAllowance(sender, spender, value); - emit Approval(sender, spender, value); - return true; - } - - /* ========== EVENTS ========== */ - - event Transfer(address indexed from, address indexed to, uint value); - - event Approval(address indexed owner, address indexed spender, uint value); - - event StateUpdated(address newState); -} diff --git a/contracts/ExternStateToken.sol b/contracts/ExternStateToken.sol new file mode 100644 index 0000000000..19f1dcc623 --- /dev/null +++ b/contracts/ExternStateToken.sol @@ -0,0 +1,189 @@ +/* +----------------------------------------------------------------- +FILE INFORMATION +----------------------------------------------------------------- + +file: ExternStateToken.sol +version: 1.3 +author: Anton Jurisevic + Dominic Romanowski + +date: 2018-05-29 + +----------------------------------------------------------------- +MODULE DESCRIPTION +----------------------------------------------------------------- + +A partial ERC20 token contract, designed to operate with a proxy. +To produce a complete ERC20 token, transfer and transferFrom +tokens must be implemented, using the provided _byProxy internal +functions. +This contract utilises an external state for upgradeability. + +----------------------------------------------------------------- +*/ + +pragma solidity 0.4.24; + + +import "contracts/SafeDecimalMath.sol"; +import "contracts/SelfDestructible.sol"; +import "contracts/TokenState.sol"; +import "contracts/Proxyable.sol"; + + +/** + * @title ERC20 Token contract, with detached state and designed to operate behind a proxy. + */ +contract ExternStateToken is SafeDecimalMath, SelfDestructible, Proxyable { + + /* ========== STATE VARIABLES ========== */ + + /* Stores balances and allowances. */ + TokenState public tokenState; + + /* Other ERC20 fields. + * Note that the decimals field is defined in SafeDecimalMath.*/ + string public name; + string public symbol; + uint public totalSupply; + + /** + * @dev Constructor. + * @param _proxy The proxy associated with this contract. + * @param _name Token's ERC20 name. + * @param _symbol Token's ERC20 symbol. + * @param _totalSupply The total supply of the token. + * @param _tokenState The TokenState contract address. + * @param _owner The owner of this contract. + */ + constructor(address _proxy, string _name, string _symbol, uint _totalSupply, + TokenState _tokenState, address _owner) + SelfDestructible(_owner) + Proxyable(_proxy, _owner) + public + { + name = _name; + symbol = _symbol; + totalSupply = _totalSupply; + tokenState = _tokenState; + } + + /* ========== VIEWS ========== */ + + /** + * @notice Returns the ERC20 allowance of one party to spend on behalf of another. + * @param owner The party authorising spending of their funds. + * @param spender The party spending tokenOwner's funds. + */ + function allowance(address owner, address spender) + public + view + returns (uint) + { + return tokenState.allowance(owner, spender); + } + + /** + * @notice Returns the ERC20 token balance of a given account. + */ + function balanceOf(address account) + public + view + returns (uint) + { + return tokenState.balanceOf(account); + } + + /* ========== MUTATIVE FUNCTIONS ========== */ + + /** + * @notice Set the address of the TokenState contract. + * @dev This can be used to "pause" transfer functionality, by pointing the tokenState at 0x000.. + * as balances would be unreachable. + */ + function setTokenState(TokenState _tokenState) + external + optionalProxy_onlyOwner + { + tokenState = _tokenState; + emitTokenStateUpdated(_tokenState); + } + + function _internalTransfer(address from, address to, uint value) + internal + returns (bool) + { + /* Disallow transfers to irretrievable-addresses. */ + require(to != address(0)); + require(to != address(this)); + require(to != address(proxy)); + + /* Insufficient balance will be handled by the safe subtraction. */ + tokenState.setBalanceOf(from, safeSub(tokenState.balanceOf(from), value)); + tokenState.setBalanceOf(to, safeAdd(tokenState.balanceOf(to), value)); + + emitTransfer(from, to, value); + + return true; + } + + /** + * @dev Perform an ERC20 token transfer. Designed to be called by transfer functions possessing + * the onlyProxy or optionalProxy modifiers. + */ + function _transfer_byProxy(address from, address to, uint value) + internal + returns (bool) + { + return _internalTransfer(from, to, value); + } + + /** + * @dev Perform an ERC20 token transferFrom. Designed to be called by transferFrom functions + * possessing the optionalProxy or optionalProxy modifiers. + */ + function _transferFrom_byProxy(address sender, address from, address to, uint value) + internal + returns (bool) + { + /* Insufficient allowance will be handled by the safe subtraction. */ + tokenState.setAllowance(from, sender, safeSub(tokenState.allowance(from, sender), value)); + return _internalTransfer(from, to, value); + } + + /** + * @notice Approves spender to transfer on the message sender's behalf. + */ + function approve(address spender, uint value) + public + optionalProxy + returns (bool) + { + address sender = messageSender; + + tokenState.setAllowance(sender, spender, value); + emitApproval(sender, spender, value); + return true; + } + + /* ========== EVENTS ========== */ + + event Transfer(address indexed from, address indexed to, uint value); + bytes32 constant TRANSFER_SIG = keccak256("Transfer(address,address,uint256)"); + function emitTransfer(address from, address to, uint value) internal { + proxy._emit(abi.encode(value), 3, TRANSFER_SIG, bytes32(from), bytes32(to), 0); + } + + event Approval(address indexed owner, address indexed spender, uint value); + bytes32 constant APPROVAL_SIG = keccak256("Approval(address,address,uint256)"); + function emitApproval(address owner, address spender, uint value) internal { + proxy._emit(abi.encode(value), 3, APPROVAL_SIG, bytes32(owner), bytes32(spender), 0); + } + + event TokenStateUpdated(address newTokenState); + bytes32 constant TOKENSTATEUPDATED_SIG = keccak256("TokenStateUpdated(address)"); + function emitTokenStateUpdated(address newTokenState) internal { + proxy._emit(abi.encode(newTokenState), 1, TOKENSTATEUPDATED_SIG, 0, 0, 0); + } +} diff --git a/contracts/FeeToken.sol b/contracts/FeeToken.sol new file mode 100644 index 0000000000..144a0c2cff --- /dev/null +++ b/contracts/FeeToken.sol @@ -0,0 +1,334 @@ +/* +----------------------------------------------------------------- +FILE INFORMATION +----------------------------------------------------------------- + +file: FeeToken.sol +version: 1.3 +author: Anton Jurisevic + Dominic Romanowski + Kevin Brown + +date: 2018-05-29 + +----------------------------------------------------------------- +MODULE DESCRIPTION +----------------------------------------------------------------- + +A token which also has a configurable fee rate +charged on its transfers. This is designed to be overridden in +order to produce an ERC20-compliant token. + +These fees accrue into a pool, from which a nominated authority +may withdraw. + +This contract utilises an external state for upgradeability. + +----------------------------------------------------------------- +*/ + +pragma solidity 0.4.24; + + +import "contracts/ExternStateToken.sol"; + + +/** + * @title ERC20 Token contract, with detached state. + * Additionally charges fees on each transfer. + */ +contract FeeToken is ExternStateToken { + + /* ========== STATE VARIABLES ========== */ + + /* ERC20 members are declared in ExternStateToken. */ + + /* A percentage fee charged on each transfer. */ + uint public transferFeeRate; + /* Fee may not exceed 10%. */ + uint constant MAX_TRANSFER_FEE_RATE = UNIT / 10; + /* The address with the authority to distribute fees. */ + address public feeAuthority; + + + /* ========== CONSTRUCTOR ========== */ + + /** + * @dev Constructor. + * @param _proxy The proxy associated with this contract. + * @param _name Token's ERC20 name. + * @param _symbol Token's ERC20 symbol. + * @param _totalSupply The total supply of the token. + * @param _transferFeeRate The fee rate to charge on transfers. + * @param _feeAuthority The address which has the authority to withdraw fees from the accumulated pool. + * @param _owner The owner of this contract. + */ + constructor(address _proxy, string _name, string _symbol, uint _totalSupply, + uint _transferFeeRate, address _feeAuthority, address _owner) + ExternStateToken(_proxy, _name, _symbol, _totalSupply, + new TokenState(_owner, address(this)), + _owner) + public + { + feeAuthority = _feeAuthority; + + /* Constructed transfer fee rate should respect the maximum fee rate. */ + require(_transferFeeRate <= MAX_TRANSFER_FEE_RATE); + transferFeeRate = _transferFeeRate; + } + + /* ========== SETTERS ========== */ + + /** + * @notice Set the transfer fee, anywhere within the range 0-10%. + * @dev The fee rate is in decimal format, with UNIT being the value of 100%. + */ + function setTransferFeeRate(uint _transferFeeRate) + external + optionalProxy_onlyOwner + { + require(_transferFeeRate <= MAX_TRANSFER_FEE_RATE); + transferFeeRate = _transferFeeRate; + emitTransferFeeRateUpdated(_transferFeeRate); + } + + /** + * @notice Set the address of the user/contract responsible for collecting or + * distributing fees. + */ + function setFeeAuthority(address _feeAuthority) + public + optionalProxy_onlyOwner + { + feeAuthority = _feeAuthority; + emitFeeAuthorityUpdated(_feeAuthority); + } + + /* ========== VIEWS ========== */ + + /** + * @notice Calculate the Fee charged on top of a value being sent + * @return Return the fee charged + */ + function transferFeeIncurred(uint value) + public + view + returns (uint) + { + return safeMul_dec(value, transferFeeRate); + /* Transfers less than the reciprocal of transferFeeRate should be completely eaten up by fees. + * This is on the basis that transfers less than this value will result in a nil fee. + * Probably too insignificant to worry about, but the following code will achieve it. + * if (fee == 0 && transferFeeRate != 0) { + * return _value; + * } + * return fee; + */ + } + + /** + * @notice The value that you would need to send so that the recipient receives + * a specified value. + */ + function transferPlusFee(uint value) + external + view + returns (uint) + { + return safeAdd(value, transferFeeIncurred(value)); + } + + /** + * @notice The amount the recipient will receive if you send a certain number of tokens. + */ + function amountReceived(uint value) + public + view + returns (uint) + { + return safeDiv_dec(value, safeAdd(UNIT, transferFeeRate)); + } + + /** + * @notice Collected fees sit here until they are distributed. + * @dev The balance of the nomin contract itself is the fee pool. + */ + function feePool() + external + view + returns (uint) + { + return tokenState.balanceOf(address(this)); + } + + /* ========== MUTATIVE FUNCTIONS ========== */ + + /** + * @notice Base of transfer functions + */ + function _internalTransfer(address from, address to, uint amount, uint fee) + internal + returns (bool) + { + /* Disallow transfers to irretrievable-addresses. */ + require(to != address(0)); + require(to != address(this)); + require(to != address(proxy)); + + /* Insufficient balance will be handled by the safe subtraction. */ + tokenState.setBalanceOf(from, safeSub(tokenState.balanceOf(from), safeAdd(amount, fee))); + tokenState.setBalanceOf(to, safeAdd(tokenState.balanceOf(to), amount)); + tokenState.setBalanceOf(address(this), safeAdd(tokenState.balanceOf(address(this)), fee)); + + /* Emit events for both the transfer itself and the fee. */ + emitTransfer(from, to, amount); + emitTransfer(from, address(this), fee); + + return true; + } + + /** + * @notice ERC20 friendly transfer function. + */ + function _transfer_byProxy(address sender, address to, uint value) + internal + returns (bool) + { + uint received = amountReceived(value); + uint fee = safeSub(value, received); + + return _internalTransfer(sender, to, received, fee); + } + + /** + * @notice ERC20 friendly transferFrom function. + */ + function _transferFrom_byProxy(address sender, address from, address to, uint value) + internal + returns (bool) + { + /* The fee is deducted from the amount sent. */ + uint received = amountReceived(value); + uint fee = safeSub(value, received); + + /* Reduce the allowance by the amount we're transferring. + * The safeSub call will handle an insufficient allowance. */ + tokenState.setAllowance(from, sender, safeSub(tokenState.allowance(from, sender), value)); + + return _internalTransfer(from, to, received, fee); + } + + /** + * @notice Ability to transfer where the sender pays the fees (not ERC20) + */ + function _transferSenderPaysFee_byProxy(address sender, address to, uint value) + internal + returns (bool) + { + /* The fee is added to the amount sent. */ + uint fee = transferFeeIncurred(value); + return _internalTransfer(sender, to, value, fee); + } + + /** + * @notice Ability to transferFrom where they sender pays the fees (not ERC20). + */ + function _transferFromSenderPaysFee_byProxy(address sender, address from, address to, uint value) + internal + returns (bool) + { + /* The fee is added to the amount sent. */ + uint fee = transferFeeIncurred(value); + uint total = safeAdd(value, fee); + + /* Reduce the allowance by the amount we're transferring. */ + tokenState.setAllowance(from, sender, safeSub(tokenState.allowance(from, sender), total)); + + return _internalTransfer(from, to, value, fee); + } + + /** + * @notice Withdraw tokens from the fee pool into a given account. + * @dev Only the fee authority may call this. + */ + function withdrawFees(address account, uint value) + external + onlyFeeAuthority + returns (bool) + { + require(account != address(0)); + + /* 0-value withdrawals do nothing. */ + if (value == 0) { + return false; + } + + /* Safe subtraction ensures an exception is thrown if the balance is insufficient. */ + tokenState.setBalanceOf(address(this), safeSub(tokenState.balanceOf(address(this)), value)); + tokenState.setBalanceOf(account, safeAdd(tokenState.balanceOf(account), value)); + + emitFeesWithdrawn(account, value); + emitTransfer(address(this), account, value); + + return true; + } + + /** + * @notice Donate tokens from the sender's balance into the fee pool. + */ + function donateToFeePool(uint n) + external + optionalProxy + returns (bool) + { + address sender = messageSender; + /* Empty donations are disallowed. */ + uint balance = tokenState.balanceOf(sender); + require(balance != 0); + + /* safeSub ensures the donor has sufficient balance. */ + tokenState.setBalanceOf(sender, safeSub(balance, n)); + tokenState.setBalanceOf(address(this), safeAdd(tokenState.balanceOf(address(this)), n)); + + emitFeesDonated(sender, n); + emitTransfer(sender, address(this), n); + + return true; + } + + + /* ========== MODIFIERS ========== */ + + modifier onlyFeeAuthority + { + require(msg.sender == feeAuthority); + _; + } + + + /* ========== EVENTS ========== */ + + event TransferFeeRateUpdated(uint newFeeRate); + bytes32 constant TRANSFERFEERATEUPDATED_SIG = keccak256("TransferFeeRateUpdated(uint256)"); + function emitTransferFeeRateUpdated(uint newFeeRate) internal { + proxy._emit(abi.encode(newFeeRate), 1, TRANSFERFEERATEUPDATED_SIG, 0, 0, 0); + } + + event FeeAuthorityUpdated(address newFeeAuthority); + bytes32 constant FEEAUTHORITYUPDATED_SIG = keccak256("FeeAuthorityUpdated(address)"); + function emitFeeAuthorityUpdated(address newFeeAuthority) internal { + proxy._emit(abi.encode(newFeeAuthority), 1, FEEAUTHORITYUPDATED_SIG, 0, 0, 0); + } + + event FeesWithdrawn(address indexed account, uint value); + bytes32 constant FEESWITHDRAWN_SIG = keccak256("FeesWithdrawn(address,uint256)"); + function emitFeesWithdrawn(address account, uint value) internal { + proxy._emit(abi.encode(value), 2, FEESWITHDRAWN_SIG, bytes32(account), 0, 0); + } + + event FeesDonated(address indexed donor, uint value); + bytes32 constant FEESDONATED_SIG = keccak256("FeesDonated(address,uint256)"); + function emitFeesDonated(address donor, uint value) internal { + proxy._emit(abi.encode(value), 2, FEESDONATED_SIG, bytes32(donor), 0, 0); + } +} diff --git a/contracts/Havven.sol b/contracts/Havven.sol index 7aa5ccb29c..da2c30bd3c 100644 --- a/contracts/Havven.sol +++ b/contracts/Havven.sol @@ -2,15 +2,13 @@ ----------------------------------------------------------------- FILE INFORMATION ----------------------------------------------------------------- + file: Havven.sol -version: 1.0 +version: 1.2 author: Anton Jurisevic Dominic Romanowski -date: 2018-02-05 - -checked: Mike Spain -approved: Samuel Brooks +date: 2018-05-15 ----------------------------------------------------------------- MODULE DESCRIPTION @@ -18,26 +16,31 @@ MODULE DESCRIPTION Havven token contract. Havvens are transferable ERC20 tokens, and also give their holders the following privileges. -An owner of havvens is entitled to a share in the fees levied on -nomin transactions, and additionally may participate in nomin -confiscation votes. +An owner of havvens may participate in nomin confiscation votes, they +may also have the right to issue nomins at the discretion of the +foundation for this version of the contract. After a fee period terminates, the duration and fees collected for that -period are computed, and the next period begins. -Thus an account may only withdraw the fees owed to them for the previous -period, and may only do so once per period. -Any unclaimed fees roll over into the common pot for the next period. +period are computed, and the next period begins. Thus an account may only +withdraw the fees owed to them for the previous period, and may only do +so once per period. Any unclaimed fees roll over into the common pot for +the next period. -The fee entitlement of a havven holder is proportional to their average -havven balance over the last fee period. This is computed by measuring the -area under the graph of a user's balance over time, and then when fees are -distributed, dividing through by the duration of the fee period. +== Average Balance Calculations == -We need only update fee entitlement on transfer when the havven balances of the sender -and recipient are modified. This is for efficiency, and adds an implicit friction to -trading in the havven market. A havven holder pays for his own recomputation whenever -he wants to change his position, which saves the foundation having to maintain a pot -dedicated to resourcing this. +The fee entitlement of a havven holder is proportional to their average +issued nomin balance over the last fee period. This is computed by +measuring the area under the graph of a user's issued nomin balance over +time, and then when a new fee period begins, dividing through by the +duration of the fee period. + +We need only update values when the balances of an account is modified. +This occurs when issuing or burning for issued nomin balances, +and when transferring for havven balances. This is for efficiency, +and adds an implicit friction to interacting with havvens. +A havven holder pays for his own recomputation whenever he wants to change +his position, which saves the foundation having to maintain a pot dedicated +to resourcing this. A hypothetical user's balance history over one fee period, pictorially: @@ -53,7 +56,7 @@ When a new transfer occurs at time n, the balance being p, we must: - Add the area p * (n - t) to the total area recorded so far - - Update the last transfer time to p + - Update the last transfer time to n So if this graph represents the entire current fee period, the average havvens held so far is ((t-f)*s + (n-t)*p) / (n-f). @@ -92,113 +95,145 @@ In the implementation, the duration of different fee periods may be slightly irr as the check that they have rolled over occurs only when state-changing havven operations are performed. -Additionally, we keep track also of the penultimate and not just the last -average balance, in order to support the voting functionality detailed in Court.sol. +== Issuance and Burning == ------------------------------------------------------------------ +In this version of the havven contract, nomins can only be issued by +those that have been nominated by the havven foundation. Nomins are assumed +to be valued at $1, as they are a stable unit of account. + +All nomins issued require a proportional value of havvens to be locked, +where the proportion is governed by the current issuance ratio. This +means for every $1 of Havvens locked up, $(issuanceRatio) nomins can be issued. +i.e. to issue 100 nomins, 100/issuanceRatio dollars of havvens need to be locked up. +To determine the value of some amount of havvens(H), an oracle is used to push +the price of havvens (P_H) in dollars to the contract. The value of H +would then be: H * P_H. + +Any havvens that are locked up by this issuance process cannot be transferred. +The amount that is locked floats based on the price of havvens. If the price +of havvens moves up, less havvens are locked, so they can be issued against, +or transferred freely. If the price of havvens moves down, more havvens are locked, +even going above the initial wallet balance. + +----------------------------------------------------------------- */ -pragma solidity ^0.4.21; +pragma solidity 0.4.24; -import "contracts/ExternStateProxyToken.sol"; -import "contracts/EtherNomin.sol"; +import "contracts/ExternStateToken.sol"; +import "contracts/Nomin.sol"; import "contracts/HavvenEscrow.sol"; import "contracts/TokenState.sol"; -import "contracts/SelfDestructible.sol"; + /** * @title Havven ERC20 contract. * @notice The Havven contracts does not only facilitate transfers and track balances, * but it also computes the quantity of fees each havven holder is entitled to. */ -contract Havven is ExternStateProxyToken, SelfDestructible { +contract Havven is ExternStateToken { /* ========== STATE VARIABLES ========== */ - /* Sums of balances*duration in the current fee period. - * range: decimals; units: havven-seconds */ - mapping(address => uint) public currentBalanceSum; - - /* Average account balances in the last completed fee period. This is proportional - * to that account's last period fee entitlement. - * (i.e. currentBalanceSum for the previous period divided through by duration) - * WARNING: This may not have been updated for the latest fee period at the - * time it is queried. - * range: decimals; units: havvens */ - mapping(address => uint) public lastAverageBalance; - - /* The average account balances in the period before the last completed fee period. - * This is used as a person's weight in a confiscation vote, so it implies that - * the vote duration must be no longer than the fee period in order to guarantee that - * no portion of a fee period used for determining vote weights falls within the - * duration of a vote it contributes to. - * WARNING: This may not have been updated for the latest fee period at the - * time it is queried. - * range: decimals; units: havvens */ - mapping(address => uint) public penultimateAverageBalance; - - /* The time an account last made a transfer. */ - mapping(address => uint) public lastTransferTimestamp; - - /* The time the current fee period began. */ - uint public feePeriodStartTime = 3; - /* The actual start of the last fee period (seconds). */ - uint public lastFeePeriodStartTime = 2; - /* The actual start of the penultimate fee period (seconds). */ - uint public penultimateFeePeriodStartTime = 1; - /* The foregoing members are initialised to past timestamps, - * as they will be updated upon the next transfer. */ - - /* Fee periods will roll over in no shorter a time than this. */ - uint public targetFeePeriodDurationSeconds = 4 weeks; - /* And may not be set to be shorter than a day. */ - uint constant MIN_FEE_PERIOD_DURATION_SECONDS = 1 days; - /* And may not be set to be longer than six months. */ - uint constant MAX_FEE_PERIOD_DURATION_SECONDS = 26 weeks; - - /* The quantity of nomins that were in the fee pot at the time - * of the last fee rollover (feePeriodStartTime). */ + /* A struct for handing values associated with average balance calculations */ + struct IssuanceData { + /* Sums of balances*duration in the current fee period. + /* range: decimals; units: havven-seconds */ + uint currentBalanceSum; + /* The last period's average balance */ + uint lastAverageBalance; + /* The last time the data was calculated */ + uint lastModified; + } + + /* Issued nomin balances for individual fee entitlements */ + mapping(address => IssuanceData) public issuanceData; + /* The total number of issued nomins for determining fee entitlements */ + IssuanceData public totalIssuanceData; + + /* The time the current fee period began */ + uint public feePeriodStartTime; + /* The time the last fee period began */ + uint public lastFeePeriodStartTime; + + /* Fee periods will roll over in no shorter a time than this. + * The fee period cannot actually roll over until a fee-relevant + * operation such as withdrawal or a fee period duration update occurs, + * so this is just a target, and the actual duration may be slightly longer. */ + uint public feePeriodDuration = 4 weeks; + /* ...and must target between 1 day and six months. */ + uint constant MIN_FEE_PERIOD_DURATION = 1 days; + uint constant MAX_FEE_PERIOD_DURATION = 26 weeks; + + /* The quantity of nomins that were in the fee pot at the time */ + /* of the last fee rollover, at feePeriodStartTime. */ uint public lastFeesCollected; - mapping(address => bool) public hasWithdrawnLastPeriodFees; + /* Whether a user has withdrawn their last fees */ + mapping(address => bool) public hasWithdrawnFees; - EtherNomin public nomin; + Nomin public nomin; HavvenEscrow public escrow; - - /* ========== CONSTRUCTOR ========== */ + /* The address of the oracle which pushes the havven price to this contract */ + address public oracle; + /* The price of havvens written in UNIT */ + uint public price; + /* The time the havven price was last updated */ + uint public lastPriceUpdateTime; + /* How long will the contract assume the price of havvens is correct */ + uint public priceStalePeriod = 3 hours; + + /* A quantity of nomins greater than this ratio + * may not be issued against a given value of havvens. */ + uint public issuanceRatio = 5 * UNIT / 100; + /* No more nomins may be issued than the value of havvens backing them. */ + uint constant MAX_ISSUANCE_RATIO = UNIT; + + /* Whether the address can issue nomins or not. */ + mapping(address => bool) public isIssuer; + /* The number of currently-outstanding nomins the user has issued. */ + mapping(address => uint) public nominsIssued; + + uint constant HAVVEN_SUPPLY = 1e8 * UNIT; + uint constant ORACLE_FUTURE_LIMIT = 10 minutes; + string constant TOKEN_NAME = "Havven"; + string constant TOKEN_SYMBOL = "HAV"; + /* ========== CONSTRUCTOR ========== */ + /** * @dev Constructor - * @param _initialState A pre-populated contract containing token balances. + * @param _tokenState A pre-populated contract containing token balances. * If the provided address is 0x0, then a fresh one will be constructed with the contract owning all tokens. * @param _owner The owner of this contract. */ - function Havven(TokenState _initialState, address _owner) - ExternStateProxyToken("Havven", "HAV", 1e8 * UNIT, address(this), _initialState, _owner) - SelfDestructible(_owner, _owner) /* Owned is initialised in ExternStateProxyToken */ + constructor(address _proxy, TokenState _tokenState, address _owner, address _oracle, uint _price) + ExternStateToken(_proxy, TOKEN_NAME, TOKEN_SYMBOL, HAVVEN_SUPPLY, _tokenState, _owner) + /* Owned is initialised in ExternStateToken */ public { - lastTransferTimestamp[this] = now; + oracle = _oracle; feePeriodStartTime = now; - lastFeePeriodStartTime = now - targetFeePeriodDurationSeconds; - penultimateFeePeriodStartTime = now - 2*targetFeePeriodDurationSeconds; + lastFeePeriodStartTime = now - feePeriodDuration; + price = _price; + lastPriceUpdateTime = now; } - /* ========== SETTERS ========== */ /** * @notice Set the associated Nomin contract to collect fees from. * @dev Only the contract owner may call this. */ - function setNomin(EtherNomin _nomin) + function setNomin(Nomin _nomin) external optionalProxy_onlyOwner { nomin = _nomin; + emitNominUpdated(_nomin); } /** @@ -210,6 +245,7 @@ contract Havven is ExternStateProxyToken, SelfDestructible { optionalProxy_onlyOwner { escrow = _escrow; + emitEscrowUpdated(_escrow); } /** @@ -218,110 +254,149 @@ contract Havven is ExternStateProxyToken, SelfDestructible { * acceptable bounds (1 day to 26 weeks). Upon resetting this the fee period * may roll over if the target duration was shortened sufficiently. */ - function setTargetFeePeriodDuration(uint duration) + function setFeePeriodDuration(uint duration) external - postCheckFeePeriodRollover optionalProxy_onlyOwner { - require(MIN_FEE_PERIOD_DURATION_SECONDS <= duration && - duration <= MAX_FEE_PERIOD_DURATION_SECONDS); - targetFeePeriodDurationSeconds = duration; - emit FeePeriodDurationUpdated(duration); + require(MIN_FEE_PERIOD_DURATION <= duration && + duration <= MAX_FEE_PERIOD_DURATION); + feePeriodDuration = duration; + emitFeePeriodDurationUpdated(duration); + rolloverFeePeriodIfElapsed(); } - - /* ========== MUTATIVE FUNCTIONS ========== */ - /** - * @notice Allow the owner of this contract to endow any address with havvens - * from the initial supply. - * @dev Since the entire initial supply resides in the havven contract, - * this disallows the foundation from withdrawing fees on undistributed balances. - * This function can also be used to retrieve any havvens sent to the Havven contract itself.S - * Only callable by the contract owner. + * @notice Set the Oracle that pushes the havven price to this contract */ - function endow(address account, uint value) + function setOracle(address _oracle) external optionalProxy_onlyOwner - returns (bool) { - - /* Use "this" in order that the havven account is the sender. - * The explicit transfer also initialises fee entitlement information. */ - return _transfer(this, account, value); + oracle = _oracle; + emitOracleUpdated(_oracle); } /** - * @notice Allow the owner of this contract to emit transfer events for - * contract setup purposes. + * @notice Set the stale period on the updated havven price + * @dev No max/minimum, as changing it wont influence anything but issuance by the foundation */ - function emitTransferEvents(address sender, address[] recipients, uint[] values) + function setPriceStalePeriod(uint time) external - onlyOwner + optionalProxy_onlyOwner { - for (uint i = 0; i < recipients.length; ++i) { - emit Transfer(sender, recipients[i], values[i]); - } + priceStalePeriod = time; } /** - * @notice ERC20 transfer function. + * @notice Set the issuanceRatio for issuance calculations. + * @dev Only callable by the contract owner. */ - function transfer(address to, uint value) + function setIssuanceRatio(uint _issuanceRatio) external - optionalProxy - returns (bool) + optionalProxy_onlyOwner { - return _transfer(messageSender, to, value); + require(_issuanceRatio <= MAX_ISSUANCE_RATIO); + issuanceRatio = _issuanceRatio; + emitIssuanceRatioUpdated(_issuanceRatio); } /** - * @dev Calls transfer() in ExternStateProxyToken to perform the transfer itself, - * and also recomputes fee entitlement information when balances are updated. - * Anything calling this must apply the optionalProxy or onlyProxy modifier. + * @notice Set whether the specified can issue nomins or not. */ - function _transfer(address sender, address to, uint value) - internal - preCheckFeePeriodRollover - returns (bool) + function setIssuer(address account, bool value) + external + optionalProxy_onlyOwner + { + isIssuer[account] = value; + emitIssuersUpdated(account, value); + } + + /* ========== VIEWS ========== */ + + function issuanceCurrentBalanceSum(address account) + external + view + returns (uint) { + return issuanceData[account].currentBalanceSum; + } - uint senderPreBalance = state.balanceOf(sender); - uint recipientPreBalance = state.balanceOf(to); + function issuanceLastAverageBalance(address account) + external + view + returns (uint) + { + return issuanceData[account].lastAverageBalance; + } + function issuanceLastModified(address account) + external + view + returns (uint) + { + return issuanceData[account].lastModified; + } + + function totalIssuanceCurrentBalanceSum() + external + view + returns (uint) + { + return totalIssuanceData.currentBalanceSum; + } + + function totalIssuanceLastAverageBalance() + external + view + returns (uint) + { + return totalIssuanceData.lastAverageBalance; + } + + function totalIssuanceLastModified() + external + view + returns (uint) + { + return totalIssuanceData.lastModified; + } + + /* ========== MUTATIVE FUNCTIONS ========== */ + + /** + * @notice ERC20 transfer function. + */ + function transfer(address to, uint value) + public + optionalProxy + returns (bool) + { + address sender = messageSender; + /* If they have enough available Havvens, it could be that + * their havvens are escrowed, however the transfer would then + * fail. This means that escrowed havvens are locked first, + * and then the actual transferable ones. */ + require(nominsIssued[sender] == 0 || value <= availableHavvens(sender)); /* Perform the transfer: if there is a problem, * an exception will be thrown in this call. */ _transfer_byProxy(sender, to, value); - /* Zero-value transfers still update fee entitlement information, - * and may roll over the fee period. */ - adjustFeeEntitlement(sender, senderPreBalance); - adjustFeeEntitlement(to, recipientPreBalance); - return true; } /** - * @notice ERC20 transferFrom function, which also performs - * fee entitlement recomputation whenever balances are updated. + * @notice ERC20 transferFrom function. */ function transferFrom(address from, address to, uint value) - external - preCheckFeePeriodRollover + public optionalProxy returns (bool) { - uint senderPreBalance = state.balanceOf(from); - uint recipientPreBalance = state.balanceOf(to); - + address sender = messageSender; + require(nominsIssued[sender] == 0 || value <= availableHavvens(from)); /* Perform the transfer: if there is a problem, * an exception will be thrown in this call. */ - _transferFrom_byProxy(messageSender, from, to, value); - - /* Zero-value transfers still update fee entitlement information, - * and may roll over the fee period. */ - adjustFeeEntitlement(from, senderPreBalance); - adjustFeeEntitlement(to, recipientPreBalance); + _transferFrom_byProxy(sender, from, to, value); return true; } @@ -330,212 +405,375 @@ contract Havven is ExternStateProxyToken, SelfDestructible { * @notice Compute the last period's fee entitlement for the message sender * and then deposit it into their nomin account. */ - function withdrawFeeEntitlement() + function withdrawFees() public - preCheckFeePeriodRollover optionalProxy { address sender = messageSender; - + rolloverFeePeriodIfElapsed(); /* Do not deposit fees into frozen accounts. */ require(!nomin.frozen(sender)); /* Check the period has rolled over first. */ - rolloverFee(sender, lastTransferTimestamp[sender], state.balanceOf(sender)); + updateIssuanceData(sender, nominsIssued[sender], nomin.totalSupply()); /* Only allow accounts to withdraw fees once per period. */ - require(!hasWithdrawnLastPeriodFees[sender]); + require(!hasWithdrawnFees[sender]); uint feesOwed; - - if (escrow != HavvenEscrow(0)) { - feesOwed = escrow.totalVestedAccountBalance(sender); + uint lastTotalIssued = totalIssuanceData.lastAverageBalance; + + if (lastTotalIssued > 0) { + /* Sender receives a share of last period's collected fees proportional + * with their average fraction of the last period's issued nomins. */ + feesOwed = safeDiv_dec( + safeMul_dec(issuanceData[sender].lastAverageBalance, lastFeesCollected), + lastTotalIssued + ); } - feesOwed = safeDiv_dec(safeMul_dec(safeAdd(feesOwed, lastAverageBalance[sender]), - lastFeesCollected), - totalSupply); + hasWithdrawnFees[sender] = true; - hasWithdrawnLastPeriodFees[sender] = true; if (feesOwed != 0) { - nomin.withdrawFee(sender, feesOwed); - emit FeesWithdrawn(sender, sender, feesOwed); + nomin.withdrawFees(sender, feesOwed); } + emitFeesWithdrawn(messageSender, feesOwed); } /** - * @notice Update the fee entitlement since the last transfer or entitlement adjustment. + * @notice Update the havven balance averages since the last transfer + * or entitlement adjustment. * @dev Since this updates the last transfer timestamp, if invoked * consecutively, this function will do nothing after the first call. + * Also, this will adjust the total issuance at the same time. */ - function adjustFeeEntitlement(address account, uint preBalance) + function updateIssuanceData(address account, uint preBalance, uint lastTotalSupply) internal { - /* The time since the last transfer clamps at the last fee rollover time - * if the last transfer was earlier than that. */ - rolloverFee(account, lastTransferTimestamp[account], preBalance); + /* update the total balances first */ + totalIssuanceData = updatedIssuanceData(lastTotalSupply, totalIssuanceData); - currentBalanceSum[account] = safeAdd( - currentBalanceSum[account], - safeMul(preBalance, now - lastTransferTimestamp[account]) - ); + if (issuanceData[account].lastModified < feePeriodStartTime) { + hasWithdrawnFees[account] = false; + } - /* Update the last time this user's balance changed. */ - lastTransferTimestamp[account] = now; + issuanceData[account] = updatedIssuanceData(preBalance, issuanceData[account]); } + /** - * @dev Update the given account's previous period fee entitlement value. - * Do nothing if the last transfer occurred since the fee period rolled over. - * If the entitlement was updated, also update the last transfer time to be - * at the timestamp of the rollover, so if this should do nothing if called more - * than once during a given period. - * - * Consider the case where the entitlement is updated. If the last transfer - * occurred at time t in the last period, then the starred region is added to the - * entitlement, the last transfer timestamp is moved to r, and the fee period is - * rolled over from k-1 to k so that the new fee period start time is at time r. - * - * k-1 | k - * s __| - * _ _ ___|**| - * |**| - * _ _ ___|**|___ __ _ _ - * | - * t | - * r - * - * Similar computations are performed according to the fee period in which the - * last transfer occurred. + * @notice Compute the new IssuanceData on the old balance */ - function rolloverFee(address account, uint lastTransferTime, uint preBalance) + function updatedIssuanceData(uint preBalance, IssuanceData preIssuance) internal + view + returns (IssuanceData) { - if (lastTransferTime < feePeriodStartTime) { - if (lastTransferTime < lastFeePeriodStartTime) { - /* The last transfer predated the previous two fee periods. */ - if (lastTransferTime < penultimateFeePeriodStartTime) { - /* The balance did nothing in the penultimate fee period, so the average balance - * in this period is their pre-transfer balance. */ - penultimateAverageBalance[account] = preBalance; - /* The last transfer occurred within the one-before-the-last fee period. */ - } else { - /* No overflow risk here: the failed guard implies (penultimateFeePeriodStartTime <= lastTransferTime). */ - penultimateAverageBalance[account] = safeDiv( - safeAdd(currentBalanceSum[account], safeMul(preBalance, (lastFeePeriodStartTime - lastTransferTime))), - (lastFeePeriodStartTime - penultimateFeePeriodStartTime) - ); - } - - /* The balance did nothing in the last fee period, so the average balance - * in this period is their pre-transfer balance. */ - lastAverageBalance[account] = preBalance; - - /* The last transfer occurred within the last fee period. */ + + uint currentBalanceSum = preIssuance.currentBalanceSum; + uint lastAverageBalance = preIssuance.lastAverageBalance; + uint lastModified = preIssuance.lastModified; + + if (lastModified < feePeriodStartTime) { + if (lastModified < lastFeePeriodStartTime) { + /* The balance was last updated before the previous fee period, so the average + * balance in this period is their pre-transfer balance. */ + lastAverageBalance = preBalance; } else { - /* The previously-last average balance becomes the penultimate balance. */ - penultimateAverageBalance[account] = lastAverageBalance[account]; - - /* No overflow risk here: the failed guard implies (lastFeePeriodStartTime <= lastTransferTime). */ - lastAverageBalance[account] = safeDiv( - safeAdd(currentBalanceSum[account], safeMul(preBalance, (feePeriodStartTime - lastTransferTime))), - (feePeriodStartTime - lastFeePeriodStartTime) - ); + /* The balance was last updated during the previous fee period. */ + /* No overflow or zero denominator problems, since lastFeePeriodStartTime < feePeriodStartTime < lastModified. + * implies these quantities are strictly positive. */ + uint timeUpToRollover = feePeriodStartTime - lastModified; + uint lastFeePeriodDuration = feePeriodStartTime - lastFeePeriodStartTime; + uint lastBalanceSum = safeAdd(currentBalanceSum, safeMul(preBalance, timeUpToRollover)); + lastAverageBalance = lastBalanceSum / lastFeePeriodDuration; } - /* Roll over to the next fee period. */ - currentBalanceSum[account] = 0; - hasWithdrawnLastPeriodFees[account] = false; - lastTransferTimestamp[account] = feePeriodStartTime; + currentBalanceSum = safeMul(preBalance, now - feePeriodStartTime); + } else { + /* The balance was last updated during the current fee period. */ + currentBalanceSum = safeAdd( + currentBalanceSum, + safeMul(preBalance, now - lastModified) + ); } + + return IssuanceData(currentBalanceSum, lastAverageBalance, now); } /** - * @dev Recompute and return the given account's average balance information. - * This also rolls over the fee period if necessary, and brings - * the account's current balance sum up to date. + * @notice Recompute and return the given account's last average balance. */ - function _recomputeAccountLastAverageBalance(address account) - internal - preCheckFeePeriodRollover + function recomputeLastAverageBalance(address account) + external returns (uint) { - adjustFeeEntitlement(account, state.balanceOf(account)); - return lastAverageBalance[account]; + updateIssuanceData(account, nominsIssued[account], nomin.totalSupply()); + return issuanceData[account].lastAverageBalance; } /** - * @notice Recompute and return the sender's average balance information. + * @notice Issue nomins against the sender's havvens. + * @dev Issuance is only allowed if the havven price isn't stale and the sender is an issuer. */ - function recomputeLastAverageBalance() + function issueNomins(uint amount) + public + optionalProxy + requireIssuer(messageSender) + /* No need to check if price is stale, as it is checked in issuableNomins. */ + { + address sender = messageSender; + require(amount <= remainingIssuableNomins(sender)); + uint lastTot = nomin.totalSupply(); + uint preIssued = nominsIssued[sender]; + nomin.issue(sender, amount); + nominsIssued[sender] = safeAdd(preIssued, amount); + updateIssuanceData(sender, preIssued, lastTot); + } + + function issueMaxNomins() external optionalProxy - returns (uint) { - return _recomputeAccountLastAverageBalance(messageSender); + issueNomins(remainingIssuableNomins(messageSender)); } /** - * @notice Recompute and return the given account's average balance information. + * @notice Burn nomins to clear issued nomins/free havvens. */ - function recomputeAccountLastAverageBalance(address account) + function burnNomins(uint amount) + /* it doesn't matter if the price is stale or if the user is an issuer, as non-issuers have issued no nomins.*/ external - returns (uint) + optionalProxy { - return _recomputeAccountLastAverageBalance(account); + address sender = messageSender; + + uint lastTot = nomin.totalSupply(); + uint preIssued = nominsIssued[sender]; + /* nomin.burn does a safeSub on balance (so it will revert if there are not enough nomins). */ + nomin.burn(sender, amount); + /* This safe sub ensures amount <= number issued */ + nominsIssued[sender] = safeSub(preIssued, amount); + updateIssuanceData(sender, preIssued, lastTot); } - /** - * @notice Check if the current fee period has terminated and, if so, roll it over. + /** + * @notice Check if the fee period has rolled over. If it has, set the new fee period start + * time, and record the fees collected in the nomin contract. */ - function rolloverFeePeriod() + function rolloverFeePeriodIfElapsed() public { - checkFeePeriodRollover(); + /* If the fee period has rolled over... */ + if (now >= feePeriodStartTime + feePeriodDuration) { + lastFeesCollected = nomin.feePool(); + lastFeePeriodStartTime = feePeriodStartTime; + feePeriodStartTime = now; + emitFeePeriodRollover(now); + } } + /* ========== Issuance/Burning ========== */ - /* ========== MODIFIERS ========== */ + /** + * @notice The maximum nomins an issuer can issue against their total havven quantity. This ignores any + * already issued nomins. + */ + function maxIssuableNomins(address issuer) + view + public + priceNotStale + returns (uint) + { + if (!isIssuer[issuer]) { + return 0; + } + if (escrow != HavvenEscrow(0)) { + uint totalOwnedHavvens = safeAdd(tokenState.balanceOf(issuer), escrow.balanceOf(issuer)); + return safeMul_dec(HAVtoUSD(totalOwnedHavvens), issuanceRatio); + } else { + return safeMul_dec(HAVtoUSD(tokenState.balanceOf(issuer)), issuanceRatio); + } + } /** - * @dev If the fee period has rolled over, then - * save the start times of the last fee period, - * as well as the penultimate fee period. + * @notice The remaining nomins an issuer can issue against their total havven quantity. */ - function checkFeePeriodRollover() - internal + function remainingIssuableNomins(address issuer) + view + public + returns (uint) { - /* If the fee period has rolled over... */ - if (feePeriodStartTime + targetFeePeriodDurationSeconds <= now) { - lastFeesCollected = nomin.feePool(); + uint issued = nominsIssued[issuer]; + uint max = maxIssuableNomins(issuer); + if (issued > max) { + return 0; + } else { + return max - issued; + } + } - /* ...shift the three period start times back one place. */ - penultimateFeePeriodStartTime = lastFeePeriodStartTime; - lastFeePeriodStartTime = feePeriodStartTime; - feePeriodStartTime = now; - - emit FeePeriodRollover(now); + /** + * @notice Havvens that are locked, which can exceed the user's total balance + escrowed + */ + function lockedHavvens(address account) + public + view + returns (uint) + { + if (nominsIssued[account] == 0) { + return 0; + } + return USDtoHAV(safeDiv_dec(nominsIssued[account], issuanceRatio)); + } + + /** + * @notice Havvens that are not locked, available for issuance + */ + function availableHavvens(address account) + public + view + returns (uint) + { + uint locked = lockedHavvens(account); + uint bal = tokenState.balanceOf(account); + if (escrow != address(0)) { + bal += escrow.balanceOf(account); + } + if (locked > bal) { + return 0; } + return bal - locked; } - modifier postCheckFeePeriodRollover + /** + * @notice The value in USD for a given amount of HAV + */ + function HAVtoUSD(uint hav_dec) + public + view + priceNotStale + returns (uint) + { + return safeMul_dec(hav_dec, price); + } + + /** + * @notice The value in HAV for a given amount of USD + */ + function USDtoHAV(uint usd_dec) + public + view + priceNotStale + returns (uint) + { + return safeDiv_dec(usd_dec, price); + } + + /** + * @notice Access point for the oracle to update the price of havvens. + */ + function updatePrice(uint newPrice, uint timeSent) + external + onlyOracle /* Should be callable only by the oracle. */ { + /* Must be the most recently sent price, but not too far in the future. + * (so we can't lock ourselves out of updating the oracle for longer than this) */ + require(lastPriceUpdateTime < timeSent && timeSent < now + ORACLE_FUTURE_LIMIT); + + price = newPrice; + lastPriceUpdateTime = timeSent; + emitPriceUpdated(newPrice, timeSent); + + /* Check the fee period rollover within this as the price should be pushed every 15min. */ + rolloverFeePeriodIfElapsed(); + } + + /** + * @notice Check if the price of havvens hasn't been updated for longer than the stale period. + */ + function priceIsStale() + public + view + returns (bool) + { + return safeAdd(lastPriceUpdateTime, priceStalePeriod) < now; + } + + /* ========== MODIFIERS ========== */ + + modifier requireIssuer(address account) + { + require(isIssuer[account]); _; - checkFeePeriodRollover(); } - modifier preCheckFeePeriodRollover + modifier onlyOracle { - checkFeePeriodRollover(); + require(msg.sender == oracle); _; } + modifier priceNotStale + { + require(!priceIsStale()); + _; + } /* ========== EVENTS ========== */ + event PriceUpdated(uint newPrice, uint timestamp); + bytes32 constant PRICEUPDATED_SIG = keccak256("PriceUpdated(uint256,uint256)"); + function emitPriceUpdated(uint newPrice, uint timestamp) internal { + proxy._emit(abi.encode(newPrice, timestamp), 1, PRICEUPDATED_SIG, 0, 0, 0); + } + + event IssuanceRatioUpdated(uint newRatio); + bytes32 constant ISSUANCERATIOUPDATED_SIG = keccak256("IssuanceRatioUpdated(uint256)"); + function emitIssuanceRatioUpdated(uint newRatio) internal { + proxy._emit(abi.encode(newRatio), 1, ISSUANCERATIOUPDATED_SIG, 0, 0, 0); + } + event FeePeriodRollover(uint timestamp); + bytes32 constant FEEPERIODROLLOVER_SIG = keccak256("FeePeriodRollover(uint256)"); + function emitFeePeriodRollover(uint timestamp) internal { + proxy._emit(abi.encode(timestamp), 1, FEEPERIODROLLOVER_SIG, 0, 0, 0); + } event FeePeriodDurationUpdated(uint duration); + bytes32 constant FEEPERIODDURATIONUPDATED_SIG = keccak256("FeePeriodDurationUpdated(uint256)"); + function emitFeePeriodDurationUpdated(uint duration) internal { + proxy._emit(abi.encode(duration), 1, FEEPERIODDURATIONUPDATED_SIG, 0, 0, 0); + } + + event FeesWithdrawn(address indexed account, uint value); + bytes32 constant FEESWITHDRAWN_SIG = keccak256("FeesWithdrawn(address,uint256)"); + function emitFeesWithdrawn(address account, uint value) internal { + proxy._emit(abi.encode(value), 2, FEESWITHDRAWN_SIG, bytes32(account), 0, 0); + } + + event OracleUpdated(address newOracle); + bytes32 constant ORACLEUPDATED_SIG = keccak256("OracleUpdated(address)"); + function emitOracleUpdated(address newOracle) internal { + proxy._emit(abi.encode(newOracle), 1, ORACLEUPDATED_SIG, 0, 0, 0); + } + + event NominUpdated(address newNomin); + bytes32 constant NOMINUPDATED_SIG = keccak256("NominUpdated(address)"); + function emitNominUpdated(address newNomin) internal { + proxy._emit(abi.encode(newNomin), 1, NOMINUPDATED_SIG, 0, 0, 0); + } + + event EscrowUpdated(address newEscrow); + bytes32 constant ESCROWUPDATED_SIG = keccak256("EscrowUpdated(address)"); + function emitEscrowUpdated(address newEscrow) internal { + proxy._emit(abi.encode(newEscrow), 1, ESCROWUPDATED_SIG, 0, 0, 0); + } + + event IssuersUpdated(address indexed account, bool indexed value); + bytes32 constant ISSUERSUPDATED_SIG = keccak256("IssuersUpdated(address,bool)"); + function emitIssuersUpdated(address account, bool value) internal { + proxy._emit(abi.encode(), 3, ISSUERSUPDATED_SIG, bytes32(account), bytes32(value ? 1 : 0), 0); + } - event FeesWithdrawn(address account, address indexed accountIndex, uint value); } diff --git a/contracts/HavvenEscrow.sol b/contracts/HavvenEscrow.sol index 0c9f3d12b5..1b7d9a4fac 100644 --- a/contracts/HavvenEscrow.sol +++ b/contracts/HavvenEscrow.sol @@ -2,16 +2,14 @@ ----------------------------------------------------------------- FILE INFORMATION ----------------------------------------------------------------- + file: HavvenEscrow.sol -version: 1.0 +version: 1.1 author: Anton Jurisevic Dominic Romanowski Mike Spain -date: 2018-02-07 - -checked: Mike Spain -approved: Samuel Brooks +date: 2018-05-29 ----------------------------------------------------------------- MODULE DESCRIPTION @@ -27,26 +25,25 @@ The fees are handled by withdrawing the entire fee allocation for all havvens inside the escrow contract, and then allowing the contract itself to subdivide that pool up proportionally within itself. Every time the fee period rolls over in the main Havven -contract, the HavvenEscrow fee pool is remitted back into the +contract, the HavvenEscrow fee pool is remitted back into the main fee pool to be redistributed in the next fee period. ----------------------------------------------------------------- - */ -pragma solidity ^0.4.21; +pragma solidity 0.4.24; import "contracts/SafeDecimalMath.sol"; import "contracts/Owned.sol"; import "contracts/Havven.sol"; -import "contracts/EtherNomin.sol"; +import "contracts/Nomin.sol"; import "contracts/LimitedSetup.sol"; /** * @title A contract to hold escrowed havvens and free them at given schedules. */ -contract HavvenEscrow is Owned, LimitedSetup(8 weeks), SafeDecimalMath { +contract HavvenEscrow is SafeDecimalMath, Owned, LimitedSetup(8 weeks) { /* The corresponding Havven contract. */ Havven public havven; @@ -60,10 +57,16 @@ contract HavvenEscrow is Owned, LimitedSetup(8 weeks), SafeDecimalMath { /* The total remaining vested balance, for verifying the actual havven balance of this contract against. */ uint public totalVestedBalance; + uint constant TIME_INDEX = 0; + uint constant QUANTITY_INDEX = 1; + + /* Limit vesting entries to disallow unbounded iteration over vesting schedules. */ + uint constant MAX_VESTING_ENTRIES = 20; + /* ========== CONSTRUCTOR ========== */ - function HavvenEscrow(address _owner, Havven _havven) + constructor(address _owner, Havven _havven) Owned(_owner) public { @@ -95,7 +98,7 @@ contract HavvenEscrow is Owned, LimitedSetup(8 weeks), SafeDecimalMath { return totalVestedAccountBalance[account]; } - /** + /** * @notice The number of vesting dates in an account's schedule. */ function numVestingEntries(address account) @@ -106,7 +109,7 @@ contract HavvenEscrow is Owned, LimitedSetup(8 weeks), SafeDecimalMath { return vestingSchedules[account].length; } - /** + /** * @notice Get a particular schedule entry for an account. * @return A pair of uints: (timestamp, havven quantity). */ @@ -126,7 +129,7 @@ contract HavvenEscrow is Owned, LimitedSetup(8 weeks), SafeDecimalMath { view returns (uint) { - return vestingSchedules[account][index][0]; + return getVestingScheduleEntry(account,index)[TIME_INDEX]; } /** @@ -137,7 +140,7 @@ contract HavvenEscrow is Owned, LimitedSetup(8 weeks), SafeDecimalMath { view returns (uint) { - return vestingSchedules[account][index][1]; + return getVestingScheduleEntry(account,index)[QUANTITY_INDEX]; } /** @@ -161,7 +164,7 @@ contract HavvenEscrow is Owned, LimitedSetup(8 weeks), SafeDecimalMath { * @notice Obtain the next schedule entry that will vest for a given user. * @return A pair of uints: (timestamp, havven quantity). */ function getNextVestingEntry(address account) - external + public view returns (uint[2]) { @@ -180,11 +183,7 @@ contract HavvenEscrow is Owned, LimitedSetup(8 weeks), SafeDecimalMath { view returns (uint) { - uint index = getNextVestingIndex(account); - if (index == numVestingEntries(account)) { - return 0; - } - return getVestingTime(account, index); + return getNextVestingEntry(account)[TIME_INDEX]; } /** @@ -195,11 +194,7 @@ contract HavvenEscrow is Owned, LimitedSetup(8 weeks), SafeDecimalMath { view returns (uint) { - uint index = getNextVestingIndex(account); - if (index == numVestingEntries(account)) { - return 0; - } - return getVestingQuantity(account, index); + return getNextVestingEntry(account)[QUANTITY_INDEX]; } @@ -212,7 +207,7 @@ contract HavvenEscrow is Owned, LimitedSetup(8 weeks), SafeDecimalMath { function withdrawHavvens(uint quantity) external onlyOwner - setupFunction + onlyDuringSetup { havven.transfer(havven, quantity); } @@ -223,7 +218,7 @@ contract HavvenEscrow is Owned, LimitedSetup(8 weeks), SafeDecimalMath { function purgeAccount(address account) external onlyOwner - setupFunction + onlyDuringSetup { delete vestingSchedules[account]; totalVestedBalance = safeSub(totalVestedBalance, totalVestedAccountBalance[account]); @@ -246,15 +241,21 @@ contract HavvenEscrow is Owned, LimitedSetup(8 weeks), SafeDecimalMath { function appendVestingEntry(address account, uint time, uint quantity) public onlyOwner - setupFunction + onlyDuringSetup { /* No empty or already-passed vesting entries allowed. */ require(now < time); require(quantity != 0); + + /* There must be enough balance in the contract to provide for the vesting entry. */ totalVestedBalance = safeAdd(totalVestedBalance, quantity); require(totalVestedBalance <= havven.balanceOf(this)); - if (vestingSchedules[account].length == 0) { + /* Disallow arbitrarily long vesting schedules in light of the gas limit. */ + uint scheduleLength = vestingSchedules[account].length; + require(scheduleLength <= MAX_VESTING_ENTRIES); + + if (scheduleLength == 0) { totalVestedAccountBalance[account] = quantity; } else { /* Disallow adding new vested havvens earlier than the last one. @@ -276,7 +277,7 @@ contract HavvenEscrow is Owned, LimitedSetup(8 weeks), SafeDecimalMath { function addVestingSchedule(address account, uint[] times, uint[] quantities) external onlyOwner - setupFunction + onlyDuringSetup { for (uint i = 0; i < times.length; i++) { appendVestingEntry(account, times[i], quantities[i]); @@ -284,10 +285,10 @@ contract HavvenEscrow is Owned, LimitedSetup(8 weeks), SafeDecimalMath { } - /** + /** * @notice Allow a user to withdraw any havvens in their schedule that have vested. */ - function vest() + function vest() external { uint numEntries = numVestingEntries(msg.sender); @@ -305,14 +306,13 @@ contract HavvenEscrow is Owned, LimitedSetup(8 weeks), SafeDecimalMath { vestingSchedules[msg.sender][i] = [0, 0]; total = safeAdd(total, qty); - totalVestedAccountBalance[msg.sender] = safeSub(totalVestedAccountBalance[msg.sender], qty); } if (total != 0) { totalVestedBalance = safeSub(totalVestedBalance, total); + totalVestedAccountBalance[msg.sender] = safeSub(totalVestedAccountBalance[msg.sender], total); havven.transfer(msg.sender, total); - emit Vested(msg.sender, msg.sender, - now, total); + emit Vested(msg.sender, now, total); } } @@ -321,5 +321,5 @@ contract HavvenEscrow is Owned, LimitedSetup(8 weeks), SafeDecimalMath { event HavvenUpdated(address newHavven); - event Vested(address beneficiary, address indexed beneficiaryIndex, uint time, uint value); -} + event Vested(address indexed beneficiary, uint time, uint value); +} \ No newline at end of file diff --git a/contracts/IssuanceController.sol b/contracts/IssuanceController.sol new file mode 100644 index 0000000000..91d3644a6b --- /dev/null +++ b/contracts/IssuanceController.sol @@ -0,0 +1,345 @@ +/* +----------------------------------------------------------------- +FILE INFORMATION +----------------------------------------------------------------- + +file: IssuanceController.sol +version: 1.0 +author: Kevin Brown + +date: 2018-05-20 + +----------------------------------------------------------------- +MODULE DESCRIPTION +----------------------------------------------------------------- + +Issuance controller contract. The issuance controller provides +a way for users to acquire nomins (Nomin.sol) by paying ETH +and a way for users to acquire havvens (Havven.sol) by paying +nomins. + +This smart contract contains a balance of each currency, and +allows the owner of the contract (the Havven Foundation) to +manage the available balances of both currencies at their +discretion. + +In future releases this functionality will gradually move away +from a centralised approach with the Havven foundation +controlling all of the currency to a decentralised exchange +approach where users can exchange these assets freely. + +----------------------------------------------------------------- +*/ + +pragma solidity 0.4.24; + +import "contracts/SelfDestructible.sol"; +import "contracts/Pausable.sol"; +import "contracts/SafeDecimalMath.sol"; +import "contracts/Havven.sol"; +import "contracts/Nomin.sol"; + +/** + * @title Issuance Controller Contract. + */ +contract IssuanceController is SafeDecimalMath, SelfDestructible, Pausable { + + /* ========== STATE VARIABLES ========== */ + Havven public havven; + Nomin public nomin; + + // Address where the ether raised is transfered to + address public fundsWallet; + + /* The address of the oracle which pushes the USD price havvens and ether to this contract */ + address public oracle; + /* Do not allow the oracle to submit times any further forward into the future than + this constant. */ + uint constant ORACLE_FUTURE_LIMIT = 10 minutes; + + /* How long will the contract assume the price of any asset is correct */ + uint public priceStalePeriod = 3 hours; + + /* The time the prices were last updated */ + uint public lastPriceUpdateTime; + /* The USD price of havvens denominated in UNIT */ + uint public usdToHavPrice; + /* The USD price of ETH denominated in UNIT */ + uint public usdToEthPrice; + + /* ========== CONSTRUCTOR ========== */ + + /** + * @dev Constructor + * @param _owner The owner of this contract. + * @param _havven The Havven contract we'll interact with for balances and sending. + * @param _nomin The Nomin contract we'll interact with for balances and sending. + * @param _oracle The address which is able to update price information. + * @param _usdToEthPrice The current price of ETH in USD, expressed in UNIT. + * @param _usdToHavPrice The current price of Havven in USD, expressed in UNIT. + */ + constructor( + // Ownable + address _owner, + + // Funds Wallet + address _fundsWallet, + + // Other contracts needed + Havven _havven, + Nomin _nomin, + + // Oracle values - Allows for price updates + address _oracle, + uint _usdToEthPrice, + uint _usdToHavPrice + ) + /* Owned is initialised in SelfDestructible */ + SelfDestructible(_owner) + Pausable(_owner) + public + { + fundsWallet = _fundsWallet; + havven = _havven; + nomin = _nomin; + oracle = _oracle; + usdToEthPrice = _usdToEthPrice; + usdToHavPrice = _usdToHavPrice; + lastPriceUpdateTime = now; + } + + /* ========== SETTERS ========== */ + + /** + * @notice Set the funds wallet where ETH raised is held + */ + function setFundsWallet(address _fundsWallet) + external + onlyOwner + { + fundsWallet = _fundsWallet; + emit FundsWalletUpdated(fundsWallet); + } + + /** + * @notice Set the Oracle that pushes the havven price to this contract + */ + function setOracle(address _oracle) + external + onlyOwner + { + oracle = _oracle; + emit OracleUpdated(oracle); + } + + /** + * @notice Set the Nomin contract that the issuance controller uses to issue Nomins. + */ + function setNomin(Nomin _nomin) + external + onlyOwner + { + nomin = _nomin; + emit NominUpdated(_nomin); + } + + /** + * @notice Set the Havven contract that the issuance controller uses to issue Havvens. + */ + function setHavven(Havven _havven) + external + onlyOwner + { + havven = _havven; + emit HavvenUpdated(_havven); + } + + /** + * @notice Set the stale period on the updated price variables + */ + function setPriceStalePeriod(uint _time) + external + onlyOwner + { + priceStalePeriod = _time; + emit PriceStalePeriodUpdated(priceStalePeriod); + } + + /* ========== MUTATIVE FUNCTIONS ========== */ + /** + * @notice Access point for the oracle to update the prices of havvens / eth. + */ + function updatePrices(uint newEthPrice, uint newHavvenPrice, uint timeSent) + external + onlyOracle + { + /* Must be the most recently sent price, but not too far in the future. + * (so we can't lock ourselves out of updating the oracle for longer than this) */ + require(lastPriceUpdateTime < timeSent && timeSent < now + ORACLE_FUTURE_LIMIT); + + usdToEthPrice = newEthPrice; + usdToHavPrice = newHavvenPrice; + lastPriceUpdateTime = timeSent; + + emit PricesUpdated(usdToEthPrice, usdToHavPrice, lastPriceUpdateTime); + } + + /** + * @notice Fallback function (exchanges ETH to nUSD) + */ + function () + external + payable + { + exchangeEtherForNomins(); + } + + + /** + * @notice Exchange ETH to nUSD. + */ + function exchangeEtherForNomins() + public + payable + pricesNotStale + notPaused + returns (uint) // Returns the number of Nomins (nUSD) received + { + // The multiplication works here because usdToEthPrice is specified in + // 18 decimal places, just like our currency base. + uint requestedToPurchase = safeMul_dec(msg.value, usdToEthPrice); + + // Store the ETH in our funds wallet + fundsWallet.transfer(msg.value); + + // Send the nomins. + // Note: Fees are calculated by the Nomin contract, so when + // we request a specific transfer here, the fee is + // automatically deducted and sent to the fee pool. + nomin.transfer(msg.sender, requestedToPurchase); + + // We don't emit our own events here because we assume that anyone + // who wants to watch what the Issuance Controller is doing can + // just watch ERC20 events from the Nomin contract filtered to our + // address. + + return requestedToPurchase; + } + + /** + * @notice Exchange nUSD for Havvens + */ + function exchangeNominsForHavvens(uint amount) + external + pricesNotStale + notPaused + returns (uint) // Returns the number of Havvens (HAV) received + { + // How many Havvens are they going to be receiving? + uint havvensToSend = havvensReceivedForNomins(amount); + + // Ok, transfer the Nomins to our address. + nomin.transferFrom(msg.sender, this, amount); + + // And send them the Havvens. + havven.transfer(msg.sender, havvensToSend); + + // We don't emit our own events here because we assume that anyone + // who wants to watch what the Issuance Controller is doing can + // just watch ERC20 events from the Nomin and/or Havven contracts + // filtered to our address. + + return havvensToSend; + } + + /** + * @notice Withdraw havvens: Allows the owner to withdraw havvens from this contract if needed. + */ + function withdrawHavvens(uint amount) + external + onlyOwner + { + havven.transfer(owner, amount); + + // We don't emit our own events here because we assume that anyone + // who wants to watch what the Issuance Controller is doing can + // just watch ERC20 events from the Nomin and/or Havven contracts + // filtered to our address. + } + + /** + * @notice Withdraw nomins: Allows the owner to withdraw nomins from this contract if needed. + */ + function withdrawNomins(uint amount) + external + onlyOwner + { + nomin.transfer(owner, amount); + + // We don't emit our own events here because we assume that anyone + // who wants to watch what the Issuance Controller is doing can + // just watch ERC20 events from the Nomin and/or Havven contracts + // filtered to our address. + } + + /* ========== VIEWS ========== */ + /** + * @notice Check if the prices haven't been updated for longer than the stale period. + */ + function pricesAreStale() + public + view + returns (bool) + { + return safeAdd(lastPriceUpdateTime, priceStalePeriod) < now; + } + + /** + * @notice Calculate how many havvens you will receive if you transfer + * an amount of nomins. + */ + function havvensReceivedForNomins(uint amount) + public + view + returns (uint) + { + uint nominsReceived = nomin.amountReceived(amount); + return safeDiv_dec(nominsReceived, usdToHavPrice); + } + + /** + * @notice Calculate how many nomins you will receive if you transfer + * an amount of ether. + */ + function nominsReceivedForEther(uint amount) + public + view + returns (uint) + { + uint nominsTransferred = safeMul_dec(amount, usdToEthPrice); + return nomin.amountReceived(nominsTransferred); + } + + /* ========== MODIFIERS ========== */ + + modifier onlyOracle + { + require(msg.sender == oracle); + _; + } + + modifier pricesNotStale + { + require(!pricesAreStale()); + _; + } + + /* ========== EVENTS ========== */ + + event FundsWalletUpdated(address newFundsWallet); + event OracleUpdated(address newOracle); + event NominUpdated(Nomin newNominContract); + event HavvenUpdated(Havven newHavvenContract); + event PriceStalePeriodUpdated(uint priceStalePeriod); + event PricesUpdated(uint newEthPrice, uint newHavvenPrice, uint timeSent); +} diff --git a/contracts/LimitedSetup.sol b/contracts/LimitedSetup.sol index 181162edae..759eaa8cd3 100644 --- a/contracts/LimitedSetup.sol +++ b/contracts/LimitedSetup.sol @@ -2,14 +2,12 @@ ----------------------------------------------------------------- FILE INFORMATION ----------------------------------------------------------------- + file: LimitedSetup.sol -version: 1.0 +version: 1.1 author: Anton Jurisevic -date: 2018-2-13 - -checked: - -approved: - +date: 2018-05-15 ----------------------------------------------------------------- MODULE DESCRIPTION @@ -23,7 +21,7 @@ conclusion of the configurable-length post-construction setup period. */ -pragma solidity ^0.4.21; +pragma solidity 0.4.24; /** * @title Any function decorated with the modifier this contract provides @@ -34,16 +32,16 @@ contract LimitedSetup { uint setupExpiryTime; /** - * @dev Constructor. + * @dev LimitedSetup Constructor. * @param setupDuration The time the setup period will last for. */ - function LimitedSetup(uint setupDuration) + constructor(uint setupDuration) public { setupExpiryTime = now + setupDuration; } - modifier setupFunction + modifier onlyDuringSetup { require(now < setupExpiryTime); _; diff --git a/contracts/Nomin.sol b/contracts/Nomin.sol new file mode 100644 index 0000000000..7db2fbc2f8 --- /dev/null +++ b/contracts/Nomin.sol @@ -0,0 +1,251 @@ +/* +----------------------------------------------------------------- +FILE INFORMATION +----------------------------------------------------------------- + +file: Nomin.sol +version: 1.2 +author: Anton Jurisevic + Mike Spain + Dominic Romanowski + Kevin Brown + +date: 2018-05-29 + +----------------------------------------------------------------- +MODULE DESCRIPTION +---------------------------------------------------------------- - + +Ether-backed nomin stablecoin contract. + +This contract issues nomins, which are tokens worth 1 USD each. + +Nomins are issuable by Havven holders who have to lock up some +value of their havvens to issue H * Cmax nomins. Where Cmax is +some value less than 1. + +A configurable fee is charged on nomin transfers and deposited +into a common pot, which havven holders may withdraw from once per +fee period. + +----------------------------------------------------------------- +*/ + +pragma solidity 0.4.24; + + +import "contracts/FeeToken.sol"; +import "contracts/TokenState.sol"; +import "contracts/Court.sol"; +import "contracts/Havven.sol"; + +contract Nomin is FeeToken { + + /* ========== STATE VARIABLES ========== */ + + // The address of the contract which manages confiscation votes. + Court public court; + Havven public havven; + + // Accounts which have lost the privilege to transact in nomins. + mapping(address => bool) public frozen; + + // Nomin transfers incur a 15 bp fee by default. + uint constant TRANSFER_FEE_RATE = 15 * UNIT / 10000; + string constant TOKEN_NAME = "Nomin USD"; + string constant TOKEN_SYMBOL = "nUSD"; + + /* ========== CONSTRUCTOR ========== */ + + constructor(address _proxy, Havven _havven, address _owner) + FeeToken(_proxy, TOKEN_NAME, TOKEN_SYMBOL, 0, // Zero nomins initially exist. + TRANSFER_FEE_RATE, + _havven, // The havven contract is the fee authority. + _owner) + public + { + require(_proxy != 0 && address(_havven) != 0 && _owner != 0); + // It should not be possible to transfer to the nomin contract itself. + frozen[this] = true; + havven = _havven; + } + + /* ========== SETTERS ========== */ + + function setCourt(Court _court) + external + optionalProxy_onlyOwner + { + court = _court; + emitCourtUpdated(_court); + } + + function setHavven(Havven _havven) + external + optionalProxy_onlyOwner + { + // havven should be set as the feeAuthority after calling this depending on + // havven's internal logic + havven = _havven; + setFeeAuthority(_havven); + emitHavvenUpdated(_havven); + } + + + /* ========== MUTATIVE FUNCTIONS ========== */ + + /* Override ERC20 transfer function in order to check + * whether the recipient account is frozen. Note that there is + * no need to check whether the sender has a frozen account, + * since their funds have already been confiscated, + * and no new funds can be transferred to it.*/ + function transfer(address to, uint value) + public + optionalProxy + returns (bool) + { + require(!frozen[to]); + return _transfer_byProxy(messageSender, to, value); + } + + /* Override ERC20 transferFrom function in order to check + * whether the recipient account is frozen. */ + function transferFrom(address from, address to, uint value) + public + optionalProxy + returns (bool) + { + require(!frozen[to]); + return _transferFrom_byProxy(messageSender, from, to, value); + } + + function transferSenderPaysFee(address to, uint value) + public + optionalProxy + returns (bool) + { + require(!frozen[to]); + return _transferSenderPaysFee_byProxy(messageSender, to, value); + } + + function transferFromSenderPaysFee(address from, address to, uint value) + public + optionalProxy + returns (bool) + { + require(!frozen[to]); + return _transferFromSenderPaysFee_byProxy(messageSender, from, to, value); + } + + /* If a confiscation court motion has passed and reached the confirmation + * state, the court may transfer the target account's balance to the fee pool + * and freeze its participation in further transactions. */ + function freezeAndConfiscate(address target) + external + onlyCourt + { + + // A motion must actually be underway. + uint motionID = court.targetMotionID(target); + require(motionID != 0); + + // These checks are strictly unnecessary, + // since they are already checked in the court contract itself. + require(court.motionConfirming(motionID)); + require(court.motionPasses(motionID)); + require(!frozen[target]); + + // Confiscate the balance in the account and freeze it. + uint balance = tokenState.balanceOf(target); + tokenState.setBalanceOf(address(this), safeAdd(tokenState.balanceOf(address(this)), balance)); + tokenState.setBalanceOf(target, 0); + frozen[target] = true; + emitAccountFrozen(target, balance); + emitTransfer(target, address(this), balance); + } + + /* The owner may allow a previously-frozen contract to once + * again accept and transfer nomins. */ + function unfreezeAccount(address target) + external + optionalProxy_onlyOwner + { + require(frozen[target] && target != address(this)); + frozen[target] = false; + emitAccountUnfrozen(target); + } + + /* Allow havven to issue a certain number of + * nomins from an account. */ + function issue(address account, uint amount) + external + onlyHavven + { + tokenState.setBalanceOf(account, safeAdd(tokenState.balanceOf(account), amount)); + totalSupply = safeAdd(totalSupply, amount); + emitTransfer(address(0), account, amount); + emitIssued(account, amount); + } + + /* Allow havven to burn a certain number of + * nomins from an account. */ + function burn(address account, uint amount) + external + onlyHavven + { + tokenState.setBalanceOf(account, safeSub(tokenState.balanceOf(account), amount)); + totalSupply = safeSub(totalSupply, amount); + emitTransfer(account, address(0), amount); + emitBurned(account, amount); + } + + /* ========== MODIFIERS ========== */ + + modifier onlyHavven() { + require(Havven(msg.sender) == havven); + _; + } + + modifier onlyCourt() { + require(Court(msg.sender) == court); + _; + } + + /* ========== EVENTS ========== */ + + event CourtUpdated(address newCourt); + bytes32 constant COURTUPDATED_SIG = keccak256("CourtUpdated(address)"); + function emitCourtUpdated(address newCourt) internal { + proxy._emit(abi.encode(newCourt), 1, COURTUPDATED_SIG, 0, 0, 0); + } + + event HavvenUpdated(address newHavven); + bytes32 constant HAVVENUPDATED_SIG = keccak256("HavvenUpdated(address)"); + function emitHavvenUpdated(address newHavven) internal { + proxy._emit(abi.encode(newHavven), 1, HAVVENUPDATED_SIG, 0, 0, 0); + } + + event AccountFrozen(address indexed target, uint balance); + bytes32 constant ACCOUNTFROZEN_SIG = keccak256("AccountFrozen(address,uint256)"); + function emitAccountFrozen(address target, uint balance) internal { + proxy._emit(abi.encode(balance), 2, ACCOUNTFROZEN_SIG, bytes32(target), 0, 0); + } + + event AccountUnfrozen(address indexed target); + bytes32 constant ACCOUNTUNFROZEN_SIG = keccak256("AccountUnfrozen(address)"); + function emitAccountUnfrozen(address target) internal { + proxy._emit(abi.encode(), 2, ACCOUNTUNFROZEN_SIG, bytes32(target), 0, 0); + } + + event Issued(address indexed account, uint amount); + bytes32 constant ISSUED_SIG = keccak256("Issued(address,uint256)"); + function emitIssued(address account, uint amount) internal { + proxy._emit(abi.encode(amount), 2, ISSUED_SIG, bytes32(account), 0, 0); + } + + event Burned(address indexed account, uint amount); + bytes32 constant BURNED_SIG = keccak256("Burned(address,uint256)"); + function emitBurned(address account, uint amount) internal { + proxy._emit(abi.encode(amount), 2, BURNED_SIG, bytes32(account), 0, 0); + } +} diff --git a/contracts/Owned.sol b/contracts/Owned.sol index c44274b595..bde14e204d 100644 --- a/contracts/Owned.sol +++ b/contracts/Owned.sol @@ -2,16 +2,14 @@ ----------------------------------------------------------------- FILE INFORMATION ----------------------------------------------------------------- + file: Owned.sol -version: 1.0 +version: 1.1 author: Anton Jurisevic Dominic Romanowski date: 2018-2-26 -checked: Mike Spain -approved: Samuel Brooks - ----------------------------------------------------------------- MODULE DESCRIPTION ----------------------------------------------------------------- @@ -28,7 +26,7 @@ previous owner change the nomination (setting it to 0). ----------------------------------------------------------------- */ -pragma solidity ^0.4.21; +pragma solidity 0.4.24; /** * @title A contract with an owner. @@ -40,19 +38,21 @@ contract Owned { address public nominatedOwner; /** - * @dev Constructor + * @dev Owned Constructor */ - function Owned(address _owner) + constructor(address _owner) public { + require(_owner != address(0)); owner = _owner; + emit OwnerChanged(address(0), _owner); } /** * @notice Nominate a new owner of this contract. * @dev Only the current owner may nominate a new owner. */ - function nominateOwner(address _owner) + function nominateNewOwner(address _owner) external onlyOwner { @@ -80,4 +80,4 @@ contract Owned { event OwnerNominated(address newOwner); event OwnerChanged(address oldOwner, address newOwner); -} +} \ No newline at end of file diff --git a/contracts/Pausable.sol b/contracts/Pausable.sol new file mode 100644 index 0000000000..ff729eb7c6 --- /dev/null +++ b/contracts/Pausable.sol @@ -0,0 +1,79 @@ +/* +----------------------------------------------------------------- +FILE INFORMATION +----------------------------------------------------------------- + +file: Pausable.sol +version: 1.0 +author: Kevin Brown + +date: 2018-05-22 + +----------------------------------------------------------------- +MODULE DESCRIPTION +----------------------------------------------------------------- + +This contract allows an inheriting contract to be marked as +paused. It also defines a modifier which can be used by the +inheriting contract to prevent actions while paused. + +----------------------------------------------------------------- +*/ + +pragma solidity 0.4.24; + + +import "contracts/Owned.sol"; + + +/** + * @title A contract that can be paused by its owner + */ +contract Pausable is Owned { + + uint public lastPauseTime; + bool public paused; + + /** + * @dev Constructor + * @param _owner The account which controls this contract. + */ + constructor(address _owner) + Owned(_owner) + public + { + // Paused will be false, and lastPauseTime will be 0 upon initialisation + } + + /** + * @notice Change the paused state of the contract + * @dev Only the contract owner may call this. + */ + function setPaused(bool _paused) + external + onlyOwner + { + // Ensure we're actually changing the state before we do anything + if (_paused == paused) { + return; + } + + // Set our paused state. + paused = _paused; + + // If applicable, set the last pause time. + if (paused) { + lastPauseTime = now; + } + + // Let everyone know that our pause state has changed. + emit PauseChanged(paused); + } + + event PauseChanged(bool isPaused); + + modifier notPaused { + require(!paused); + _; + } +} diff --git a/contracts/Proxy.sol b/contracts/Proxy.sol index 4945ffd6c8..e9cff06089 100644 --- a/contracts/Proxy.sol +++ b/contracts/Proxy.sol @@ -2,14 +2,12 @@ ----------------------------------------------------------------- FILE INFORMATION ----------------------------------------------------------------- + file: Proxy.sol -version: 1.0 +version: 1.3 author: Anton Jurisevic -date: 2018-2-28 - -checked: Mike Spain -approved: Samuel Brooks +date: 2018-05-29 ----------------------------------------------------------------- MODULE DESCRIPTION @@ -19,146 +17,125 @@ A proxy contract that, if it does not recognise the function being called on it, passes all value and call data to an underlying target contract. -Additionally this file contains the Proxyable interface. -Any contract the proxy wraps must implement this, in order -for the proxy to be able to pass msg.sender into the underlying -contract as the state parameter, messageSender. +This proxy has the capacity to toggle between DELEGATECALL +and CALL style proxy functionality. + +The former executes in the proxy's context, and so will preserve +msg.sender and store data at the proxy address. The latter will not. +Therefore, any contract the proxy wraps in the CALL style must +implement the Proxyable interface, in order that it can pass msg.sender +into the underlying contract as the state parameter, messageSender. ----------------------------------------------------------------- */ -pragma solidity ^0.4.21; +pragma solidity 0.4.24; + import "contracts/Owned.sol"; +import "contracts/Proxyable.sol"; + -/** - * @title Passes through function calls to an underlying Proxyable contract. - */ contract Proxy is Owned { - Proxyable public _target; - - /** - * @dev Constructor - * @param _initialTarget The address of the underlying contract to attach this proxy to. - * The target must implement the Proxyable interface. - * @param _owner The owner of this contract, who may change its target address. - */ - function Proxy(Proxyable _initialTarget, address _owner) + + Proxyable public target; + bool public useDELEGATECALL; + + constructor(address _owner) Owned(_owner) public - { - _target = _initialTarget; - emit TargetChanged(_initialTarget); - } + {} - /** - * @notice Direct this proxy to a new target contract. - */ - function _setTarget(address newTarget) + function setTarget(Proxyable _target) external onlyOwner { - require(newTarget != address(0)); - _target = Proxyable(newTarget); - emit TargetChanged(newTarget); + target = _target; + emit TargetUpdated(_target); } - /** - * @dev Fallback function passes through all data and ether to the target contract - * and returns the result that the target returns. - */ - function () - public - payable - { - _target.setMessageSender(msg.sender); - assembly { - /* Copy call data into free memory region. */ - let free_ptr := mload(0x40) - calldatacopy(free_ptr, 0, calldatasize) - - /* Forward all gas, ether, and data to the target contract. */ - let result := call(gas, sload(_target_slot), callvalue, free_ptr, calldatasize, 0, 0) - returndatacopy(free_ptr, 0, returndatasize) - - /* Revert if the call failed, otherwise return the result. */ - if iszero(result) { revert(free_ptr, calldatasize) } - return(free_ptr, returndatasize) - } - } - - event TargetChanged(address targetAddress); -} - - -/** - * @title Accepts function calls passed through from a Proxy contract. - */ -contract Proxyable is Owned { - /* the proxy this contract exists behind. */ - Proxy public proxy; - - /* The caller of the proxy, passed through to this contract. - * Note that every function using this member must apply the onlyProxy or - * optionalProxy modifiers, otherwise their invocations can use stale values. */ - address messageSender; - - /** - * @dev Constructor - * @param _owner The account that owns this contract. It may change the proxy address. - */ - function Proxyable(address _owner) - Owned(_owner) - public { } - - /** - * @notice Set the proxy associated with this contract. - * @dev Only the contract owner may call this. - */ - function setProxy(Proxy _proxy) + function setUseDELEGATECALL(bool value) external onlyOwner { - proxy = _proxy; - emit ProxyChanged(_proxy); + useDELEGATECALL = value; } - /** - * @notice Set the address that this contract believes initiated the current function call. - * @dev Only the proxy contract may call this, but it is also set inside the optionalProxy - * modifier. - */ - function setMessageSender(address sender) + function _emit(bytes callData, uint numTopics, + bytes32 topic1, bytes32 topic2, + bytes32 topic3, bytes32 topic4) external - onlyProxy + onlyTarget { - messageSender = sender; - } + uint size = callData.length; + bytes memory _callData = callData; - modifier onlyProxy - { - require(Proxy(msg.sender) == proxy); - _; + assembly { + /* The first 32 bytes of callData contain its length (as specified by the abi). + * Length is assumed to be a uint256 and therefore maximum of 32 bytes + * in length. It is also leftpadded to be a multiple of 32 bytes. + * This means moving call_data across 32 bytes guarantees we correctly access + * the data itself. */ + switch numTopics + case 0 { + log0(add(_callData, 32), size) + } + case 1 { + log1(add(_callData, 32), size, topic1) + } + case 2 { + log2(add(_callData, 32), size, topic1, topic2) + } + case 3 { + log3(add(_callData, 32), size, topic1, topic2, topic3) + } + case 4 { + log4(add(_callData, 32), size, topic1, topic2, topic3, topic4) + } + } } - modifier optionalProxy + function() + external + payable { - if (Proxy(msg.sender) != proxy) { - messageSender = msg.sender; + if (useDELEGATECALL) { + assembly { + /* Copy call data into free memory region. */ + let free_ptr := mload(0x40) + calldatacopy(free_ptr, 0, calldatasize) + + /* Forward all gas and call data to the target contract. */ + let result := delegatecall(gas, sload(target_slot), free_ptr, calldatasize, 0, 0) + returndatacopy(free_ptr, 0, returndatasize) + + /* Revert if the call failed, otherwise return the result. */ + if iszero(result) { revert(free_ptr, returndatasize) } + return(free_ptr, returndatasize) + } + } else { + /* Here we are as above, but must send the messageSender explicitly + * since we are using CALL rather than DELEGATECALL. */ + target.setMessageSender(msg.sender); + assembly { + let free_ptr := mload(0x40) + calldatacopy(free_ptr, 0, calldatasize) + + /* We must explicitly forward ether to the underlying contract as well. */ + let result := call(gas, sload(target_slot), callvalue, free_ptr, calldatasize, 0, 0) + returndatacopy(free_ptr, 0, returndatasize) + + if iszero(result) { revert(free_ptr, returndatasize) } + return(free_ptr, returndatasize) + } } - _; } - modifier optionalProxy_onlyOwner - { - if (Proxy(msg.sender) != proxy) { - messageSender = msg.sender; - } - require(messageSender == owner); + modifier onlyTarget { + require(Proxyable(msg.sender) == target); _; } - event ProxyChanged(address proxyAddress); - + event TargetUpdated(Proxyable newTarget); } diff --git a/contracts/Proxyable.sol b/contracts/Proxyable.sol new file mode 100644 index 0000000000..a4ed8f84e3 --- /dev/null +++ b/contracts/Proxyable.sol @@ -0,0 +1,88 @@ +/* +----------------------------------------------------------------- +FILE INFORMATION +----------------------------------------------------------------- + +file: Proxyable.sol +version: 1.1 +author: Anton Jurisevic + +date: 2018-05-15 + +checked: Mike Spain +approved: Samuel Brooks + +----------------------------------------------------------------- +MODULE DESCRIPTION +----------------------------------------------------------------- + +A proxyable contract that works hand in hand with the Proxy contract +to allow for anyone to interact with the underlying contract both +directly and through the proxy. + +----------------------------------------------------------------- +*/ + + +pragma solidity 0.4.24; + +import "contracts/Owned.sol"; +import "contracts/Proxy.sol"; + +// This contract should be treated like an abstract contract +contract Proxyable is Owned { + /* The proxy this contract exists behind. */ + Proxy public proxy; + + /* The caller of the proxy, passed through to this contract. + * Note that every function using this member must apply the onlyProxy or + * optionalProxy modifiers, otherwise their invocations can use stale values. */ + address messageSender; + + constructor(address _proxy, address _owner) + Owned(_owner) + public + { + proxy = Proxy(_proxy); + emit ProxyUpdated(_proxy); + } + + function setProxy(address _proxy) + external + onlyOwner + { + proxy = Proxy(_proxy); + emit ProxyUpdated(_proxy); + } + + function setMessageSender(address sender) + external + onlyProxy + { + messageSender = sender; + } + + modifier onlyProxy { + require(Proxy(msg.sender) == proxy); + _; + } + + modifier optionalProxy + { + if (Proxy(msg.sender) != proxy) { + messageSender = msg.sender; + } + _; + } + + modifier optionalProxy_onlyOwner + { + if (Proxy(msg.sender) != proxy) { + messageSender = msg.sender; + } + require(messageSender == owner); + _; + } + + event ProxyUpdated(address proxyAddress); +} diff --git a/contracts/SafeDecimalMath.sol b/contracts/SafeDecimalMath.sol index 3364aea5e5..68e6384fdc 100644 --- a/contracts/SafeDecimalMath.sol +++ b/contracts/SafeDecimalMath.sol @@ -2,6 +2,7 @@ ----------------------------------------------------------------- FILE INFORMATION ----------------------------------------------------------------- + file: SafeDecimalMath.sol version: 1.0 author: Anton Jurisevic @@ -25,7 +26,7 @@ occur. ----------------------------------------------------------------- */ -pragma solidity ^0.4.21; +pragma solidity 0.4.24; /** @@ -119,7 +120,7 @@ contract SafeDecimalMath { /** * @return The result of multiplying x and y, interpreting the operands as fixed-point - * demicimals. Throws an exception in case of overflow. + * decimals. Throws an exception in case of overflow. * * @dev A unit factor is divided out after the product of x and y is evaluated, * so that product must be less than 2**256. diff --git a/contracts/SelfDestructible.sol b/contracts/SelfDestructible.sol index e55fd4a7f1..33b0be07e7 100644 --- a/contracts/SelfDestructible.sol +++ b/contracts/SelfDestructible.sol @@ -2,14 +2,12 @@ ----------------------------------------------------------------- FILE INFORMATION ----------------------------------------------------------------- + file: SelfDestructible.sol -version: 1.0 +version: 1.2 author: Anton Jurisevic -date: 2018-2-28 - -checked: Mike Spain -approved: Samuel Brooks +date: 2018-05-29 ----------------------------------------------------------------- MODULE DESCRIPTION @@ -17,52 +15,58 @@ MODULE DESCRIPTION This contract allows an inheriting contract to be destroyed after its owner indicates an intention and then waits for a period -without changing their mind. +without changing their mind. All ether contained in the contract +is forwarded to a nominated beneficiary upon destruction. ----------------------------------------------------------------- */ -pragma solidity ^0.4.21; +pragma solidity 0.4.24; import "contracts/Owned.sol"; + /** - * @title A contract that can be destroyed by its owner after a timer elapses. + * @title A contract that can be destroyed by its owner after a delay elapses. */ contract SelfDestructible is Owned { - uint public initiationTime = ~uint(0); - uint constant SD_DURATION = 3 days; - address public beneficiary; + uint public initiationTime; + bool public selfDestructInitiated; + address public selfDestructBeneficiary; + uint public constant SELFDESTRUCT_DELAY = 4 weeks; /** * @dev Constructor * @param _owner The account which controls this contract. - * @param _beneficiary The account to forward all ether in this contract upon self-destruction */ - function SelfDestructible(address _owner, address _beneficiary) - public - Owned(_owner) + constructor(address _owner) + Owned(_owner) + public { - beneficiary = _beneficiary; + require(_owner != address(0)); + selfDestructBeneficiary = _owner; + emit SelfDestructBeneficiaryUpdated(_owner); } /** * @notice Set the beneficiary address of this contract. - * @dev Only the contract owner may call this. + * @dev Only the contract owner may call this. The provided beneficiary must be non-null. + * @param _beneficiary The address to pay any eth contained in this contract to upon self-destruction. */ - function setBeneficiary(address _beneficiary) + function setSelfDestructBeneficiary(address _beneficiary) external onlyOwner { - beneficiary = _beneficiary; + require(_beneficiary != address(0)); + selfDestructBeneficiary = _beneficiary; emit SelfDestructBeneficiaryUpdated(_beneficiary); } /** * @notice Begin the self-destruction counter of this contract. - * Once the three-day timer has elapsed, the contract may be self-destructed. + * Once the delay has elapsed, the contract may be self-destructed. * @dev Only the contract owner may call this. */ function initiateSelfDestruct() @@ -70,7 +74,8 @@ contract SelfDestructible is Owned { onlyOwner { initiationTime = now; - emit SelfDestructInitiated(SD_DURATION); + selfDestructInitiated = true; + emit SelfDestructInitiated(SELFDESTRUCT_DELAY); } /** @@ -81,12 +86,13 @@ contract SelfDestructible is Owned { external onlyOwner { - initiationTime = ~uint(0); + initiationTime = 0; + selfDestructInitiated = false; emit SelfDestructTerminated(); } /** - * @notice If the self-destruction timer has elapsed, destroy this contract and + * @notice If the self-destruction delay has elapsed, destroy this contract and * remit any ether it owns to the beneficiary address. * @dev Only the contract owner may call this. */ @@ -94,17 +100,14 @@ contract SelfDestructible is Owned { external onlyOwner { - require(initiationTime + SD_DURATION < now); + require(selfDestructInitiated && initiationTime + SELFDESTRUCT_DELAY < now); + address beneficiary = selfDestructBeneficiary; emit SelfDestructed(beneficiary); selfdestruct(beneficiary); } - event SelfDestructBeneficiaryUpdated(address newBeneficiary); - - event SelfDestructInitiated(uint duration); - event SelfDestructTerminated(); - event SelfDestructed(address beneficiary); + event SelfDestructInitiated(uint selfDestructDelay); + event SelfDestructBeneficiaryUpdated(address newBeneficiary); } - diff --git a/contracts/State.sol b/contracts/State.sol new file mode 100644 index 0000000000..3fff178c95 --- /dev/null +++ b/contracts/State.sol @@ -0,0 +1,76 @@ +/* +----------------------------------------------------------------- +FILE INFORMATION +----------------------------------------------------------------- + +file: State.sol +version: 1.1 +author: Dominic Romanowski + Anton Jurisevic + +date: 2018-05-15 + +----------------------------------------------------------------- +MODULE DESCRIPTION +----------------------------------------------------------------- + +This contract is used side by side with external state token +contracts, such as Havven and Nomin. +It provides an easy way to upgrade contract logic while +maintaining all user balances and allowances. This is designed +to make the changeover as easy as possible, since mappings +are not so cheap or straightforward to migrate. + +The first deployed contract would create this state contract, +using it as its store of balances. +When a new contract is deployed, it links to the existing +state contract, whose owner would then change its associated +contract to the new one. + +----------------------------------------------------------------- +*/ + + +pragma solidity 0.4.24; + + +import "contracts/Owned.sol"; + + +contract State is Owned { + // the address of the contract that can modify variables + // this can only be changed by the owner of this contract + address public associatedContract; + + + constructor(address _owner, address _associatedContract) + Owned(_owner) + public + { + associatedContract = _associatedContract; + emit AssociatedContractUpdated(_associatedContract); + } + + /* ========== SETTERS ========== */ + + // Change the associated contract to a new address + function setAssociatedContract(address _associatedContract) + external + onlyOwner + { + associatedContract = _associatedContract; + emit AssociatedContractUpdated(_associatedContract); + } + + /* ========== MODIFIERS ========== */ + + modifier onlyAssociatedContract + { + require(msg.sender == associatedContract); + _; + } + + /* ========== EVENTS ========== */ + + event AssociatedContractUpdated(address associatedContract); +} diff --git a/contracts/TokenState.sol b/contracts/TokenState.sol index 17383bc218..440132cfde 100644 --- a/contracts/TokenState.sol +++ b/contracts/TokenState.sol @@ -2,15 +2,13 @@ ----------------------------------------------------------------- FILE INFORMATION ----------------------------------------------------------------- + file: TokenState.sol -version: 1.0 +version: 1.1 author: Dominic Romanowski Anton Jurisevic -date: 2018-2-24 - -checked: Anton Jurisevic -approved: Samuel Brooks +date: 2018-05-15 ----------------------------------------------------------------- MODULE DESCRIPTION @@ -19,10 +17,10 @@ MODULE DESCRIPTION A contract that holds the state of an ERC20 compliant token. This contract is used side by side with external state token -contracts, such as Havven and EtherNomin. +contracts, such as Havven and Nomin. It provides an easy way to upgrade contract logic while maintaining all user balances and allowances. This is designed -to to make the changeover as easy as possible, since mappings +to make the changeover as easy as possible, since mappings are not so cheap or straightforward to migrate. The first deployed contract would create this state contract, @@ -34,20 +32,16 @@ contract to the new one. ----------------------------------------------------------------- */ -pragma solidity ^0.4.21; +pragma solidity 0.4.24; -import "contracts/Owned.sol"; +import "contracts/State.sol"; /** * @title ERC20 Token State * @notice Stores balance information of an ERC20 token contract. */ -contract TokenState is Owned { - - /* The address of the contract that can modify balances and allowances; - * this can only be changed by the owner of this contract. */ - address public associatedContract; +contract TokenState is State { /* ERC20 fields. */ mapping(address => uint) public balanceOf; @@ -58,28 +52,13 @@ contract TokenState is Owned { * @param _owner The address which controls this contract. * @param _associatedContract The ERC20 contract whose state this composes. */ - function TokenState(address _owner, address _associatedContract) - Owned(_owner) + constructor(address _owner, address _associatedContract) + State(_owner, _associatedContract) public - { - associatedContract = _associatedContract; - emit AssociatedContractUpdated(_associatedContract); - } + {} /* ========== SETTERS ========== */ - /** - * @notice Change the associated contract to a new address. - * @dev Only the contract owner may call this. - */ - function setAssociatedContract(address _associatedContract) - external - onlyOwner - { - associatedContract = _associatedContract; - emit AssociatedContractUpdated(_associatedContract); - } - /** * @notice Set ERC20 allowance. * @dev Only the associated contract may call this. @@ -100,24 +79,11 @@ contract TokenState is Owned { * @dev Only the associated contract may call this. * @param account The account whose value to set. * @param value The new balance of the given account. - */ + */ function setBalanceOf(address account, uint value) external onlyAssociatedContract { balanceOf[account] = value; } - - - /* ========== MODIFIERS ========== */ - - modifier onlyAssociatedContract - { - require(msg.sender == associatedContract); - _; - } - - /* ========== EVENTS ========== */ - - event AssociatedContractUpdated(address _associatedContract); } diff --git a/deploy.py b/deploy.py index b7821b3913..502e6a45c3 100644 --- a/deploy.py +++ b/deploy.py @@ -1,77 +1,162 @@ -from utils.deployutils import attempt, compile_contracts, attempt_deploy, W3, mine_txs, UNIT, MASTER -from utils.testutils import ZERO_ADDRESS + +from web3 import Web3, HTTPProvider +from solc import compile_files +from utils.generalutils import TERMCOLORS +from deploy_settings import MASTER_ADDRESS, MASTER_KEY +# deploy_settings is a file that contains two strings, or however you want to decompile your private key + # Source files to compile from -SOLIDITY_SOURCES = ["contracts/Havven.sol", "contracts/EtherNomin.sol", +SOLIDITY_SOURCES = ["contracts/Havven.sol", "contracts/Nomin.sol", "contracts/Court.sol", "contracts/HavvenEscrow.sol", - "contracts/ExternStateProxyFeeToken.sol", "contracts/ExternStateProxyToken.sol", + "contracts/ExternStateFeeToken.sol", "contracts/DestructibleExternStateToken.sol", "contracts/Proxy.sol"] -OWNER = MASTER -ORACLE = MASTER -LIQUIDATION_BENEFICIARY = MASTER -INITIAL_ETH_PRICE = 1000 * UNIT + +BLOCKCHAIN_ADDRESS = "http://localhost:8545" +W3 = Web3(HTTPProvider(BLOCKCHAIN_ADDRESS)) +POLLING_INTERVAL = 0.1 +STATUS_ALIGN_SPACING = 6 + +# The number representing 1 in our contracts. +UNIT = 10**18 +ZERO_ADDRESS = "0x" + "0" * 40 + + +def attempt(function, func_args, init_string, func_kwargs=None, print_status=True, print_exception=True): + if func_kwargs is None: + func_kwargs = {} + + if print_status: + print(init_string, end="", flush=True) + + pad = (STATUS_ALIGN_SPACING - len(init_string)) % STATUS_ALIGN_SPACING + reset = TERMCOLORS.RESET + try: + result = function(*func_args, **func_kwargs) + if print_status: + print(f"{TERMCOLORS.GREEN}{' '*pad}Done!{reset}") + return result + except Exception as e: + if print_status: + print(f"{TERMCOLORS.RED}{' '*pad}Failed.{reset}") + if print_exception: + print(f"{TERMCOLORS.YELLOW}{TERMCOLORS.BOLD}ERROR:{reset} {TERMCOLORS.BOLD}{e}{reset}") + return None + + +def sign_and_mine_txs(from_acc, key, txs): + receipts = [] + for item in txs: + print("Sending transaction") + tx = item.buildTransaction({ + 'from': from_acc, + 'gasPrice': W3.toWei('5', 'gwei'), + 'nonce': W3.eth.getTransactionCount(from_acc, "pending") + }) + tx['gas'] = W3.eth.estimateGas(tx) + signed = W3.eth.account.signTransaction(tx, key) + txh = W3.eth.sendRawTransaction(signed.rawTransaction) + print("Transaction hash:", txh) + txn_receipt = W3.eth.waitForTransactionReceipt(txh) + print("Transaction accepted") + receipts.append(txn_receipt) + return receipts + + +def compile_contracts(files, remappings=None): + if remappings is None: + remappings = [] + contract_interfaces = {} + compiled = compile_files(files, import_remappings=remappings, optimize=True) + for key in compiled: + name = key.split(':')[-1] + contract_interfaces[name] = compiled[key] + return contract_interfaces + + +def attempt_deploy_signed(compiled_sol, contract_name, from_acc, key, constructor_args=None, gas=6000000): + if constructor_args is None: + constructor_args = [] + print("Deploying", contract_name) + contract_interface = compiled_sol[contract_name] + contract = W3.eth.contract(abi=contract_interface['abi'], bytecode=contract_interface['bin']) + const_f = contract.constructor(*constructor_args) + tx = const_f.buildTransaction({'from': from_acc, 'nonce': W3.eth.getTransactionCount(from_acc), 'gas': gas}) + tx['gasPrice'] = W3.toWei('5', 'gwei') + signed = W3.eth.account.signTransaction(tx, key) + txh = W3.eth.sendRawTransaction(signed.rawTransaction) + txn_receipt = W3.eth.waitForTransactionReceipt(txh) + address = txn_receipt.contractAddress + print("Deployed to", address) + contract.address = address + return contract, txn_receipt def deploy_havven(print_addresses=False): print("Deployment initiated.\n") compiled = attempt(compile_contracts, [SOLIDITY_SOURCES], "Compiling contracts... ") - - # Deploy contracts - havven_contract, hvn_txr = attempt_deploy(compiled, 'Havven', - MASTER, [ZERO_ADDRESS, OWNER]) - nomin_contract, nom_txr = attempt_deploy(compiled, 'EtherNomin', - MASTER, - [havven_contract.address, ORACLE, - LIQUIDATION_BENEFICIARY, - INITIAL_ETH_PRICE, OWNER, ZERO_ADDRESS]) - court_contract, court_txr = attempt_deploy(compiled, 'Court', - MASTER, - [havven_contract.address, nomin_contract.address, - OWNER]) - escrow_contract, escrow_txr = attempt_deploy(compiled, 'HavvenEscrow', - MASTER, - [OWNER, havven_contract.address]) - - # Wrap contracts with proxies - havven_proxy, _ = attempt_deploy(compiled, 'Proxy', - MASTER, [havven_contract.address, MASTER]) - - nomin_proxy, _ = attempt_deploy(compiled, 'Proxy', - MASTER, [nomin_contract.address, MASTER]) - txs = [havven_contract.functions.setProxy(havven_proxy.address).transact({'from': MASTER}), - nomin_contract.functions.setProxy(nomin_proxy.address).transact({'from': MASTER})] - - attempt(mine_txs, [txs], "Deploying Proxies...") - - proxy_havven = W3.eth.contract(address=havven_proxy.address, abi=compiled['Havven']['abi']) - proxy_nomin = W3.eth.contract(address=nomin_proxy.address, abi=compiled['EtherNomin']['abi']) + # + # # Deploy contracts + # havven_proxy, h_prox_txr = attempt_deploy_signed( + # compiled, 'Proxy', MASTER_ADDRESS, MASTER_KEY, [MASTER_ADDRESS] + # ) + # + # nomin_proxy, h_prox_txr = attempt_deploy_signed( + # compiled, 'Proxy', MASTER_ADDRESS, MASTER_KEY, [MASTER_ADDRESS] + # ) + # + # havven_contract, hvn_txr = attempt_deploy_signed( + # compiled, 'Havven', MASTER_ADDRESS, MASTER_KEY, + # [havven_proxy.address, ZERO_ADDRESS, MASTER_ADDRESS, MASTER_ADDRESS, UNIT // 2] + # ) + # nomin_contract, nom_txr = attempt_deploy_signed( + # compiled, 'Nomin', MASTER_ADDRESS, MASTER_KEY, + # [nomin_proxy.address, havven_contract.address, MASTER_ADDRESS, ZERO_ADDRESS] + # ) + # + # court_contract, court_txr = attempt_deploy_signed( + # compiled, 'Court', MASTER_ADDRESS, MASTER_KEY, + # [havven_contract.address, nomin_contract.address, MASTER_ADDRESS]) + # + # escrow_contract, escrow_txr = attempt_deploy_signed( + # compiled, 'HavvenEscrow', MASTER_ADDRESS, MASTER_KEY, [MASTER_ADDRESS, havven_contract.address] + # ) + + havven_proxy = W3.eth.contract(abi=compiled['Proxy']['abi'], address='0xEF630f892b69acb7Fd80a908f9ea4e84DE588e01') + nomin_proxy = W3.eth.contract(abi=compiled['Proxy']['abi'], address='0xE77c61cD53301EfB6d9361fa91f5Fb6cd10d2253') + havven_contract = W3.eth.contract(abi=compiled['Havven']['abi'], address='0xfC92FeBD60E6B4A28F959e5a833d0C16B46fe905') + nomin_contract = W3.eth.contract(abi=compiled['Nomin']['abi'], address='0x95537CdC53Ef318b97A31BF66A620C2f760c962B') + court_contract = W3.eth.contract(abi=compiled['Court']['abi'], address='0x0248f9f62e7613EFD3e10C09eeDd6153B2f2EAA2') + escrow_contract = W3.eth.contract(abi=compiled['HavvenEscrow']['abi'], address='0xA05Abe0Eba145E9A5b9B4D049772A3f92D45638e') # Hook up each of those contracts to each other - txs = [havven_contract.functions.setNomin(nomin_contract.address).transact({'from': MASTER}), - havven_contract.functions.setEscrow(escrow_contract.address).transact({'from': MASTER}), - nomin_contract.functions.setCourt(court_contract.address).transact({'from': MASTER})] - attempt(mine_txs, [txs], "Linking contracts... ") + sign_and_mine_txs(MASTER_ADDRESS, MASTER_KEY, [ + havven_proxy.functions.setTarget(havven_contract.address), + nomin_proxy.functions.setTarget(nomin_contract.address), + havven_contract.functions.setNomin(nomin_contract.address), + nomin_contract.functions.setCourt(court_contract.address), + nomin_contract.functions.setHavven(havven_contract.address), + havven_contract.functions.setEscrow(escrow_contract.address) + ]) print("\nDeployment complete.\n") if print_addresses: print("Addresses") print("========\n") - print(f"Havven: {havven_contract.address}") print(f"Havven Proxy: {havven_proxy.address}") - print(f"Nomin: {nomin_contract.address}") - print(f"Nomin Proxy: {nomin_proxy.address}") - print(f"Court: {court_contract.address}") - print(f"Escrow: {escrow_contract.address}") + print(f"Nomin Proxy: {nomin_proxy.address}") + print(f"Havven: {havven_contract.address}") + print(f"Nomin: {nomin_contract.address}") + print(f"Court: {court_contract.address}") + print(f"Escrow: {escrow_contract.address}") print() - return havven_contract, nomin_contract, havven_proxy, nomin_proxy, court_contract, escrow_contract, hvn_txr, nom_txr, court_txr + return havven_proxy, nomin_proxy, havven_contract, nomin_contract, court_contract, escrow_contract if __name__ == "__main__": deploy_havven(True) - print(f"Owner: {OWNER}") - print(f"Oracle: {ORACLE}") - print(f"Liquidation beneficiary: {LIQUIDATION_BENEFICIARY}") + print(f"Owner: {MASTER_ADDRESS}") diff --git a/run_tests.py b/run_tests.py old mode 100644 new mode 100755 index 8f1258e7b4..5e2b7bf113 --- a/run_tests.py +++ b/run_tests.py @@ -1,3 +1,5 @@ +#!/usr/local/bin/python3 + import sys import os import subprocess @@ -5,9 +7,8 @@ from unittest import TestSuite, TestLoader, TextTestRunner from utils.generalutils import load_test_settings, ganache_error_message - if __name__ == '__main__': - num_agents = "120" + num_agents = "150" eth_per_agent = "1000000000000" print("Launching ganache", end="", flush=True) @@ -38,7 +39,49 @@ print("Running test suite...\n") result = TextTestRunner(verbosity=2).run(test_suite) + from utils.deployutils import PERFORMANCE_DATA + + for i in PERFORMANCE_DATA: + for j in PERFORMANCE_DATA[i]: + vals = PERFORMANCE_DATA[i][j] + # (avg, min, max, calls) + PERFORMANCE_DATA[i][j] = (vals[0]//vals[1], vals[2], vals[3], vals[1]) + + PERFORMANCE_DATA['CONTRACT'] = {'METHOD': ['AVG_GAS', 'MIN_GAS', 'MAX_GAS', "CALLS"]} + num_fields = len(PERFORMANCE_DATA['CONTRACT']['METHOD']) + + # PRINT TABLE | CONTRACT | METHOD | AVG GAS | MIN GAS | MAX GAS | CALLS | + max_contract_name = max([len(i) for i in list(PERFORMANCE_DATA.keys())]) + max_method_name = max([max([len(str(i)) for i in PERFORMANCE_DATA[j].keys()]) for j in PERFORMANCE_DATA.keys()]) + max_gas_len = max([max([max([len(str(i)) for i in PERFORMANCE_DATA[k][j]]) for j in PERFORMANCE_DATA[k]]) for k in PERFORMANCE_DATA]) + + print("\nGas performance data") + current = 'CONTRACT' + remaining = sorted(list(PERFORMANCE_DATA.keys())) + remaining.pop(remaining.index('CONTRACT')) + + print('┌' + '─'*(2 + max_contract_name) + '┬' + + '─'*(2 + max_method_name) + '┬' + + ('─'*(2 + max_gas_len) + '┬')*(num_fields - 1) + '─'*(2 + max_gas_len) + '┐') + while True: + for method in sorted(PERFORMANCE_DATA[current].keys()): + vals = PERFORMANCE_DATA[current][method] + print('│ ' + current + ' '*(1 + max_contract_name - len(current)) + '│ ' + + str(method) + ' '*(1 + max_method_name - len(str(method))) + '│ ' + + ''.join([str(i) + ' ' * (1 + max_gas_len - len(str(i))) + '│ ' for i in vals]) + ) + if len(remaining) == 0: + break + print('├' + '─'*(2 + max_contract_name) + '┼' + + '─'*(2 + max_method_name) + '┼' + + ('─'*(2 + max_gas_len) + '┼')*(num_fields - 1) + '─'*(2 + max_gas_len) + '┤') + current = remaining.pop(0) + print('└' + '─'*(2 + max_contract_name) + '┴' + + '─'*(2 + max_method_name) + '┴' + + ('─'*(2 + max_gas_len) + '┴')*(num_fields - 1) + '─'*(2 + max_gas_len) + '┘') + process.terminate() + print("\nTesting complete.") sys.exit(0 if result.wasSuccessful() else 1) diff --git a/tests/__init__.py b/tests/__init__.py index bf36464b97..3d5b4753e8 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,15 +1,16 @@ import tests.test_Court -import tests.test_Deploy -import tests.test_ExternStateProxyToken -import tests.test_ExternStateProxyFeeToken -import tests.test_EtherNomin +import tests.test_ExternStateToken +import tests.test_FeeToken import tests.test_FeeCollection import tests.test_Havven import tests.test_HavvenEscrow +import tests.test_Issuance +import tests.test_IssuanceController +import tests.test_Pausable import tests.test_LimitedSetup +import tests.test_Nomin import tests.test_Owned import tests.test_Proxy import tests.test_SafeDecimalMath import tests.test_SelfDestructible import tests.test_TokenState -import tests.test_Upgrade diff --git a/tests/contract_interfaces/court_interface.py b/tests/contract_interfaces/court_interface.py new file mode 100644 index 0000000000..41aecfda3a --- /dev/null +++ b/tests/contract_interfaces/court_interface.py @@ -0,0 +1,110 @@ +from tests.contract_interfaces.safe_decimal_math_interface import SafeDecimalMathInterface +from tests.contract_interfaces.owned_interface import OwnedInterface +from utils.deployutils import mine_tx + + +class CourtInterface(SafeDecimalMathInterface, OwnedInterface): + def __init__(self, contract, name): + SafeDecimalMathInterface.__init__(self, contract, name) + OwnedInterface.__init__(self, contract, name) + + self.contract = contract + self.contract_name = name + + # Public variables + self.motionTarget = lambda index: self.contract.functions.motionTarget(index).call() + self.targetMotionID = lambda address: self.contract.functions.targetMotionID(address).call() + self.motionStartTime = lambda account: self.contract.functions.motionStartTime(account).call() + self.votesFor = lambda account: self.contract.functions.votesFor(account).call() + self.votesAgainst = lambda account: self.contract.functions.votesAgainst(account).call() + self.vote = lambda account, motionID: self.contract.functions.vote(account, motionID).call() + + # Setters + self.setMinStandingBalance = lambda sender, balance: mine_tx( + self.contract.functions.setMinStandingBalance(balance).transact({'from': sender}), "setMinStandingBalance", self.contract_name) + self.setVotingPeriod = lambda sender, duration: mine_tx( + self.contract.functions.setVotingPeriod(duration).transact({'from': sender}), "setVotingPeriod", self.contract_name) + self.setConfirmationPeriod = lambda sender, duration: mine_tx( + self.contract.functions.setConfirmationPeriod(duration).transact({'from': sender}), "setConfirmationPeriod", self.contract_name) + self.setRequiredParticipation = lambda sender, fraction: mine_tx( + self.contract.functions.setRequiredParticipation(fraction).transact({'from': sender}), "setRequiredParticipation", self.contract_name) + self.setRequiredMajority = lambda sender, fraction: mine_tx( + self.contract.functions.setRequiredMajority(fraction).transact({'from': sender}), "setRequiredMajority", self.contract_name) + + # Views + self.hasVoted = lambda sender, motionID: self.contract.functions.hasVoted(sender, motionID).call() + self.motionVoting = lambda target: self.contract.functions.motionVoting(target).call() + self.motionConfirming = lambda target: self.contract.functions.motionConfirming(target).call() + self.motionWaiting = lambda target: self.contract.functions.motionWaiting(target).call() + self.motionPasses = lambda target: self.contract.functions.motionPasses(target).call() + + # Mutators + self.beginMotion = lambda sender, target: mine_tx( + self.contract.functions.beginMotion(target).transact({'from': sender}), "beginMotion", self.contract_name) + self.voteFor = lambda sender, target: mine_tx( + self.contract.functions.voteFor(target).transact({'from': sender}), "voteFor", self.contract_name) + self.voteAgainst = lambda sender, target: mine_tx( + self.contract.functions.voteAgainst(target).transact({'from': sender}), "voteAgainst", self.contract_name) + self.cancelVote = lambda sender, target: mine_tx( + self.contract.functions.cancelVote(target).transact({'from': sender}), "cancelVote", self.contract_name) + self.closeMotion = lambda sender, target: mine_tx( + self.contract.functions.closeMotion(target).transact({'from': sender}), "closeMotion", self.contract_name) + + # Owner only + self.approveMotion = lambda sender, target: mine_tx( + self.contract.functions.approveMotion(target).transact({'from': sender}), "approveMotion", self.contract_name) + self.vetoMotion = lambda sender, target: mine_tx( + self.contract.functions.vetoMotion(target).transact({'from': sender}), "vetoMotion", self.contract_name) + + +class PublicCourtInterface(CourtInterface): + def __init__(self, contract, name): + CourtInterface.__init__(self, contract, name) + self.contract = contract + self.contract_name = name + + self.getHavven = lambda: self.contract.functions._havven().call() + self.getNomin = lambda: self.contract.functions._nomin().call() + self.minStandingBalance = lambda: self.contract.functions.minStandingBalance().call() + self.votingPeriod = lambda: self.contract.functions.votingPeriod().call() + self.MIN_VOTING_PERIOD = lambda: self.contract.functions._MIN_VOTING_PERIOD().call() + self.MAX_VOTING_PERIOD = lambda: self.contract.functions._MAX_VOTING_PERIOD().call() + self.confirmationPeriod = lambda: self.contract.functions.confirmationPeriod().call() + self.MIN_CONFIRMATION_PERIOD = lambda: self.contract.functions._MIN_CONFIRMATION_PERIOD().call() + self.MAX_CONFIRMATION_PERIOD = lambda: self.contract.functions._MAX_CONFIRMATION_PERIOD().call() + self.requiredParticipation = lambda: self.contract.functions.requiredParticipation().call() + self.MIN_REQUIRED_PARTICIPATION = lambda: self.contract.functions._MIN_REQUIRED_PARTICIPATION().call() + self.requiredMajority = lambda: self.contract.functions.requiredMajority().call() + self.MIN_REQUIRED_MAJORITY = lambda: self.contract.functions._MIN_REQUIRED_MAJORITY().call() + self.voteWeight = lambda account, motionID: self.contract.functions._voteWeight(account, motionID).call() + self.nextMotionID = lambda: self.contract.functions._nextMotionID().call() + + # Internal + self.setupVote = lambda sender, target: mine_tx( + self.contract.functions.publicSetupVote(target).transact({'from': sender}), "setupVote", self.contract_name) + + self.setHavven = lambda sender, addr: mine_tx( + self.contract.functions.setHavven(addr).transact({'from': sender}), "setHavven", self.contract_name) + self.setNomin = lambda sender, addr: mine_tx( + self.contract.functions.setNomin(addr).transact({'from': sender}), "setNomin", self.contract_name) + + +class FakeCourtInterface: + def __init__(self, contract, name): + self.contract = contract + self.contract_name = name + + self.setNomin = lambda sender, new_nomin: mine_tx( + self.contract.functions.setNomin(new_nomin).transact({'from': sender}), "setNomin", "FakeCourt") + self.setConfirming = lambda sender, target, status: mine_tx( + self.contract.functions.setConfirming(target, status).transact({'from': sender}), "setConfirming", + "FakeCourt") + self.setVotePasses = lambda sender, target, status: mine_tx( + self.contract.functions.setVotePasses(target, status).transact({'from': sender}), "setVotePasses", + "FakeCourt") + self.setTargetMotionID = lambda sender, target, motionID: mine_tx( + self.contract.functions.setTargetMotionID(target, motionID).transact({'from': sender}), "setTargetMotionID", + "FakeCourt") + self.freezeAndConfiscate = lambda sender, target: mine_tx( + self.contract.functions.freezeAndConfiscate(target).transact({'from': sender}), "freezeAndConfiscate", + "FakeCourt") \ No newline at end of file diff --git a/tests/contract_interfaces/extern_state_token_interface.py b/tests/contract_interfaces/extern_state_token_interface.py new file mode 100644 index 0000000000..9e32a2b34c --- /dev/null +++ b/tests/contract_interfaces/extern_state_token_interface.py @@ -0,0 +1,27 @@ +from tests.contract_interfaces.safe_decimal_math_interface import SafeDecimalMathInterface +from tests.contract_interfaces.self_destructible_interface import SelfDestructibleInterface +from utils.deployutils import mine_tx + + +class ExternStateTokenInterface(SafeDecimalMathInterface, SelfDestructibleInterface): + def __init__(self, contract, name): + SafeDecimalMathInterface.__init__(self, contract, name) + SelfDestructibleInterface.__init__(self, contract, name) + self.contract = contract + self.contract_name = name + + self.totalSupply = lambda: self.contract.functions.totalSupply().call() + self.tokenState = lambda: self.contract.functions.tokenState().call() + self.name = lambda: self.contract.functions.name().call() + self.symbol = lambda: self.contract.functions.symbol().call() + self.balanceOf = lambda account: self.contract.functions.balanceOf(account).call() + self.allowance = lambda account, spender: self.contract.functions.allowance(account, spender).call() + + self.setTokenState = lambda sender, new_state: mine_tx( + self.contract.functions.setTokenState(new_state).transact({'from': sender}), "setTokenState", self.contract_name) + self.transfer = lambda sender, to, value: mine_tx( + self.contract.functions.transfer(to, value).transact({'from': sender}), "transfer", self.contract_name) + self.approve = lambda sender, spender, value: mine_tx( + self.contract.functions.approve(spender, value).transact({'from': sender}), "approve", self.contract_name) + self.transferFrom = lambda sender, frm, to, value: mine_tx( + self.contract.functions.transferFrom(frm, to, value).transact({'from': sender}), "transferFrom", self.contract_name) diff --git a/tests/contract_interfaces/fee_token_interface.py b/tests/contract_interfaces/fee_token_interface.py new file mode 100644 index 0000000000..4094cda8d4 --- /dev/null +++ b/tests/contract_interfaces/fee_token_interface.py @@ -0,0 +1,40 @@ +from tests.contract_interfaces.extern_state_token_interface import ExternStateTokenInterface +from utils.deployutils import mine_tx + + +class FeeTokenInterface(ExternStateTokenInterface): + def __init__(self, contract, name): + ExternStateTokenInterface.__init__(self, contract, name) + + self.contract = contract + self.contract_name = name + + self.feePool = lambda: self.contract.functions.feePool().call() + self.feeAuthority = lambda: self.contract.functions.feeAuthority().call() + self.transferFeeRate = lambda: self.contract.functions.transferFeeRate().call() + + self.transferFeeIncurred = lambda value: self.contract.functions.transferFeeIncurred(value).call() + self.transferPlusFee = lambda value: self.contract.functions.transferPlusFee(value).call() + self.amountReceived = lambda value: self.contract.functions.amountReceived(value).call() + + self.setTransferFeeRate = lambda sender, new_fee_rate: mine_tx( + self.contract.functions.setTransferFeeRate(new_fee_rate).transact({'from': sender}), "setTransferFeeRate", self.contract_name) + self.setFeeAuthority = lambda sender, new_fee_authority: mine_tx( + self.contract.functions.setFeeAuthority(new_fee_authority).transact({'from': sender}), "setFeeAuthority", self.contract_name) + self.transferSenderPaysFee = lambda sender, to, value: mine_tx( + self.contract.functions.transferSenderPaysFee(to, value).transact({'from': sender}), "transferSenderPaysFee", self.contract_name) + self.transferFromSenderPaysFee = lambda sender, frm, to, value: mine_tx( + self.contract.functions.transferFromSenderPaysFee(frm, to, value).transact({'from': sender}), "transferFromSenderPaysFee", self.contract_name) + self.withdrawFees = lambda sender, account, value: mine_tx( + self.contract.functions.withdrawFees(account, value).transact({'from': sender}), "withdrawFees", self.contract_name) + self.donateToFeePool = lambda sender, value: mine_tx( + self.contract.functions.donateToFeePool(value).transact({'from': sender}), "donateToFeePool", self.contract_name) + +class PublicFeeTokenInterface(FeeTokenInterface): + def __init__(self, contract, name): + FeeTokenInterface.__init__(self, contract, name) + + self.clearTokens = lambda sender, address: mine_tx( + self.contract.functions.clearTokens(address).transact({"from": sender}), "clearTokens", self.contract_name) + self.giveTokens = lambda sender, address, value: mine_tx( + self.contract.functions.giveTokens(address, value).transact({"from": sender}), "giveTokens", self.contract_name) \ No newline at end of file diff --git a/tests/contract_interfaces/havven_escrow_interface.py b/tests/contract_interfaces/havven_escrow_interface.py new file mode 100644 index 0000000000..520c5fe91c --- /dev/null +++ b/tests/contract_interfaces/havven_escrow_interface.py @@ -0,0 +1,49 @@ +from tests.contract_interfaces.safe_decimal_math_interface import SafeDecimalMathInterface +from tests.contract_interfaces.owned_interface import OwnedInterface +from tests.contract_interfaces.limited_setup_interface import LimitedSetupInterface +from utils.deployutils import mine_tx + + +class HavvenEscrowInterface(SafeDecimalMathInterface, OwnedInterface, LimitedSetupInterface): + def __init__(self, contract, name): + SafeDecimalMathInterface.__init__(self, contract, name) + OwnedInterface.__init__(self, contract, name) + LimitedSetupInterface.__init__(self, contract, name) + + self.contract = contract + self.contract_name = name + + self.havven = lambda: self.contract.functions.havven().call() + self.vestingSchedules = lambda account, index, i: self.contract.functions.vestingSchedules(account, index, i).call() + self.numVestingEntries = lambda account: self.contract.functions.numVestingEntries(account).call() + self.getVestingScheduleEntry = lambda account, index: self.contract.functions.getVestingScheduleEntry(account, index).call() + self.getVestingTime = lambda account, index: self.contract.functions.getVestingTime(account, index).call() + self.getVestingQuantity = lambda account, index: self.contract.functions.getVestingQuantity(account, index).call() + self.totalVestedAccountBalance = lambda account: self.contract.functions.totalVestedAccountBalance(account).call() + self.totalVestedBalance = lambda: self.contract.functions.totalVestedBalance().call() + self.getNextVestingIndex = lambda account: self.contract.functions.getNextVestingIndex(account).call() + self.getNextVestingEntry = lambda account: self.contract.functions.getNextVestingEntry(account).call() + self.getNextVestingTime = lambda account: self.contract.functions.getNextVestingTime(account).call() + self.getNextVestingQuantity = lambda account: self.contract.functions.getNextVestingQuantity(account).call() + self.balanceOf = lambda account: self.contract.functions.balanceOf(account).call() + + self.setHavven = lambda sender, account: mine_tx( + self.contract.functions.setHavven(account).transact({'from': sender}), "setHavven", self.contract_name) + self.purgeAccount = lambda sender, account: mine_tx( + self.contract.functions.purgeAccount(account).transact({'from': sender}), "purgeAccount", self.contract_name) + self.withdrawHavvens = lambda sender, quantity: mine_tx( + self.contract.functions.withdrawHavvens(quantity).transact({'from': sender}), "withdrawHavvens", self.contract_name) + self.appendVestingEntry = lambda sender, account, time, quantity: mine_tx( + self.contract.functions.appendVestingEntry(account, time, quantity).transact({'from': sender}), "appendVestingEntry", self.contract_name) + self.addVestingSchedule = lambda sender, account, times, quantities: mine_tx( + self.contract.functions.addVestingSchedule(account, times, quantities).transact({'from': sender}), "addVestingSchedule", self.contract_name) + self.vest = lambda sender: mine_tx(self.contract.functions.vest().transact({'from': sender}), "vest", self.contract_name) + + +class PublicHavvenEscrowInterface(HavvenEscrowInterface): + def __init__(self, contract, name): + HavvenEscrowInterface.__init__(self, contract, name) + self.contract = contract + self.contract_name = name + self.addRegularVestingSchedule = lambda sender, account, time, quantity, periods: mine_tx( + self.contract.functions.addRegularVestingSchedule(account, time, quantity, periods).transact({'from': sender}), "addRegularVestingSchedule", self.contract_name) diff --git a/tests/contract_interfaces/havven_interface.py b/tests/contract_interfaces/havven_interface.py new file mode 100644 index 0000000000..c836c78b41 --- /dev/null +++ b/tests/contract_interfaces/havven_interface.py @@ -0,0 +1,89 @@ +from tests.contract_interfaces.extern_state_token_interface import ExternStateTokenInterface +from utils.deployutils import mine_tx + + +class HavvenInterface(ExternStateTokenInterface): + def __init__(self, contract, name): + ExternStateTokenInterface.__init__(self, contract, name) + + self.contract = contract + self.contract_name = name + + # HAVVEN + + # getters + self.feePeriodStartTime = lambda: self.contract.functions.feePeriodStartTime().call() + self.lastFeePeriodStartTime = lambda: self.contract.functions.lastFeePeriodStartTime().call() + self.feePeriodDuration = lambda: self.contract.functions.feePeriodDuration().call() + self.lastFeesCollected = lambda: self.contract.functions.lastFeesCollected().call() + self.nomin = lambda: self.contract.functions.nomin().call() + self.escrow = lambda: self.contract.functions.escrow().call() + self.oracle = lambda: self.contract.functions.oracle().call() + self.price = lambda: self.contract.functions.price().call() + self.lastPriceUpdateTime = lambda: self.contract.functions.lastPriceUpdateTime().call() + self.priceStalePeriod = lambda: self.contract.functions.priceStalePeriod().call() + self.issuanceRatio = lambda: self.contract.functions.issuanceRatio().call() + self.priceIsStale = lambda: self.contract.functions.priceIsStale().call() + + self.hasWithdrawnFees = lambda acc: self.contract.functions.hasWithdrawnFees(acc).call() + self.isIssuer = lambda acc: self.contract.functions.isIssuer(acc).call() + self.nominsIssued = lambda acc: self.contract.functions.nominsIssued(acc).call() + self.issuanceData = lambda acc: self.contract.functions.issuanceData(acc).call() + self.totalIssuanceData = lambda: self.contract.functions.totalIssuanceData().call() + self.issuanceCurrentBalanceSum = lambda acc: self.contract.functions.issuanceCurrentBalanceSum(acc).call() + self.issuanceLastAverageBalance = lambda acc: self.contract.functions.issuanceLastAverageBalance(acc).call() + self.issuanceLastModified = lambda acc: self.contract.functions.issuanceLastModified(acc).call() + self.totalIssuanceCurrentBalanceSum = lambda: self.contract.functions.totalIssuanceCurrentBalanceSum().call() + self.totalIssuanceLastAverageBalance = lambda: self.contract.functions.totalIssuanceLastAverageBalance().call() + self.totalIssuanceLastModified = lambda: self.contract.functions.totalIssuanceLastModified().call() + self.availableHavvens = lambda acc: self.contract.functions.availableHavvens(acc).call() + self.lockedHavvens = lambda acc: self.contract.functions.lockedHavvens(acc).call() + self.maxIssuableNomins = lambda acc: self.contract.functions.maxIssuableNomins(acc).call() + self.remainingIssuableNomins = lambda acc: self.contract.functions.remainingIssuableNomins(acc).call() + + # utility function + self.havValue = lambda havWei: self.contract.functions.havValue(havWei).call() + + # mutable functions + self.setNomin = lambda sender, addr: mine_tx(self.contract.functions.setNomin(addr).transact({'from': sender}), "setNomin", self.contract_name) + self.setEscrow = lambda sender, addr: mine_tx(self.contract.functions.setEscrow(addr).transact({'from': sender}), "setEscrow", self.contract_name) + self.setFeePeriodDuration = lambda sender, duration: mine_tx(self.contract.functions.setFeePeriodDuration(duration).transact({'from': sender}), "setFeePeriodDuration", self.contract_name) + self.setOracle = lambda sender, addr: mine_tx(self.contract.functions.setOracle(addr).transact({'from': sender}), "setOracle", self.contract_name) + self.setIssuanceRatio = lambda sender, val: mine_tx(self.contract.functions.setIssuanceRatio(val).transact({'from': sender}), "setIssuanceRatio", self.contract_name) + self.setPriceStalePeriod = lambda sender, val: mine_tx(self.contract.functions.setPriceStalePeriod(val).transact({'from': sender}), "setPriceStalePeriod", self.contract_name) + self.endow = lambda sender, to, val: mine_tx(self.contract.functions.endow(to, val).transact({'from': sender}), "endow", self.contract_name) + self.setIssuer = lambda sender, acc, val: mine_tx(self.contract.functions.setIssuer(acc, val).transact({'from': sender}), "setIssuer", self.contract_name) + self.transfer = lambda sender, to, val: mine_tx(self.contract.functions.transfer(to, val).transact({'from': sender}), "transfer", self.contract_name) + self.transferFrom = lambda sender, frm, to, val: mine_tx(self.contract.functions.transferFrom(frm, to, val).transact({'from': sender}), "transferFrom", self.contract_name) + self.withdrawFees = lambda sender: mine_tx(self.contract.functions.withdrawFees().transact({'from': sender}), "withdrawFees", self.contract_name) + self.recomputeLastAverageBalance = lambda sender, acc: mine_tx(self.contract.functions.recomputeLastAverageBalance(acc).transact({'from': sender}), "recomputeLastAverageBalance", self.contract_name) + self.rolloverFeePeriodIfElapsed = lambda sender: mine_tx(self.contract.functions.rolloverFeePeriodIfElapsed().transact({'from': sender}), "rolloverFeePeriodIfElapsed", self.contract_name) + self.issueMaxNomins = lambda sender: mine_tx(self.contract.functions.issueMaxNomins().transact({'from': sender}), "issueMaxNomins", self.contract_name) + self.issueNomins = lambda sender, amt: mine_tx(self.contract.functions.issueNomins(amt).transact({'from': sender}), "issueNomins", self.contract_name) + self.burnNomins = lambda sender, amt: mine_tx(self.contract.functions.burnNomins(amt).transact({'from': sender}), "burnNomins", self.contract_name) + self.updatePrice = lambda sender, price, time: mine_tx(self.contract.functions.updatePrice(price, time).transact({'from': sender}), "updatePrice", self.contract_name) + + @staticmethod + def issuance_data_current_balance_sum(issuance_data): + return balance_data[0] + + @staticmethod + def issuance_data_last_average_balance(issuance_data): + return balance_data[1] + + @staticmethod + def issuance_data_last_modified(issuance_data): + return balance_data[2] + + +class PublicHavvenInterface(HavvenInterface): + def __init__(self, contract, name): + HavvenInterface.__init__(self, contract, name) + + self.contract = contract + self.contract_name = name + + self.MIN_FEE_PERIOD_DURATION = lambda: self.contract.functions.MIN_FEE_PERIOD_DURATION().call() + self.MAX_FEE_PERIOD_DURATION = lambda: self.contract.functions.MAX_FEE_PERIOD_DURATION().call() + + self.currentTime = lambda: self.contract.functions.currentTime().call() diff --git a/tests/contract_interfaces/issuanceController_interface.py b/tests/contract_interfaces/issuanceController_interface.py new file mode 100644 index 0000000000..ffaa3326bb --- /dev/null +++ b/tests/contract_interfaces/issuanceController_interface.py @@ -0,0 +1,58 @@ +from utils.deployutils import mine_tx + +class IssuanceControllerInterface(): + def __init__(self, contract, name): + self.contract = contract + self.contract_name = name + + self.priceStalePeriod = lambda: self.contract.functions.priceStalePeriod().call() + self.fundsWallet = lambda: self.contract.functions.fundsWallet().call() + self.havven = lambda: self.contract.functions.havven().call() + self.nomin = lambda: self.contract.functions.nomin().call() + self.oracle = lambda: self.contract.functions.oracle().call() + self.owner = lambda: self.contract.functions.owner().call() + self.usdToEthPrice = lambda: self.contract.functions.usdToEthPrice().call() + self.usdToHavPrice = lambda: self.contract.functions.usdToHavPrice().call() + self.lastPriceUpdateTime = lambda: self.contract.functions.lastPriceUpdateTime().call() + self.selfDestructDelay = lambda: self.contract.functions.selfDestructDelay().call() + self.selfDestructBeneficiary = lambda: self.contract.functions.selfDestructBeneficiary().call() + self.priceStalePeriod = lambda: self.contract.functions.priceStalePeriod().call() + self.havvensReceivedForNomins = lambda amount: self.contract.functions.havvensReceivedForNomins(amount).call() + self.nominsReceivedForEther = lambda amount: self.contract.functions.nominsReceivedForEther(amount).call() + + self.setOracle = lambda sender, newAddress: mine_tx( + self.contract.functions.setOracle(newAddress).transact({'from': sender}), "setOracle", self.contract_name + ) + self.setHavven = lambda sender, newAddress: mine_tx( + self.contract.functions.setHavven(newAddress).transact({'from': sender}), "setHavven", self.contract_name + ) + self.setNomin = lambda sender, newAddress: mine_tx( + self.contract.functions.setNomin(newAddress).transact({'from': sender}), "setNomin", self.contract_name + ) + self.setFundsWallet = lambda sender, newAddress: mine_tx( + self.contract.functions.setFundsWallet(newAddress).transact({'from': sender}), "setFundsWallet", self.contract_name + ) + self.setPriceStalePeriod = lambda sender, newPriceStalePeriod: mine_tx( + self.contract.functions.setPriceStalePeriod(newPriceStalePeriod).transact({'from': sender}), "setPriceStalePeriod", self.contract_name + ) + self.updatePrices = lambda sender, newEthPrice, newHavPrice, timeSent: mine_tx( + self.contract.functions.updatePrices(newEthPrice, newHavPrice, timeSent).transact({'from': sender}), "updatePrices", self.contract_name + ) + self.exchangeEtherForNomins = lambda sender, value: mine_tx( + self.contract.functions.exchangeEtherForNomins().transact({'from': sender, 'value': value}), "exchangeEtherForNomins", self.contract_name + ) + self.exchangeNominsForHavvens = lambda sender, value: mine_tx( + self.contract.functions.exchangeNominsForHavvens(value).transact({'from': sender}), "exchangeNominsForHavvens", self.contract_name + ) + self.withdrawHavvens = lambda sender, value: mine_tx( + self.contract.functions.withdrawHavvens(value).transact({'from': sender}), "withdrawHavvens", self.contract_name + ) + self.withdrawNomins = lambda sender, value: mine_tx( + self.contract.functions.withdrawNomins(value).transact({'from': sender}), "withdrawNomins", self.contract_name + ) + # TODO: Add inherited interface here instead + self.setPaused = lambda sender, paused: mine_tx( + self.contract.functions.setPaused(paused).transact({'from': sender}), "setPaused", self.contract_name + ) + + diff --git a/tests/contract_interfaces/limited_setup_interface.py b/tests/contract_interfaces/limited_setup_interface.py new file mode 100644 index 0000000000..a76525b324 --- /dev/null +++ b/tests/contract_interfaces/limited_setup_interface.py @@ -0,0 +1,9 @@ +from utils.deployutils import mine_tx + + +class LimitedSetupInterface: + def __init__(self, contract, name): + self.contract = contract + self.contract_name = name + + # Limited setup is all private... diff --git a/tests/contract_interfaces/nomin_interface.py b/tests/contract_interfaces/nomin_interface.py new file mode 100644 index 0000000000..a1bf5ea799 --- /dev/null +++ b/tests/contract_interfaces/nomin_interface.py @@ -0,0 +1,70 @@ +from tests.contract_interfaces.fee_token_interface import FeeTokenInterface +from utils.deployutils import mine_tx + + +class NominInterface(FeeTokenInterface): + def __init__(self, contract, name): + FeeTokenInterface.__init__(self, contract, name) + self.contract = contract + self.contract_name = name + + self.court = lambda: self.contract.functions.court().call() + self.havven = lambda: self.contract.functions.havven().call() + self.frozen = lambda address: self.contract.functions.frozen(address).call() + + self.setCourt = lambda sender, address: mine_tx( + self.contract.functions.setCourt(address).transact({'from': sender}), "setCourt", self.contract_name) + self.setHavven = lambda sender, address: mine_tx( + self.contract.functions.setHavven(address).transact({'from': sender}), "setHavven", self.contract_name) + + self.transferPlusFee = lambda value: self.contract.functions.transferPlusFee(value).call() + self.transferFeeIncurred = lambda value: self.contract.functions.transferFeeIncurred(value).call() + self.transfer = lambda sender, recipient, value: mine_tx( + self.contract.functions.transfer(recipient, value).transact({'from': sender}), "transfer", self.contract_name) + self.transferFrom = lambda sender, frm, to, value: mine_tx( + self.contract.functions.transferFrom(frm, to, value).transact({'from': sender}), "transferFrom", self.contract_name) + self.transferSenderPaysFee = lambda sender, recipient, value: mine_tx( + self.contract.functions.transferSenderPaysFee(recipient, value).transact({'from': sender}), "transferSenderPaysFee", self.contract_name) + self.transferFromSenderPaysFee = lambda sender, frm, to, value: mine_tx( + self.contract.functions.transferFromSenderPaysFee(frm, to, value).transact({'from': sender}), "transferFromSenderPaysFee", self.contract_name) + + self.approve = lambda sender, spender, value: mine_tx( + self.contract.functions.approve(spender, value).transact({'from': sender}), "approve", self.contract_name) + + # onlyCourt + self.freezeAndConfiscate = lambda sender, target: mine_tx( + self.contract.functions.freezeAndConfiscate(target).transact({'from': sender}), "freezeAndConfiscate", self.contract_name) + # onlyOwner + self.unfreezeAccount = lambda sender, target: mine_tx( + self.contract.functions.unfreezeAccount(target).transact({'from': sender}), "unfreezeAccount", self.contract_name) + + # onlyHavven + self.burn = lambda sender, target, amount: mine_tx( + self.contract.functions.burn(target, amount).transact({'from': sender}), "burn", self.contract_name) + self.issue = lambda sender, target, amount: mine_tx( + self.contract.functions.issue(target, amount).transact({'from': sender}), "issue", self.contract_name) + + +class PublicNominInterface(NominInterface): + def __init__(self, contract, name): + NominInterface.__init__(self, contract, name) + self.contract = contract + self.contract_name = name + + self.debugEmptyFeePool = lambda sender: mine_tx( + self.contract.functions.debugEmptyFeePool().transact({'from': sender}), "debugEmptyFeePool", self.contract_name) + self.debugFreezeAccount = lambda sender, target: mine_tx( + self.contract.functions.debugFreezeAccount(target).transact({'from': sender}), "debugFreezeAccount", self.contract_name) + + self.giveNomins = lambda sender, target, amount: mine_tx( + self.contract.functions.giveNomins(target, amount).transact({'from': sender}), "giveNomins", self.contract_name) + self.clearNomins = lambda sender, target: mine_tx( + self.contract.functions.clearNomins(target).transact({'from': sender}), "clearNomins", self.contract_name) + + self.generateFees = lambda sender, amt: mine_tx( + self.contract.functions.generateFees(amt).transact({'from': sender}), "generateFees", self.contract_name) + + self.publicBurn = lambda sender, target, amount: mine_tx( + self.contract.functions.publicBurn(target, amount).transact({'from': sender}), "publicBurn", self.contract_name) + self.publicIssue = lambda sender, target, amount: mine_tx( + self.contract.functions.publicIssue(target, amount).transact({'from': sender}), "publicIssue", self.contract_name) diff --git a/tests/contract_interfaces/owned_interface.py b/tests/contract_interfaces/owned_interface.py new file mode 100644 index 0000000000..e3f34f0c21 --- /dev/null +++ b/tests/contract_interfaces/owned_interface.py @@ -0,0 +1,15 @@ +from utils.deployutils import mine_tx + + +class OwnedInterface: + def __init__(self, contract, name): + self.contract = contract + self.contract_name = name + + self.owner = lambda: self.contract.functions.owner().call() + self.nominatedOwner = lambda: self.contract.functions.nominatedOwner().call() + + self.nominateNewOwner = lambda sender, addr: mine_tx( + self.contract.functions.nominateNewOwner(addr).transact({'from': sender}), "nominateNewOwner", self.contract_name) + self.acceptOwnership = lambda sender: mine_tx( + self.contract.functions.acceptOwnership().transact({'from': sender}), "acceptOwnership", self.contract_name) diff --git a/tests/contract_interfaces/pausable_interface.py b/tests/contract_interfaces/pausable_interface.py new file mode 100644 index 0000000000..ef2c3e9def --- /dev/null +++ b/tests/contract_interfaces/pausable_interface.py @@ -0,0 +1,19 @@ +from utils.deployutils import mine_tx + +class PausableInterface(): + def __init__(self, contract, name): + self.contract = contract + self.contract_name = name + + self.owner = lambda: self.contract.functions.owner().call() + self.paused = lambda: self.contract.functions.paused().call() + self.lastPauseTime = lambda: self.contract.functions.lastPauseTime().call() + self.getSomeValue = lambda: self.contract.functions.someValue().call() + + self.setPaused = lambda sender, paused: mine_tx( + self.contract.functions.setPaused(paused).transact({'from': sender}), "setPaused", self.contract_name + ) + self.setSomeValue = lambda sender, someValue: mine_tx( + self.contract.functions.setSomeValue(someValue).transact({'from': sender}), "setSomeValue", self.contract_name + ) + diff --git a/tests/contract_interfaces/proxy_interface.py b/tests/contract_interfaces/proxy_interface.py new file mode 100644 index 0000000000..ac8d3e3a0a --- /dev/null +++ b/tests/contract_interfaces/proxy_interface.py @@ -0,0 +1,16 @@ +from utils.deployutils import mine_tx +from tests.contract_interfaces.owned_interface import OwnedInterface + + +class ProxyInterface(OwnedInterface): + def __init__(self, contract, name): + OwnedInterface.__init__(self, contract, name) + self.contract = contract + self.contract_name = name + + self.target = lambda: self.contract.functions.target().call() + self.useDELEGATECALL = lambda: self.contract.functions.useDELEGATECALL().call() + + self.setTarget = lambda sender, addr: mine_tx( + self.contract.functions.setTarget(addr).transact({'from': sender}), "setTarget", self.contract_name) + diff --git a/tests/contract_interfaces/safe_decimal_math_interface.py b/tests/contract_interfaces/safe_decimal_math_interface.py new file mode 100644 index 0000000000..86ae40b543 --- /dev/null +++ b/tests/contract_interfaces/safe_decimal_math_interface.py @@ -0,0 +1,20 @@ + +class SafeDecimalMathInterface: + def __init__(self, contract, name): + self.contract = contract + self.contract_name = name + + self.decimals = lambda: self.contract.functions.decimals().call() + self.UNIT = lambda: self.contract.functions.UNIT().call() + + self.addIsSafe = lambda x, y: self.contract.functions.pubAddIsSafe(x, y).call() + self.safeAdd = lambda x, y: self.contract.functions.pubSafeAdd(x, y).call() + self.subIsSafe = lambda x, y: self.contract.functions.pubSubIsSafe(x, y).call() + self.safeSub = lambda x, y: self.contract.functions.pubSafeSub(x, y).call() + self.mulIsSafe = lambda x, y: self.contract.functions.pubMulIsSafe(x, y).call() + self.safeMul = lambda x, y: self.contract.functions.pubSafeMul(x, y).call() + self.safeMul_dec = lambda x, y: self.contract.functions.pubSafeMul_dec(x, y).call() + self.divIsSafe = lambda x, y: self.contract.functions.pubDivIsSafe(x, y).call() + self.safeDiv = lambda x, y: self.contract.functions.pubSafeDiv(x, y).call() + self.safeDiv_dec = lambda x, y: self.contract.functions.pubSafeDiv_dec(x, y).call() + self.intToDec = lambda i: self.contract.functions.pubIntToDec(i).call() diff --git a/tests/contract_interfaces/self_destructible_interface.py b/tests/contract_interfaces/self_destructible_interface.py new file mode 100644 index 0000000000..11e06d21fa --- /dev/null +++ b/tests/contract_interfaces/self_destructible_interface.py @@ -0,0 +1,23 @@ +from tests.contract_interfaces.owned_interface import OwnedInterface +from utils.deployutils import mine_tx + + +class SelfDestructibleInterface(OwnedInterface): + def __init__(self, contract, name): + OwnedInterface.__init__(self, contract, name) + self.contract = contract + self.contract_name = name + + self.initiationTime = lambda: self.contract.functions.initiationTime().call() + self.selfDestructBeneficiary = lambda: self.contract.functions.selfDestructBeneficiary().call() + self.selfDestructInitiated = lambda: self.contract.functions.selfDestructInitiated().call() + self.SELFDESTRUCT_DELAY = lambda: self.contract.functions.SELFDESTRUCT_DELAY().call() + + self.setSelfDestructBeneficiary = lambda sender, beneficiary: mine_tx( + self.contract.functions.setSelfDestructBeneficiary(beneficiary).transact({'from': sender}), "setSelfDestructBeneficiary", self.contract_name) + self.initiateSelfDestruct = lambda sender: mine_tx( + self.contract.functions.initiateSelfDestruct().transact({'from': sender}), "initiateSelfDestruct", self.contract_name) + self.terminateSelfDestruct = lambda sender: mine_tx( + self.contract.functions.terminateSelfDestruct().transact({'from': sender}), "terminateSelfDestruct", self.contract_name) + self.selfDestruct = lambda sender: mine_tx( + self.contract.functions.selfDestruct().transact({'from': sender}), "selfDestruct", self.contract_name) diff --git a/tests/contract_interfaces/state_interface.py b/tests/contract_interfaces/state_interface.py new file mode 100644 index 0000000000..fc2cf1df49 --- /dev/null +++ b/tests/contract_interfaces/state_interface.py @@ -0,0 +1,14 @@ +from tests.contract_interfaces.owned_interface import OwnedInterface +from utils.deployutils import mine_tx + + +class StateInterface(OwnedInterface): + def __init__(self, contract, name): + OwnedInterface.__init__(self, contract, name) + + self.contract = contract + self.contract_name = name + + self.associatedContract = lambda: self.contract.functions.associatedContract().call() + + self.setAssociatedContract = lambda sender, addr: mine_tx(self.contract.functions.setAssociatedContract(addr).transact({'from': sender}), "setAssociatedContract", self.contract_name) \ No newline at end of file diff --git a/tests/contract_interfaces/token_state_interface.py b/tests/contract_interfaces/token_state_interface.py new file mode 100644 index 0000000000..c96e4cf5e7 --- /dev/null +++ b/tests/contract_interfaces/token_state_interface.py @@ -0,0 +1,17 @@ +from tests.contract_interfaces.state_interface import StateInterface +from utils.deployutils import mine_tx + + +class TokenStateInterface(StateInterface): + def __init__(self, contract, name): + StateInterface.__init__(self, contract, name) + self.contract = contract + self.contract_name = name + + self.balanceOf = lambda acc: self.contract.functions.balanceOf(acc).call() + self.allowance = lambda frm, to: self.contract.functions.allowance(frm, to).call() + + self.setAllowance = lambda sender, token_owner, spender, value: mine_tx( + self.contract.functions.setAllowance(token_owner, spender, value).transact({'from': sender}), "setAllowance", self.contract_name) + self.setBalanceOf = lambda sender, account, value: mine_tx( + self.contract.functions.setBalanceOf(account, value).transact({'from': sender}), "setBalanceOf", self.contract_name) \ No newline at end of file diff --git a/tests/contracts/FakeCourt.sol b/tests/contracts/FakeCourt.sol index 5c0a58b631..1aa13be96b 100644 --- a/tests/contracts/FakeCourt.sol +++ b/tests/contracts/FakeCourt.sol @@ -1,15 +1,15 @@ -pragma solidity ^0.4.21; +pragma solidity ^0.4.23; -import "contracts/EtherNomin.sol"; +import "contracts/Nomin.sol"; contract FakeCourt { - EtherNomin public nomin; + Nomin public nomin; mapping(uint => bool) public motionConfirming; mapping(uint => bool) public motionPasses; mapping(address => uint) public targetMotionID; - function setNomin(EtherNomin newNomin) + function setNomin(Nomin newNomin) public { nomin = newNomin; @@ -33,9 +33,9 @@ contract FakeCourt { targetMotionID[target] = motionID; } - function confiscateBalance(address target) + function freezeAndConfiscate(address target) public { - nomin.confiscateBalance(target); + nomin.freezeAndConfiscate(target); } } diff --git a/tests/contracts/FakeProxy.sol b/tests/contracts/FakeProxy.sol deleted file mode 100644 index 72df7acac6..0000000000 --- a/tests/contracts/FakeProxy.sol +++ /dev/null @@ -1,3 +0,0 @@ -pragma solidity ^0.4.21; - -contract FakeProxy {} diff --git a/tests/contracts/OneWeekSetup.sol b/tests/contracts/OneWeekSetup.sol index a54766386c..7648ebfebd 100644 --- a/tests/contracts/OneWeekSetup.sol +++ b/tests/contracts/OneWeekSetup.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.21; +pragma solidity ^0.4.23; import "contracts/LimitedSetup.sol"; @@ -7,7 +7,7 @@ import "contracts/LimitedSetup.sol"; contract OneWeekSetup is LimitedSetup(1 weeks) { function testFunc() public - setupFunction + onlyDuringSetup returns (bool) { return true; diff --git a/tests/contracts/PayableSD.sol b/tests/contracts/PayableSD.sol index 35cff38ca2..f74a684c80 100644 --- a/tests/contracts/PayableSD.sol +++ b/tests/contracts/PayableSD.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.21; +pragma solidity ^0.4.23; import "contracts/SelfDestructible.sol"; @@ -6,8 +6,8 @@ import "contracts/SelfDestructible.sol"; contract PayableSD is SelfDestructible { - function PayableSD(address _owner, address _beneficiary) - SelfDestructible(_owner, _beneficiary) public {} + constructor(address _owner) + SelfDestructible(_owner) public {} function () public payable {} } diff --git a/tests/contracts/PublicCourt.sol b/tests/contracts/PublicCourt.sol index cfd2caf0ce..ba1d5ec115 100644 --- a/tests/contracts/PublicCourt.sol +++ b/tests/contracts/PublicCourt.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.21; +pragma solidity ^0.4.23; import "contracts/Court.sol"; @@ -6,13 +6,13 @@ import "contracts/Court.sol"; contract PublicCourt is Court { - function PublicCourt(Havven _havven, EtherNomin _nomin, address _owner) + constructor(Havven _havven, Nomin _nomin, address _owner) Court(_havven, _nomin, _owner) public {} function _havven() - public + public view returns (address) { @@ -20,7 +20,7 @@ contract PublicCourt is Court { } function _nomin() - public + public view returns (address) { @@ -96,7 +96,7 @@ contract PublicCourt is Court { returns (uint) { uint weight = setupVote(voteIndex); - SetupVoteReturnValue(weight); + emit SetupVoteReturnValue(weight); return weight; } diff --git a/tests/contracts/PublicEST.sol b/tests/contracts/PublicEST.sol new file mode 100644 index 0000000000..69116da3cf --- /dev/null +++ b/tests/contracts/PublicEST.sol @@ -0,0 +1,25 @@ +pragma solidity ^0.4.23; + +import "contracts/ExternStateToken.sol"; + +contract PublicEST is ExternStateToken { + constructor(address _proxy, string _name, string _symbol, uint _totalSupply, + TokenState _state, address _owner) + ExternStateToken(_proxy, _name, _symbol, _totalSupply, _state, _owner) + public + {} + + function transfer(address to, uint value) + optionalProxy + external + { + _transfer_byProxy(messageSender, to, value); + } + + function transferFrom(address from, address to, uint value) + optionalProxy + external + { + _transferFrom_byProxy(messageSender, from, to, value); + } +} diff --git a/tests/contracts/PublicEtherNomin.sol b/tests/contracts/PublicEtherNomin.sol deleted file mode 100644 index 45d3ab4640..0000000000 --- a/tests/contracts/PublicEtherNomin.sol +++ /dev/null @@ -1,60 +0,0 @@ -/* PublicEtherNomin.sol: expose the internal functions in EtherNomin - * for testing purposes. - */ -pragma solidity ^0.4.21; - - -import "contracts/EtherNomin.sol"; -import "contracts/TokenState.sol"; - - -contract PublicEtherNomin is EtherNomin { - - function PublicEtherNomin(address _havven, address _oracle, - address _beneficiary, - uint initialEtherPrice, - address _owner, TokenState initialState) - EtherNomin(_havven, _oracle, _beneficiary, initialEtherPrice, _owner, initialState) - public {} - - function publicEtherValueAllowStale(uint n) - public - view - returns (uint) - { - return etherValueAllowStale(n); - } - - function publicSaleProceedsEtherAllowStale(uint n) - public - view - returns (uint) - { - return saleProceedsEtherAllowStale(n); - } - - function currentTime() - public - returns (uint) - { - return now; - } - - function debugWithdrawAllEther(address recipient) - public - { - recipient.transfer(balanceOf(this)); - } - - function debugEmptyFeePool() - public - { - state.setBalanceOf(address(this), 0); - } - - function debugFreezeAccount(address target) - public - { - frozen[target] = true; - } -} diff --git a/tests/contracts/PublicExternStateProxyFeeToken.sol b/tests/contracts/PublicExternStateProxyFeeToken.sol deleted file mode 100644 index 393a788c9b..0000000000 --- a/tests/contracts/PublicExternStateProxyFeeToken.sol +++ /dev/null @@ -1,43 +0,0 @@ -pragma solidity ^0.4.21; - -import "contracts/ExternStateProxyFeeToken.sol"; -import "contracts/TokenState.sol"; - -contract PublicExternStateProxyFeeToken is ExternStateProxyFeeToken { - function PublicExternStateProxyFeeToken(string _name, string _symbol, - uint _feeRate, address _feeAuthority, - TokenState _state, address _owner) - ExternStateProxyFeeToken(_name, _symbol, _feeRate, _feeAuthority, _state, _owner) - public {} - - function transfer_byProxy(address to, uint value) - public - optionalProxy - returns (bool) - { - return _transfer_byProxy(messageSender, to, value); - } - - function transferFrom_byProxy(address from, address to, uint value) - public - optionalProxy - returns (bool) - { - return _transferFrom_byProxy(messageSender, from, to, value); - } - - function _messageSender() - public - returns (address) - { - return messageSender; - } - - function _optionalProxy_tester() - public - optionalProxy - returns (address) - { - return messageSender; - } -} diff --git a/tests/contracts/PublicExternStateProxyToken.sol b/tests/contracts/PublicExternStateProxyToken.sol deleted file mode 100644 index 7da7510c03..0000000000 --- a/tests/contracts/PublicExternStateProxyToken.sol +++ /dev/null @@ -1,43 +0,0 @@ -pragma solidity ^0.4.21; - -import "contracts/ExternStateProxyToken.sol"; -import "contracts/TokenState.sol"; - -contract PublicExternStateProxyToken is ExternStateProxyToken { - function PublicExternStateProxyToken(string _name, string _symbol, - uint initialSupply, address initialBeneficiary, - TokenState _state, address _owner) - ExternStateProxyToken(_name, _symbol, initialSupply, initialBeneficiary, _state, _owner) - public {} - - function transfer_byProxy(address to, uint value) - public - optionalProxy - returns (bool) - { - return _transfer_byProxy(messageSender, to, value); - } - - function transferFrom_byProxy(address from, address to, uint value) - public - optionalProxy - returns (bool) - { - return _transferFrom_byProxy(messageSender, from, to, value); - } - - function _messageSender() - public - returns (address) - { - return messageSender; - } - - function _optionalProxy_tester() - public - optionalProxy - returns (address) - { - return messageSender; - } -} diff --git a/tests/contracts/PublicFeeToken.sol b/tests/contracts/PublicFeeToken.sol new file mode 100644 index 0000000000..64cf193e3e --- /dev/null +++ b/tests/contracts/PublicFeeToken.sol @@ -0,0 +1,56 @@ +pragma solidity ^0.4.23; + +import "contracts/FeeToken.sol"; + +contract PublicFeeToken is FeeToken { + constructor(address _proxy, string _name, string _symbol, uint _transferFeeRate, address _feeAuthority, + address _owner) + FeeToken(_proxy, _name, _symbol, 0, _transferFeeRate, _feeAuthority, _owner) + public + {} + + function transfer(address to, uint value) + optionalProxy + external + { + _transfer_byProxy(messageSender, to, value); + } + + function transferFrom(address from, address to, uint value) + optionalProxy + external + { + _transferFrom_byProxy(messageSender, from, to, value); + } + + function transferSenderPaysFee(address to, uint value) + optionalProxy + external + { + _transferSenderPaysFee_byProxy(messageSender, to, value); + } + + function transferFromSenderPaysFee(address from, address to, uint value) + optionalProxy + external + { + _transferFromSenderPaysFee_byProxy(messageSender, from, to, value); + } + + function giveTokens(address account, uint amount) + optionalProxy + public + { + tokenState.setBalanceOf(account, safeAdd(amount, tokenState.balanceOf(account))); + totalSupply = safeAdd(totalSupply, amount); + } + + function clearTokens(address account) + optionalProxy + public + { + totalSupply = safeSub(totalSupply, tokenState.balanceOf(account)); + tokenState.setBalanceOf(account, 0); + } + +} diff --git a/tests/contracts/PublicHavven.sol b/tests/contracts/PublicHavven.sol index 1e76d80d94..ad433e939b 100644 --- a/tests/contracts/PublicHavven.sol +++ b/tests/contracts/PublicHavven.sol @@ -2,7 +2,7 @@ * for testing purposes. */ -pragma solidity ^0.4.21; +pragma solidity ^0.4.23; import "contracts/Havven.sol"; @@ -11,83 +11,44 @@ import "contracts/TokenState.sol"; // Public getters for all items in the Havven contract, used for debugging/testing contract PublicHavven is Havven { + // generate getters for constants + uint constant public MIN_FEE_PERIOD_DURATION = 1 days; + uint constant public MAX_FEE_PERIOD_DURATION = 26 weeks; - function PublicHavven(TokenState initialState, address _owner) - Havven(initialState, _owner) + constructor(address _proxy, TokenState _state, address _owner, address _oracle, uint _price) + Havven(_proxy, _state, _owner, _oracle, _price) public {} - function _currentBalanceSum(address account) + /** + * @notice Allow the owner of this contract to endow any address with havvens + * from the initial supply. + * @dev Since the entire initial supply resides in the havven contract, + * this disallows the foundation from withdrawing fees on undistributed balances. + * This function can also be used to retrieve any havvens sent to the Havven contract itself. + * Only callable by the contract owner. + */ + function endow(address to, uint value) + external + optionalProxy_onlyOwner + { + address sender = this; + /* If they have enough available Havvens, it could be that + * their havvens are escrowed, however the transfer would then + * fail. This means that escrowed havvens are locked first, + * and then the actual transferable ones. */ + require(nominsIssued[sender] == 0 || value <= availableHavvens(sender)); + /* Perform the transfer: if there is a problem, + * an exception will be thrown in this call. */ + tokenState.setBalanceOf(sender, safeSub(tokenState.balanceOf(sender), value)); + tokenState.setBalanceOf(to, safeAdd(tokenState.balanceOf(to), value)); + emitTransfer(sender, to, value); + } + + function currentTime() public - view returns (uint) { - return currentBalanceSum[account]; - } - - function _lastTransferTimestamp(address account) - public - view - returns (uint) - { - return lastTransferTimestamp[account]; - } - - function _hasWithdrawnLastPeriodFees(address account) - public - view - returns (bool) - { - return hasWithdrawnLastPeriodFees[account]; - } - - function _lastFeePeriodStartTime() - public - view - returns (uint) - { - return lastFeePeriodStartTime; - } - - function _penultimateFeePeriodStartTime() - public - view - returns (uint) - { - return penultimateFeePeriodStartTime; - } - - function _MIN_FEE_PERIOD_DURATION_SECONDS() - public - view - returns (uint) - { - return MIN_FEE_PERIOD_DURATION_SECONDS; - } - - function _MAX_FEE_PERIOD_DURATION_SECONDS() - public - view - returns (uint) - { - return MAX_FEE_PERIOD_DURATION_SECONDS; - } - - function _adjustFeeEntitlement(address account, uint preBalance) - public - { - return adjustFeeEntitlement(account, preBalance); - } - - function _rolloverFee(address account, uint lastTransferTime, uint preBalance) - public - { - return rolloverFee(account, lastTransferTime, preBalance); - } - - function _checkFeePeriodRollover() - public - { - checkFeePeriodRollover(); + return now; } } diff --git a/tests/contracts/PublicHavvenEscrow.sol b/tests/contracts/PublicHavvenEscrow.sol index 47e5dedc68..960727ccda 100644 --- a/tests/contracts/PublicHavvenEscrow.sol +++ b/tests/contracts/PublicHavvenEscrow.sol @@ -1,7 +1,7 @@ -/* PublicEtherNomin.sol: expose the internal functions in EtherNomin +/* PublicNomin.sol: expose the internal functions in Nomin * for testing purposes. */ -pragma solidity ^0.4.21; +pragma solidity ^0.4.23; import "contracts/HavvenEscrow.sol"; @@ -10,8 +10,7 @@ import "contracts/Havven.sol"; contract PublicHavvenEscrow is HavvenEscrow { - function PublicHavvenEscrow(address _owner, - Havven _havven) + constructor(address _owner, Havven _havven) HavvenEscrow(_owner, _havven) public { @@ -23,7 +22,7 @@ contract PublicHavvenEscrow is HavvenEscrow { uint totalQuantity, uint vestingPeriods) external onlyOwner - setupFunction + onlyDuringSetup { // safeSub prevents a conclusionTime in the past. uint totalDuration = safeSub(conclusionTime, now); diff --git a/tests/contracts/PublicMath.sol b/tests/contracts/PublicMath.sol index 8c2a8d69b5..755447ad6d 100644 --- a/tests/contracts/PublicMath.sol +++ b/tests/contracts/PublicMath.sol @@ -1,7 +1,7 @@ /* PublicMath.sol: expose the internal functions in SafeDecimalMath * for testing purposes. */ -pragma solidity ^0.4.21; +pragma solidity ^0.4.23; import "contracts/SafeDecimalMath.sol"; diff --git a/tests/contracts/PublicNomin.sol b/tests/contracts/PublicNomin.sol new file mode 100644 index 0000000000..36422243e1 --- /dev/null +++ b/tests/contracts/PublicNomin.sol @@ -0,0 +1,83 @@ +/* PublicNomin.sol: expose the internal functions in Nomin + * for testing purposes. + */ +pragma solidity ^0.4.23; + + +import "contracts/Havven.sol"; +import "contracts/Nomin.sol"; + + +contract PublicNomin is Nomin { + + uint constant MAX_TRANSFER_FEE_RATE = UNIT; // allow for 100% fees + + constructor(address _proxy, Havven _havven, address _owner) + Nomin(_proxy, _havven, _owner) + public {} + + function debugEmptyFeePool() + public + { + tokenState.setBalanceOf(address(this), 0); + } + + function debugFreezeAccount(address target) + optionalProxy + public + { + require(!frozen[target]); + uint balance = tokenState.balanceOf(target); + tokenState.setBalanceOf(address(this), safeAdd(tokenState.balanceOf(address(this)), balance)); + tokenState.setBalanceOf(target, 0); + frozen[target] = true; + emitAccountFrozen(target, balance); + emitTransfer(target, address(this), balance); + } + + function giveNomins(address account, uint amount) + optionalProxy + public + { + tokenState.setBalanceOf(account, safeAdd(amount, tokenState.balanceOf(account))); + totalSupply = safeAdd(totalSupply, amount); + } + + function clearNomins(address account) + optionalProxy + public + { + totalSupply = safeSub(totalSupply, tokenState.balanceOf(account)); + tokenState.setBalanceOf(account, 0); + } + + function generateFees(uint amount) + optionalProxy + public + { + totalSupply = safeAdd(totalSupply, amount); + tokenState.setBalanceOf(address(this), safeAdd(balanceOf(address(this)), amount)); + } + + /* Allow havven to issue a certain number of + * nomins from a target address */ + function publicIssue(address target, uint amount) + public + { + tokenState.setBalanceOf(target, safeAdd(tokenState.balanceOf(target), amount)); + totalSupply = safeAdd(totalSupply, amount); + emitTransfer(address(0), target, amount); + emitIssued(target, amount); + } + + /* Allow havven to burn a certain number of + * nomins from a target address */ + function publicBurn(address target, uint amount) + public + { + tokenState.setBalanceOf(target, safeSub(tokenState.balanceOf(target), amount)); + totalSupply = safeSub(totalSupply, amount); + emitTransfer(target, address(0), amount); + emitBurned(target, amount); + } +} diff --git a/tests/contracts/TestablePausable.sol b/tests/contracts/TestablePausable.sol new file mode 100644 index 0000000000..ac49b3b7d5 --- /dev/null +++ b/tests/contracts/TestablePausable.sol @@ -0,0 +1,25 @@ +pragma solidity 0.4.24; + +import "contracts/Pausable.sol"; + +/** + * @title An implementation of Pausable. Used to test the features of the Pausable contract that can only be tested by an implementation. + */ +contract TestablePausable is Pausable { + + uint public someValue; + + constructor(address _owner) + Pausable(_owner) + public + {} + + function setSomeValue(uint _value) + external + notPaused + { + someValue = _value; + } + +} + diff --git a/tests/test_Court.py b/tests/test_Court.py index 58019be267..260e0244ae 100644 --- a/tests/test_Court.py +++ b/tests/test_Court.py @@ -1,924 +1,895 @@ -import unittest -from utils.deployutils import attempt, compile_contracts, attempt_deploy, W3, mine_txs, mine_tx, UNIT, MASTER, \ - fast_forward, DUMMY, take_snapshot, restore_snapshot, fresh_account, fresh_accounts -from utils.testutils import assertReverts, assertClose -from utils.testutils import generate_topic_event_map, get_event_data_from_log -from utils.testutils import ZERO_ADDRESS +from utils.deployutils import ( + UNIT, MASTER, DUMMY, W3, + attempt_deploy, mine_txs, + fast_forward, to_seconds, + take_snapshot, restore_snapshot, + fresh_account, fresh_accounts +) +from utils.testutils import ( + HavvenTestCase, block_time, + get_event_data_from_log, + ZERO_ADDRESS +) -SOLIDITY_SOURCES = ["tests/contracts/PublicCourt.sol", - "contracts/EtherNomin.sol", - "tests/contracts/PublicHavven.sol", - "contracts/Proxy.sol"] - - -def deploy_public_court(): - print("Deployment Initiated. \n") - - compiled = attempt(compile_contracts, [SOLIDITY_SOURCES], "Compiling contracts...") - court_abi = compiled['PublicCourt']['abi'] - nomin_abi = compiled['EtherNomin']['abi'] - - havven_contract, havven_txr = attempt_deploy(compiled, 'PublicHavven', MASTER, [ZERO_ADDRESS, MASTER]) - nomin_contract, nomin_txr = attempt_deploy(compiled, 'EtherNomin', MASTER, - [havven_contract.address, MASTER, MASTER, 1000 * UNIT, MASTER, - ZERO_ADDRESS]) - court_contract, court_txr = attempt_deploy(compiled, 'PublicCourt', MASTER, - [havven_contract.address, nomin_contract.address, MASTER]) - - # Install proxies - havven_proxy, _ = attempt_deploy(compiled, 'Proxy', - MASTER, [havven_contract.address, MASTER]) - mine_tx(havven_contract.functions.setProxy(havven_proxy.address).transact({'from': MASTER})) - proxy_havven = W3.eth.contract(address=havven_proxy.address, abi=compiled['PublicHavven']['abi']) - - nomin_proxy, _ = attempt_deploy(compiled, 'Proxy', - MASTER, [nomin_contract.address, MASTER]) - mine_tx(nomin_contract.functions.setProxy(nomin_proxy.address).transact({'from': MASTER})) - proxy_nomin = W3.eth.contract(address=nomin_proxy.address, abi=compiled['EtherNomin']['abi']) - - txs = [havven_contract.functions.setNomin(nomin_contract.address).transact({'from': MASTER}), - nomin_contract.functions.setCourt(court_contract.address).transact({'from': MASTER})] - attempt(mine_txs, [txs], "Linking contracts... ") - - print("\nDeployment complete.\n") - return (proxy_havven, proxy_nomin, havven_proxy, nomin_proxy, havven_contract, - nomin_contract, court_contract, nomin_abi, court_abi) +from tests.contract_interfaces.court_interface import PublicCourtInterface +from tests.contract_interfaces.havven_interface import PublicHavvenInterface +from tests.contract_interfaces.nomin_interface import NominInterface def setUpModule(): print("Testing Court...") + print("================") + print() def tearDownModule(): print() + print() -class TestCourt(unittest.TestCase): +class TestCourt(HavvenTestCase): def setUp(self): self.snapshot = take_snapshot() def tearDown(self): restore_snapshot(self.snapshot) + @classmethod + def deployContracts(cls): + print("Deployment Initiated. \n") + sources = ["tests/contracts/PublicCourt.sol", + "contracts/Nomin.sol", + "tests/contracts/PublicHavven.sol"] + + compiled, cls.event_maps = cls.compileAndMapEvents(sources) + + court_abi = compiled['PublicCourt']['abi'] + nomin_abi = compiled['Nomin']['abi'] + havven_abi = compiled['Havven']['abi'] + + havven_proxy, _ = attempt_deploy(compiled, 'Proxy', MASTER, [MASTER]) + nomin_proxy, _ = attempt_deploy(compiled, 'Proxy', MASTER, [MASTER]) + proxied_havven = W3.eth.contract(address=havven_proxy.address, abi=havven_abi) + proxied_nomin = W3.eth.contract(address=nomin_proxy.address, abi=nomin_abi) + + tokenstate, _ = attempt_deploy(compiled, 'TokenState', + MASTER, [MASTER, MASTER]) + havven_contract, hvn_txr = attempt_deploy( + compiled, 'PublicHavven', MASTER, [havven_proxy.address, tokenstate.address, MASTER, MASTER, UNIT//2] + ) + nomin_contract, nom_txr = attempt_deploy( + compiled, 'Nomin', MASTER, [nomin_proxy.address, havven_contract.address, MASTER] + ) + court_contract, court_txr = attempt_deploy( + compiled, 'PublicCourt', MASTER, [havven_contract.address, nomin_contract.address, MASTER] + ) + + mine_txs([ + tokenstate.functions.setBalanceOf(havven_contract.address, 100000000 * UNIT).transact({'from': MASTER}), + tokenstate.functions.setAssociatedContract(havven_contract.address).transact({'from': MASTER}), + havven_proxy.functions.setTarget(havven_contract.address).transact({'from': MASTER}), + nomin_proxy.functions.setTarget(nomin_contract.address).transact({'from': MASTER}), + havven_contract.functions.setNomin(nomin_contract.address).transact({'from': MASTER}), + nomin_contract.functions.setCourt(court_contract.address).transact({'from': MASTER}) + ]) + + print("\nDeployment complete.\n") + return havven_proxy, proxied_havven, nomin_proxy, proxied_nomin, havven_contract, nomin_contract, court_contract, nomin_abi, court_abi + @classmethod def setUpClass(cls): - cls.assertReverts = assertReverts - cls.assertClose = assertClose - - cls.havven, cls.nomin, cls.havven_proxy, cls.nomin_proxy, cls.havven_real, \ - cls.nomin_real, cls.court, cls.nomin_abi, cls.court_abi = deploy_public_court() - - # Event stuff - cls.court_event_dict = generate_topic_event_map(cls.court_abi) - cls.nomin_event_dict = generate_topic_event_map(cls.nomin_abi) - - # Inherited - cls.owner = lambda self: self.court.functions.owner().call() - - # Non-public variables - cls.getHavven = lambda self: self.court.functions._havven().call() - cls.getNomin = lambda self: self.court.functions._nomin().call() - cls.minStandingBalance = lambda self: self.court.functions.minStandingBalance().call() - cls.votingPeriod = lambda self: self.court.functions.votingPeriod().call() - cls.MIN_VOTING_PERIOD = lambda self: self.court.functions._MIN_VOTING_PERIOD().call() - cls.MAX_VOTING_PERIOD = lambda self: self.court.functions._MAX_VOTING_PERIOD().call() - cls.confirmationPeriod = lambda self: self.court.functions.confirmationPeriod().call() - cls.MIN_CONFIRMATION_PERIOD = lambda self: self.court.functions._MIN_CONFIRMATION_PERIOD().call() - cls.MAX_CONFIRMATION_PERIOD = lambda self: self.court.functions._MAX_CONFIRMATION_PERIOD().call() - cls.requiredParticipation = lambda self: self.court.functions.requiredParticipation().call() - cls.MIN_REQUIRED_PARTICIPATION = lambda self: self.court.functions._MIN_REQUIRED_PARTICIPATION().call() - cls.requiredMajority = lambda self: self.court.functions.requiredMajority().call() - cls.MIN_REQUIRED_MAJORITY = lambda self: self.court.functions._MIN_REQUIRED_MAJORITY().call() - cls.voteWeight = lambda self, account, motionID: self.court.functions._voteWeight(account, motionID).call() - cls.nextMotionID = lambda self: self.court.functions._nextMotionID().call() - - # Public variables - cls.motionTarget = lambda self, index: self.court.functions.motionTarget(index).call() - cls.targetMotionID = lambda self, address: self.court.functions.targetMotionID(address).call() - cls.motionStartTime = lambda self, account: self.court.functions.motionStartTime(account).call() - cls.votesFor = lambda self, account: self.court.functions.votesFor(account).call() - cls.votesAgainst = lambda self, account: self.court.functions.votesAgainst(account).call() - cls.vote = lambda self, account, motionID: self.court.functions.vote(account, motionID).call() - - # Inherited setters - cls.nominateOwner = lambda self, sender, address: mine_tx( - self.court.functions.nominateOwner(address).transact({'from': sender})) - cls.acceptOwnership = lambda self, sender: mine_tx( - self.court.functions.acceptOwnership().transact({'from': sender})) - - # Setters - cls.setMinStandingBalance = lambda self, sender, balance: mine_tx( - self.court.functions.setMinStandingBalance(balance).transact({'from': sender})) - cls.setVotingPeriod = lambda self, sender, duration: mine_tx( - self.court.functions.setVotingPeriod(duration).transact({'from': sender})) - cls.setConfirmationPeriod = lambda self, sender, duration: mine_tx( - self.court.functions.setConfirmationPeriod(duration).transact({'from': sender})) - cls.setRequiredParticipation = lambda self, sender, fraction: mine_tx( - self.court.functions.setRequiredParticipation(fraction).transact({'from': sender})) - cls.setRequiredMajority = lambda self, sender, fraction: mine_tx( - self.court.functions.setRequiredMajority(fraction).transact({'from': sender})) - - # Views - cls.hasVoted = lambda self, sender, motionID: self.court.functions.hasVoted(sender, motionID).call() - cls.motionVoting = lambda self, target: self.court.functions.motionVoting(target).call() - cls.motionConfirming = lambda self, target: self.court.functions.motionConfirming(target).call() - cls.motionWaiting = lambda self, target: self.court.functions.motionWaiting(target).call() - cls.motionPasses = lambda self, target: self.court.functions.motionPasses(target).call() - - # Mutators - cls.beginMotion = lambda self, sender, target: mine_tx( - self.court.functions.beginMotion(target).transact({'from': sender})) - cls.voteFor = lambda self, sender, target: mine_tx( - self.court.functions.voteFor(target).transact({'from': sender})) - cls.voteAgainst = lambda self, sender, target: mine_tx( - self.court.functions.voteAgainst(target).transact({'from': sender})) - cls.cancelVote = lambda self, sender, target: mine_tx( - self.court.functions.cancelVote(target).transact({'from': sender})) - cls.closeMotion = lambda self, sender, target: mine_tx( - self.court.functions.closeMotion(target).transact({'from': sender})) - - # Owner only - cls.approveMotion = lambda self, sender, target: mine_tx( - self.court.functions.approveMotion(target).transact({'from': sender})) - cls.vetoMotion = lambda self, sender, target: mine_tx( - self.court.functions.vetoMotion(target).transact({'from': sender})) - - # Internal - cls.setupVote = lambda self, sender, target: mine_tx( - self.court.functions.publicSetupVote(target).transact({'from': sender})) - - # Havven getters - cls.havvenSupply = lambda self: self.havven.functions.totalSupply().call() - cls.havvenBalance = lambda self, account: self.havven.functions.balanceOf(account).call() - cls.havvenTargetFeePeriodDurationSeconds = lambda self: \ - self.havven.functions.targetFeePeriodDurationSeconds().call() - cls.havvenPenultimateAverageBalance = lambda self, addr: \ - self.havven.functions.penultimateAverageBalance(addr).call() - cls.havvenLastAverageBalance = lambda self, addr: self.havven.functions.lastAverageBalance(addr).call() - - # Havven mutators - cls.havvenEndow = lambda self, sender, account, value: mine_tx( - self.havven.functions.endow(account, value).transact({'from': sender})) - cls.havvenTransfer = lambda self, sender, to, value: mine_tx( - self.havven.functions.transfer(to, value).transact({'from': sender})) - cls.havvenCheckFeePeriodRollover = lambda self, sender: mine_tx( - self.havven.functions._checkFeePeriodRollover().transact({'from': sender})) - cls.havvenAdjustFeeEntitlement = lambda self, sender, acc, p_bal: mine_tx( - self.havven.functions._adjustFeeEntitlement(acc, p_bal).transact({'from': sender})) - cls.havvenSetTargetFeePeriodDuration = lambda self, sender, duration: mine_tx( - self.havven.functions.setTargetFeePeriodDuration(duration).transact({'from': sender})) - - # Nomin getter - cls.nominIsFrozen = lambda self, account: self.nomin.functions.frozen(account).call() - - # Solidity convenience - cls.days = 86400 - cls.weeks = 604800 - cls.months = 2628000 - cls.unit = 10**18 - - # Extract vote index from a transaction receipt returned by a call to beginMotion + cls.havven_proxy, cls.proxied_havven, cls.nomin_proxy, cls.proxied_nomin, cls.havven_contract, cls.nomin_contract, cls.court_contract, cls.nomin_abi, cls.court_abi = cls.deployContracts() + + cls.event_map = cls.event_maps['PublicCourt'] + + cls.court = PublicCourtInterface(cls.court_contract, "Court") + cls.havven = PublicHavvenInterface(cls.havven_contract, "Havven") + cls.nomin = NominInterface(cls.nomin_contract, "Nomin") + + # + # HELPER FUNCTIONS + # + + def allow_acc_max_vote(self, account): + if self.havven.lastPriceUpdateTime() < block_time(): + self.havven.updatePrice(MASTER, UNIT, block_time() + 1) + self.havven.setIssuanceRatio(MASTER, UNIT) + self.havven.setIssuer(MASTER, account, True) + self.havven.issueMaxNomins(account) + + # Extract vote index from a transaction receipt returned by a call to beginMotion def get_motion_index(self, tx_receipt): - event_data = get_event_data_from_log(self.court_event_dict, tx_receipt.logs[0]) + event_data = get_event_data_from_log(self.event_map, tx_receipt.logs[-1]) self.assertEqual(event_data['event'], "MotionBegun") return event_data['args']['motionID'] + def startVotingPeriod(self, voter, target): + tx = self.court.beginMotion(voter, target) + motion_id = self.get_motion_index(tx) + + self.assertReverts(self.court.closeMotion, voter, motion_id) + # fast forward to voting period + fast_forward(self.court.motionStartTime(motion_id) - block_time() + 1) + return motion_id + + def validate_MotionBegun_data(self, tx_receipt, expected_initiator, expected_target, expected_motion_id): + event_data = get_event_data_from_log(self.event_map, tx_receipt.logs[0]) + self.assertEqual(event_data['event'], "MotionBegun") + self.assertEqual(event_data['args']['initiator'], expected_initiator) + self.assertEqual(event_data['args']['target'], expected_target) + self.assertEqual(event_data['args']['motionID'], expected_motion_id) + + def validate_MotionClosed_data(self, tx_receipt, log_index, expected_motion_id): + closed_data = get_event_data_from_log(self.event_map, tx_receipt.logs[log_index]) + self.assertEqual(closed_data['event'], "MotionClosed") + self.assertEqual(closed_data['args']['motionID'], expected_motion_id) + + def validate_MotionVetoed_data(self, tx_receipt, log_index, expected_motion_id): + veto_data = get_event_data_from_log(self.event_map, tx_receipt.logs[log_index]) + self.assertEqual(veto_data['event'], "MotionVetoed") + self.assertEqual(veto_data['args']['motionID'], expected_motion_id) + + def validate_MotionApproved_data(self, tx_receipt, log_index, expected_motion_id): + approved_data = get_event_data_from_log(self.event_map, tx_receipt.logs[log_index]) + self.assertEqual(approved_data['event'], "MotionApproved") + self.assertEqual(approved_data['args']['motionID'], expected_motion_id) + + def validate_Confiscation_data(self, tx_receipt, log_index, expected_target, expected_balance=None): + freeze_data = get_event_data_from_log(self.event_maps["Nomin"], tx_receipt.logs[log_index]) + self.assertEqual(freeze_data['event'], "AccountFrozen") + self.assertEqual(freeze_data['args']['target'], expected_target) + if expected_balance is not None: + self.assertEqual(freeze_data['args']['balance'], expected_balance) + xfer_data = get_event_data_from_log(self.event_maps["Nomin"], tx_receipt.logs[log_index + 1]) + self.assertEqual(xfer_data['event'], "Transfer") + + # + # UNIT TESTS + # def test_constructor(self): - self.assertEqual(self.owner(), MASTER) - self.assertEqual(self.havven_real.address, self.getHavven()) - self.assertEqual(self.nomin_real.address, self.getNomin()) - self.assertEqual(self.minStandingBalance(), 100 * UNIT) - self.assertEqual(self.votingPeriod(), 1 * self.weeks) - self.assertEqual(self.MIN_VOTING_PERIOD(), 3 * self.days) - self.assertEqual(self.MAX_VOTING_PERIOD(), 4 * self.weeks) - self.assertEqual(self.confirmationPeriod(), 1 * self.weeks) - self.assertEqual(self.MIN_CONFIRMATION_PERIOD(), 1 * self.days) - self.assertEqual(self.MAX_CONFIRMATION_PERIOD(), 2 * self.weeks) - self.assertEqual(self.requiredParticipation(), 3 * UNIT / 10) - self.assertEqual(self.MIN_REQUIRED_PARTICIPATION(), UNIT / 10) - self.assertEqual(self.requiredMajority(), (2 * UNIT) // 3) - self.assertEqual(self.MIN_REQUIRED_MAJORITY(), UNIT / 2) + self.assertEqual(self.court.owner(), MASTER) + self.assertEqual(self.havven.contract.address, self.court.getHavven()) + self.assertEqual(self.nomin.contract.address, self.court.getNomin()) + self.assertEqual(self.court.minStandingBalance(), 100 * UNIT) + self.assertEqual(self.court.votingPeriod(), to_seconds(weeks=1)) + self.assertEqual(self.court.MIN_VOTING_PERIOD(), to_seconds(days=3)) + self.assertEqual(self.court.MAX_VOTING_PERIOD(), to_seconds(weeks=4)) + self.assertEqual(self.court.confirmationPeriod(), to_seconds(weeks=1)) + self.assertEqual(self.court.MIN_CONFIRMATION_PERIOD(), to_seconds(days=1)) + self.assertEqual(self.court.MAX_CONFIRMATION_PERIOD(), to_seconds(weeks=2)) + self.assertEqual(self.court.requiredParticipation(), 3 * UNIT / 10) + self.assertEqual(self.court.MIN_REQUIRED_PARTICIPATION(), UNIT / 10) + self.assertEqual(self.court.requiredMajority(), (2 * UNIT) // 3) + self.assertEqual(self.court.MIN_REQUIRED_MAJORITY(), UNIT / 2) def test_setOwner(self): - owner = self.owner() + owner = self.court.owner() # Only owner can change the owner. - self.assertReverts(self.nominateOwner, DUMMY, DUMMY) - self.nominateOwner(owner, DUMMY) - self.acceptOwnership(DUMMY) - self.assertEqual(self.owner(), DUMMY) + self.assertReverts(self.court.nominateNewOwner, DUMMY, DUMMY) + self.court.nominateNewOwner(owner, DUMMY) + self.court.acceptOwnership(DUMMY) + self.assertEqual(self.court.owner(), DUMMY) def test_setMinStandingBalance(self): - owner = self.owner() + owner = self.court.owner() new_min_standing_balance = 200 * UNIT # Only owner can set minStandingBalance. - self.assertReverts(self.setMinStandingBalance, DUMMY, new_min_standing_balance) - tx_receipt = self.setMinStandingBalance(owner, new_min_standing_balance) - self.assertEqual(self.minStandingBalance(), new_min_standing_balance) + self.assertReverts(self.court.setMinStandingBalance, DUMMY, new_min_standing_balance) + self.court.setMinStandingBalance(owner, new_min_standing_balance) + self.assertEqual(self.court.minStandingBalance(), new_min_standing_balance) def test_setVotingPeriod(self): - owner = self.owner() - new_voting_period = 2 * self.weeks + owner = self.court.owner() + new_voting_period = to_seconds(weeks=2) # Only owner can set votingPeriod. - self.assertReverts(self.setVotingPeriod, DUMMY, new_voting_period) - tx_receipt = self.setVotingPeriod(owner, new_voting_period) - self.assertEqual(self.votingPeriod(), new_voting_period) + self.assertReverts(self.court.setVotingPeriod, DUMMY, new_voting_period) + self.court.setVotingPeriod(owner, new_voting_period) + self.assertEqual(self.court.votingPeriod(), new_voting_period) # Voting period must be > than MIN_VOTING_PERIOD (~ currently 3 days). - bad_voting_period = 3 * self.days - 1 - self.assertReverts(self.setVotingPeriod, owner, bad_voting_period) + bad_voting_period = to_seconds(days=3) - 1 + self.assertReverts(self.court.setVotingPeriod, owner, bad_voting_period) # Voting period must be < than MAX_VOTING_PERIOD (~ currently 4 weeks). - bad_voting_period = 4 * self.weeks + 1 - self.assertReverts(self.setVotingPeriod, owner, bad_voting_period) + bad_voting_period = to_seconds(weeks=4) + 1 + self.assertReverts(self.court.setVotingPeriod, owner, bad_voting_period) # Voting period must be <= the havven target fee period duration. - fee_period_duration = 2 * self.weeks - self.havvenSetTargetFeePeriodDuration(owner, fee_period_duration) - self.assertEqual(self.havvenTargetFeePeriodDurationSeconds(), fee_period_duration) + fee_period_duration = to_seconds(weeks=2) + self.havven.setFeePeriodDuration(owner, fee_period_duration) + self.assertEqual(self.havven.feePeriodDuration(), fee_period_duration) # Voting period must be <= fee period duration. - bad_voting_period = 2 * self.weeks + 1 - self.assertReverts(self.setVotingPeriod, owner, bad_voting_period) + bad_voting_period = to_seconds(weeks=2) + 1 + self.assertReverts(self.court.setVotingPeriod, owner, bad_voting_period) def test_setConfirmationPeriod(self): - owner = self.owner() - new_confirmation_period = 2 * self.weeks + owner = self.court.owner() + new_confirmation_period = to_seconds(weeks=2) # Only owner can set confirmationPeriod. - self.assertReverts(self.setConfirmationPeriod, DUMMY, new_confirmation_period) - tx_receipt = self.setConfirmationPeriod(owner, new_confirmation_period) - self.assertEqual(self.confirmationPeriod(), new_confirmation_period) + self.assertReverts(self.court.setConfirmationPeriod, DUMMY, new_confirmation_period) + self.court.setConfirmationPeriod(owner, new_confirmation_period) + self.assertEqual(self.court.confirmationPeriod(), new_confirmation_period) # Confirmation period must be > than MIN_CONFIRMATION_PERIOD (~ currently 1 days). - bad_confirmation_period = 1 * self.days - 1 - self.assertReverts(self.setConfirmationPeriod, owner, bad_confirmation_period) + bad_confirmation_period = to_seconds(days=1) - 1 + self.assertReverts(self.court.setConfirmationPeriod, owner, bad_confirmation_period) # Confirmation period must be < than MAX_CONFIRMATION_PERIOD (~ 3 weeks). - bad_confirmation_period = 3 * self.weeks + 1 - self.assertReverts(self.setConfirmationPeriod, owner, bad_confirmation_period) + bad_confirmation_period = to_seconds(weeks=3) + 1 + self.assertReverts(self.court.setConfirmationPeriod, owner, bad_confirmation_period) def test_setRequiredParticipation(self): - owner = self.owner() + owner = self.court.owner() new_required_participation = 5 * UNIT // 10 # Only owner can set requiredParticipation. - self.assertReverts(self.setRequiredParticipation, DUMMY, new_required_participation) - tx_receipt = self.setRequiredParticipation(owner, new_required_participation) - self.assertEqual(self.requiredParticipation(), new_required_participation) + self.assertReverts(self.court.setRequiredParticipation, DUMMY, new_required_participation) + self.court.setRequiredParticipation(owner, new_required_participation) + self.assertEqual(self.court.requiredParticipation(), new_required_participation) # Required participation must be >= than 10%. bad_required_participation = UNIT // 10 - 1 - self.assertReverts(self.setRequiredParticipation, owner, bad_required_participation) + self.assertReverts(self.court.setRequiredParticipation, owner, bad_required_participation) def test_setRequiredMajority(self): - owner = self.owner() + owner = self.court.owner() new_required_majority = (3 * UNIT) // 4 # Only owner can set requiredMajority. - self.assertReverts(self.setRequiredMajority, DUMMY, new_required_majority) - tx_receipt = self.setRequiredMajority(owner, new_required_majority) - self.assertEqual(self.requiredMajority(), new_required_majority) + self.assertReverts(self.court.setRequiredMajority, DUMMY, new_required_majority) + self.court.setRequiredMajority(owner, new_required_majority) + self.assertEqual(self.court.requiredMajority(), new_required_majority) # Required majority must be >= than 50%. bad_required_majority = UNIT // 2 - 1 - self.assertReverts(self.setRequiredMajority, owner, bad_required_majority) + self.assertReverts(self.court.setRequiredMajority, owner, bad_required_majority) def test_nextMotionID(self): - owner = self.owner() + owner = self.court.owner() voter = fresh_account() - fee_period = self.havvenTargetFeePeriodDurationSeconds() + fee_period = self.havven.feePeriodDuration() - self.havvenEndow(owner, voter, 1000 * UNIT) - self.assertEqual(self.havvenBalance(voter), 1000 * UNIT) + self.havven.endow(owner, voter, 1000 * UNIT) + self.allow_acc_max_vote(voter) + self.assertEqual(self.havven.balanceOf(voter), 1000 * UNIT) # Fast forward to update the vote weight. fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) + self.havven.recomputeLastAverageBalance(voter, voter) address_pattern = "0x" + "0" * 39 + "{}" - motion_id = self.get_motion_index(self.beginMotion(voter, address_pattern.format(1))) + motion_id = self.get_motion_index(self.court.beginMotion(voter, address_pattern.format(1))) self.assertEqual(motion_id, 1) for i in range(2, 6): - self.assertEqual(self.get_motion_index(self.beginMotion(voter, address_pattern.format(i))), i) + self.assertEqual(self.get_motion_index(self.court.beginMotion(voter, address_pattern.format(i))), i) def test_motionTarget_targetMotionID(self): - owner = self.owner() + owner = self.court.owner() voter, target1, target2, target3 = fresh_accounts(4) - fee_period = self.havvenTargetFeePeriodDurationSeconds() - voting_period = self.votingPeriod() - confirmation_period = self.confirmationPeriod() + fee_period = self.havven.feePeriodDuration() + voting_period = self.court.votingPeriod() + confirmation_period = self.court.confirmationPeriod() - self.havvenEndow(owner, voter, self.havvenSupply() // 2) + self.havven.endow(owner, voter, self.havven.totalSupply() // 2) + self.allow_acc_max_vote(voter) - # Fast forward to update the vote weight. fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) + self.havven.rolloverFeePeriodIfElapsed(MASTER) + # Fast forward to update the vote weight. fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) + self.havven.rolloverFeePeriodIfElapsed(MASTER) + self.havven.recomputeLastAverageBalance(voter, voter) # Start three motions to close them in three different ways. - motion_id1 = self.get_motion_index(self.beginMotion(voter, target1)) - motion_id2 = self.get_motion_index(self.beginMotion(voter, target2)) - motion_id3 = self.get_motion_index(self.beginMotion(voter, target3)) + motion_id1 = self.get_motion_index(self.court.beginMotion(voter, target1)) + motion_id2 = self.get_motion_index(self.court.beginMotion(voter, target2)) + motion_id3 = self.get_motion_index(self.court.beginMotion(voter, target3)) - self.assertEqual(self.motionTarget(motion_id1), target1) - self.assertEqual(self.motionTarget(motion_id2), target2) - self.assertEqual(self.motionTarget(motion_id3), target3) + fast_forward(self.court.motionStartTime(motion_id1) - block_time() + 1) - self.assertEqual(self.targetMotionID(target1), motion_id1) - self.assertEqual(self.targetMotionID(target2), motion_id2) - self.assertEqual(self.targetMotionID(target3), motion_id3) + self.assertEqual(self.court.motionTarget(motion_id1), target1) + self.assertEqual(self.court.motionTarget(motion_id2), target2) + self.assertEqual(self.court.motionTarget(motion_id3), target3) - self.vetoMotion(owner, motion_id1) - self.assertEqual(int(self.motionTarget(motion_id1), 16), 0) - self.assertEqual(self.targetMotionID(target1), 0) + self.assertEqual(self.court.targetMotionID(target1), motion_id1) + self.assertEqual(self.court.targetMotionID(target2), motion_id2) + self.assertEqual(self.court.targetMotionID(target3), motion_id3) - self.voteFor(voter, motion_id2) + self.court.vetoMotion(owner, motion_id1) + self.assertEqual(int(self.court.motionTarget(motion_id1), 16), 0) + self.assertEqual(self.court.targetMotionID(target1), 0) - fast_forward(voting_period + 1) + self.court.voteFor(voter, motion_id2) - self.approveMotion(owner, motion_id2) - self.assertEqual(int(self.motionTarget(motion_id2), 16), 0) - self.assertEqual(self.targetMotionID(target2), 0) + fast_forward(voting_period + 1) + self.court.approveMotion(owner, motion_id2) + self.assertEqual(int(self.court.motionTarget(motion_id2), 16), 0) + self.assertEqual(self.court.targetMotionID(target2), 0) fast_forward(confirmation_period + 1) - self.closeMotion(voter, motion_id3) - self.assertEqual(int(self.motionTarget(motion_id3), 16), 0) - self.assertEqual(self.targetMotionID(target3), 0) + self.court.closeMotion(voter, motion_id3) + self.assertEqual(int(self.court.motionTarget(motion_id3), 16), 0) + self.assertEqual(self.court.targetMotionID(target3), 0) def test_waiting_voting_confirming_state_transitions(self): - owner = self.owner() + owner = self.court.owner() suspect = fresh_account() - voting_period = self.votingPeriod() - confirmation_period = self.confirmationPeriod() - motion_id = self.nextMotionID() + voting_period = self.court.votingPeriod() + confirmation_period = self.court.confirmationPeriod() + motion_id = self.court.nextMotionID() # Before a confiscation motion begins, should be in the waiting state. - self.assertTrue(self.motionWaiting(motion_id)) - self.assertFalse(self.motionVoting(motion_id)) - self.assertFalse(self.motionConfirming(motion_id)) + self.assertTrue(self.court.motionWaiting(motion_id)) + self.assertFalse(self.court.motionVoting(motion_id)) + self.assertFalse(self.court.motionConfirming(motion_id)) # Begin a confiscation motion against the suspect, should move to the voting state. - actual_motion_id = self.get_motion_index(self.beginMotion(owner, suspect)) + actual_motion_id = self.startVotingPeriod(owner, suspect) self.assertEqual(motion_id, actual_motion_id) - self.assertFalse(self.motionWaiting(motion_id)) - self.assertTrue(self.motionVoting(motion_id)) - self.assertFalse(self.motionConfirming(motion_id)) + self.assertFalse(self.court.motionWaiting(motion_id)) + self.assertTrue(self.court.motionVoting(motion_id)) + self.assertFalse(self.court.motionConfirming(motion_id)) # Fast forward to the middle of the voting period, should still be in the voting state. fast_forward(voting_period / 2) - self.assertFalse(self.motionWaiting(motion_id)) - self.assertTrue(self.motionVoting(motion_id)) - self.assertFalse(self.motionConfirming(motion_id)) + self.assertFalse(self.court.motionWaiting(motion_id)) + self.assertTrue(self.court.motionVoting(motion_id)) + self.assertFalse(self.court.motionConfirming(motion_id)) # When the voting period finishes, should move to confirming state. fast_forward(voting_period / 2) - self.assertFalse(self.motionWaiting(motion_id)) - self.assertFalse(self.motionVoting(motion_id)) - self.assertTrue(self.motionConfirming(motion_id)) + self.assertFalse(self.court.motionWaiting(motion_id)) + self.assertFalse(self.court.motionVoting(motion_id)) + self.assertTrue(self.court.motionConfirming(motion_id)) # Fast forward to the middle of the confirmation period, should still be in the confirming state. fast_forward(confirmation_period / 2) - self.assertFalse(self.motionWaiting(motion_id)) - self.assertFalse(self.motionVoting(motion_id)) - self.assertTrue(self.motionConfirming(motion_id)) + self.assertFalse(self.court.motionWaiting(motion_id)) + self.assertFalse(self.court.motionVoting(motion_id)) + self.assertTrue(self.court.motionConfirming(motion_id)) # When the voting confirmation period finishes, should move to waiting state. fast_forward(confirmation_period / 2) - self.assertTrue(self.motionWaiting(motion_id)) - self.assertFalse(self.motionVoting(motion_id)) - self.assertFalse(self.motionConfirming(motion_id)) + self.assertTrue(self.court.motionWaiting(motion_id)) + self.assertFalse(self.court.motionVoting(motion_id)) + self.assertFalse(self.court.motionConfirming(motion_id)) def test_hasVoted(self): - owner = self.owner() + owner = self.court.owner() voter, suspect = fresh_accounts(2) - fee_period = self.havvenTargetFeePeriodDurationSeconds() + fee_period = self.havven.feePeriodDuration() # Give 1000 havven tokens to our voter. - self.havvenEndow(owner, voter, 1000) - self.assertEqual(self.havvenBalance(voter), 1000) + self.havven.endow(owner, voter, 1000) + self.allow_acc_max_vote(voter) + + self.assertEqual(self.havven.balanceOf(voter), 1000) # Fast forward to update the vote weight. fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) - self.havvenAdjustFeeEntitlement(voter, voter, self.havvenBalance(voter)) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) + self.havven.recomputeLastAverageBalance(voter, voter) # This should fail because no confiscation motion has begun. - next_motion_id = self.nextMotionID() - self.assertFalse(self.hasVoted(voter, next_motion_id)) - motion_id = self.get_motion_index(self.beginMotion(owner, suspect)) + next_motion_id = self.court.nextMotionID() + self.assertFalse(self.court.hasVoted(voter, next_motion_id)) + motion_id = self.get_motion_index(self.court.beginMotion(owner, suspect)) self.assertEqual(motion_id, next_motion_id) + fast_forward(self.court.motionStartTime(motion_id) - block_time() + 1) + # This should return false because the voter has not voted yet. - self.assertFalse(self.hasVoted(voter, motion_id)) - self.voteFor(voter, motion_id) + self.assertFalse(self.court.hasVoted(voter, motion_id)) + self.court.voteFor(voter, motion_id) # This should return true because the voter has voted. - self.assertTrue(self.hasVoted(voter, motion_id)) + self.assertTrue(self.court.hasVoted(voter, motion_id)) # And false when they cancel their vote. - self.cancelVote(voter, motion_id) - self.assertFalse(self.hasVoted(voter, motion_id)) + self.court.cancelVote(voter, motion_id) + self.assertFalse(self.court.hasVoted(voter, motion_id)) # And true again if they vote against. - self.voteFor(voter, motion_id) - self.assertTrue(self.hasVoted(voter, motion_id)) + self.court.voteFor(voter, motion_id) + self.assertTrue(self.court.hasVoted(voter, motion_id)) def test_motionPasses(self): - owner = self.owner() - accounts = fresh_accounts(11) + owner = self.court.owner() + accounts = fresh_accounts(12) suspect = accounts[0] - voters = accounts[1:] - required_participation = self.requiredParticipation() - required_majority = self.requiredMajority() - fee_period = self.havvenTargetFeePeriodDurationSeconds() - tokens = self.havvenSupply() // 20 + other = accounts[1] + voters = accounts[2:] + self.court.requiredParticipation() + self.court.requiredMajority() + fee_period = self.havven.feePeriodDuration() + tokens = self.havven.totalSupply() // 20 # Give 1/20th of the token supply to each of our 10 voters. In total 50% of tokens distributed. for voter in voters: - self.havvenEndow(owner, voter, tokens) - self.assertEqual(self.havvenBalance(voter), tokens) + self.havven.endow(owner, voter, tokens) + self.allow_acc_max_vote(voter) + self.assertEqual(self.havven.balanceOf(voter), tokens) + self.havven.endow(owner, other, self.havven.balanceOf(self.havven.contract.address)) + self.allow_acc_max_vote(other) # Fast forward to update the vote weights. fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) # Begin a confiscation motion against the suspect. - motion_id = self.get_motion_index(self.beginMotion(owner, suspect)) - self.assertFalse(self.motionPasses(motion_id)) + motion_id = self.get_motion_index(self.court.beginMotion(owner, suspect)) + self.assertFalse(self.court.motionPasses(motion_id)) + + fast_forward(self.court.motionStartTime(motion_id) - block_time() + 1) # 100% in favour and 0% against (50% participation). for voter in voters: - self.voteFor(voter, motion_id) - self.assertTrue(self.motionPasses(motion_id)) - self.assertEqual(self.votesFor(motion_id), self.havvenSupply() // 2) + self.court.voteFor(voter, motion_id) + self.assertTrue(self.court.motionPasses(motion_id)) + self.assertEqual(self.court.votesFor(motion_id), self.havven.totalSupply() // 2) # All cancel votes. for voter in voters: - self.cancelVote(voter, motion_id) - self.assertFalse(self.motionPasses(motion_id)) - self.assertEqual(self.votesFor(motion_id), 0) + self.court.cancelVote(voter, motion_id) + self.assertFalse(self.court.motionPasses(motion_id)) + self.assertEqual(self.court.votesFor(motion_id), 0) # 100% against and 0% in favour (50% participation). for voter in voters: - self.voteAgainst(voter, motion_id) - self.assertFalse(self.motionPasses(motion_id)) - self.assertEqual(self.votesAgainst(motion_id), self.havvenSupply() // 2) + self.court.voteAgainst(voter, motion_id) + self.assertFalse(self.court.motionPasses(motion_id)) + self.assertEqual(self.court.votesAgainst(motion_id), self.havven.totalSupply() // 2) # All cancel votes. for voter in voters: - self.cancelVote(voter, motion_id) - self.assertEqual(self.votesAgainst(motion_id), 0) + self.court.cancelVote(voter, motion_id) + self.assertEqual(self.court.votesAgainst(motion_id), 0) # 60% in favour and 0% against (30% participation) for voter in voters[:6]: - self.voteFor(voter, motion_id) + self.court.voteFor(voter, motion_id) # Required participation must be > than 30%. - self.assertFalse(self.motionPasses(motion_id)) + self.assertFalse(self.court.motionPasses(motion_id)) # But if another user votes in favour, participation = 35% which is sufficient for a vote to pass. - self.voteFor(voters[7], motion_id) - self.assertTrue(self.motionPasses(motion_id)) + self.court.voteFor(voters[7], motion_id) + self.assertTrue(self.court.motionPasses(motion_id)) # The last 3 vote against, 70% in favour and 30% against (required majority is 2/3). for voter in voters[8:]: - self.voteAgainst(voter, motion_id) - self.assertTrue(self.motionPasses(motion_id)) + self.court.voteAgainst(voter, motion_id) + self.assertTrue(self.court.motionPasses(motion_id)) - # If one changes their vote for to against, should not pass since 60% in favour 40% against (less than the min required majority of 2/3). - self.cancelVote(voters[7], motion_id) - self.voteAgainst(voters[7], motion_id) - self.assertFalse(self.motionPasses(motion_id)) + # If one changes their vote for to against, should not pass since 60% in favour 40% against + # (less than the min required majority of 2/3). + self.court.cancelVote(voters[7], motion_id) + self.court.voteAgainst(voters[7], motion_id) + self.assertFalse(self.court.motionPasses(motion_id)) def test_beginMotion(self): - owner = self.owner() + owner = self.court.owner() accounts = fresh_accounts(6)[1:] insufficient_standing = accounts[0] sufficient_standing = accounts[1] voter = accounts[2] suspects = accounts[3:] - voting_period = self.votingPeriod() - fee_period = self.havvenTargetFeePeriodDurationSeconds() - controlling_share = self.havvenSupply() // 2 + voting_period = self.court.votingPeriod() + fee_period = self.havven.feePeriodDuration() + controlling_share = self.havven.totalSupply() // 2 # Assert that accounts are unique. - l = [owner] + accounts - for i in range(len(l)): - for j in range(len(l)): + li = [owner] + accounts + for i in range(len(li)): + for j in range(len(li)): if j == i: continue - self.assertNotEqual(l[i], l[j]) + self.assertNotEqual(li[i], li[j]) # Give 50% of the havven tokens to voter, enough to pass a confiscation motion on their own. - self.havvenEndow(owner, voter, controlling_share) - self.assertEqual(self.havvenBalance(voter), controlling_share) + self.havven.endow(owner, voter, controlling_share) + self.allow_acc_max_vote(voter) + self.assertEqual(self.havven.balanceOf(voter), controlling_share) + + self.havven.endow(owner, insufficient_standing, 99 * UNIT) + self.allow_acc_max_vote(insufficient_standing) + self.havven.endow(owner, sufficient_standing, 100 * UNIT) + self.allow_acc_max_vote(sufficient_standing) # Fast forward to update the voter's weight. fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) + self.havven.rolloverFeePeriodIfElapsed(MASTER) fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) - self.havvenAdjustFeeEntitlement(voter, voter, self.havvenBalance(voter)) - self.havvenEndow(owner, insufficient_standing, 99 * UNIT) - self.havvenEndow(owner, sufficient_standing, 100 * UNIT) - + self.havven.rolloverFeePeriodIfElapsed(DUMMY) + self.havven.recomputeLastAverageBalance(voter, voter) + self.havven.recomputeLastAverageBalance(insufficient_standing, insufficient_standing) + self.havven.recomputeLastAverageBalance(sufficient_standing,sufficient_standing) # Must have at least 100 havvens to begin a confiscation motion. - self.assertReverts(self.beginMotion, insufficient_standing, suspects[0]) - tx_receipt = self.beginMotion(sufficient_standing, suspects[0]) + self.assertReverts(self.court.beginMotion, insufficient_standing, suspects[0]) + tx_receipt = self.court.beginMotion(sufficient_standing, suspects[0]) # Check that event is emitted properly. - self.assertEqual(get_event_data_from_log(self.court_event_dict, tx_receipt.logs[0])['event'], "MotionBegun") + self.assertEqual(get_event_data_from_log(self.event_map, tx_receipt.logs[-1])['event'], "MotionBegun") motion_id_0 = self.get_motion_index(tx_receipt) - self.assertTrue(self.motionVoting(motion_id_0)) + fast_forward(self.court.motionStartTime(motion_id_0) - block_time() + 1) + + self.assertTrue(self.court.motionVoting(motion_id_0)) # Initial vote balances should be zero. - self.assertEqual(self.votesFor(motion_id_0), 0) - self.assertEqual(self.votesAgainst(motion_id_0), 0) + self.assertEqual(self.court.votesFor(motion_id_0), 0) + self.assertEqual(self.court.votesAgainst(motion_id_0), 0) + + self.court.voteFor(voter, motion_id_0) # The contract owner can also begin a motion, regardless of the token requirement. - motion_id_1 = self.get_motion_index(self.beginMotion(owner, suspects[1])) + self.court.beginMotion(owner, suspects[1]) # Cannot open multiple confiscation motions on one suspect. - self.assertReverts(self.beginMotion, owner, suspects[0]) - self.voteFor(voter, motion_id_0) - fast_forward(voting_period) - self.approveMotion(owner, motion_id_0) - self.assertTrue(self.nominIsFrozen(suspects[0])) + self.assertReverts(self.court.beginMotion, owner, suspects[0]) + fast_forward(voting_period + 1) + self.court.approveMotion(owner, motion_id_0) + self.assertTrue(self.nomin.frozen(suspects[0])) # Cannot open a vote on an account that has already been frozen. - self.assertReverts(self.beginMotion, owner, suspects[0]) + self.assertReverts(self.court.beginMotion, owner, suspects[0]) def test_setupVote(self): - owner = self.owner() + owner = self.court.owner() non_voter, voter, suspect = fresh_accounts(3) voter_weight = 50 * UNIT # Give the voter some voting weight. - self.havvenEndow(owner, voter, voter_weight) - fee_period = self.havvenTargetFeePeriodDurationSeconds() + self.havven.endow(owner, voter, voter_weight) + self.allow_acc_max_vote(voter) + + fee_period = self.havven.feePeriodDuration() fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) # Start the vote itself - motion_id = self.get_motion_index(self.beginMotion(owner, suspect)) + motion_id = self.startVotingPeriod(owner, suspect) # Zero-weight voters should not be able to cast votes. - self.assertEqual(self.voteWeight(non_voter, motion_id), 0) - self.assertReverts(self.setupVote, non_voter, motion_id) + self.assertEqual(self.court.voteWeight(non_voter, motion_id), 0) + self.assertReverts(self.court.setupVote, non_voter, motion_id) # Test that internal function properly updates state - self.assertEqual(self.voteWeight(voter, motion_id), 0) - self.assertEqual(self.vote(voter, motion_id), 0) - self.assertTrue(self.motionVoting(motion_id)) - self.assertFalse(self.hasVoted(voter, motion_id)) - tx_receipt = self.setupVote(voter, motion_id) + self.assertEqual(self.court.voteWeight(voter, motion_id), 0) + self.assertEqual(self.court.vote(voter, motion_id), 0) + self.assertTrue(self.court.motionVoting(motion_id)) + self.assertFalse(self.court.hasVoted(voter, motion_id)) + tx_receipt = self.court.setupVote(voter, motion_id) # Additionally ensure that the vote recomputed the voter's fee totals. - self.assertClose(self.havvenLastAverageBalance(voter), voter_weight) - self.assertClose(self.havvenPenultimateAverageBalance(voter), voter_weight) - self.assertClose(self.voteWeight(voter, motion_id), voter_weight) - self.assertClose(int(tx_receipt.logs[0].data, 16), voter_weight) + self.assertClose(self.havven.issuanceLastAverageBalance(voter), voter_weight) + self.assertClose(self.court.voteWeight(voter, motion_id), voter_weight) + self.assertClose(int(tx_receipt.logs[-1].data, 16), voter_weight) # If already voted, cannot setup again - self.voteFor(voter, motion_id) - self.assertReverts(self.setupVote, voter, motion_id) + self.court.voteFor(voter, motion_id) + self.assertReverts(self.court.setupVote, voter, motion_id) def test_voteFor(self): - owner = self.owner() + owner = self.court.owner() voter, no_tokens, suspect = fresh_accounts(3) - voting_period = self.votingPeriod() - fee_period = self.havvenTargetFeePeriodDurationSeconds() + fee_period = self.havven.feePeriodDuration() # Give some havven tokens to our voter. - self.havvenEndow(owner, voter, 1000) - self.assertEqual(self.havvenBalance(voter), 1000) + self.havven.endow(owner, voter, 1000) + self.allow_acc_max_vote(voter) + + self.assertEqual(self.havven.balanceOf(voter), 1000) # Cannot vote unless there is a confiscation motion. - self.assertReverts(self.voteFor, voter, 10) + self.assertReverts(self.court.voteFor, voter, 10) # Fast forward to update the voter's weight. fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) # Begin a confiscation motion against the suspect. - motion_id = self.get_motion_index(self.beginMotion(owner, suspect)) - self.assertTrue(self.motionVoting(motion_id)) + motion_id = self.startVotingPeriod(owner, suspect) + self.assertTrue(self.court.motionVoting(motion_id)) # Cast a vote in favour of confiscation. - tx_receipt = self.voteFor(voter, motion_id) + tx_receipt = self.court.voteFor(voter, motion_id) # Check that event is emitted properly. - self.assertEqual(get_event_data_from_log(self.court_event_dict, tx_receipt.logs[0])['event'], "VotedFor") + self.assertEqual(get_event_data_from_log(self.event_map, tx_receipt.logs[-1])['event'], "VotedFor") # And that the totals have been updated properly. - self.assertEqual(self.votesFor(motion_id), 1000) - self.assertEqual(self.voteWeight(voter, motion_id), 1000) - self.assertEqual(self.vote(voter, motion_id), 1) + self.assertEqual(self.court.votesFor(motion_id), 1000) + self.assertEqual(self.court.voteWeight(voter, motion_id), 1000) + self.assertEqual(self.court.vote(voter, motion_id), 1) # It should not be possible to cast a repeat vote without cancelling first. - self.assertReverts(self.voteFor, voter, motion_id) - self.assertReverts(self.voteAgainst, voter, motion_id) + self.assertReverts(self.court.voteFor, voter, motion_id) + self.assertReverts(self.court.voteAgainst, voter, motion_id) # It should not be possible to vote without any vote weight. - self.assertReverts(self.voteFor, no_tokens, motion_id) + self.assertReverts(self.court.voteFor, no_tokens, motion_id) # And a target should not be able to vote for themself. - self.assertReverts(self.voteFor, suspect, motion_id) + self.assertReverts(self.court.voteFor, suspect, motion_id) def test_voteAgainst(self): - owner = self.owner() + owner = self.court.owner() voter, no_tokens, suspect = fresh_accounts(3) - voting_period = self.votingPeriod() - fee_period = self.havvenTargetFeePeriodDurationSeconds() + fee_period = self.havven.feePeriodDuration() # Give some havven tokens to our voter. - self.havvenEndow(owner, voter, 1000) - self.assertEqual(self.havvenBalance(voter), 1000) + self.havven.endow(owner, voter, 1000) + self.allow_acc_max_vote(voter) + + self.assertEqual(self.havven.balanceOf(voter), 1000) # Cannot vote unless there is a confiscation motion. - self.assertReverts(self.voteAgainst, voter, 10) + self.assertReverts(self.court.voteAgainst, voter, 10) # Fast forward two fee periods to update the voter's weight. fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) # Begin a confiscation motion against the suspect. - motion_id = self.get_motion_index(self.beginMotion(owner, suspect)) - self.assertTrue(self.motionVoting(motion_id)) + motion_id = self.startVotingPeriod(owner, suspect) + self.assertTrue(self.court.motionVoting(motion_id)) # Cast a vote against confiscation. - tx_receipt = self.voteAgainst(voter, motion_id) + tx_receipt = self.court.voteAgainst(voter, motion_id) # Check that event is emitted properly. - self.assertEqual(get_event_data_from_log(self.court_event_dict, tx_receipt.logs[0])['event'], "VotedAgainst") + self.assertEqual(get_event_data_from_log(self.event_map, tx_receipt.logs[-1])['event'], "VotedAgainst") # And that the totals have been updated properly. - self.assertEqual(self.votesAgainst(motion_id), 1000) - self.assertEqual(self.voteWeight(voter, motion_id), 1000) - self.assertEqual(self.vote(voter, motion_id), 2) + self.assertEqual(self.court.votesAgainst(motion_id), 1000) + self.assertEqual(self.court.voteWeight(voter, motion_id), 1000) + self.assertEqual(self.court.vote(voter, motion_id), 2) # It should not be possible to cast a repeat vote without cancelling first. - self.assertReverts(self.voteFor, voter, motion_id) - self.assertReverts(self.voteAgainst, voter, motion_id) + self.assertReverts(self.court.voteFor, voter, motion_id) + self.assertReverts(self.court.voteAgainst, voter, motion_id) # It should not be possible to vote without any vote weight. - self.assertReverts(self.voteAgainst, no_tokens, motion_id) + self.assertReverts(self.court.voteAgainst, no_tokens, motion_id) # And a target should not be able to vote against themself. - self.assertReverts(self.voteAgainst, suspect, motion_id) + self.assertReverts(self.court.voteAgainst, suspect, motion_id) def test_cancelVote(self): - owner = self.owner() + owner = self.court.owner() voter, voter2, suspect = fresh_accounts(3) - voting_period = self.votingPeriod() - confirmation_period = self.confirmationPeriod() - fee_period = self.havvenTargetFeePeriodDurationSeconds() - + voting_period = self.court.votingPeriod() + confirmation_period = self.court.confirmationPeriod() + fee_period = self.havven.feePeriodDuration() + fast_forward(fee_period + 1) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) # Give some havven tokens to our voter. - self.havvenEndow(owner, voter, 1000) - self.havvenEndow(owner, voter2, 1000) - self.assertEqual(self.havvenBalance(voter), 1000) + self.havven.endow(owner, voter, 1000) + self.allow_acc_max_vote(voter) + self.havven.endow(owner, voter2, 1000) + self.allow_acc_max_vote(voter2) + + self.assertEqual(self.havven.balanceOf(voter), 1000) fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) - self.havvenAdjustFeeEntitlement(voter, voter, self.havvenBalance(voter)) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) + + self.havven.recomputeLastAverageBalance(voter, voter) + self.havven.recomputeLastAverageBalance(voter2, voter2) # Begin a confiscation motion against the suspect. - motion_id = self.get_motion_index(self.beginMotion(owner, suspect)) + motion_id = self.startVotingPeriod(owner, suspect) # Cast a vote in favour of confiscation. - self.voteFor(voter, motion_id) - self.assertEqual(self.votesFor(motion_id), 1000) - tx_receipt = self.cancelVote(voter, motion_id) + self.court.voteFor(voter, motion_id) + self.assertEqual(self.court.votesFor(motion_id), 1000) + tx_receipt = self.court.cancelVote(voter, motion_id) # Check that event is emitted properly. - self.assertEqual(get_event_data_from_log(self.court_event_dict, tx_receipt.logs[0])['event'], "VoteCancelled") - self.assertEqual(self.votesFor(motion_id), 0) - self.assertEqual(self.vote(voter, motion_id), 0) + self.assertEqual(get_event_data_from_log(self.event_map, tx_receipt.logs[0])['event'], "VoteCancelled") + self.assertEqual(self.court.votesFor(motion_id), 0) + self.assertEqual(self.court.vote(voter, motion_id), 0) # Cast a vote against confiscation. - self.voteAgainst(voter, motion_id) - self.assertEqual(self.votesAgainst(motion_id), 1000) - self.cancelVote(voter, motion_id) - self.assertEqual(self.votesAgainst(motion_id), 0) - self.assertEqual(self.vote(voter, motion_id), 0) - self.assertEqual(self.voteWeight(voter, motion_id), 0) + self.court.voteAgainst(voter, motion_id) + self.assertEqual(self.court.votesAgainst(motion_id), 1000) + self.court.cancelVote(voter, motion_id) + self.assertEqual(self.court.votesAgainst(motion_id), 0) + self.assertEqual(self.court.vote(voter, motion_id), 0) + self.assertEqual(self.court.voteWeight(voter, motion_id), 0) # We should be able to re-vote after cancelling, both for and against. - self.voteFor(voter2, motion_id) - self.cancelVote(voter2, motion_id) - self.voteAgainst(voter2, motion_id) - self.cancelVote(voter2, motion_id) - self.voteFor(voter2, motion_id) + self.court.voteFor(voter2, motion_id) + self.court.cancelVote(voter2, motion_id) + self.court.voteAgainst(voter2, motion_id) + self.court.cancelVote(voter2, motion_id) + self.court.voteFor(voter2, motion_id) # Cannot cancel a vote during the confirmation period. - self.voteFor(voter, motion_id) - self.assertEqual(self.vote(voter2, motion_id), 1) + self.court.voteFor(voter, motion_id) + self.assertEqual(self.court.vote(voter2, motion_id), 1) fast_forward(voting_period) - self.assertReverts(self.cancelVote, voter, motion_id) - self.assertEqual(self.vote(voter, motion_id), 1) + self.assertReverts(self.court.cancelVote, voter, motion_id) + self.assertEqual(self.court.vote(voter, motion_id), 1) # Can cancel it after the confirmation period. fast_forward(confirmation_period) - self.cancelVote(voter, motion_id) - self.assertEqual(self.vote(voter, motion_id), 0) - self.assertEqual(self.voteWeight(voter, motion_id), 0) + self.court.cancelVote(voter, motion_id) + self.assertEqual(self.court.vote(voter, motion_id), 0) + self.assertEqual(self.court.voteWeight(voter, motion_id), 0) # And after the vote has been closed. - self.closeMotion(voter2, motion_id) - self.cancelVote(voter2, motion_id) - self.assertEqual(self.vote(voter2, motion_id), 0) - self.assertEqual(self.voteWeight(voter2, motion_id), 0) + self.court.closeMotion(voter2, motion_id) + self.court.cancelVote(voter2, motion_id) + self.assertEqual(self.court.vote(voter2, motion_id), 0) + self.assertEqual(self.court.voteWeight(voter2, motion_id), 0) def test_closeMotion(self): - owner = self.owner() - voter, suspect = fresh_accounts(2) - voting_period = self.votingPeriod() - fee_period = self.havvenTargetFeePeriodDurationSeconds() + owner = self.court.owner() + voter, other, suspect = fresh_accounts(3) + voting_period = self.court.votingPeriod() + fee_period = self.havven.feePeriodDuration() # Give some havven tokens to our voter. - self.havvenEndow(owner, voter, 1000) + self.havven.endow(owner, voter, 1000) + self.havven.endow(owner, other, 10000) - # Fast forward two fee periods to update the voter's weight. - fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) + self.allow_acc_max_vote(voter) + self.allow_acc_max_vote(other) + + # Fast forward one fee period to update the voter's weight. fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) - self.havvenAdjustFeeEntitlement(voter, voter, self.havvenBalance(voter)) - motion_id = self.get_motion_index(self.beginMotion(owner, suspect)) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) + + self.havven.recomputeLastAverageBalance(voter, voter) + self.havven.recomputeLastAverageBalance(other, other) + motion_id = self.startVotingPeriod(owner, suspect) # Should not be able to close vote in the voting period. - self.assertReverts(self.closeMotion, voter, motion_id) - fast_forward(voting_period) - self.assertTrue(self.motionConfirming(motion_id)) - tx_receipt = self.closeMotion(voter, motion_id) + self.assertReverts(self.court.closeMotion, voter, motion_id) + + fast_forward(voting_period + 1) + + self.assertTrue(self.court.motionConfirming(motion_id)) + tx_receipt = self.court.closeMotion(voter, motion_id) # Check that event is emitted properly. - self.assertEqual(get_event_data_from_log(self.court_event_dict, tx_receipt.logs[0])['event'], "MotionClosed") + self.assertEqual(get_event_data_from_log(self.event_map, tx_receipt.logs[0])['event'], "MotionClosed") + + fast_forward(fee_period + 1) # Start another confiscation motion. - motion_id = self.get_motion_index(self.beginMotion(owner, suspect)) - self.voteFor(voter, motion_id) - fast_forward(voting_period) + motion_id = self.startVotingPeriod(owner, suspect) + + self.court.voteFor(voter, motion_id) + fast_forward(voting_period + 1) # After vote has closed, voteStarTimes and votesFor/votesAgainst should be 0 and suspect should be waiting. - self.closeMotion(voter, motion_id) - self.assertEqual(self.targetMotionID(suspect), 0) - self.assertEqual(self.motionTarget(motion_id), ZERO_ADDRESS) - self.assertEqual(self.votesFor(motion_id), 0) - self.assertEqual(self.votesAgainst(motion_id), 0) - self.assertEqual(self.motionStartTime(motion_id), 0) - self.assertTrue(self.motionWaiting(motion_id)) + + self.court.closeMotion(voter, motion_id) + self.assertEqual(self.court.targetMotionID(suspect), 0) + self.assertEqual(self.court.motionTarget(motion_id), ZERO_ADDRESS) + self.assertEqual(self.court.votesFor(motion_id), 0) + self.assertEqual(self.court.votesAgainst(motion_id), 0) + self.assertEqual(self.court.motionStartTime(motion_id), 0) + self.assertTrue(self.court.motionWaiting(motion_id)) def test_approveMotion(self): - owner = self.owner() + owner = self.court.owner() _, voter, guilty = fresh_accounts(3) - voting_period = self.votingPeriod() - fee_period = self.havvenTargetFeePeriodDurationSeconds() - controlling_share = self.havvenSupply() // 2 + voting_period = self.court.votingPeriod() + fee_period = self.havven.feePeriodDuration() + controlling_share = self.havven.totalSupply() // 2 self.assertNotEqual(owner, voter) self.assertNotEqual(owner, guilty) self.assertNotEqual(voter, guilty) # Give 50% of all havven tokens to our voter. - self.havvenEndow(owner, voter, controlling_share) + self.havven.endow(owner, voter, controlling_share) + self.allow_acc_max_vote(voter) # Fast forward two fee periods to update the voter's weight. fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) - fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) - self.havvenAdjustFeeEntitlement(voter, voter, self.havvenBalance(voter)) - tx_receipt = self.beginMotion(owner, guilty) - motion_id = self.get_motion_index(tx_receipt) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) + + motion_id = self.startVotingPeriod(owner, guilty) # Cast a vote in favour of confiscation. - tx_receipt = self.voteFor(voter, motion_id) + self.court.voteFor(voter, motion_id) # It should not be possible to approve in the voting state. - self.assertReverts(self.approveMotion, owner, motion_id) + self.assertReverts(self.court.approveMotion, owner, motion_id) fast_forward(voting_period) - self.assertTrue(self.motionConfirming(motion_id)) + self.assertTrue(self.court.motionConfirming(motion_id)) # Only the owner can approve the confiscation of a balance. - self.assertReverts(self.approveMotion, voter, motion_id) - tx_receipt = self.approveMotion(owner, motion_id) + self.assertReverts(self.court.approveMotion, voter, motion_id) + tx_receipt = self.court.approveMotion(owner, motion_id) - self.assertEqual(get_event_data_from_log(self.nomin_event_dict, tx_receipt.logs[0])['event'], "AccountFrozen") - self.assertEqual(get_event_data_from_log(self.nomin_event_dict, tx_receipt.logs[1])['event'], "Transfer") - self.assertEqual(get_event_data_from_log(self.court_event_dict, tx_receipt.logs[2])['event'], "MotionClosed") - self.assertEqual(get_event_data_from_log(self.court_event_dict, tx_receipt.logs[3])['event'], "MotionApproved") - self.assertEqual(self.motionStartTime(motion_id), 0) - self.assertEqual(self.votesFor(motion_id), 0) + self.assertEqual(get_event_data_from_log(self.event_maps["Nomin"], tx_receipt.logs[0])['event'], "AccountFrozen") + self.assertEqual(get_event_data_from_log(self.event_maps["Nomin"], tx_receipt.logs[1])['event'], "Transfer") + self.assertEqual(get_event_data_from_log(self.event_map, tx_receipt.logs[2])['event'], "MotionClosed") + self.assertEqual(get_event_data_from_log(self.event_map, tx_receipt.logs[3])['event'], "MotionApproved") + self.assertEqual(self.court.motionStartTime(motion_id), 0) + self.assertEqual(self.court.votesFor(motion_id), 0) # After confiscation, their nomin balance should be frozen. - self.assertTrue(self.nominIsFrozen(guilty)) + self.assertTrue(self.nomin.frozen(guilty)) def test_vetoMotion(self): - owner = self.owner() + owner = self.court.owner() voter, acquitted = fresh_accounts(2) - voting_period = self.votingPeriod() - fee_period = self.havvenTargetFeePeriodDurationSeconds() - controlling_share = self.havvenSupply() // 2 - self.havvenEndow(owner, voter, controlling_share) + voting_period = self.court.votingPeriod() + fee_period = self.havven.feePeriodDuration() + controlling_share = self.havven.totalSupply() // 2 + self.havven.endow(owner, voter, controlling_share) + self.allow_acc_max_vote(voter) # Fast forward two fee periods to update the voter's weight. fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) - fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) - self.havvenAdjustFeeEntitlement(voter, voter, self.havvenBalance(voter)) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) # Cannot veto when there is no vote in progress. - self.assertReverts(self.vetoMotion, owner, 10) - motion_id = self.get_motion_index(self.beginMotion(owner, acquitted)) + self.assertReverts(self.court.vetoMotion, owner, 10) + motion_id = self.startVotingPeriod(owner, acquitted) # Only owner can veto. - self.assertReverts(self.vetoMotion, DUMMY, motion_id) - self.vetoMotion(owner, motion_id) + self.assertReverts(self.court.vetoMotion, DUMMY, motion_id) + self.court.vetoMotion(owner, motion_id) # After veto motion, suspect should be back in the waiting stage. - self.assertTrue(self.motionWaiting(motion_id)) - motion_id_2 = self.get_motion_index(self.beginMotion(owner, acquitted)) + self.assertTrue(self.court.motionWaiting(motion_id)) + motion_id_2 = self.startVotingPeriod(owner, acquitted) self.assertNotEqual(motion_id, motion_id_2) - self.voteFor(voter, motion_id_2) - self.assertTrue(self.motionPasses(motion_id_2)) + self.court.voteFor(voter, motion_id_2) + self.assertTrue(self.court.motionPasses(motion_id_2)) fast_forward(voting_period) - self.assertTrue(self.motionConfirming(motion_id_2)) + self.assertTrue(self.court.motionConfirming(motion_id_2)) # Once a vote has been passed, the owner can veto it. - tx_receipt = self.vetoMotion(owner, motion_id_2) + tx_receipt = self.court.vetoMotion(owner, motion_id_2) # Check that event is emitted properly. - self.assertEqual(get_event_data_from_log(self.court_event_dict, tx_receipt.logs[0])['event'], "MotionClosed") - self.assertEqual(get_event_data_from_log(self.court_event_dict, tx_receipt.logs[1])['event'], "MotionVetoed") + self.assertEqual(get_event_data_from_log(self.event_map, tx_receipt.logs[0])['event'], "MotionClosed") + self.assertEqual(get_event_data_from_log(self.event_map, tx_receipt.logs[1])['event'], "MotionVetoed") # After veto motion, suspect should be back in the waiting stage. - self.assertTrue(self.motionWaiting(motion_id)) - self.assertTrue(self.motionWaiting(motion_id_2)) + self.assertTrue(self.court.motionWaiting(motion_id)) + self.assertTrue(self.court.motionWaiting(motion_id_2)) # Votes should be reset. - self.assertEqual(self.motionStartTime(motion_id), 0) - self.assertEqual(self.votesFor(motion_id), 0) - self.assertEqual(self.votesAgainst(motion_id), 0) - self.assertTrue(self.motionWaiting(motion_id)) - self.assertEqual(self.motionStartTime(motion_id_2), 0) - self.assertEqual(self.votesFor(motion_id_2), 0) - self.assertEqual(self.votesAgainst(motion_id_2), 0) - self.assertTrue(self.motionWaiting(motion_id_2)) - - def validate_MotionBegun_data(self, tx_receipt, expected_initiator, expected_target, expected_motionID): - event_data = get_event_data_from_log(self.court_event_dict, tx_receipt.logs[0]) - self.assertEqual(event_data['event'], "MotionBegun") - self.assertEqual(event_data['args']['initiator'], expected_initiator) - self.assertEqual(event_data['args']['target'], expected_target) - self.assertEqual(event_data['args']['motionID'], expected_motionID) - - def validate_MotionClosed_data(self, tx_receipt, log_index, expected_motionID): - closed_data = get_event_data_from_log(self.court_event_dict, tx_receipt.logs[log_index]) - self.assertEqual(closed_data['event'], "MotionClosed") - self.assertEqual(closed_data['args']['motionID'], expected_motionID) + self.assertEqual(self.court.motionStartTime(motion_id), 0) + self.assertEqual(self.court.votesFor(motion_id), 0) + self.assertEqual(self.court.votesAgainst(motion_id), 0) + self.assertTrue(self.court.motionWaiting(motion_id)) + self.assertEqual(self.court.motionStartTime(motion_id_2), 0) + self.assertEqual(self.court.votesFor(motion_id_2), 0) + self.assertEqual(self.court.votesAgainst(motion_id_2), 0) + self.assertTrue(self.court.motionWaiting(motion_id_2)) - def validate_MotionVetoed_data(self, tx_receipt, log_index, expected_motionID): - veto_data = get_event_data_from_log(self.court_event_dict, tx_receipt.logs[log_index]) - self.assertEqual(veto_data['event'], "MotionVetoed") - self.assertEqual(veto_data['args']['motionID'], expected_motionID) - - def validate_MotionApproved_data(self, tx_receipt, log_index, expected_motionID): - approved_data = get_event_data_from_log(self.court_event_dict, tx_receipt.logs[log_index]) - self.assertEqual(approved_data['event'], "MotionApproved") - self.assertEqual(approved_data['args']['motionID'], expected_motionID) - - def validate_Confiscation_data(self, tx_receipt, log_index, expected_target, expected_balance=None): - freeze_data = get_event_data_from_log(self.nomin_event_dict, tx_receipt.logs[log_index]) - self.assertEqual(freeze_data['event'], "AccountFrozen") - self.assertEqual(freeze_data['args']['target'], expected_target) - if expected_balance is not None: - self.assertEqual(freeze_data['args']['balance'], expected_balance) - xfer_data = get_event_data_from_log(self.nomin_event_dict, tx_receipt.logs[log_index + 1]) - self.assertEqual(xfer_data['event'], "Transfer") - def test_multi_vote(self): - owner = self.owner() - voting_period = self.votingPeriod() - confirmation_period = self.confirmationPeriod() - fee_period = self.havvenTargetFeePeriodDurationSeconds() - required_participation = self.requiredParticipation() / self.unit - required_majority = self.requiredMajority() / self.unit + owner = self.court.owner() + voting_period = self.court.votingPeriod() + confirmation_period = self.court.confirmationPeriod() + fee_period = self.havven.feePeriodDuration() + required_participation = self.court.requiredParticipation() / UNIT + required_majority = self.court.requiredMajority() / UNIT # Generate a bunch of voters with equal voting power num_voters = 50 @@ -926,228 +897,243 @@ def test_multi_vote(self): accounts = fresh_accounts(num_voters + num_targets) voters, targets = accounts[:num_voters], accounts[num_voters:] for voter in voters: - self.havvenEndow(owner, voter, self.havvenSupply() // num_voters) + self.havven.endow(owner, voter, self.havven.totalSupply() // num_voters) + self.allow_acc_max_vote(voter) frozen, unfrozen = [], [] - # Update their fee info. + # Fast forward to update the vote weight. fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) + self.havven.rolloverFeePeriodIfElapsed(MASTER) fast_forward(fee_period + 1) - self.havvenCheckFeePeriodRollover(DUMMY) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) - # Run a shitload of votes simultaneously: + # Run a load of votes simultaneously: motions = [] target_index = 0 - motion_id = self.nextMotionID() + motion_id = self.court.nextMotionID() # pass (unanimous) unanimous_target = targets[target_index] frozen.append(unanimous_target) - tx_receipt = self.beginMotion(owner, unanimous_target) + tx_receipt = self.court.beginMotion(owner, unanimous_target) self.validate_MotionBegun_data(tx_receipt, owner, unanimous_target, motion_id) motion_id += 1 unanimous_vote = self.get_motion_index(tx_receipt) target_index += 1 motions.append(unanimous_vote) - for voter in voters: - self.voteFor(voter, unanimous_vote) - for voter in voters: - self.assertTrue(self.hasVoted(voter, unanimous_vote)) # pass (majority) majority_target = targets[target_index] frozen.append(majority_target) - tx_receipt = self.beginMotion(owner, majority_target) + tx_receipt = self.court.beginMotion(owner, majority_target) self.validate_MotionBegun_data(tx_receipt, owner, majority_target, motion_id) motion_id += 1 majority_vote = self.get_motion_index(tx_receipt) target_index += 1 motions.append(majority_vote) - n_yeas = int(num_voters * 0.67) + 1 - yeas, nays = voters[:n_yeas], voters[n_yeas:] - for voter in yeas: - self.voteFor(voter, majority_vote) - for voter in nays: - self.voteAgainst(voter, majority_vote) - for voter in voters: - self.assertTrue(self.hasVoted(voter, majority_vote)) # pass (bare) bare_target = targets[target_index] frozen.append(bare_target) - tx_receipt = self.beginMotion(owner, bare_target) + tx_receipt = self.court.beginMotion(owner, bare_target) self.validate_MotionBegun_data(tx_receipt, owner, bare_target, motion_id) motion_id += 1 bare_majority_vote = self.get_motion_index(tx_receipt) target_index += 1 motions.append(bare_majority_vote) - n_yeas = int(num_voters * required_majority) + 1 - yeas, nays = voters[:n_yeas], voters[n_yeas:] - for voter in yeas: - self.voteFor(voter, bare_majority_vote) - for voter in nays: - self.voteAgainst(voter, bare_majority_vote) - for voter in voters: - self.assertTrue(self.hasVoted(voter, bare_majority_vote)) # pass (barely enough participation) quorum_target = targets[target_index] frozen.append(quorum_target) - tx_receipt = self.beginMotion(owner, quorum_target) + tx_receipt = self.court.beginMotion(owner, quorum_target) self.validate_MotionBegun_data(tx_receipt, owner, quorum_target, motion_id) motion_id += 1 bare_quorum_vote = self.get_motion_index(tx_receipt) target_index += 1 motions.append(bare_quorum_vote) - bare_quorum = int(num_voters * required_participation) + 1 - n_yeas = int(bare_quorum * required_majority) + 1 - yeas, nays = voters[:n_yeas], voters[n_yeas:bare_quorum] - for voter in yeas: - self.voteFor(voter, bare_quorum_vote) - for voter in nays: - self.voteAgainst(voter, bare_quorum_vote) - for voter in voters[:bare_quorum]: - self.assertTrue(self.hasVoted(voter, bare_quorum_vote)) - for voter in voters[bare_quorum:]: - self.assertFalse(self.hasVoted(voter, bare_quorum_vote)) # fail (just-insufficient participation) target = targets[target_index] unfrozen.append(target) - tx_receipt = self.beginMotion(owner, target) + tx_receipt = self.court.beginMotion(owner, target) self.validate_MotionBegun_data(tx_receipt, owner, target, motion_id) motion_id += 1 not_quite_quorum_vote = self.get_motion_index(tx_receipt) target_index += 1 motions.append(not_quite_quorum_vote) - not_quite_quorum = int(num_voters * 0.3) - 1 - n_yeas = (not_quite_quorum // 2) + 1 - yeas, nays = voters[:n_yeas], voters[n_yeas:not_quite_quorum] - for voter in yeas: - self.voteFor(voter, not_quite_quorum_vote) - for voter in nays: - self.voteAgainst(voter, not_quite_quorum_vote) - for voter in voters[:not_quite_quorum]: - self.assertTrue(self.hasVoted(voter, not_quite_quorum_vote)) - for voter in voters[not_quite_quorum:]: - self.assertFalse(self.hasVoted(voter, not_quite_quorum_vote)) # fail (zero participation) target = targets[target_index] unfrozen.append(target) - tx_receipt = self.beginMotion(owner, target) + tx_receipt = self.court.beginMotion(owner, target) self.validate_MotionBegun_data(tx_receipt, owner, target, motion_id) motion_id += 1 zero_participation_vote = self.get_motion_index(tx_receipt) target_index += 1 motions.append(zero_participation_vote) - for voter in voters: - self.assertFalse(self.hasVoted(voter, zero_participation_vote)) # fail (insufficient majority) target = targets[target_index] unfrozen.append(target) - tx_receipt = self.beginMotion(owner, target) + tx_receipt = self.court.beginMotion(owner, target) self.validate_MotionBegun_data(tx_receipt, owner, target, motion_id) motion_id += 1 insufficient_majority_vote = self.get_motion_index(tx_receipt) target_index += 1 motions.append(insufficient_majority_vote) - n_yeas = int(num_voters * 0.66) - 1 - yeas, nays = voters[:n_yeas], voters[n_yeas:] - for voter in yeas: - self.voteFor(voter, insufficient_majority_vote) - for voter in nays: - self.voteAgainst(voter, insufficient_majority_vote) - for voter in voters: - self.assertTrue(self.hasVoted(voter, insufficient_majority_vote)) # fail (zero majority) target = targets[target_index] unfrozen.append(target) - tx_receipt = self.beginMotion(owner, target) + tx_receipt = self.court.beginMotion(owner, target) self.validate_MotionBegun_data(tx_receipt, owner, target, motion_id) motion_id += 1 no_majority_vote = self.get_motion_index(tx_receipt) target_index += 1 motions.append(no_majority_vote) - for voter in voters: - self.voteAgainst(voter, no_majority_vote) - for voter in voters: - self.assertTrue(self.hasVoted(voter, no_majority_vote)) # fail (timeout) target = targets[target_index] unfrozen.append(target) - tx_receipt = self.beginMotion(owner, target) + tx_receipt = self.court.beginMotion(owner, target) self.validate_MotionBegun_data(tx_receipt, owner, target, motion_id) motion_id += 1 timeout_vote = self.get_motion_index(tx_receipt) target_index += 1 motions.append(timeout_vote) - for voter in voters: - self.voteFor(voter, timeout_vote) - for voter in voters: - self.assertTrue(self.hasVoted(voter, timeout_vote)) # fail (veto during proceedings) target = targets[target_index] unfrozen.append(target) - tx_receipt = self.beginMotion(owner, target) + tx_receipt = self.court.beginMotion(owner, target) self.validate_MotionBegun_data(tx_receipt, owner, target, motion_id) motion_id += 1 mid_veto_vote = self.get_motion_index(tx_receipt) target_index += 1 motions.append(mid_veto_vote) - for voter in voters: - self.voteFor(voter, mid_veto_vote) - for voter in voters: - self.assertTrue(self.hasVoted(voter, mid_veto_vote)) # fail (veto during confirmation) target = targets[target_index] unfrozen.append(target) - tx_receipt = self.beginMotion(owner, target) + tx_receipt = self.court.beginMotion(owner, target) self.validate_MotionBegun_data(tx_receipt, owner, target, motion_id) motion_id += 1 post_veto_vote = self.get_motion_index(tx_receipt) target_index += 1 motions.append(post_veto_vote) - for voter in voters: - self.voteFor(voter, post_veto_vote) - for voter in voters: - self.assertTrue(self.hasVoted(voter, post_veto_vote)) + + fast_forward(self.court.motionStartTime(motion_id-1) - block_time() + 1) # All these motions should now be voting. for motion in motions: - self.assertTrue(self.motionVoting(motion)) + self.assertTrue(self.court.motionVoting(motion)) + + # Have all voters vote proportionally on every target + for voter in voters: + self.court.voteFor(voter, unanimous_vote) + for voter in voters: + self.assertTrue(self.court.hasVoted(voter, unanimous_vote)) + + n_yeas = int(num_voters * 0.67) + 1 + yeas, nays = voters[:n_yeas], voters[n_yeas:] + for voter in yeas: + self.court.voteFor(voter, majority_vote) + for voter in nays: + self.court.voteAgainst(voter, majority_vote) + for voter in voters: + self.assertTrue(self.court.hasVoted(voter, majority_vote)) + + n_yeas = int(num_voters * required_majority) + 1 + yeas, nays = voters[:n_yeas], voters[n_yeas:] + for voter in yeas: + self.court.voteFor(voter, bare_majority_vote) + for voter in nays: + self.court.voteAgainst(voter, bare_majority_vote) + for voter in voters: + self.assertTrue(self.court.hasVoted(voter, bare_majority_vote)) + + bare_quorum = int(num_voters * required_participation) + 1 + n_yeas = int(bare_quorum * required_majority) + 1 + yeas, nays = voters[:n_yeas], voters[n_yeas:bare_quorum] + for voter in yeas: + self.court.voteFor(voter, bare_quorum_vote) + for voter in nays: + self.court.voteAgainst(voter, bare_quorum_vote) + for voter in voters[:bare_quorum]: + self.assertTrue(self.court.hasVoted(voter, bare_quorum_vote)) + for voter in voters[bare_quorum:]: + self.assertFalse(self.court.hasVoted(voter, bare_quorum_vote)) + + not_quite_quorum = int(num_voters * 0.3) - 1 + n_yeas = (not_quite_quorum // 2) + 1 + yeas, nays = voters[:n_yeas], voters[n_yeas:not_quite_quorum] + + fast_forward(self.court.motionStartTime(motion_id) - block_time() + 1) + + for voter in yeas: + self.court.voteFor(voter, not_quite_quorum_vote) + for voter in nays: + self.court.voteAgainst(voter, not_quite_quorum_vote) + for voter in voters[:not_quite_quorum]: + self.assertTrue(self.court.hasVoted(voter, not_quite_quorum_vote)) + for voter in voters[not_quite_quorum:]: + self.assertFalse(self.court.hasVoted(voter, not_quite_quorum_vote)) + + for voter in voters: + self.assertFalse(self.court.hasVoted(voter, zero_participation_vote)) + + n_yeas = int(num_voters * 0.66) - 1 + yeas, nays = voters[:n_yeas], voters[n_yeas:] + for voter in yeas: + self.court.voteFor(voter, insufficient_majority_vote) + for voter in nays: + self.court.voteAgainst(voter, insufficient_majority_vote) + for voter in voters: + self.assertTrue(self.court.hasVoted(voter, insufficient_majority_vote)) + + for voter in voters: + self.court.voteAgainst(voter, no_majority_vote) + for voter in voters: + self.assertTrue(self.court.hasVoted(voter, no_majority_vote)) + + for voter in voters: + self.court.voteFor(voter, timeout_vote) + for voter in voters: + self.assertTrue(self.court.hasVoted(voter, timeout_vote)) + + for voter in voters: + self.court.voteFor(voter, mid_veto_vote) + for voter in voters: + self.assertTrue(self.court.hasVoted(voter, mid_veto_vote)) + + for voter in voters: + self.court.voteFor(voter, post_veto_vote) + for voter in voters: + self.assertTrue(self.court.hasVoted(voter, post_veto_vote)) # Fast forward to mid voting period... fast_forward(voting_period // 2) for motion in motions: - self.assertTrue(self.motionVoting(motion)) + self.assertTrue(self.court.motionVoting(motion)) - tx_receipt = self.vetoMotion(owner, mid_veto_vote) + tx_receipt = self.court.vetoMotion(owner, mid_veto_vote) self.validate_MotionClosed_data(tx_receipt, 0, mid_veto_vote) self.validate_MotionVetoed_data(tx_receipt, 1, mid_veto_vote) - self.assertTrue(self.motionWaiting(mid_veto_vote)) + self.assertTrue(self.court.motionWaiting(mid_veto_vote)) - fast_forward(voting_period // 2 + 1) + fast_forward(voting_period // 2 + 10) for motion, target in [(unanimous_vote, unanimous_target), (majority_vote, majority_target), (bare_majority_vote, bare_target), (bare_quorum_vote, quorum_target)]: - self.assertTrue(self.motionConfirming(motion)) + self.assertTrue(self.court.motionConfirming(motion)) - yeas = self.votesFor(motion) - nays = self.votesAgainst(motion) - totalVotes = yeas + nays - self.assertTrue(self.motionPasses(motion)) + self.assertTrue(self.court.motionPasses(motion)) - tx_receipt = self.approveMotion(owner, motion) - self.assertTrue(self.motionWaiting(motion)) - self.assertTrue(self.nominIsFrozen(target)) + tx_receipt = self.court.approveMotion(owner, motion) + self.assertTrue(self.court.motionWaiting(motion)) + self.assertTrue(self.nomin.frozen(target)) self.validate_Confiscation_data(tx_receipt, 0, target) self.validate_MotionClosed_data(tx_receipt, 2, motion) @@ -1155,24 +1141,21 @@ def test_multi_vote(self): for motion in [not_quite_quorum_vote, zero_participation_vote, insufficient_majority_vote, no_majority_vote]: - self.assertTrue(self.motionConfirming(motion)) - tx_receipt = self.closeMotion(owner, motion) + self.assertTrue(self.court.motionConfirming(motion)) + tx_receipt = self.court.closeMotion(owner, motion) self.validate_MotionClosed_data(tx_receipt, 0, motion) - self.assertTrue(self.motionWaiting(motion)) + self.assertTrue(self.court.motionWaiting(motion)) - self.assertTrue(self.motionConfirming(post_veto_vote)) - self.assertReverts(self.closeMotion, owner, post_veto_vote) - tx_receipt = self.vetoMotion(owner, post_veto_vote) + self.assertTrue(self.court.motionConfirming(post_veto_vote)) + self.assertReverts(self.court.closeMotion, owner, post_veto_vote) + tx_receipt = self.court.vetoMotion(owner, post_veto_vote) self.validate_MotionClosed_data(tx_receipt, 0, post_veto_vote) self.validate_MotionVetoed_data(tx_receipt, 1, post_veto_vote) - self.assertTrue(self.motionWaiting(post_veto_vote)) + self.assertTrue(self.court.motionWaiting(post_veto_vote)) - self.assertTrue(self.motionConfirming(timeout_vote)) - self.assertReverts(self.closeMotion, owner, timeout_vote) + self.assertTrue(self.court.motionConfirming(timeout_vote)) + self.assertReverts(self.court.closeMotion, owner, timeout_vote) fast_forward(confirmation_period + 1) - self.assertTrue(self.motionWaiting(timeout_vote)) - self.closeMotion(owner, timeout_vote) - self.assertTrue(self.motionWaiting(timeout_vote)) - - def test_weight_invariance(self): - pass + self.assertTrue(self.court.motionWaiting(timeout_vote)) + self.court.closeMotion(owner, timeout_vote) + self.assertTrue(self.court.motionWaiting(timeout_vote)) diff --git a/tests/test_Deploy.py b/tests/test_Deploy.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/test_EtherNomin.py b/tests/test_EtherNomin.py deleted file mode 100644 index aeff0e3733..0000000000 --- a/tests/test_EtherNomin.py +++ /dev/null @@ -1,1277 +0,0 @@ -import unittest -import time - -import utils.generalutils -from utils.generalutils import to_seconds -from utils.deployutils import W3, UNIT, MASTER, DUMMY, ETHER -from utils.deployutils import compile_contracts, attempt_deploy, mine_tx -from utils.deployutils import take_snapshot, restore_snapshot, fast_forward -from utils.testutils import assertReverts, block_time, send_value, get_eth_balance -from utils.testutils import generate_topic_event_map, get_event_data_from_log -from utils.testutils import ZERO_ADDRESS - -ETHERNOMIN_SOURCE = "tests/contracts/PublicEtherNomin.sol" -FAKECOURT_SOURCE = "tests/contracts/FakeCourt.sol" -PROXY_SOURCE = "contracts/Proxy.sol" - - -def setUpModule(): - print("Testing EtherNomin...") - - -def tearDownModule(): - print() - - -class TestEtherNomin(unittest.TestCase): - def setUp(self): - self.snapshot = take_snapshot() - utils.generalutils.time_fast_forwarded = 0 - self.initial_time = round(time.time()) - # Reset the price at the start of tests so that it's never stale. - self.updatePrice(self.oracle(), self.etherPrice(), self.now_block_time() + 1) - fast_forward(2) - # Reset the liquidation timestamp so that it's never active. - owner = self.owner() - self.forceLiquidation(owner) - self.terminateLiquidation(owner) - - def tearDown(self): - restore_snapshot(self.snapshot) - - def _test_time_elapsed(self): - return utils.generalutils.time_fast_forwarded + (round(time.time()) - self.initial_time) - - def now_block_time(self): - return block_time() + self._test_time_elapsed() - - @classmethod - def setUpClass(cls): - cls.assertReverts = assertReverts - - compiled = compile_contracts([ETHERNOMIN_SOURCE, FAKECOURT_SOURCE, PROXY_SOURCE], - remappings=['""=contracts']) - cls.nomin_abi = compiled['PublicEtherNomin']['abi'] - cls.nomin_event_dict = generate_topic_event_map(cls.nomin_abi) - - cls.nomin_havven = W3.eth.accounts[1] - cls.nomin_oracle = W3.eth.accounts[2] - cls.nomin_beneficiary = W3.eth.accounts[3] - cls.nomin_owner = W3.eth.accounts[0] - - cls.nomin_real, cls.construction_txr = attempt_deploy(compiled, 'PublicEtherNomin', MASTER, - [cls.nomin_havven, cls.nomin_oracle, - cls.nomin_beneficiary, - 1000 * UNIT, cls.nomin_owner, ZERO_ADDRESS]) - cls.construction_price_time = cls.nomin_real.functions.lastPriceUpdateTime().call() - cls.initial_time = cls.construction_price_time - - cls.fake_court, _ = attempt_deploy(compiled, 'FakeCourt', MASTER, []) - - cls.fake_court.setNomin = lambda sender, new_nomin: mine_tx( - cls.fake_court.functions.setNomin(new_nomin).transact({'from': sender})) - cls.fake_court.setConfirming = lambda sender, target, status: mine_tx( - cls.fake_court.functions.setConfirming(target, status).transact({'from': sender})) - cls.fake_court.setVotePasses = lambda sender, target, status: mine_tx( - cls.fake_court.functions.setVotePasses(target, status).transact({'from': sender})) - cls.fake_court.setTargetMotionID = lambda sender, target, motion_id: mine_tx( - cls.fake_court.functions.setTargetMotionID(target, motion_id).transact({'from': sender})) - cls.fake_court.confiscateBalance = lambda sender, target: mine_tx( - cls.fake_court.functions.confiscateBalance(target).transact({'from': sender})) - cls.fake_court.setNomin(W3.eth.accounts[0], cls.nomin_real.address) - - cls.nomin_proxy, _ = attempt_deploy(compiled, 'Proxy', - MASTER, [cls.nomin_real.address, cls.nomin_owner]) - mine_tx(cls.nomin_real.functions.setProxy(cls.nomin_proxy.address).transact({'from': cls.nomin_owner})) - #cls.nomin = W3.eth.contract(address=cls.nomin_proxy.address, abi=compiled['PublicEtherNomin']['abi']) - cls.nomin = cls.nomin_real - - mine_tx(cls.nomin_real.functions.setCourt(cls.fake_court.address).transact({'from': cls.nomin_owner})) - - cls.owner = lambda self: cls.nomin.functions.owner().call() - cls.oracle = lambda self: cls.nomin.functions.oracle().call() - cls.court = lambda self: cls.nomin.functions.court().call() - cls.beneficiary = lambda self: cls.nomin.functions.beneficiary().call() - cls.nominPool = lambda self: cls.nomin.functions.nominPool().call() - cls.poolFeeRate = lambda self: cls.nomin.functions.poolFeeRate().call() - cls.liquidationPeriod = lambda self: cls.nomin.functions.liquidationPeriod().call() - cls.liquidationTimestamp = lambda self: cls.nomin.functions.liquidationTimestamp().call() - cls.etherPrice = lambda self: cls.nomin.functions.etherPrice().call() - cls.frozen = lambda self, address: cls.nomin.functions.frozen(address).call() - cls.lastPriceUpdateTime = lambda self: cls.nomin.functions.lastPriceUpdateTime().call() - cls.stalePeriod = lambda self: cls.nomin.functions.stalePeriod().call() - - cls.nominateOwner = lambda self, sender, address: mine_tx( - cls.nomin.functions.nominateOwner(address).transact({'from': sender})) - cls.acceptOwnership = lambda self, sender: mine_tx( - cls.nomin.functions.acceptOwnership().transact({'from': sender})) - cls.setOracle = lambda self, sender, address: mine_tx( - cls.nomin.functions.setOracle(address).transact({'from': sender})) - cls.setCourt = lambda self, sender, address: mine_tx( - cls.nomin.functions.setCourt(address).transact({'from': sender})) - cls.setBeneficiary = lambda self, sender, address: mine_tx( - cls.nomin.functions.setBeneficiary(address).transact({'from': sender})) - cls.setPoolFeeRate = lambda self, sender, rate: mine_tx( - cls.nomin.functions.setPoolFeeRate(rate).transact({'from': sender})) - cls.updatePrice = lambda self, sender, price, timeSent: mine_tx( - cls.nomin_real.functions.updatePrice(price, timeSent).transact({'from': sender})) - cls.setStalePeriod = lambda self, sender, period: mine_tx( - cls.nomin.functions.setStalePeriod(period).transact({'from': sender})) - - cls.fiatValue = lambda self, eth: cls.nomin.functions.fiatValue(eth).call() - cls.fiatBalance = lambda self: cls.nomin.functions.fiatBalance().call() - cls.collateralisationRatio = lambda self: cls.nomin.functions.collateralisationRatio().call() - cls.etherValue = lambda self, fiat: cls.nomin.functions.etherValue(fiat).call() - cls.etherValueAllowStale = lambda self, fiat: cls.nomin.functions.publicEtherValueAllowStale(fiat).call() - cls.poolFeeIncurred = lambda self, n: cls.nomin.functions.poolFeeIncurred(n).call() - cls.purchaseCostFiat = lambda self, n: cls.nomin.functions.purchaseCostFiat(n).call() - cls.purchaseCostEther = lambda self, n: cls.nomin.functions.purchaseCostEther(n).call() - cls.saleProceedsFiat = lambda self, n: cls.nomin.functions.saleProceedsFiat(n).call() - cls.saleProceedsEther = lambda self, n: cls.nomin.functions.saleProceedsEther(n).call() - cls.saleProceedsEtherAllowStale = lambda self, n: cls.nomin.functions.publicSaleProceedsEtherAllowStale( - n).call() - cls.priceIsStale = lambda self: cls.nomin.functions.priceIsStale().call() - cls.isLiquidating = lambda self: cls.nomin.functions.isLiquidating().call() - cls.canSelfDestruct = lambda self: cls.nomin.functions.canSelfDestruct().call() - - cls.transferPlusFee = lambda self, value: cls.nomin.functions.transferPlusFee(value).call() - cls.transfer = lambda self, sender, recipient, value: mine_tx( - cls.nomin.functions.transfer(recipient, value).transact({'from': sender})) - cls.transferFrom = lambda self, sender, fromAccount, to, value: mine_tx( - cls.nomin.functions.transferFrom(fromAccount, to, value).transact({'from': sender})) - cls.approve = lambda self, sender, spender, value: mine_tx( - cls.nomin.functions.approve(spender, value).transact({'from': sender})) - cls.replenishPool = lambda self, sender, n, value: mine_tx( - cls.nomin.functions.replenishPool(n).transact({'from': sender, 'value': value})) - cls.diminishPool = lambda self, sender, n: mine_tx(cls.nomin.functions.diminishPool(n).transact({'from': sender})) - cls.buy = lambda self, sender, n, value: mine_tx( - cls.nomin.functions.buy(n).transact({'from': sender, 'value': value})) - cls.sell = lambda self, sender, n: mine_tx( - cls.nomin.functions.sell(n).transact({'from': sender, 'gasPrice': 10})) - - cls.forceLiquidation = lambda self, sender: mine_tx( - cls.nomin.functions.forceLiquidation().transact({'from': sender})) - cls.liquidate = lambda self, sender: mine_tx(cls.nomin.functions.liquidate().transact({'from': sender})) - cls.extendLiquidationPeriod = lambda self, sender, extension: mine_tx( - cls.nomin.functions.extendLiquidationPeriod(extension).transact({'from': sender})) - cls.terminateLiquidation = lambda self, sender: mine_tx( - cls.nomin.functions.terminateLiquidation().transact({'from': sender})) - cls.selfDestruct = lambda self, sender: mine_tx(cls.nomin.functions.selfDestruct().transact({'from': sender})) - - cls.confiscateBalance = lambda self, sender, target: mine_tx( - cls.nomin.functions.confiscateBalance(target).transact({'from': sender})) - cls.unfreezeAccount = lambda self, sender, target: mine_tx( - cls.nomin.functions.unfreezeAccount(target).transact({'from': sender})) - - cls.name = lambda self: cls.nomin.functions.name().call() - cls.symbol = lambda self: cls.nomin.functions.symbol().call() - cls.totalSupply = lambda self: cls.nomin.functions.totalSupply().call() - cls.balanceOf = lambda self, account: cls.nomin.functions.balanceOf(account).call() - cls.transferFeeRate = lambda self: cls.nomin.functions.transferFeeRate().call() - cls.feePool = lambda self: cls.nomin.functions.feePool().call() - cls.feeAuthority = lambda self: cls.nomin.functions.feeAuthority().call() - - cls.debugWithdrawAllEther = lambda self, sender, recipient: mine_tx( - cls.nomin.functions.debugWithdrawAllEther(recipient).transact({'from': sender})) - cls.debugEmptyFeePool = lambda self, sender: mine_tx( - cls.nomin.functions.debugEmptyFeePool().transact({'from': sender})) - cls.debugFreezeAccount = lambda self, sender, target: mine_tx( - cls.nomin.functions.debugFreezeAccount(target).transact({'from': sender})) - - def test_constructor(self): - # Nomin-specific members - self.assertEqual(self.owner(), self.nomin_owner) - self.assertEqual(self.oracle(), self.nomin_oracle) - self.assertEqual(self.beneficiary(), self.nomin_beneficiary) - self.assertEqual(self.etherPrice(), 1000 * UNIT) - self.assertEqual(self.stalePeriod(), 60 * 60) # default one hour - self.assertEqual(self.liquidationTimestamp(), 2**256 - 1) - self.assertEqual(self.liquidationPeriod(), 14 * 24 * 60 * 60) # default fourteen days - self.assertEqual(self.poolFeeRate(), UNIT / 200) # default fifty basis points - self.assertEqual(self.nominPool(), 0) - construct_time = block_time(self.construction_txr.blockNumber) - self.assertEqual(construct_time, self.construction_price_time) - self.assertTrue(self.frozen(self.nomin_real.address)) - - # ExternStateProxyFeeToken members - self.assertEqual(self.name(), "Ether-Backed USD Nomins") - self.assertEqual(self.symbol(), "eUSD") - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.balanceOf(MASTER), 0) - self.assertEqual(self.transferFeeRate(), 15 * UNIT // 10000) - self.assertEqual(self.feeAuthority(), self.nomin_havven) - self.assertEqual(self.nomin.functions.decimals().call(), 18) - - def test_getSetOwner(self): - pre_owner = self.owner() - new_owner = DUMMY - - # Only the owner must be able to set the owner. - self.assertReverts(self.nominateOwner, new_owner, new_owner) - self.nominateOwner(pre_owner, new_owner) - self.acceptOwnership(new_owner) - self.assertEqual(self.owner(), new_owner) - - def test_getSetOracle(self): - pre_oracle = self.oracle() - new_oracle = DUMMY - - # Only the owner must be able to set the oracle. - self.assertReverts(self.setOracle, new_oracle, new_oracle) - - self.setOracle(self.owner(), new_oracle) - self.assertEqual(self.oracle(), new_oracle) - - def test_getSetCourt(self): - new_court = DUMMY - - # Only the owner must be able to set the court. - self.assertReverts(self.setOracle, new_court, new_court) - - self.setCourt(self.owner(), new_court) - self.assertEqual(self.court(), new_court) - - def test_getSetBeneficiary(self): - new_beneficiary = DUMMY - - # Only the owner must be able to set the beneficiary. - self.assertReverts(self.setBeneficiary, new_beneficiary, new_beneficiary) - - self.setBeneficiary(self.owner(), new_beneficiary) - self.assertEqual(self.beneficiary(), new_beneficiary) - - def test_getSetPoolFeeRate(self): - owner = self.owner() - new_rate = UNIT // 10 - - # Only the owner must be able to set the pool fee rate. - self.assertReverts(self.setPoolFeeRate, DUMMY, new_rate) - - # Pool fee rate must be no greater than UNIT. - self.assertReverts(self.setPoolFeeRate, owner, UNIT + 1) - self.assertReverts(self.setPoolFeeRate, owner, 2**256 - 1) - self.setPoolFeeRate(owner, UNIT) - self.assertEqual(self.poolFeeRate(), UNIT) - - self.setPoolFeeRate(owner, new_rate) - self.assertEqual(self.poolFeeRate(), new_rate) - - def test_getSetStalePeriod(self): - owner = self.owner() - new_period = 52 * 7 * 24 * 60 * 60 - - # Only the owner should be able to set the pool fee rate. - self.assertReverts(self.setStalePeriod, DUMMY, new_period) - - self.setStalePeriod(owner, new_period) - self.assertEqual(self.stalePeriod(), new_period) - - def test_updatePrice(self): - owner = self.owner() - pre_price = self.etherPrice() - new_price = 10**8 * UNIT # one hundred million dollar ethers $$$$$$ - new_price2 = UNIT // 10**6 # one ten thousandth of a cent ethers :( - pre_oracle = self.oracle() - new_oracle = DUMMY - - # Only the oracle must be able to set the current price. - self.assertReverts(self.updatePrice, new_oracle, new_price, self.now_block_time()) - - # Check if everything works with nothing in the pool. - t = self.now_block_time() - tx_receipt = self.updatePrice(pre_oracle, new_price, t) - tx_time = W3.eth.getBlock(tx_receipt.blockNumber)['timestamp'] - fast_forward(2) - self.assertEqual(self.lastPriceUpdateTime(), t) - self.assertEqual(self.etherPrice(), new_price) - - self.setOracle(owner, new_oracle) - - self.assertReverts(self.updatePrice, pre_oracle, pre_price, self.now_block_time()) - - t = self.now_block_time() - tx_receipt = self.updatePrice(new_oracle, new_price2, t) - tx_time = W3.eth.getBlock(tx_receipt.blockNumber)['timestamp'] - fast_forward(2) - - self.assertEqual(self.lastPriceUpdateTime(), t) - self.assertEqual(self.etherPrice(), new_price2) - - # Check if everything works with something in the pool. - self.updatePrice(new_oracle, UNIT, self.now_block_time()) - fast_forward(2) - backing = self.etherValue(10 * UNIT) - self.replenishPool(owner, UNIT, backing) - - t = self.now_block_time() - tx_receipt = self.updatePrice(new_oracle, pre_price, t) - fast_forward(2) - tx_time = W3.eth.getBlock(tx_receipt.blockNumber)['timestamp'] - self.assertEqual(self.lastPriceUpdateTime(), t) - self.assertEqual(self.etherPrice(), pre_price) - - # Check that an old transaction doesn't overwrite a new one. - t = self.now_block_time() - tx_receipt = self.updatePrice(new_oracle, pre_price, t) - tx_time = W3.eth.getBlock(tx_receipt.blockNumber)['timestamp'] - self.assertEqual(self.lastPriceUpdateTime(), t) - self.assertEqual(self.etherPrice(), pre_price) - # A transaction that was sent 10 seconds before the above one should fail. - self.assertReverts(self.updatePrice, new_oracle, new_price2, t - 10) - fast_forward(2) - - # Check that a transaction with the same sentTime doesn't overwrite the most recently received one. - t = self.now_block_time() - tx_receipt = self.updatePrice(new_oracle, pre_price, t) - tx_time = W3.eth.getBlock(tx_receipt.blockNumber)['timestamp'] - self.assertEqual(self.lastPriceUpdateTime(), t) - self.assertEqual(self.etherPrice(), pre_price) - # A transaction that was sent at the same time as the last one should fail. - self.assertReverts(self.updatePrice, new_oracle, new_price2, t) - # A transaction send more than 10 minutes in the future should not work. - self.assertReverts(self.updatePrice, new_oracle, new_price2, t + to_seconds(minutes=10) + 10) - # ...but 9 minutes should work. - self.updatePrice(new_oracle, new_price2, t + to_seconds(minutes=9)) - - def test_fiatValue(self): - oracle = self.oracle() - - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.fiatValue(ETHER), ETHER) - self.assertEqual(self.fiatValue(777 * ETHER), 777 * ETHER) - self.assertEqual(self.fiatValue(ETHER // 777), ETHER // 777) - self.assertEqual(self.fiatValue(10**8 * ETHER), 10**8 * ETHER) - self.assertEqual(self.fiatValue(ETHER // 10**12), ETHER // 10**12) - - self.updatePrice(oracle, 10**8 * UNIT, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.fiatValue(ETHER), 10**8 * ETHER) - self.assertEqual(self.fiatValue(317 * ETHER), 317 * 10**8 * ETHER) - self.assertEqual(self.fiatValue(ETHER // 317), 10**8 * (ETHER // 317)) - self.assertEqual(self.fiatValue(10**8 * ETHER), 10**16 * ETHER) - self.assertEqual(self.fiatValue(ETHER // 10**12), ETHER // 10**4) - - self.updatePrice(oracle, UNIT // 10**12, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.fiatValue(ETHER), ETHER // 10**12) - self.assertEqual(self.fiatValue(10**15 * ETHER), 10**3 * ETHER) - self.assertEqual(self.fiatValue((7 * ETHER) // 3), ((7 * ETHER) // 3) // 10**12) - - def test_fiatBalance(self): - owner = self.owner() - oracle = self.oracle() - pre_price = self.etherPrice() - - send_value(owner, self.nomin_real.address, ETHER // 2) - send_value(owner, self.nomin.address, ETHER // 2) - self.assertEqual(self.fiatBalance(), pre_price) - self.updatePrice(oracle, UNIT // 10**12, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.fiatBalance(), UNIT // 10**12) - self.updatePrice(oracle, 300 * UNIT, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.fiatBalance(), 300 * UNIT) - send_value(owner, self.nomin_real.address, ETHER // 2) - send_value(owner, self.nomin.address, ETHER // 2) - self.assertEqual(self.fiatBalance(), 600 * UNIT) - - def test_etherValue(self): - oracle = self.oracle() - - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.etherValue(UNIT), ETHER) - self.assertEqual(self.etherValue(777 * UNIT), 777 * ETHER) - self.assertEqual(self.etherValue(UNIT // 777), ETHER // 777) - self.assertEqual(self.etherValue(10**8 * UNIT), 10**8 * ETHER) - self.assertEqual(self.etherValue(UNIT // 10**12), ETHER // 10**12) - - self.updatePrice(oracle, 10 * UNIT, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.etherValue(UNIT), ETHER // 10) - self.assertEqual(self.etherValue(2 * UNIT), ETHER // 5) - - for v in [0.0004, 2.1, 1, 49994, 49.29384, 0.00000028, 1235759872, 2.5 * 10**25]: - vi = int(v * UNIT) - self.assertEqual(self.etherValue(vi), self.etherValueAllowStale(vi)) - - def test_collateralisationRatio(self): - owner = self.owner() - oracle = self.oracle() - - # When there are no nomins in the contract, a zero denominator causes reversion. - self.assertReverts(self.collateralisationRatio) - - # Set the ether price to $1, and issue one nomin against 2 ether. - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.replenishPool(owner, UNIT, 2 * ETHER) - self.assertEqual(self.collateralisationRatio(), 2 * UNIT) - - # Set the ether price to $2, now the collateralisation ratio should double to 4. - self.updatePrice(oracle, 2 * UNIT, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.collateralisationRatio(), 4 * UNIT) - - # Now set the ether price to 50 cents, so that the collateralisation is exactly 1 - self.updatePrice(oracle, UNIT // 2, self.now_block_time()) - fast_forward(2) - # (this should not trigger liquidation) - self.assertFalse(self.isLiquidating()) - self.assertEqual(self.collateralisationRatio(), UNIT) - - # Now double the ether in the contract to 2. - send_value(owner, self.nomin.address, ETHER) - send_value(owner, self.nomin_real.address, ETHER) - self.assertEqual(self.collateralisationRatio(), 2 * UNIT) - - def test_poolFeeIncurred(self): - poolFeeRate = self.poolFeeRate() - - self.assertEqual(self.poolFeeIncurred(0), 0) - self.assertEqual(self.poolFeeIncurred(UNIT), poolFeeRate) - self.assertEqual(self.poolFeeIncurred(10 * UNIT), 10 * poolFeeRate) - self.assertEqual(self.poolFeeIncurred(UNIT // 2), poolFeeRate // 2) - self.setPoolFeeRate(self.owner(), UNIT // 10**7) - self.assertEqual(self.poolFeeIncurred(UNIT), UNIT // 10**7) - self.assertEqual(self.poolFeeIncurred(100 * UNIT), UNIT // 10**5) - self.assertEqual(self.poolFeeIncurred(UNIT // 2), UNIT // (2 * 10**7)) - - def test_purchaseCostFiat(self): - owner = self.owner() - poolFeeRate = self.poolFeeRate() - - self.assertGreater(self.purchaseCostFiat(UNIT), UNIT) - self.assertGreater(self.purchaseCostFiat(UNIT), self.saleProceedsFiat(UNIT)) - - self.assertEqual(self.purchaseCostFiat(0), 0) - self.assertEqual(self.purchaseCostFiat(UNIT), UNIT + poolFeeRate) - self.assertEqual(self.purchaseCostFiat(10 * UNIT), 10 * (UNIT + poolFeeRate)) - self.assertEqual(self.purchaseCostFiat(UNIT // 2), (UNIT + poolFeeRate) // 2) - self.setPoolFeeRate(owner, UNIT // 10**7) - self.assertEqual(self.purchaseCostFiat(UNIT), (UNIT + UNIT // 10**7)) - self.assertEqual(self.purchaseCostFiat(100 * UNIT), 100 * (UNIT + UNIT // 10**7)) - self.assertEqual(self.purchaseCostFiat(UNIT // 2), (UNIT + UNIT // 10**7) // 2) - self.setPoolFeeRate(owner, poolFeeRate) - - def test_purchaseCostEther(self): - owner = self.owner() - oracle = self.oracle() - poolFeeRate = self.poolFeeRate() - - self.assertGreater(self.purchaseCostEther(UNIT), self.etherValue(UNIT)) - self.assertGreater(self.purchaseCostEther(UNIT), self.saleProceedsEther(UNIT)) - - self.assertEqual(self.purchaseCostEther(0), 0) - - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.purchaseCostEther(UNIT), UNIT + poolFeeRate) - self.assertEqual(self.purchaseCostEther(UNIT // 2), (UNIT + poolFeeRate) // 2) - self.setPoolFeeRate(owner, UNIT // 10**7) - self.assertEqual(self.purchaseCostEther(UNIT), (UNIT + UNIT // 10**7)) - self.assertEqual(self.purchaseCostEther(100 * UNIT), 100 * (UNIT + UNIT // 10**7)) - - self.setPoolFeeRate(owner, poolFeeRate) - self.updatePrice(oracle, UNIT // 2, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.purchaseCostEther(UNIT // 2), UNIT + poolFeeRate) - self.assertEqual(self.purchaseCostEther(3 * UNIT), 6 * (UNIT + poolFeeRate)) - - def test_purchaseCostEtherShoppingSpree(self): - owner = self.owner() - oracle = self.oracle() - self.replenishPool(owner, 800000 * UNIT, 8 * 10**9 * UNIT) - - price_multiples = [12398, 1.2384889, 7748.22, 0.238838, 0.00049944, 5.7484, 87.2211111] - qty_multiples = [2.3, 84.4828, 284.10002, 0.4992, 105.289299991, 7.651948, 0.01, 100000] - - total_qty = 0 - total_cost = 0 - pre_balance = get_eth_balance(owner) - - for price_mult in price_multiples: - for qty_mult in qty_multiples: - price = int(price_mult * UNIT) - qty = int(qty_mult * UNIT) - total_qty += qty - self.updatePrice(oracle, price, self.now_block_time()) - fast_forward(2) - cost = self.purchaseCostEther(qty) - total_cost += cost - self.buy(owner, qty, cost) - - self.assertEqual(self.balanceOf(owner), total_qty) - - # We assert only almost equal because we're ignoring gas costs. - self.assertAlmostEqual((pre_balance - get_eth_balance(owner)) / UNIT, total_cost / UNIT) - - def test_saleProceedsFiat(self): - owner = self.owner() - poolFeeRate = self.poolFeeRate() - - self.assertLess(self.saleProceedsFiat(UNIT), UNIT) - self.assertLess(self.saleProceedsFiat(UNIT), self.purchaseCostFiat(UNIT)) - - self.assertEqual(self.saleProceedsFiat(0), 0) - self.assertEqual(self.saleProceedsFiat(UNIT), UNIT - poolFeeRate) - self.assertEqual(self.saleProceedsFiat(10 * UNIT), 10 * (UNIT - poolFeeRate)) - self.assertEqual(self.saleProceedsFiat(UNIT // 2), (UNIT - poolFeeRate) // 2) - self.setPoolFeeRate(owner, UNIT // 10**7) - self.assertEqual(self.saleProceedsFiat(UNIT), (UNIT - UNIT // 10**7)) - self.assertEqual(self.saleProceedsFiat(100 * UNIT), 100 * (UNIT - UNIT // 10**7)) - self.assertEqual(self.saleProceedsFiat(UNIT // 2), (UNIT - UNIT // 10**7) // 2) - self.setPoolFeeRate(owner, poolFeeRate) - - def test_saleProceedsEther(self): - owner = self.owner() - oracle = self.oracle() - poolFeeRate = self.poolFeeRate() - - self.assertLess(self.saleProceedsEther(UNIT), self.etherValue(UNIT)) - self.assertLess(self.saleProceedsEther(UNIT), self.purchaseCostEther(UNIT)) - - self.assertEqual(self.saleProceedsEther(0), 0) - - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.saleProceedsEther(UNIT), UNIT - poolFeeRate) - self.assertEqual(self.saleProceedsEther(UNIT // 2), (UNIT - poolFeeRate) // 2) - self.setPoolFeeRate(owner, UNIT // 10**7) - self.assertEqual(self.saleProceedsEther(UNIT), (UNIT - UNIT // 10**7)) - self.assertEqual(self.saleProceedsEther(100 * UNIT), 100 * (UNIT - UNIT // 10**7)) - - self.setPoolFeeRate(owner, poolFeeRate) - self.updatePrice(oracle, UNIT // 2, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.saleProceedsEther(UNIT // 2), UNIT - poolFeeRate) - self.assertEqual(self.saleProceedsEther(3 * UNIT), 6 * (UNIT - poolFeeRate)) - - for v in [0.0004, 2.1, 1, 49994, 49.29384, 0.00000028, 1235759872, 2.5 * 10**25]: - vi = int(v * UNIT) - self.assertEqual(self.saleProceedsEther(vi), self.saleProceedsEtherAllowStale(vi)) - - def test_saleProceedsEtherBearMarket(self): - owner = self.owner() - oracle = self.oracle() - - initial_qty = 800000 * UNIT - self.replenishPool(owner, initial_qty, 8 * 10**9 * UNIT) - self.buy(owner, initial_qty, self.purchaseCostEther(initial_qty)) - - price_multiples = [12398, 1.2384889, 7748.22, 0.238838, 0.00049944, 5.7484, 87.2211111] - qty_multiples = [2.3, 84.4828, 284.10002, 0.4992, 105.289299991, 7.651948, 0.01, 100000] - - total_qty = 0 - total_proceeds = 0 - pre_balance = get_eth_balance(owner) - - for price_mult in price_multiples: - for qty_mult in qty_multiples: - price = int(price_mult * UNIT) - qty = int(qty_mult * UNIT) - total_qty += qty - self.updatePrice(oracle, price, self.now_block_time()) - fast_forward(2) - proceeds = self.saleProceedsEther(qty) - total_proceeds += proceeds - self.sell(owner, qty) - - self.assertEqual(initial_qty - self.balanceOf(owner), total_qty) - - # We assert only almost equal because we're ignoring gas costs. - self.assertAlmostEqual((get_eth_balance(owner) - pre_balance) / UNIT, total_proceeds / UNIT) - - def test_priceIsStale(self): - oracle = self.oracle() - owner = self.owner() - stale_period = self.stalePeriod() - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - # Price is not stale immediately following an update. - self.assertFalse(self.priceIsStale()) - - # Price is not stale after part of the period has elapsed. - fast_forward(seconds=stale_period // 2) - self.assertFalse(self.priceIsStale()) - - # Price is not stale right up to just before the period has elapsed. - fast_forward(seconds=stale_period // 2 - 10) - self.assertFalse(self.priceIsStale()) - - # Price becomes stale immediately after the period has elapsed. - fast_forward(seconds=20) - self.assertTrue(self.priceIsStale()) - - # Price stays stale for ages. - fast_forward(seconds=100 * stale_period) - self.assertTrue(self.priceIsStale()) - - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.assertFalse(self.priceIsStale()) - - # Lengthening stale periods should not trigger staleness. - self.setStalePeriod(owner, 2 * stale_period) - self.assertFalse(self.priceIsStale()) - - # Shortening them to longer than the current elapsed period should not trigger staleness. - self.setStalePeriod(owner, stale_period) - self.assertFalse(self.priceIsStale()) - - # Shortening to shorter than the current elapsed period should trigger staleness. - fast_forward(seconds=3 * stale_period // 4) - self.setStalePeriod(owner, stale_period // 2) - self.assertTrue(self.priceIsStale()) - - # Yet if we are able to update the stale period while the price is stale, - # we should be able to turn off staleness by extending the period. - # It's an interesting question of trust as to whether we should be able to do this, say if we - # do not have access to the oracle to send a price update. But as an owner, we could just - # reset the oracle address anyway, so we allow this. - self.setStalePeriod(owner, stale_period) - self.assertFalse(self.priceIsStale()) - - def test_staleness(self): - owner = self.owner() - oracle = self.oracle() - target = W3.eth.accounts[5] - - # Set up target balance to be confiscatable for later testing. - self.assertEqual(self.court(), self.fake_court.address) - motion_id = 1 - self.fake_court.setTargetMotionID(owner, target, motion_id) - self.fake_court.setConfirming(owner, motion_id, True) - self.fake_court.setVotePasses(owner, motion_id, True) - - # Create some nomins and set a convenient price. - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - pce = self.purchaseCostEther(UNIT) - self.replenishPool(owner, 3 * UNIT, 7 * UNIT) - self.buy(owner, UNIT, pce) - - # Enter stale period. - fast_forward(seconds=10 * self.stalePeriod()) - self.assertTrue(self.priceIsStale()) - - # These calls should work if the price is stale. - oracle = self.oracle() - court = self.court() - beneficiary = self.beneficiary() - poolFeeRate = self.poolFeeRate() - stalePeriod = self.stalePeriod() - self.nominPool() - self.liquidationPeriod() - self.liquidationTimestamp() - self.etherPrice() - self.lastPriceUpdateTime() - self.frozen(self.nomin_real.address) - self.setOracle(owner, oracle) - self.setCourt(owner, court) - self.setBeneficiary(owner, beneficiary) - self.setPoolFeeRate(owner, poolFeeRate) - self.setStalePeriod(owner, stalePeriod) - self.etherValueAllowStale(UNIT) - self.saleProceedsEtherAllowStale(UNIT) - self.poolFeeIncurred(UNIT) - self.purchaseCostFiat(UNIT) - self.saleProceedsFiat(UNIT) - self.priceIsStale() - self.isLiquidating() - self.assertFalse(self.frozen(MASTER)) - self.transfer(MASTER, MASTER, 0) - self.transferFrom(MASTER, MASTER, MASTER, 0) - self.fake_court.confiscateBalance(owner, target) - self.unfreezeAccount(owner, target) - self.diminishPool(owner, UNIT) - - # These calls should not work when the price is stale. - # That they work when not stale is guaranteed by other tests, hopefully. - self.assertReverts(self.fiatValue, UNIT) - self.assertReverts(self.fiatBalance) - self.assertReverts(self.etherValue, UNIT) - self.assertReverts(self.collateralisationRatio) - self.assertReverts(self.purchaseCostEther, UNIT) - self.assertReverts(self.saleProceedsEther, UNIT) - self.assertGreater(get_eth_balance(owner), 7 * UNIT) - self.assertEqual(self.nominPool(), UNIT) - self.assertEqual(self.balanceOf(owner), UNIT) - self.assertReverts(self.replenishPool, owner, UNIT, 5 * UNIT) - self.assertReverts(self.buy, owner, UNIT, pce) - self.assertReverts(self.sell, owner, UNIT) - - # Liquidation things should work while stale... - self.forceLiquidation(owner) - self.assertTrue(self.isLiquidating()) - self.extendLiquidationPeriod(owner, 1) - - # ...except that we can't terminate liquidation unless the price is fresh. - self.assertReverts(self.terminateLiquidation, owner) - - # Confirm that sell works regardless of staleness when in liquidation - self.sell(owner, UNIT) - # We can also burn under these conditions. - self.diminishPool(owner, UNIT) - - # Finally that updating the price gets us out of the stale period - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.assertFalse(self.priceIsStale()) - self.terminateLiquidation(owner) - - # And that self-destruction works during stale period. - self.forceLiquidation(owner) - fast_forward(seconds=self.stalePeriod() + self.liquidationPeriod()) - self.assertTrue(self.isLiquidating()) - self.assertTrue(self.priceIsStale()) - self.selfDestruct(owner) - with self.assertRaises(Exception): - self.etherPrice() - - def test_transfer(self): - owner = self.owner() - oracle = self.oracle() - target = W3.eth.accounts[1] - - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.replenishPool(owner, 10 * UNIT, 20 * ETHER) - ethercost = self.purchaseCostEther(10 * UNIT) - self.buy(owner, 10 * UNIT, ethercost) - - self.assertEqual(self.balanceOf(owner), 10 * UNIT) - self.assertEqual(self.balanceOf(target), 0) - - # Should be impossible to transfer to the nomin contract itself. - self.assertReverts(self.transfer, owner, self.nomin_real.address, UNIT) - - self.transfer(owner, target, 5 * UNIT) - remainder = 10 * UNIT - self.transferPlusFee(5 * UNIT) - self.assertEqual(self.balanceOf(owner), remainder) - self.assertEqual(self.balanceOf(target), 5 * UNIT) - - self.debugFreezeAccount(owner, target) - - self.assertReverts(self.transfer, owner, target, UNIT) - # self.assertReverts(self.transfer, target, owner, UNIT) - - self.unfreezeAccount(owner, target) - - qty = (5 * UNIT * UNIT) // self.transferPlusFee(UNIT) + 1 - self.transfer(target, owner, qty) - - self.assertEqual(self.balanceOf(owner), remainder + qty) - self.assertEqual(self.balanceOf(target), 0) - - def test_transferFrom(self): - owner = self.owner() - oracle = self.oracle() - target = W3.eth.accounts[1] - proxy = W3.eth.accounts[2] - - # Unauthorized transfers should not work - self.assertReverts(self.transferFrom, proxy, owner, target, UNIT) - - # Neither should transfers that are too large for the allowance. - self.approve(owner, proxy, UNIT) - self.assertReverts(self.transferFrom, proxy, owner, target, 2 * UNIT) - - self.approve(owner, proxy, 10000 * UNIT) - - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.replenishPool(owner, 10 * UNIT, 20 * ETHER) - ethercost = self.purchaseCostEther(10 * UNIT) - self.buy(owner, 10 * UNIT, ethercost) - - self.assertEqual(self.balanceOf(owner), 10 * UNIT) - self.assertEqual(self.balanceOf(target), 0) - - # Should be impossible to transfer to the nomin contract itself. - self.assertReverts(self.transferFrom, proxy, owner, self.nomin_real.address, UNIT) - - self.transferFrom(proxy, owner, target, 5 * UNIT) - remainder = 10 * UNIT - self.transferPlusFee(5 * UNIT) - self.assertEqual(self.balanceOf(owner), remainder) - self.assertEqual(self.balanceOf(target), 5 * UNIT) - - self.debugFreezeAccount(owner, target) - - self.assertReverts(self.transferFrom, proxy, owner, target, UNIT) - self.assertReverts(self.transferFrom, proxy, target, owner, UNIT) - - self.unfreezeAccount(owner, target) - - qty = (5 * UNIT * UNIT) // self.transferPlusFee(UNIT) + 1 - self.transfer(target, owner, qty) - - self.assertEqual(self.balanceOf(owner), remainder + qty) - self.assertEqual(self.balanceOf(target), 0) - - def test_replenishPool(self): - owner = self.owner() - oracle = self.oracle() - - # Only the contract owner should be able to issue new nomins. - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.assertReverts(self.replenishPool, W3.eth.accounts[4], UNIT, 2 * ETHER) - - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.nominPool(), 0) - - # Revert if less than 2x collateral is provided - self.assertReverts(self.replenishPool, owner, UNIT, 2 * ETHER - 1) - - # Issue a nomin into the pool - self.replenishPool(owner, UNIT, 2 * ETHER) - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.nominPool(), UNIT) - self.assertEqual(get_eth_balance(self.nomin_real.address), 2 * ETHER) - - # Issuing more nomins should stack with existing supply - self.replenishPool(owner, UNIT, 2 * ETHER) - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.nominPool(), 2 * UNIT) - self.assertEqual(get_eth_balance(self.nomin_real.address), 4 * ETHER) - - # Issue more into the pool for free if price goes up - self.updatePrice(oracle, 2 * UNIT, self.now_block_time()) - fast_forward(2) - self.assertFalse(self.isLiquidating()) - self.assertReverts(self.replenishPool, owner, 2 * UNIT + 1, 0) - self.replenishPool(owner, 2 * UNIT, 0) - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.nominPool(), 4 * UNIT) - self.assertEqual(get_eth_balance(self.nomin_real.address), 4 * ETHER) - - # provide more than 2x collateral for new issuance if price drops - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.assertFalse(self.isLiquidating()) - self.assertReverts(self.replenishPool, owner, UNIT, 2 * ETHER) - self.assertReverts(self.replenishPool, owner, UNIT, 6 * ETHER - 1) - self.replenishPool(owner, UNIT, 6 * ETHER) - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.nominPool(), 5 * UNIT) - self.assertEqual(get_eth_balance(self.nomin_real.address), 10 * ETHER) - - def test_diminishPool(self): - owner = self.owner() - oracle = self.oracle() - - # issue some nomins to be burned - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.replenishPool(owner, 10 * UNIT, 20 * ETHER) - - # Only the contract owner should be able to burn nomins. - self.assertReverts(self.diminishPool, W3.eth.accounts[4], UNIT) - - # It should not be possible to burn more nomins than are in the pool. - self.assertReverts(self.diminishPool, owner, 11 * UNIT) - - # Burn part of the pool - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.nominPool(), 10 * UNIT) - self.diminishPool(owner, UNIT) - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.nominPool(), 9 * UNIT) - - # Burn the remainder of the pool - self.diminishPool(owner, self.nominPool()) - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.nominPool(), 0) - - def test_buy(self): - self.updatePrice(self.oracle(), UNIT, self.now_block_time()) - fast_forward(2) - buyer = W3.eth.accounts[4] - - # Should not be possible to buy when there's no supply - cost = self.purchaseCostEther(UNIT) - self.assertReverts(self.buy, buyer, UNIT, cost) - - # issue some nomins to be burned - self.replenishPool(self.owner(), 5 * UNIT, 10 * ETHER) - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.nominPool(), 5 * UNIT) - - # Should not be able to purchase with the wrong quantity of ether. - self.assertReverts(self.buy, buyer, UNIT, cost + 1) - self.assertReverts(self.buy, buyer, UNIT, cost - 1) - - self.assertEqual(self.balanceOf(buyer), 0) - self.buy(buyer, UNIT, cost) - self.assertEqual(self.totalSupply(), UNIT) - self.assertEqual(self.nominPool(), 4 * UNIT) - self.assertEqual(self.balanceOf(buyer), UNIT) - - # It should not be possible to buy fewer nomins than the purchase minimum - purchaseMin = UNIT // 100 - self.assertReverts(self.buy, buyer, purchaseMin - 1, self.purchaseCostEther(purchaseMin - 1)) - - # But it should be possible to buy exactly that quantity - self.buy(buyer, purchaseMin, self.purchaseCostEther(purchaseMin)) - self.assertEqual(self.totalSupply(), UNIT + UNIT // 100) - self.assertEqual(self.nominPool(), 4 * UNIT - (UNIT // 100)) - self.assertEqual(self.balanceOf(buyer), UNIT + UNIT // 100) - - # It should not be possible to buy more tokens than are in the pool - total = self.nominPool() - self.assertReverts(self.buy, buyer, total + 1, self.purchaseCostEther(total + 1)) - - self.buy(buyer, total, self.purchaseCostEther(total)) - self.assertEqual(self.totalSupply(), 5 * UNIT) - self.assertEqual(self.nominPool(), 0) - self.assertEqual(self.balanceOf(buyer), 5 * UNIT) - - # Should not be possible to buy when there's nothing in the pool - self.assertReverts(self.buy, buyer, UNIT, self.purchaseCostEther(UNIT)) - - def test_sell(self): - # Prepare a seller who owns some nomins. - self.updatePrice(self.oracle(), UNIT, self.now_block_time()) - fast_forward(2) - seller = W3.eth.accounts[4] - self.replenishPool(self.owner(), 5 * UNIT, 10 * ETHER) - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.nominPool(), 5 * UNIT) - self.assertEqual(self.balanceOf(seller), 0) - - # It should not be possible to sell nomins if you have none. - self.assertReverts(self.sell, seller, UNIT) - - self.buy(seller, 5 * UNIT, self.purchaseCostEther(5 * UNIT)) - self.assertEqual(self.totalSupply(), 5 * UNIT) - self.assertEqual(self.nominPool(), 0) - self.assertEqual(self.balanceOf(seller), 5 * UNIT) - - # It should not be possible to sell more nomins than you possess. - self.assertReverts(self.sell, seller, 5 * UNIT + 1) - - # Selling nomins should yield back the right amount of ether. - pre_balance = get_eth_balance(seller) - self.sell(seller, 2 * UNIT) - # This assertAlmostEqual hack is only because ganache refuses to be sensible about gas prices. - # The receipt refuses to include the gas price and the values appear are inconsistent anyway. - self.assertAlmostEqual(self.saleProceedsEther(2 * UNIT) / UNIT, (get_eth_balance(seller) - pre_balance) / UNIT) - self.assertEqual(self.totalSupply(), 3 * UNIT) - self.assertEqual(self.nominPool(), 2 * UNIT) - self.assertEqual(self.balanceOf(seller), 3 * UNIT) - - def test_isLiquidating(self): - self.assertFalse(self.isLiquidating()) - self.forceLiquidation(self.owner()) - self.assertTrue(self.isLiquidating()) - - def test_forceLiquidation(self): - owner = self.owner() - # non-owners should not be able to force liquidation. - non_owner = W3.eth.accounts[6] - self.assertNotEqual(owner, non_owner) - self.assertReverts(self.forceLiquidation, non_owner) - - self.assertFalse(self.isLiquidating()) - tx_receipt = self.forceLiquidation(owner) - self.assertTrue(self.isLiquidating()) - self.assertEqual(block_time(tx_receipt.blockNumber), self.liquidationTimestamp()) - self.assertEqual(len(tx_receipt.logs), 1) - self.assertEqual(get_event_data_from_log(self.nomin_event_dict, tx_receipt.logs[0])['event'], "LiquidationBegun") - - # This call should not work if liquidation has begun. - self.assertReverts(self.forceLiquidation, owner) - - def test_autoLiquidation(self): - owner = self.owner() - oracle = self.oracle() - - # Do not liquidate if there's nothing in the pool. - self.updatePrice(oracle, UNIT // 10, self.now_block_time()) - fast_forward(2) - self.assertFalse(self.isLiquidating()) - - # Issue something so that we can liquidate. - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.replenishPool(owner, UNIT, 2 * UNIT) - - # Ordinary price updates don't cause liquidation. - self.updatePrice(oracle, UNIT // 2, self.now_block_time()) - fast_forward(2) - self.assertFalse(self.isLiquidating()) - - # Price updates inducing sub-unity collateralisation ratios cause liquidation. - tx_receipt = self.updatePrice(oracle, UNIT // 2 - 1, self.now_block_time()) - fast_forward(2) - self.assertTrue(self.isLiquidating()) - self.assertEqual(len(tx_receipt.logs), 2) - price_update_log = get_event_data_from_log(self.nomin_event_dict, tx_receipt.logs[0]) - liquidation_log = get_event_data_from_log(self.nomin_event_dict, tx_receipt.logs[1]) - self.assertEqual(price_update_log['event'], 'PriceUpdated') - self.assertEqual(liquidation_log['event'], 'LiquidationBegun') - - # The auto liquidation check should do nothing when already under liquidation. - tx_receipt = self.updatePrice(oracle, UNIT // 3 - 1, self.now_block_time()) - fast_forward(2) - self.assertEqual(len(tx_receipt.logs), 1) - price_update_log = get_event_data_from_log(self.nomin_event_dict, tx_receipt.logs[0]) - self.assertEqual(price_update_log['event'], 'PriceUpdated') - self.assertTrue(self.isLiquidating()) - - def test_extendLiquidationPeriod(self): - owner = self.owner() - - # Only owner should be able to call this. - non_owner = W3.eth.accounts[6] - self.assertNotEqual(owner, non_owner) - self.assertReverts(self.forceLiquidation, non_owner) - - fourteenDays = 14 * 24 * 60 * 60 - oneEightyDays = 180 * 24 * 60 * 60 - - self.forceLiquidation(owner) - self.assertEqual(self.liquidationPeriod(), fourteenDays) # Default 14 days. - self.assertReverts(self.extendLiquidationPeriod, owner, 12309198139871) - self.extendLiquidationPeriod(owner, 1) - self.assertEqual(self.liquidationPeriod(), fourteenDays + 1) - self.extendLiquidationPeriod(owner, 12345) - self.assertEqual(self.liquidationPeriod(), fourteenDays + 12346) - self.extendLiquidationPeriod(owner, oneEightyDays - (fourteenDays + 12346)) - self.assertEqual(self.liquidationPeriod(), oneEightyDays) - self.assertReverts(self.extendLiquidationPeriod, owner, 1) - self.assertReverts(self.extendLiquidationPeriod, owner, 12309198139871) - - def test_terminateLiquidation(self): - owner = self.owner() - oracle = self.oracle() - - # Terminating liquidation should not work when not liquidating. - self.assertReverts(self.terminateLiquidation, owner) - - self.forceLiquidation(owner) - - # Only the owner should be able to terminate liquidation. - self.assertReverts(self.terminateLiquidation, oracle) - - # Should be able to terminate liquidation if there is no supply. - tx_receipt = self.terminateLiquidation(owner) - self.assertEqual(self.liquidationTimestamp(), 2**256 - 1) - self.assertEqual(self.liquidationPeriod(), 14 * 24 * 60 * 60) - self.assertEqual(len(tx_receipt.logs), 1) - self.assertEqual(get_event_data_from_log(self.nomin_event_dict, tx_receipt.logs[0])['event'], - "LiquidationTerminated") - - # Should not be able to terminate liquidation if the supply is undercollateralised. - self.updatePrice(oracle, 2 * UNIT, self.now_block_time()) - fast_forward(2) - self.replenishPool(owner, UNIT, UNIT) - self.updatePrice(oracle, UNIT - 1, self.now_block_time()) - fast_forward(2) - self.assertTrue(self.isLiquidating()) # Price update triggers liquidation. - self.assertReverts(self.terminateLiquidation, owner) - - # But if the price recovers we should be fine to terminate. - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.terminateLiquidation(owner) - self.assertFalse(self.isLiquidating()) - - # And we should not be able to terminate liquidation if the price is stale. - self.forceLiquidation(owner) - fast_forward(seconds=2 * self.stalePeriod()) - self.assertTrue(self.priceIsStale()) - self.assertReverts(self.terminateLiquidation, owner) - - def test_canSelfDestruct(self): - owner = self.owner() - oracle = self.oracle() - - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.replenishPool(owner, 2 * UNIT, 4 * UNIT) - self.buy(owner, UNIT, self.purchaseCostEther(UNIT)) - - self.assertFalse(self.canSelfDestruct()) - self.forceLiquidation(owner) - - # Not enough time elapsed. - self.assertFalse(self.canSelfDestruct()) - fast_forward(seconds=self.liquidationPeriod() + 10) - self.assertTrue(self.canSelfDestruct()) - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - - self.terminateLiquidation(owner) - self.sell(owner, UNIT) - self.forceLiquidation(owner) - - self.assertFalse(self.canSelfDestruct()) - fast_forward(weeks=3) - self.assertTrue(self.canSelfDestruct()) - - def test_selfDestruct(self): - owner = self.owner() - oracle = self.oracle() - not_owner = W3.eth.accounts[5] - self.assertNotEqual(not_owner, owner) - - # Buy some nomins so that we can't short circuit self-destruction. - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.replenishPool(owner, UNIT, 2 * UNIT) - self.buy(owner, UNIT, self.purchaseCostEther(UNIT)) - - self.assertFalse(self.isLiquidating()) - self.assertFalse(self.canSelfDestruct()) - - # This should not work if not liquidating yet. - self.assertReverts(self.selfDestruct, owner) - - self.forceLiquidation(owner) - - # Should not work if the full liquidation period has not elapsed. - self.assertReverts(self.selfDestruct, owner) - - # Should not work if the liquidationPeriod has been extended. - buff = 1000 - fast_forward(seconds=self.liquidationPeriod() + buff // 2) - self.extendLiquidationPeriod(owner, buff) - self.assertReverts(self.selfDestruct, owner) - - # Go past the end of the liquidation period. - fast_forward(seconds=buff) - - # Only the owner should be able to call this. - self.assertReverts(self.selfDestruct, not_owner) - - # Should not be able to self-destruct if the period was terminated. - # Refresh the price so we can terminate liquidation. - self.updatePrice(self.oracle(), self.etherPrice(), self.now_block_time()) - fast_forward(2) - self.terminateLiquidation(owner) - self.assertReverts(self.selfDestruct, owner) - - # Check that the beneficiary receives the entire balance of the smart contract. - self.forceLiquidation(owner) - fast_forward(seconds=self.liquidationPeriod() + 1) - value = get_eth_balance(self.nomin_real.address) - beneficiary = self.beneficiary() - pre_balance = get_eth_balance(beneficiary) - self.selfDestruct(owner) - self.assertEqual(get_eth_balance(beneficiary) - pre_balance, value) - - def test_selfDestructShortCircuit(self): - owner = self.owner() - oracle = self.oracle() - not_owner = W3.eth.accounts[5] - self.assertNotEqual(not_owner, owner) - - # Buy some nomins so that we can't immediately short circuit self-destruction. - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.replenishPool(owner, UNIT, 2 * UNIT) - self.buy(owner, UNIT // 2, self.purchaseCostEther(UNIT // 2)) - - self.forceLiquidation(owner) - - # Should not be able to self-destruct, as one week has not yet elapsed. - self.assertReverts(self.selfDestruct, owner) - - fast_forward(weeks=2) - - # Should not be able to self-destruct as there are still some nomins in circulation. - self.assertReverts(self.selfDestruct, owner) - - # Sell all nomins back, we should be able to selfdestruct. - self.sell(owner, UNIT // 2) - tx_receipt = self.selfDestruct(owner) - self.assertEqual(len(tx_receipt.logs), 1) - log = get_event_data_from_log(self.nomin_event_dict, tx_receipt.logs[0]) - self.assertEqual(log['event'], "SelfDestructed") - - def test_confiscateBalance(self): - owner = self.owner() - target = W3.eth.accounts[2] - - self.assertEqual(self.court(), self.fake_court.address) - - # The target must have some nomins. We will issue 10 for him to buy - self.updatePrice(self.oracle(), UNIT, self.now_block_time()) - fast_forward(2) - self.replenishPool(owner, 10 * UNIT, 20 * ETHER) - ethercost = self.purchaseCostEther(10 * UNIT) - send_value(owner, target, ethercost) - self.buy(target, 10 * UNIT, ethercost) - self.assertEqual(self.balanceOf(target), 10 * UNIT) - - motion_id = 1 - self.fake_court.setTargetMotionID(owner, target, motion_id) - - # Attempt to confiscate even though the conditions are not met. - self.fake_court.setConfirming(owner, motion_id, False) - self.fake_court.setVotePasses(owner, motion_id, False) - self.assertReverts(self.fake_court.confiscateBalance, owner, target) - - self.fake_court.setConfirming(owner, motion_id, True) - self.fake_court.setVotePasses(owner, motion_id, False) - self.assertReverts(self.fake_court.confiscateBalance, owner, target) - - self.fake_court.setConfirming(owner, motion_id, False) - self.fake_court.setVotePasses(owner, motion_id, True) - self.assertReverts(self.fake_court.confiscateBalance, owner, target) - - # Set up the target balance to be confiscatable. - self.fake_court.setConfirming(owner, motion_id, True) - self.fake_court.setVotePasses(owner, motion_id, True) - - # Only the court should be able to confiscate balances. - self.assertReverts(self.confiscateBalance, owner, target) - - # Actually confiscate the balance. - pre_feePool = self.feePool() - pre_balance = self.balanceOf(target) - self.fake_court.confiscateBalance(owner, target) - self.assertEqual(self.balanceOf(target), 0) - self.assertEqual(self.feePool(), pre_feePool + pre_balance) - self.assertTrue(self.frozen(target)) - - def test_unfreezeAccount(self): - owner = self.owner() - target = W3.eth.accounts[1] - - # The nomin contract itself should not be unfreezable. - tx_receipt = self.unfreezeAccount(owner, self.nomin_real.address) - self.assertTrue(self.frozen(self.nomin_real.address)) - self.assertEqual(len(tx_receipt.logs), 0) - - # Unfreezing non-frozen accounts should not do anything. - self.assertFalse(self.frozen(target)) - tx_receipt = self.unfreezeAccount(owner, target) - self.assertFalse(self.frozen(target)) - self.assertEqual(len(tx_receipt.logs), 0) - - self.debugFreezeAccount(owner, target) - self.assertTrue(self.frozen(target)) - - # Only the owner should be able to unfreeze an account. - self.assertReverts(self.unfreezeAccount, target, target) - - tx_receipt = self.unfreezeAccount(owner, target) - self.assertFalse(self.frozen(target)) - - # Unfreezing should emit the appropriate log. - log = get_event_data_from_log(self.nomin_event_dict, tx_receipt.logs[0]) - self.assertEqual(log['event'], 'AccountUnfrozen') - - def test_fallback(self): - # Fallback function should be payable. - owner = self.owner() - self.debugWithdrawAllEther(owner, owner) - self.debugEmptyFeePool(owner) - self.assertEqual(get_eth_balance(self.nomin_real.address), 0) - send_value(owner, self.nomin.address, ETHER // 2) - send_value(owner, self.nomin_real.address, ETHER // 2) - self.assertEqual(get_eth_balance(self.nomin_real.address), ETHER) diff --git a/tests/test_ExternStateProxyFeeToken.py b/tests/test_ExternStateProxyFeeToken.py deleted file mode 100644 index d908b18458..0000000000 --- a/tests/test_ExternStateProxyFeeToken.py +++ /dev/null @@ -1,445 +0,0 @@ -import unittest - -from utils.deployutils import W3, UNIT, MASTER, DUMMY, fresh_account, fresh_accounts -from utils.deployutils import compile_contracts, attempt_deploy, mine_tx -from utils.deployutils import take_snapshot, restore_snapshot -from utils.testutils import assertReverts -from utils.testutils import generate_topic_event_map, get_event_data_from_log -from utils.testutils import ZERO_ADDRESS - - -ExternStateProxyFeeToken_SOURCE = "tests/contracts/PublicExternStateProxyFeeToken.sol" -Proxy_SOURCE = "contracts/Proxy.sol" -TokenState_SOURCE = "contracts/TokenState.sol" - - -def setUpModule(): - print("Testing ExternStateProxyFeeToken...") - - -def tearDownModule(): - print() - - -class TestExternStateProxyFeeToken(unittest.TestCase): - def setUp(self): - self.snapshot = take_snapshot() - - def tearDown(self): - restore_snapshot(self.snapshot) - - @classmethod - def setUpClass(cls): - cls.assertReverts = assertReverts - cls.initial_beneficiary, cls.fee_authority, cls.token_owner = fresh_accounts(3) - - cls.compiled = compile_contracts([ExternStateProxyFeeToken_SOURCE, Proxy_SOURCE, TokenState_SOURCE], - remappings=['""=contracts']) - cls.feetoken_abi = cls.compiled['PublicExternStateProxyFeeToken']['abi'] - cls.feetoken_event_dict = generate_topic_event_map(cls.feetoken_abi) - cls.feetoken_real, cls.construction_txr = attempt_deploy( - cls.compiled, "PublicExternStateProxyFeeToken", MASTER, ["Test Fee Token", "FEE", - UNIT // 20, cls.fee_authority, - ZERO_ADDRESS, cls.token_owner] - ) - - cls.feestate, txr = attempt_deploy( - cls.compiled, "TokenState", MASTER, - [cls.token_owner, cls.token_owner] - ) - mine_tx(cls.feestate.functions.setBalanceOf(cls.initial_beneficiary, 1000 * UNIT).transact({'from': cls.token_owner})) - mine_tx(cls.feestate.functions.setAssociatedContract(cls.feetoken_real.address).transact({'from': cls.token_owner})) - - cls.feetoken_proxy, _ = attempt_deploy(cls.compiled, 'Proxy', - MASTER, [cls.feetoken_real.address, cls.token_owner]) - mine_tx(cls.feetoken_real.functions.setProxy(cls.feetoken_proxy.address).transact( - {'from': cls.token_owner})) - cls.feetoken = W3.eth.contract(address=cls.feetoken_proxy.address, - abi=cls.compiled['PublicExternStateProxyFeeToken']['abi']) - - mine_tx( - cls.feetoken_real.functions.setState(cls.feestate.address).transact({'from': cls.token_owner})) - - cls.owner = lambda self: cls.feetoken.functions.owner().call() - cls.totalSupply = lambda self: cls.feetoken.functions.totalSupply().call() - cls.state = lambda self: cls.feetoken.functions.state().call() - cls.name = lambda self: cls.feetoken.functions.name().call() - cls.symbol = lambda self: cls.feetoken.functions.symbol().call() - cls.balanceOf = lambda self, account: self.feetoken.functions.balanceOf(account).call() - cls.allowance = lambda self, account, spender: self.feetoken.functions.allowance(account, spender).call() - cls.transferFeeRate = lambda self: cls.feetoken.functions.transferFeeRate().call() - cls.maxTransferFeeRate = lambda self: cls.feetoken.functions.maxTransferFeeRate().call() - cls.feePool = lambda self: cls.feetoken.functions.feePool().call() - cls.feeAuthority = lambda self: cls.feetoken.functions.feeAuthority().call() - - cls.transferFeeIncurred = lambda self, value: cls.feetoken.functions.transferFeeIncurred(value).call() - cls.transferPlusFee = lambda self, value: cls.feetoken.functions.transferPlusFee(value).call() - cls.priceToSpend = lambda self, value: cls.feetoken.functions.priceToSpend(value).call() - - cls.nominateOwner = lambda self, sender, address: mine_tx( - cls.feetoken.functions.nominateOwner(address).transact({'from': sender})) - cls.acceptOwnership = lambda self, sender: mine_tx( - cls.feetoken.functions.acceptOwnership().transact({'from': sender})) - cls.setTransferFeeRate = lambda self, sender, new_fee_rate: mine_tx( - cls.feetoken.functions.setTransferFeeRate(new_fee_rate).transact({'from': sender})) - cls.setFeeAuthority = lambda self, sender, new_fee_authority: mine_tx( - cls.feetoken.functions.setFeeAuthority(new_fee_authority).transact({'from': sender})) - cls.setState = lambda self, sender, new_state: mine_tx( - cls.feetoken.functions.setState(new_state).transact({'from': sender})) - cls.transfer_byProxy = lambda self, sender, to, value: mine_tx( - cls.feetoken.functions.transfer_byProxy(to, value).transact({'from': sender})) - cls.approve = lambda self, sender, spender, value: mine_tx( - cls.feetoken.functions.approve(spender, value).transact({'from': sender})) - cls.transferFrom_byProxy = lambda self, sender, fromAccount, to, value: mine_tx( - cls.feetoken.functions.transferFrom_byProxy(fromAccount, to, value).transact({'from': sender})) - - cls.withdrawFee = lambda self, sender, account, value: mine_tx( - cls.feetoken_real.functions.withdrawFee(account, value).transact({'from': sender})) - cls.donateToFeePool = lambda self, sender, value: mine_tx( - cls.feetoken.functions.donateToFeePool(value).transact({'from': sender})) - - cls.debug_messageSender = lambda self: cls.feetoken_real.functions._messageSender().call() - cls.debug_optionalProxy = lambda self, sender: mine_tx(cls.feetoken.functions._optionalProxy_tester().transact({'from': sender})) - cls.debug_optionalProxy_direct = lambda self, sender: mine_tx(cls.feetoken_real.functions._optionalProxy_tester().transact({'from': sender})) - - def test_constructor(self): - self.assertEqual(self.name(), "Test Fee Token") - self.assertEqual(self.symbol(), "FEE") - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.transferFeeRate(), UNIT // 20) - self.assertEqual(self.feeAuthority(), self.fee_authority) - self.assertEqual(self.state(), self.feestate.address) - self.assertEqual(self.feestate.functions.associatedContract().call(), self.feetoken_real.address) - - def test_provide_state(self): - feestate, _ = attempt_deploy(self.compiled, 'TokenState', - MASTER, [MASTER, self.feetoken.address]) - - feetoken, _ = attempt_deploy(self.compiled, 'PublicExternStateProxyFeeToken', - MASTER, - ["Test Fee Token", "FEE", - UNIT // 20, self.fee_authority, - ZERO_ADDRESS, DUMMY]) - self.assertNotEqual(feetoken.functions.state().call(), ZERO_ADDRESS) - - feetoken, _ = attempt_deploy(self.compiled, 'PublicExternStateProxyFeeToken', - MASTER, - ["Test Fee Token", "FEE", - UNIT // 20, self.fee_authority, - feestate.address, DUMMY]) - self.assertEqual(feetoken.functions.state().call(), feestate.address) - - def test_getSetOwner(self): - owner = self.owner() - new_owner = DUMMY - self.assertNotEqual(owner, new_owner) - - # Only the owner must be able to change the new owner. - self.assertReverts(self.nominateOwner, new_owner, new_owner) - - self.nominateOwner(owner, new_owner) - self.acceptOwnership(new_owner) - self.assertEqual(self.owner(), new_owner) - self.nominateOwner(new_owner, owner) - self.acceptOwnership(owner) - - def test_getSetTransferFeeRate(self): - transfer_fee_rate = self.transferFeeRate() - new_transfer_fee_rate = transfer_fee_rate + UNIT // 20 - owner = self.owner() - fake_owner = DUMMY - self.assertNotEqual(owner, fake_owner) - - # Only the owner is able to set the Transfer Fee Rate. - self.assertReverts(self.setTransferFeeRate, fake_owner, new_transfer_fee_rate) - tx_receipt = self.setTransferFeeRate(owner, new_transfer_fee_rate) - # Check that event is emitted. - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], - "TransferFeeRateUpdated") - self.assertEqual(self.transferFeeRate(), new_transfer_fee_rate) - - # Maximum fee rate is UNIT /10. - bad_transfer_fee_rate = UNIT - self.assertReverts(self.setTransferFeeRate, owner, bad_transfer_fee_rate) - self.assertEqual(self.transferFeeRate(), new_transfer_fee_rate) - - def test_getSetFeeAuthority(self): - new_fee_authority = fresh_account() - owner = self.owner() - - # Only the owner is able to set the Fee Authority. - self.assertReverts(self.setFeeAuthority, new_fee_authority, new_fee_authority) - tx_receipt = self.setFeeAuthority(owner, new_fee_authority) - # Check that event is emitted. - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], - "FeeAuthorityUpdated") - self.assertEqual(self.feeAuthority(), new_fee_authority) - - def test_getSetState(self): - _, new_state = fresh_accounts(2) - owner = self.owner() - self.assertNotEqual(new_state, owner) - - # Only the owner is able to set the Fee Authority. - self.assertReverts(self.setState, new_state, new_state) - tx_receipt = self.setState(owner, new_state) - # Check that event is emitted. - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], - "StateUpdated") - self.assertEqual(self.state(), new_state) - - def test_getTransferFeeIncurred(self): - value = 10 * UNIT - fee = value * self.transferFeeRate() // UNIT - self.assertEqual(self.transferFeeIncurred(value), fee) - - self.assertEqual(self.transferFeeIncurred(0), 0) - - def test_getTransferPlusFee(self): - value = 10 * UNIT - fee = value * self.transferFeeRate() // UNIT - total = value + fee - self.assertEqual(self.transferPlusFee(value), total) - - self.assertEqual(self.transferPlusFee(0), 0) - - def test_priceToSpend(self): - value = 10 * UNIT - self.assertEqual(self.priceToSpend(0), 0) - fee_rate = self.transferFeeRate() - self.assertEqual(self.priceToSpend(value), (UNIT * value) // (UNIT + fee_rate)) - fee_rate = 13 * UNIT // 10000 - self.setTransferFeeRate(self.token_owner, fee_rate) - self.assertEqual(self.priceToSpend(value), (UNIT * value) // (UNIT + fee_rate)) - - def test_transfer(self): - sender = self.initial_beneficiary - sender_balance = self.balanceOf(sender) - - receiver = fresh_account() - receiver_balance = self.balanceOf(receiver) - self.assertEqual(receiver_balance, 0) - - value = 10 * UNIT - fee = self.transferFeeIncurred(value) - total_value = self.transferPlusFee(value) - total_supply = self.totalSupply() - fee_pool = self.feePool() - - # This should fail because receiver has no tokens - self.assertReverts(self.transfer_byProxy, receiver, sender, value) - - tx_receipt = self.transfer_byProxy(sender, receiver, value) - # Check that events are emitted properly. - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Transfer") - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[1])['event'], - "TransferFeePaid") - self.assertEqual(self.balanceOf(receiver), receiver_balance + value) - self.assertEqual(self.balanceOf(sender), sender_balance - total_value) - self.assertEqual(self.totalSupply(), total_supply) - self.assertEqual(self.feePool(), fee_pool + fee) - - value = 1001 * UNIT - - # This should fail because balance < value - self.assertReverts(self.transfer_byProxy, sender, receiver, value) - - # 0 Value transfers are allowed and incur no fee. - value = 0 - total_supply = self.totalSupply() - fee_pool = self.feePool() - - tx_receipt = self.transfer_byProxy(sender, receiver, value) - # Check that events are emitted properly. - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Transfer") - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[1])['event'], - "TransferFeePaid") - self.assertEqual(self.totalSupply(), total_supply) - self.assertEqual(self.feePool(), fee_pool) - - # It is also possible to send 0 value transfer from an account with 0 balance - value = 0 - no_tokens = fresh_account() - self.assertEqual(self.balanceOf(no_tokens), 0) - fee = self.transferFeeIncurred(value) - total_supply = self.totalSupply() - fee_pool = self.feePool() - - tx_receipt = self.transfer_byProxy(no_tokens, receiver, value) - # Check that events are emitted properly. - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Transfer") - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[1])['event'], - "TransferFeePaid") - self.assertEqual(self.balanceOf(no_tokens), 0) - self.assertEqual(self.totalSupply(), total_supply) - self.assertEqual(self.feePool(), fee_pool) - - def test_approve(self): - approver = MASTER - spender = fresh_account() - approval_amount = 1 * UNIT - - tx_receipt = self.approve(approver, spender, approval_amount) - # Check that event is emitted properly. - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Approval") - self.assertEqual(self.allowance(approver, spender), approval_amount) - - # Any positive approval amount is valid, even greater than total_supply. - approval_amount = self.totalSupply() * 100 - tx_receipt = self.approve(approver, spender, approval_amount) - # Check that event is emitted properly. - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Approval") - self.assertEqual(self.allowance(approver, spender), approval_amount) - - def test_transferFrom(self): - approver = self.initial_beneficiary - spender, receiver = fresh_accounts(2) - self.assertNotEqual(approver, spender) - self.assertNotEqual(approver, receiver) - - approver_balance = self.balanceOf(approver) - spender_balance = self.balanceOf(spender) - receiver_balance = self.balanceOf(receiver) - - value = 10 * UNIT - fee = self.transferFeeIncurred(value) - total_value = self.transferPlusFee(value) - total_supply = self.totalSupply() - fee_pool = self.feePool() - - # This fails because there has been no approval yet. - self.assertReverts(self.transferFrom_byProxy, spender, approver, receiver, value) - - # Approve total amount inclusive of fee. - tx_receipt = self.approve(approver, spender, total_value) - # Check that event is emitted properly. - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Approval") - self.assertEqual(self.allowance(approver, spender), total_value) - - tx_receipt = self.transferFrom_byProxy(spender, approver, receiver, value // 10) - # Check that events are emitted properly. - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Transfer") - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[1])['event'], - "TransferFeePaid") - self.assertEqual(self.allowance(approver, spender), 9 * total_value // 10) - - self.assertEqual(self.balanceOf(approver), approver_balance - total_value // 10) - self.assertEqual(self.balanceOf(spender), spender_balance) - self.assertEqual(self.balanceOf(receiver), receiver_balance + value // 10) - self.assertEqual(self.totalSupply(), total_supply) - self.assertEqual(self.feePool(), fee_pool + fee // 10) - - tx_receipt = self.transferFrom_byProxy(spender, approver, receiver, 9 * value // 10) - # Check that events are emitted properly. - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Transfer") - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[1])['event'], - "TransferFeePaid") - self.assertEqual(self.allowance(approver, spender), 0) - self.assertEqual(self.balanceOf(approver), approver_balance - total_value) - self.assertEqual(self.balanceOf(spender), spender_balance) - self.assertEqual(self.balanceOf(receiver), receiver_balance + value) - self.assertEqual(self.totalSupply(), total_supply) - self.assertEqual(self.feePool(), fee_pool + fee) - - approver = fresh_account() - # This account has no tokens. - approver_balance = self.balanceOf(approver) - self.assertEqual(approver_balance, 0) - - tx_receipt = self.approve(approver, spender, total_value) - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Approval") - - # This should fail because the approver has no tokens. - self.assertReverts(self.transferFrom_byProxy, spender, approver, receiver, value) - - def test_withdrawFee(self): - receiver, fee_receiver, not_fee_authority = fresh_accounts(3) - self.assertNotEqual(self.fee_authority, not_fee_authority) - self.assertNotEqual(self.fee_authority, receiver) - self.assertNotEqual(self.fee_authority, fee_receiver) - - value = 500 * UNIT - total_value = self.transferPlusFee(value) - tx_receipt = self.transfer_byProxy(self.initial_beneficiary, self.fee_authority, total_value) - # Check that event is emitted properly. - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Transfer") - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[1])['event'], - "TransferFeePaid") - self.assertEqual(self.balanceOf(self.fee_authority), total_value) - - fee = self.transferFeeIncurred(value) - total_supply = self.totalSupply() - fee_pool = self.feePool() - - fee_authority_balance = self.balanceOf(self.fee_authority) - receiver_balance = self.balanceOf(receiver) - fee_receiver_balance = self.balanceOf(fee_receiver) - - tx_receipt = self.transfer_byProxy(self.fee_authority, receiver, value) - # Check that event is emitted properly. - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Transfer") - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[1])['event'], - "TransferFeePaid") - - self.assertEqual(self.balanceOf(receiver), receiver_balance + value) - self.assertEqual(self.balanceOf(self.fee_authority), fee_authority_balance - total_value) - self.assertEqual(self.totalSupply(), total_supply) - self.assertEqual(self.feePool(), fee_pool + fee) - - fee_pool = self.feePool() - - # This should fail because only the Fee Authority can withdraw fees - self.assertReverts(self.withdrawFee, not_fee_authority, not_fee_authority, fee_pool) - - # Failure due to too-large a withdrawal. - self.assertReverts(self.withdrawFee, self.fee_authority, fee_receiver, fee_pool + 1) - - # Partial withdrawal leaves stuff in the pool - tx_receipt = self.withdrawFee(self.fee_authority, fee_receiver, fee_pool // 4) - # Check that event is emitted properly. - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], - "FeesWithdrawn") - self.assertEqual(3 * fee_pool // 4, self.feePool()) - self.assertEqual(self.balanceOf(fee_receiver), fee_receiver_balance + fee_pool // 4) - - # Withdraw the rest - tx_receipt = self.withdrawFee(self.fee_authority, fee_receiver, 3 * fee_pool // 4) - # Check that event is emitted properly. - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], - "FeesWithdrawn") - - self.assertEqual(self.balanceOf(fee_receiver), fee_receiver_balance + fee_pool) - self.assertEqual(self.totalSupply(), total_supply) - self.assertEqual(self.feePool(), 0) - - def test_donateToFeePool(self): - donor, pauper = fresh_accounts(2) - self.assertNotEqual(donor, pauper) - - self.assertGreater(self.balanceOf(self.initial_beneficiary), 10 * UNIT) - - self.transfer_byProxy(self.initial_beneficiary, donor, 10 * UNIT) - self.withdrawFee(self.fee_authority, self.initial_beneficiary, self.feePool()) - - # No donations by people with no money... - self.assertReverts(self.donateToFeePool, pauper, 10 * UNIT) - # ...even if they donate nothing. - self.assertReverts(self.donateToFeePool, pauper, 0) - - # No donations more than you possess. - self.assertReverts(self.donateToFeePool, donor, 11 * UNIT) - - self.assertEqual(self.feePool(), 0) - self.assertEqual(self.balanceOf(donor), 10 * UNIT) - self.assertTrue(self.donateToFeePool(donor, UNIT)) - self.assertEqual(self.feePool(), UNIT) - self.assertEqual(self.balanceOf(donor), 9 * UNIT) - self.assertTrue(self.donateToFeePool(donor, 5 * UNIT)) - self.assertEqual(self.feePool(), 6 * UNIT) - self.assertEqual(self.balanceOf(donor), 4 * UNIT) - - # And it should emit the right event. - tx_receipt = self.donateToFeePool(donor, UNIT) - self.assertEqual(len(tx_receipt.logs), 2) - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], 'FeesDonated') - self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[1])['event'], 'Transfer') diff --git a/tests/test_ExternStateProxyToken.py b/tests/test_ExternStateProxyToken.py deleted file mode 100644 index d5713340cd..0000000000 --- a/tests/test_ExternStateProxyToken.py +++ /dev/null @@ -1,221 +0,0 @@ -import unittest - -from utils.deployutils import W3, UNIT, MASTER, DUMMY, fresh_account, fresh_accounts -from utils.deployutils import compile_contracts, attempt_deploy, mine_tx -from utils.deployutils import take_snapshot, restore_snapshot -from utils.testutils import assertReverts -from utils.testutils import generate_topic_event_map, get_event_data_from_log -from utils.testutils import ZERO_ADDRESS - -ExternStateProxyToken_SOURCE = "tests/contracts/PublicExternStateProxyToken.sol" -TokenState_SOURCE = "contracts/TokenState.sol" -Proxy_SOURCE = "contracts/Proxy.sol" - - -def setUpModule(): - print("Testing ExternStateProxyToken...") - - -def tearDownModule(): - print() - - -class TestExternStateProxyToken(unittest.TestCase): - def setUp(self): - self.snapshot = take_snapshot() - - def tearDown(self): - restore_snapshot(self.snapshot) - - @classmethod - def setUpClass(cls): - cls.assertReverts = assertReverts - - cls.the_owner = DUMMY - - cls.compiled = compile_contracts([ExternStateProxyToken_SOURCE, TokenState_SOURCE, Proxy_SOURCE], - remappings=['""=contracts']) - cls.token_abi = cls.compiled['PublicExternStateProxyToken']['abi'] - cls.token_event_dict = generate_topic_event_map(cls.token_abi) - cls.token_real, cls.construction_txr = attempt_deploy(cls.compiled, 'PublicExternStateProxyToken', - MASTER, - ["Test Token", "TEST", - 1000 * UNIT, cls.the_owner, - ZERO_ADDRESS, cls.the_owner]) - - cls.tokenstate = W3.eth.contract(address=cls.token_real.functions.state().call(), - abi=cls.compiled['TokenState']['abi']) - - mine_tx(cls.token_real.functions.setState(cls.tokenstate.address).transact({'from': cls.the_owner})) - - cls.tokenproxy, _ = attempt_deploy(cls.compiled, 'Proxy', - MASTER, [cls.token_real.address, cls.the_owner]) - mine_tx(cls.token_real.functions.setProxy(cls.tokenproxy.address).transact({'from': cls.the_owner})) - cls.token = W3.eth.contract(address=cls.tokenproxy.address, abi=cls.compiled['PublicExternStateProxyToken']['abi']) - - cls.owner = lambda self: cls.token.functions.owner().call() - cls.totalSupply = lambda self: cls.token.functions.totalSupply().call() - cls.state = lambda self: cls.token.functions.state().call() - cls.name = lambda self: cls.token.functions.name().call() - cls.symbol = lambda self: cls.token.functions.symbol().call() - cls.balanceOf = lambda self, account: cls.token.functions.balanceOf(account).call() - cls.allowance = lambda self, account, spender: cls.token.functions.allowance(account, spender).call() - - cls.setState = lambda self, sender, new_state: mine_tx( - cls.token.functions.setState(new_state).transact({'from': sender})) - cls.transfer_byProxy = lambda self, sender, to, value: mine_tx( - cls.token.functions.transfer_byProxy(to, value).transact({'from': sender})) - cls.approve = lambda self, sender, spender, value: mine_tx( - cls.token.functions.approve(spender, value).transact({'from': sender})) - cls.transferFrom_byProxy = lambda self, sender, fromAccount, to, value: mine_tx( - cls.token.functions.transferFrom_byProxy(fromAccount, to, value).transact({'from': sender})) - - def test_constructor(self): - self.assertEqual(self.name(), "Test Token") - self.assertEqual(self.symbol(), "TEST") - self.assertEqual(self.totalSupply(), 1000 * UNIT) - self.assertEqual(self.balanceOf(self.the_owner), 1000 * UNIT) - self.assertEqual(self.state(), self.tokenstate.address) - self.assertEqual(self.tokenstate.functions.associatedContract().call(), self.token_real.address) - - def test_provide_state(self): - tokenstate, _ = attempt_deploy(self.compiled, 'TokenState', - MASTER, - [self.the_owner, self.token_real.address]) - - token, _ = attempt_deploy(self.compiled, 'PublicExternStateProxyToken', - MASTER, - ["Test Token", "TEST", - 1000 * UNIT, MASTER, - ZERO_ADDRESS, DUMMY]) - self.assertNotEqual(token.functions.state().call(), ZERO_ADDRESS) - - token, _ = attempt_deploy(self.compiled, 'PublicExternStateProxyToken', - MASTER, - ["Test Token", "TEST", - 1000 * UNIT, MASTER, - tokenstate.address, DUMMY]) - self.assertEqual(token.functions.state().call(), tokenstate.address) - - def test_getSetState(self): - new_state = fresh_account() - owner = self.owner() - self.assertNotEqual(new_state, owner) - - # Only the owner is able to set the Fee Authority. - self.assertReverts(self.setState, new_state, new_state) - tx_receipt = self.setState(owner, new_state) - # Check that event is emitted. - self.assertEqual(get_event_data_from_log(self.token_event_dict, tx_receipt.logs[0])['event'], - "StateUpdated") - self.assertEqual(self.state(), new_state) - - def test_transfer(self): - sender = self.the_owner - sender_balance = self.balanceOf(sender) - - receiver = fresh_account() - receiver_balance = self.balanceOf(receiver) - self.assertEqual(receiver_balance, 0) - - value = 10 * UNIT - total_supply = self.totalSupply() - - # This should fail because receiver has no tokens - self.assertReverts(self.transfer_byProxy, receiver, sender, value) - tx_receipt = self.transfer_byProxy(sender, receiver, value) - # Check event is emitted properly. - self.assertEqual(get_event_data_from_log(self.token_event_dict, tx_receipt.logs[0])['event'], "Transfer") - self.assertEqual(self.balanceOf(receiver), receiver_balance + value) - self.assertEqual(self.balanceOf(sender), sender_balance - value) - - # transfers should leave the supply unchanged - self.assertEqual(self.totalSupply(), total_supply) - - value = 1001 * UNIT - # This should fail because balance < value and balance > totalSupply - self.assertReverts(self.transfer_byProxy, sender, receiver, value) - - # 0 value transfers are allowed. - value = 0 - pre_sender_balance = self.balanceOf(sender) - pre_receiver_balance = self.balanceOf(receiver) - tx_receipt = self.transfer_byProxy(sender, receiver, value) - # Check event is emitted properly. - self.assertEqual(get_event_data_from_log(self.token_event_dict, tx_receipt.logs[0])['event'], "Transfer") - self.assertEqual(self.balanceOf(receiver), pre_receiver_balance) - self.assertEqual(self.balanceOf(sender), pre_sender_balance) - - # It is also possible to send 0 value transfer from an account with 0 balance. - no_tokens = fresh_account() - self.assertEqual(self.balanceOf(no_tokens), 0) - tx_receipt = self.transfer_byProxy(no_tokens, receiver, value) - # Check event is emitted properly. - self.assertEqual(get_event_data_from_log(self.token_event_dict, tx_receipt.logs[0])['event'], "Transfer") - self.assertEqual(self.balanceOf(no_tokens), 0) - - def test_approve(self): - approver, spender = fresh_accounts(2) - approval_amount = 1 * UNIT - - tx_receipt = self.approve(approver, spender, approval_amount) - - # Check event is emitted properly. - self.assertEqual(get_event_data_from_log(self.token_event_dict, tx_receipt.logs[0])['event'], "Approval") - self.assertEqual(self.allowance(approver, spender), approval_amount) - - # Any positive approval amount is valid, even greater than total_supply. - approval_amount = self.totalSupply() * 100 - tx_receipt = self.approve(approver, spender, approval_amount) - # Check event is emitted properly. - self.assertEqual(get_event_data_from_log(self.token_event_dict, tx_receipt.logs[0])['event'], "Approval") - self.assertEqual(self.allowance(approver, spender), approval_amount) - - def test_transferFrom(self): - approver = self.the_owner - spender, receiver = fresh_accounts(2) - - approver_balance = self.balanceOf(approver) - spender_balance = self.balanceOf(spender) - receiver_balance = self.balanceOf(receiver) - - value = 10 * UNIT - total_supply = self.totalSupply() - - # This fails because there has been no approval yet - self.assertReverts(self.transferFrom_byProxy, spender, approver, receiver, value) - - tx_receipt = self.approve(approver, spender, 2 * value) - # Check event is emitted properly. - self.assertEqual(get_event_data_from_log(self.token_event_dict, tx_receipt.logs[0])['event'], "Approval") - self.assertEqual(self.allowance(approver, spender), 2 * value) - - self.assertReverts(self.transferFrom_byProxy, spender, approver, receiver, 2 * value + 1) - tx_receipt = self.transferFrom_byProxy(spender, approver, receiver, value) - # Check event is emitted properly. - self.assertEqual(get_event_data_from_log(self.token_event_dict, tx_receipt.logs[0])['event'], "Transfer") - - self.assertEqual(self.balanceOf(approver), approver_balance - value) - self.assertEqual(self.balanceOf(spender), spender_balance) - self.assertEqual(self.balanceOf(receiver), receiver_balance + value) - self.assertEqual(self.allowance(approver, spender), value) - self.assertEqual(self.totalSupply(), total_supply) - - # Empty the account - tx_receipt = self.transferFrom_byProxy(spender, approver, receiver, value) - # Check event is emitted properly. - self.assertEqual(get_event_data_from_log(self.token_event_dict, tx_receipt.logs[0])['event'], "Transfer") - - approver = fresh_account() - # This account has no tokens - approver_balance = self.balanceOf(approver) - self.assertEqual(approver_balance, 0) - self.assertEqual(self.allowance(approver, spender), 0) - - tx_receipt = self.approve(approver, spender, value) - # Check event is emitted properly. - self.assertEqual(get_event_data_from_log(self.token_event_dict, tx_receipt.logs[0])['event'], "Approval") - self.assertEqual(self.allowance(approver, spender), value) - - # This should fail because the approver has no tokens. - self.assertReverts(self.transferFrom_byProxy, spender, approver, receiver, value) diff --git a/tests/test_ExternStateToken.py b/tests/test_ExternStateToken.py new file mode 100644 index 0000000000..5828ebb6d8 --- /dev/null +++ b/tests/test_ExternStateToken.py @@ -0,0 +1,247 @@ +from utils.deployutils import ( + W3, UNIT, MASTER, DUMMY, + fresh_account, fresh_accounts, + attempt_deploy, + mine_txs, take_snapshot, restore_snapshot +) +from utils.testutils import ( + HavvenTestCase, ZERO_ADDRESS, + generate_topic_event_map, get_event_data_from_log +) +from tests.contract_interfaces.extern_state_token_interface import ExternStateTokenInterface + + +def setUpModule(): + print("Testing ExternStateToken...") + print("=======================================") + print() + + +def tearDownModule(): + print() + print() + + +class TestExternStateToken(HavvenTestCase): + def setUp(self): + self.snapshot = take_snapshot() + + def tearDown(self): + restore_snapshot(self.snapshot) + + @classmethod + def deploy_contracts(cls): + sources = ['tests/contracts/PublicEST.sol', + 'contracts/ExternStateToken.sol', + 'contracts/TokenState.sol', 'contracts/Proxy.sol'] + + compiled, cls.event_maps = cls.compileAndMapEvents(sources) + + proxy_contract, _ = attempt_deploy( + compiled, "Proxy", MASTER, [MASTER] + ) + + tokenstate, _ = attempt_deploy(compiled, 'TokenState', + MASTER, [MASTER, MASTER]) + + token_contract, construction_txr = attempt_deploy( + compiled, 'PublicEST', MASTER, + [proxy_contract.address, "Test Token", "TEST", 1000 * UNIT, tokenstate.address, MASTER] + ) + + token_abi = compiled['PublicEST']['abi'] + token_event_dict = generate_topic_event_map(token_abi) + + proxied_token = W3.eth.contract(address=proxy_contract.address, abi=token_abi) + + mine_txs([ + tokenstate.functions.setBalanceOf(MASTER, 1000 * UNIT).transact({'from': MASTER}), + tokenstate.functions.setAssociatedContract(token_contract.address).transact({'from': MASTER}), + proxy_contract.functions.setTarget(token_contract.address).transact({'from': MASTER}) + ]) + return proxy_contract, proxied_token, compiled, token_contract, token_abi, token_event_dict, tokenstate + + @classmethod + def setUpClass(cls): + cls.proxy, cls.proxied_token, cls.compiled, cls.token_contract, cls.token_abi, cls.token_event_dict, cls.tokenstate = cls.deploy_contracts() + cls.event_map = cls.event_maps['ExternStateToken'] + cls.token = ExternStateTokenInterface(cls.proxied_token, "ExternStateToken") + + def test_constructor(self): + self.assertEqual(self.token.name(), "Test Token") + self.assertEqual(self.token.symbol(), "TEST") + self.assertEqual(self.token.decimals(), 18) + self.assertEqual(self.token.totalSupply(), 1000 * UNIT) + self.assertEqual(self.token.balanceOf(MASTER), 1000 * UNIT) + self.assertEqual(self.token.tokenState(), self.tokenstate.address) + self.assertEqual(self.tokenstate.functions.associatedContract().call(), self.token_contract.address) + + def test_provide_state(self): + tokenstate, _ = attempt_deploy(self.compiled, 'TokenState', + MASTER, + [MASTER, self.token_contract.address]) + token, _ = attempt_deploy(self.compiled, 'ExternStateToken', + MASTER, + [self.proxy.address, "Test Token", "TEST", + 1000 * UNIT, + tokenstate.address, DUMMY]) + self.assertEqual(token.functions.tokenState().call(), tokenstate.address) + + def test_getSetTokenState(self): + new_tokenstate = fresh_account() + owner = self.token.owner() + self.assertNotEqual(new_tokenstate, owner) + + # Only the owner is able to set the Fee Authority. + self.assertReverts(self.token.setTokenState, new_tokenstate, new_tokenstate) + tx_receipt = self.token.setTokenState(owner, new_tokenstate) + # Check that event is emitted. + self.assertEqual(get_event_data_from_log(self.token_event_dict, tx_receipt.logs[0])['event'], + "TokenStateUpdated") + self.assertEqual(self.token.tokenState(), new_tokenstate) + + def test_transfer(self): + sender = MASTER + sender_balance = self.token.balanceOf(sender) + + receiver = fresh_account() + receiver_balance = self.token.balanceOf(receiver) + self.assertEqual(receiver_balance, 0) + + value = 10 * UNIT + total_supply = self.token.totalSupply() + + # This should fail because receiver has no tokens + self.assertReverts(self.token.transfer, receiver, sender, value) + tx_receipt = self.token.transfer(sender, receiver, value) + # Check event is emitted properly. + self.assertEqual(get_event_data_from_log(self.token_event_dict, tx_receipt.logs[0])['event'], "Transfer") + self.assertEqual(self.token.balanceOf(receiver), receiver_balance + value) + self.assertEqual(self.token.balanceOf(sender), sender_balance - value) + + # transfers should leave the supply unchanged + self.assertEqual(self.token.totalSupply(), total_supply) + + value = 1001 * UNIT + # This should fail because balance < value and balance > totalSupply + self.assertReverts(self.token.transfer, sender, receiver, value) + + # 0 value transfers are allowed. + value = 0 + pre_sender_balance = self.token.balanceOf(sender) + pre_receiver_balance = self.token.balanceOf(receiver) + + # Disallow transfers to zero, to the contract itself, and to its proxy. + self.assertReverts(self.token.transfer, sender, ZERO_ADDRESS, value) + self.assertReverts(self.token.transfer, sender, self.token_contract.address, value) + self.assertReverts(self.token.transfer, sender, self.proxy.address, value) + + tx_receipt = self.token.transfer(sender, receiver, value) + # Check event is emitted properly. + self.assertEqual(get_event_data_from_log(self.token_event_dict, tx_receipt.logs[0])['event'], "Transfer") + self.assertEqual(self.token.balanceOf(receiver), pre_receiver_balance) + self.assertEqual(self.token.balanceOf(sender), pre_sender_balance) + + # It is also possible to send 0 value transfer from an account with 0 balance. + no_tokens = fresh_account() + self.assertEqual(self.token.balanceOf(no_tokens), 0) + tx_receipt = self.token.transfer(no_tokens, receiver, value) + # Check event is emitted properly. + self.assertEqual(get_event_data_from_log(self.token_event_dict, tx_receipt.logs[0])['event'], "Transfer") + self.assertEqual(self.token.balanceOf(no_tokens), 0) + + def test_approve(self): + approver, spender = fresh_accounts(2) + approval_amount = 1 * UNIT + + tx_receipt = self.token.approve(approver, spender, approval_amount) + + # Check event is emitted properly. + self.assertEqual(get_event_data_from_log(self.token_event_dict, tx_receipt.logs[0])['event'], "Approval") + self.assertEqual(self.token.allowance(approver, spender), approval_amount) + + # Any positive approval amount is valid, even greater than total_supply. + approval_amount = self.token.totalSupply() * 100 + tx_receipt = self.token.approve(approver, spender, approval_amount) + # Check event is emitted properly. + self.assertEqual(get_event_data_from_log(self.token_event_dict, tx_receipt.logs[0])['event'], "Approval") + self.assertEqual(self.token.allowance(approver, spender), approval_amount) + + def test_transferFrom(self): + approver = MASTER + spender, receiver = fresh_accounts(2) + + approver_balance = self.token.balanceOf(approver) + spender_balance = self.token.balanceOf(spender) + receiver_balance = self.token.balanceOf(receiver) + + value = 10 * UNIT + total_supply = self.token.totalSupply() + + # This fails because there has been no approval yet + self.assertReverts(self.token.transferFrom, spender, approver, receiver, value) + + tx_receipt = self.token.approve(approver, spender, 2 * value) + # Check event is emitted properly. + self.assertEqual(get_event_data_from_log(self.token_event_dict, tx_receipt.logs[0])['event'], "Approval") + self.assertEqual(self.token.allowance(approver, spender), 2 * value) + + self.assertReverts(self.token.transferFrom, spender, approver, receiver, 2 * value + 1) + tx_receipt = self.token.transferFrom(spender, approver, receiver, value) + # Check event is emitted properly. + self.assertEqual(get_event_data_from_log(self.token_event_dict, tx_receipt.logs[0])['event'], "Transfer") + + self.assertEqual(self.token.balanceOf(approver), approver_balance - value) + self.assertEqual(self.token.balanceOf(spender), spender_balance) + self.assertEqual(self.token.balanceOf(receiver), receiver_balance + value) + self.assertEqual(self.token.allowance(approver, spender), value) + self.assertEqual(self.token.totalSupply(), total_supply) + + # Empty the account + tx_receipt = self.token.transferFrom(spender, approver, receiver, value) + # Check event is emitted properly. + self.assertEqual(get_event_data_from_log(self.token_event_dict, tx_receipt.logs[0])['event'], "Transfer") + + approver = fresh_account() + # This account has no tokens + approver_balance = self.token.balanceOf(approver) + self.assertEqual(approver_balance, 0) + self.assertEqual(self.token.allowance(approver, spender), 0) + + tx_receipt = self.token.approve(approver, spender, value) + # Check event is emitted properly. + self.assertEqual(get_event_data_from_log(self.token_event_dict, tx_receipt.logs[0])['event'], "Approval") + self.assertEqual(self.token.allowance(approver, spender), value) + + # This should fail because the approver has no tokens. + self.assertReverts(self.token.transferFrom, spender, approver, receiver, value) + + def test_event_Transfer(self): + receiver = fresh_account() + self.assertNotEqual(receiver, MASTER) + tx = self.token.transfer(MASTER, receiver, 10 * UNIT) + self.assertEventEquals(self.token_event_dict, + tx.logs[0], "Transfer", + {"from": MASTER, + "to": receiver, + "value": 10 * UNIT}, + self.proxy.address) + + def test_event_Approval(self): + receiver = fresh_account() + self.assertNotEqual(receiver, MASTER) + tx = self.token.approve(MASTER, receiver, 10 * UNIT) + self.assertEventEquals(self.token_event_dict, + tx.logs[0], "Approval", + {"owner": MASTER, + "spender": receiver, + "value": 10 * UNIT}, + self.proxy.address) + + def test_event_TokenStateUpdated(self): + new_tokenstate = fresh_account() + tx = self.token.setTokenState(MASTER, new_tokenstate) + self.assertEventEquals(self.token_event_dict, + tx.logs[0], "TokenStateUpdated", + {"newTokenState": new_tokenstate}, + self.proxy.address) diff --git a/tests/test_FeeCollection.py b/tests/test_FeeCollection.py index 999c71f8cd..ec39ae33cb 100644 --- a/tests/test_FeeCollection.py +++ b/tests/test_FeeCollection.py @@ -1,593 +1,411 @@ -import unittest -import time - -import utils.generalutils -from utils.deployutils import attempt, compile_contracts, attempt_deploy, W3, mine_txs, mine_tx, \ - UNIT, MASTER, DUMMY, fast_forward, fresh_accounts, take_snapshot, restore_snapshot, ETHER -from utils.testutils import assertReverts, block_time, assertClose, ZERO_ADDRESS - -SOLIDITY_SOURCES = ["tests/contracts/PublicHavven.sol", "tests/contracts/PublicEtherNomin.sol", - "tests/contracts/FakeCourt.sol", "contracts/Havven.sol"] - - -def deploy_public_contracts(): - print("Deployment initiated.\n") - - compiled = attempt(compile_contracts, [SOLIDITY_SOURCES], "Compiling contracts... ") - - # Deploy contracts - havven_contract, hvn_txr = attempt_deploy(compiled, 'PublicHavven', - MASTER, [ZERO_ADDRESS, MASTER]) - nomin_contract, nom_txr = attempt_deploy(compiled, 'PublicEtherNomin', - MASTER, - [havven_contract.address, MASTER, MASTER, - 1000 * UNIT, MASTER, ZERO_ADDRESS]) - court_contract, court_txr = attempt_deploy(compiled, 'FakeCourt', - MASTER, - [havven_contract.address, nomin_contract.address, - MASTER]) - - # Install proxies - havven_proxy, _ = attempt_deploy(compiled, 'Proxy', - MASTER, [havven_contract.address, MASTER]) - mine_tx(havven_contract.functions.setProxy(havven_proxy.address).transact({'from': MASTER})) - proxy_havven = W3.eth.contract(address=havven_proxy.address, abi=compiled['PublicHavven']['abi']) - - nomin_proxy, _ = attempt_deploy(compiled, 'Proxy', - MASTER, [nomin_contract.address, MASTER]) - mine_tx(nomin_contract.functions.setProxy(nomin_proxy.address).transact({'from': MASTER})) - proxy_nomin = W3.eth.contract(address=nomin_proxy.address, abi=compiled['PublicEtherNomin']['abi']) - - # Hook up each of those contracts to each other - txs = [havven_contract.functions.setNomin(nomin_contract.address).transact({'from': MASTER}), - nomin_contract.functions.setCourt(court_contract.address).transact({'from': MASTER})] - attempt(mine_txs, [txs], "Linking contracts... ") - - print("\nDeployment complete.\n") - return proxy_havven, proxy_nomin, havven_proxy, nomin_proxy, havven_contract, nomin_contract, court_contract +from utils.deployutils import ( + W3, UNIT, MASTER, DUMMY, + fast_forward, fresh_accounts, fresh_account, + take_snapshot, restore_snapshot, + attempt_deploy, mine_tx, + mine_txs +) +from utils.testutils import HavvenTestCase, ZERO_ADDRESS, block_time +from tests.contract_interfaces.havven_interface import PublicHavvenInterface +from tests.contract_interfaces.nomin_interface import PublicNominInterface +from tests.contract_interfaces.court_interface import FakeCourtInterface def setUpModule(): print("Testing FeeCollection...") + print("========================") + print() def tearDownModule(): print() + print() -class TestHavven(unittest.TestCase): +class TestFeeCollection(HavvenTestCase): def setUp(self): self.snapshot = take_snapshot() - utils.generalutils.time_fast_forwarded = 0 - self.initial_time = round(time.time()) - time_remaining = self.h_targetFeePeriodDurationSeconds() + self.h_feePeriodStartTime() - block_time() - fast_forward(time_remaining + 1) - self.h_recomputeLastAverageBalance(MASTER) - - # Reset the price at the start of tests so that it's never stale. - self.n_updatePrice(self.n_oracle(), self.n_etherPrice(), self.now_block_time()) - # Reset the liquidation timestamp so that it's never active. - owner = self.n_owner() - self.n_forceLiquidation(owner) - self.n_terminateLiquidation(owner) def tearDown(self): restore_snapshot(self.snapshot) - def test_time_elapsed(self): - return utils.generalutils.time_fast_forwarded + (round(time.time()) - self.initial_time) - - def now_block_time(self): - return block_time() + self.test_time_elapsed() + @classmethod + def deployContracts(cls): + sources = ["tests/contracts/PublicHavven.sol", "tests/contracts/PublicNomin.sol", + "tests/contracts/FakeCourt.sol", "contracts/Havven.sol"] + print("Deployment initiated.\n") + + compiled, cls.event_maps = cls.compileAndMapEvents(sources) + + # Deploy contracts + havven_proxy, _ = attempt_deploy(compiled, 'Proxy', MASTER, [MASTER]) + nomin_proxy, _ = attempt_deploy(compiled, 'Proxy', MASTER, [MASTER]) + proxied_havven = W3.eth.contract(address=havven_proxy.address, abi=compiled['PublicHavven']['abi']) + proxied_nomin = W3.eth.contract(address=nomin_proxy.address, abi=compiled['PublicNomin']['abi']) + tokenstate, _ = attempt_deploy(compiled, 'TokenState', + MASTER, [MASTER, MASTER]) + havven_contract, hvn_txr = attempt_deploy(compiled, 'PublicHavven', + MASTER, [havven_proxy.address, tokenstate.address, MASTER, MASTER, UNIT//2]) + nomin_contract, nom_txr = attempt_deploy(compiled, 'PublicNomin', + MASTER, + [nomin_proxy.address, havven_contract.address, MASTER]) + court_contract, court_txr = attempt_deploy(compiled, 'FakeCourt', + MASTER, + [havven_contract.address, nomin_contract.address, + MASTER]) + + # Hook up each of those contracts to each other + mine_txs([tokenstate.functions.setBalanceOf(havven_contract.address, 100000000 * UNIT).transact({'from': MASTER}), + tokenstate.functions.setAssociatedContract(havven_contract.address).transact({'from': MASTER}), + havven_proxy.functions.setTarget(havven_contract.address).transact({'from': MASTER}), + nomin_proxy.functions.setTarget(nomin_contract.address).transact({'from': MASTER}), + havven_contract.functions.setNomin(nomin_contract.address).transact({'from': MASTER}), + nomin_contract.functions.setCourt(court_contract.address).transact({'from': MASTER})]) + + print("\nDeployment complete.\n") + return havven_proxy, proxied_havven, nomin_proxy, proxied_nomin, havven_contract, nomin_contract, court_contract @classmethod def setUpClass(cls): - cls.havven, cls.nomin, cls.havven_proxy, cls.nomin_proxy, cls.havven_real, cls.nomin_real, cls.fake_court = deploy_public_contracts() - - cls.assertClose = assertClose - cls.assertReverts = assertReverts - fast_forward(weeks=102) - - # INHERITED - # OWNED - cls.h_owner = lambda self: self.havven.functions.owner().call() - cls.h_nominateOwner = lambda self, sender, addr: mine_tx( - self.havven.functions.nominateOwner(addr).transact({'from': sender})) - cls.h_acceptOwnership = lambda self, sender: mine_tx( - self.havven.functions.acceptOwnership().transact({'from': sender})) - - # ExternStateProxyToken (transfer/transferFrom are overwritten) - # totalSupply - cls.h_totalSupply = lambda self: self.havven.functions.totalSupply().call() - cls.h_name = lambda self: self.havven.functions.name().call() - cls.h_symbol = lambda self: self.havven.functions.symbol().call() - cls.h_balanceOf = lambda self, a: self.havven.functions.balanceOf(a).call() - cls.h_allowance = lambda self, owner, spender: self.havven.functions.allowance(owner, spender).call() - cls.h_approve = lambda self, sender, spender, val: mine_tx( - self.havven.functions.approve(spender, val).transact({"from": sender})) - - # HAVVEN - # GETTERS - cls.h_currentBalanceSum = lambda self, addr: self.havven.functions._currentBalanceSum(addr).call() - cls.h_lastAverageBalance = lambda self, addr: self.havven.functions.lastAverageBalance(addr).call() - cls.h_penultimateAverageBalance = lambda self, addr: self.havven.functions.penultimateAverageBalance( - addr).call() - cls.h_lastTransferTimestamp = lambda self, addr: self.havven.functions._lastTransferTimestamp(addr).call() - cls.h_hasWithdrawnLastPeriodFees = lambda self, addr: self.havven.functions._hasWithdrawnLastPeriodFees( - addr).call() - cls.h_lastAverageBalanceNeedsRecomputation = lambda self, addr: \ - self.havven.functions.lastAverageBalanceNeedsRecomputation(addr).call() - - cls.h_feePeriodStartTime = lambda self: self.havven.functions.feePeriodStartTime().call() - cls.h_lastFeePeriodStartTime = lambda self: self.havven.functions._lastFeePeriodStartTime().call() - cls.h_penultimateFeePeriodStartTime = lambda self: self.havven.functions._penultimateFeePeriodStartTime().call() - cls.h_targetFeePeriodDurationSeconds = lambda \ - self: self.havven.functions.targetFeePeriodDurationSeconds().call() - cls.h_minFeePeriodDurationSeconds = lambda self: self.havven.functions._minFeePeriodDurationSeconds().call() - cls.h_maxFeePeriodDurationSeconds = lambda self: self.havven.functions._maxFeePeriodDurationSeconds().call() - cls.h_lastFeesCollected = lambda self: self.havven.functions.lastFeesCollected().call() - - cls.h_get_nomin = lambda self: self.havven.functions.nomin().call() - - # - # SETTERS - cls.h_setNomin = lambda self, sender, addr: mine_tx( - self.havven.functions.setNomin(addr).transact({'from': sender})) - cls.h_setTargetFeePeriodDuration = lambda self, sender, dur: mine_tx( - self.havven.functions.setTargetFeePeriodDuration(dur).transact({'from': sender})) + cls.havven_proxy, cls.proxied_havven, cls.nomin_proxy, cls.proxied_nomin, cls.havven_contract, cls.nomin_contract, cls.fake_court_contract = cls.deployContracts() - # - # FUNCTIONS - cls.h_endow = lambda self, sender, addr, amt: mine_tx( - self.havven.functions.endow(addr, amt).transact({'from': sender})) - cls.h_transfer = lambda self, sender, addr, amt: mine_tx( - self.havven.functions.transfer(addr, amt).transact({'from': sender})) - cls.h_transferFrom = lambda self, sender, frm, to, amt: mine_tx( - self.havven.functions.transferFrom(frm, to, amt).transact({'from': sender})) - cls.h_recomputeLastAverageBalance = lambda self, sender: mine_tx( - self.havven.functions.recomputeLastAverageBalance().transact({'from': sender})) - cls.h_rolloverFeePeriod = lambda self, sender: mine_tx( - self.havven.functions.rolloverFeePeriod().transact({'from': sender})) + cls.event_map = cls.event_maps['Havven'] - # - # INTERNAL - cls.h_adjustFeeEntitlement = lambda self, sender, acc, p_bal: mine_tx( - self.havven.functions._adjustFeeEntitlement(acc, p_bal).transact({'from': sender})) - # rolloverFee (ltt->last_transfer_time) - cls.h_rolloverFee = lambda self, sender, acc, ltt, p_bal: mine_tx( - self.havven.functions._rolloverFee(acc, ltt, p_bal).transact({'from': sender})) + cls.havven = PublicHavvenInterface(cls.proxied_havven, "Havven") + cls.nomin = PublicNominInterface(cls.proxied_nomin, "Nomin") - # withdrawFeeEntitlement - cls.h_withdrawFeeEntitlement = lambda self, sender: mine_tx( - self.havven.functions.withdrawFeeEntitlement().transact({'from': sender})) + fast_forward(weeks=102) - # - # MODIFIERS - # postCheckFeePeriodRollover - cls.h_checkFeePeriodRollover = lambda self, sender: mine_tx( - self.havven.functions._checkFeePeriodRollover().transact({'from': sender})) - - cls.fake_court_setNomin = lambda sender, new_nomin: mine_tx( - cls.fake_court.functions.setNomin(new_nomin).transact({'from': sender})) - cls.fake_court_setConfirming = lambda sender, target, status: mine_tx( - cls.fake_court.functions.setConfirming(target, status).transact({'from': sender})) - cls.fake_court_setVotePasses = lambda sender, target, status: mine_tx( - cls.fake_court.functions.setVotePasses(target, status).transact({'from': sender})) - cls.fake_court_confiscateBalance = lambda sender, target: mine_tx( - cls.fake_court.functions.confiscateBalance(target).transact({'from': sender})) - cls.fake_court_setNomin(W3.eth.accounts[0], cls.nomin.address) - - cls.n_owner = lambda self: cls.nomin.functions.owner().call() - cls.n_oracle = lambda self: cls.nomin.functions.oracle().call() - cls.n_court = lambda self: cls.nomin.functions.court().call() - cls.n_beneficiary = lambda self: cls.nomin.functions.beneficiary().call() - cls.n_nominPool = lambda self: cls.nomin.functions.nominPool().call() - cls.n_poolFeeRate = lambda self: cls.nomin.functions.poolFeeRate().call() - cls.n_liquidationPeriod = lambda self: cls.nomin.functions.liquidationPeriod().call() - cls.n_liquidationTimestamp = lambda self: cls.nomin.functions.liquidationTimestamp().call() - cls.n_etherPrice = lambda self: cls.nomin.functions.etherPrice().call() - cls.n_frozen = lambda self, address: cls.nomin.functions.frozen(address).call() - cls.n_lastPriceUpdate = lambda self: cls.nomin.functions.lastPriceUpdate().call() - cls.n_stalePeriod = lambda self: cls.nomin.functions.stalePeriod().call() - - cls.n_nominateOwner = lambda self, sender, address: mine_tx( - cls.nomin.functions.nominateOwner(address).transact({'from': sender})) - cls.n_acceptOwnership = lambda self, sender: mine_tx( - cls.nomin.functions.acceptOwnership().transact({'from': sender})) - cls.n_setOracle = lambda self, sender, address: mine_tx( - cls.nomin.functions.setOracle(address).transact({'from': sender})) - cls.n_setCourt = lambda self, sender, address: mine_tx( - cls.nomin.functions.setCourt(address).transact({'from': sender})) - cls.n_setBeneficiary = lambda self, sender, address: mine_tx( - cls.nomin.functions.setBeneficiary(address).transact({'from': sender})) - cls.n_setPoolFeeRate = lambda self, sender, rate: mine_tx( - cls.nomin.functions.setPoolFeeRate(rate).transact({'from': sender})) - cls.n_updatePrice = lambda self, sender, price, timeSent: mine_tx( - cls.nomin_real.functions.updatePrice(price, timeSent).transact({'from': sender})) - cls.n_setStalePeriod = lambda self, sender, period: mine_tx( - cls.nomin.functions.setStalePeriod(period).transact({'from': sender})) - - cls.n_fiatValue = lambda self, eth: cls.nomin.functions.fiatValue(eth).call() - cls.n_fiatBalance = lambda self: cls.nomin.functions.fiatBalance().call() - cls.n_collateralisationRatio = lambda self: cls.nomin.functions.collateralisationRatio().call() - cls.n_etherValue = lambda self, fiat: cls.nomin.functions.etherValue(fiat).call() - cls.n_etherValueAllowStale = lambda self, fiat: cls.nomin.functions.publicEtherValueAllowStale(fiat).call() - cls.n_poolFeeIncurred = lambda self, n: cls.nomin.functions.poolFeeIncurred(n).call() - cls.n_purchaseCostFiat = lambda self, n: cls.nomin.functions.purchaseCostFiat(n).call() - cls.n_purchaseCostEther = lambda self, n: cls.nomin.functions.purchaseCostEther(n).call() - cls.n_saleProceedsFiat = lambda self, n: cls.nomin.functions.saleProceedsFiat(n).call() - cls.n_saleProceedsEther = lambda self, n: cls.nomin.functions.saleProceedsEther(n).call() - cls.n_saleProceedsEtherAllowStale = lambda self, n: cls.nomin.functions.publicSaleProceedsEtherAllowStale( - n).call() - cls.n_priceIsStale = lambda self: cls.nomin.functions.priceIsStale().call() - cls.n_isLiquidating = lambda self: cls.nomin.functions.isLiquidating().call() - cls.n_canSelfDestruct = lambda self: cls.nomin.functions.canSelfDestruct().call() - - cls.n_transferPlusFee = lambda self, value: cls.nomin.functions.transferPlusFee(value).call() - cls.n_transfer = lambda self, sender, recipient, value: mine_tx( - cls.nomin.functions.transfer(recipient, value).transact({'from': sender})) - cls.n_transferFrom = lambda self, sender, fromAccount, to, value: mine_tx( - cls.nomin.functions.transferFrom(fromAccount, to, value).transact({'from': sender})) - cls.n_approve = lambda self, sender, spender, value: mine_tx( - cls.nomin.functions.approve(spender, value).transact({'from': sender})) - cls.n_replenishPool = lambda self, sender, n, value: mine_tx( - cls.nomin.functions.replenishPool(n).transact({'from': sender, 'value': value})) - cls.n_diminishPool = lambda self, sender, n: mine_tx(cls.nomin.functions.diminishPool(n).transact({'from': sender})) - cls.n_buy = lambda self, sender, n, value: mine_tx( - cls.nomin.functions.buy(n).transact({'from': sender, 'value': value})) - cls.n_sell = lambda self, sender, n: mine_tx( - cls.nomin.functions.sell(n).transact({'from': sender, 'gasPrice': 10})) - - cls.n_forceLiquidation = lambda self, sender: mine_tx( - cls.nomin.functions.forceLiquidation().transact({'from': sender})) - cls.n_liquidate = lambda self, sender: mine_tx(cls.nomin.functions.liquidate().transact({'from': sender})) - cls.n_extendLiquidationPeriod = lambda self, sender, extension: mine_tx( - cls.nomin.functions.extendLiquidationPeriod(extension).transact({'from': sender})) - cls.n_terminateLiquidation = lambda self, sender: mine_tx( - cls.nomin.functions.terminateLiquidation().transact({'from': sender})) - cls.n_selfDestruct = lambda self, sender: mine_tx(cls.nomin.functions.selfDestruct().transact({'from': sender})) - - cls.n_confiscateBalance = lambda self, sender, target: mine_tx( - cls.nomin.functions.confiscateBalance(target).transact({'from': sender})) - cls.n_unfreezeAccount = lambda self, sender, target: mine_tx( - cls.nomin.functions.unfreezeAccount(target).transact({'from': sender})) - - cls.n_name = lambda self: cls.nomin.functions.name().call() - cls.n_symbol = lambda self: cls.nomin.functions.symbol().call() - cls.n_totalSupply = lambda self: cls.nomin.functions.totalSupply().call() - cls.n_balanceOf = lambda self, account: cls.nomin.functions.balanceOf(account).call() - cls.n_transferFeeRate = lambda self: cls.nomin.functions.transferFeeRate().call() - cls.n_feePool = lambda self: cls.nomin.functions.feePool().call() - cls.n_feeAuthority = lambda self: cls.nomin.functions.feeAuthority().call() - - cls.n_debugWithdrawAllEther = lambda self, sender, recipient: mine_tx( - cls.nomin.functions.debugWithdrawAllEther(recipient).transact({'from': sender})) - cls.n_debugEmptyFeePool = lambda self, sender: mine_tx( - cls.nomin.functions.debugEmptyFeePool().transact({'from': sender})) - cls.n_debugFreezeAccount = lambda self, sender, target: mine_tx( - cls.nomin.functions.debugFreezeAccount(target).transact({'from': sender})) - - def give_master_nomins(self, amt): - fast_forward(1) # fast forward to allow price to not clash with previous block - self.n_updatePrice(MASTER, UNIT, self.now_block_time()) - self.n_replenishPool(MASTER, amt * UNIT, 2 * amt * ETHER) - ethercost = self.n_purchaseCostEther(amt * UNIT) - self.n_buy(MASTER, amt * UNIT, ethercost) + cls.fake_court = FakeCourtInterface(cls.fake_court_contract, 'FakeCourt') + cls.fake_court.setNomin(MASTER, cls.nomin_contract.address) + + def havven_updatePrice(self, sender, price, time): + return mine_tx(self.havven_contract.functions.updatePrice(price, time).transact({'from': sender}), 'updatePrice', 'Havven') + + def rollover_and_validate(self, duration=None): + time = duration if duration is not None else self.havven.feePeriodDuration() + 1 + fast_forward(time) + tx = self.havven.rolloverFeePeriodIfElapsed(DUMMY) + rollover_time = block_time(tx.blockNumber) + self.assertEventEquals(self.event_map, + tx.logs[0], "FeePeriodRollover", + {"timestamp": rollover_time}, + self.havven_proxy.address) + + def withdraw_and_validate(self, addr): + self.havven.recomputeLastAverageBalance(addr, addr) + self.assertFalse(self.havven.hasWithdrawnFees(addr)) + self.havven.withdrawFees(addr) + self.assertTrue(self.havven.hasWithdrawnFees(addr)) + + def test_hasWithdrawnFees(self): + issuer = fresh_account() + self.havven.endow(MASTER, issuer, UNIT) + self.havven.setIssuer(MASTER, issuer, True) + self.havven_updatePrice(self.havven.oracle(), UNIT, block_time()) + self.havven.issueNomins(issuer, self.havven.maxIssuableNomins(issuer)) + self.nomin.generateFees(MASTER, 100 * UNIT) + self.rollover_and_validate() + self.withdraw_and_validate(issuer) # Scenarios to test # Basic: # people transferring nomins, other people collecting - def check_fees_collected(self, percentage_havvens, hav_holders, nom_users): + def check_fees_collected(self, percentage_havvens, hav_holders): """ Check that a single fee periods fees are collected correctly by some % of havven holders :param percentage_havvens: the percent of havvens being used :param hav_holders: a list (later normalised) of quantities each havven holder will have - :param nom_users: a list of nomin users, and how many nomins each holds """ - addresses = fresh_accounts(len(hav_holders) + len(nom_users) + 1) + addresses = fresh_accounts(len(hav_holders) + 1) # Normalise havven holders quantites sum_vals = sum(hav_holders) hav_holders = [((i / sum_vals) * percentage_havvens) for i in hav_holders] # give the percentage of havvens to each holder - h_total_supply = self.h_totalSupply() + h_total_supply = self.havven.totalSupply() hav_addr = addresses[:len(hav_holders)] for i in range(len(hav_holders)): # use int to clear float imprecision - self.h_endow(MASTER, hav_addr[i], int(100 * hav_holders[i]) * h_total_supply // 100) - self.assertClose(self.h_balanceOf(hav_addr[i]), h_total_supply * hav_holders[i], precision=5) + self.havven.endow(MASTER, hav_addr[i], int(hav_holders[i] * h_total_supply) // UNIT * UNIT) + self.assertClose( + self.havven.balanceOf(hav_addr[i]), + h_total_supply * hav_holders[i], + precision=5 + ) + self.assertClose( + sum([self.havven.balanceOf(addr) for addr in hav_addr]), + int(h_total_supply * percentage_havvens), + precision=5 + ) + + self.havven_updatePrice(self.havven.oracle(), UNIT, self.havven.currentTime() + 1) + + self.rollover_and_validate() - self.assertClose(sum([self.h_balanceOf(addr) for addr in hav_addr]), int(h_total_supply * percentage_havvens), - precision=5) - - # give each nomin holder their share of nomins - nom_addr = addresses[len(hav_holders):-1] - self.give_master_nomins(sum(nom_users) * 2) - - for i in range(len(nom_users)): - self.n_transfer(MASTER, nom_addr[i], nom_users[i] * UNIT) - self.assertEqual(self.n_balanceOf(nom_addr[i]), nom_users[i] * UNIT) - - # will receive and send back nomins - receiver = addresses[-1] + for addr in hav_addr: + # period hasn't rolled over yet, so no fees should get collected + self.assertEqual(self.havven.nominsIssued(addr), 0) + self.assertEqual(self.nomin.balanceOf(addr), 0) - # generate some fees - for addr in nom_addr: - self.n_transfer(addr, receiver, int(((self.n_balanceOf(addr) * UNIT) // (self.n_transferFeeRate() + UNIT)))) - self.n_transfer(receiver, addr, - int(((self.n_balanceOf(receiver) * UNIT) // (self.n_transferFeeRate() + UNIT)))) + self.havven_updatePrice(self.havven.oracle(), UNIT, self.havven.currentTime() + 1) for addr in hav_addr: - self.h_withdrawFeeEntitlement(addr) - self.assertEqual(self.n_balanceOf(addr), 0) + self.havven.setIssuer(MASTER, addr, True) + self.havven.issueNomins(addr, self.havven.maxIssuableNomins(addr)) + self.nomin.generateFees(MASTER, 100 * UNIT) # fast forward to next period - fast_forward(2 * self.h_targetFeePeriodDurationSeconds()) - self.h_checkFeePeriodRollover(DUMMY) - inital_pool = self.n_feePool() + self.rollover_and_validate() + + fee_pool = self.nomin.feePool() + self.assertEqual(fee_pool, self.havven.lastFeesCollected()) total_fees_collected = 0 - for addr in hav_addr: - self.h_withdrawFeeEntitlement(addr) + + self.havven_updatePrice(self.havven.oracle(), UNIT, self.havven.currentTime() + 1) # ensure price not stale + for n, addr in enumerate(hav_addr): + self.withdraw_and_validate(addr) if percentage_havvens == 0: - self.assertEqual(self.n_balanceOf(addr), 0) + self.assertEqual(self.nomin.balanceOf(addr), 0) else: - self.assertNotEqual(self.n_balanceOf(addr), 0) - total_fees_collected += self.n_balanceOf(addr) - - self.assertClose(self.n_feePool() + total_fees_collected, inital_pool, precision=1) - - self.assertClose(inital_pool * percentage_havvens, total_fees_collected) + self.assertClose( + self.nomin.balanceOf(addr) - self.havven.nominsIssued(addr), + fee_pool * hav_holders[n] / sum(hav_holders), + precision=3 + ) + total_fees_collected += self.nomin.balanceOf(addr) - self.havven.nominsIssued(addr) + + if percentage_havvens == 0: + self.assertEqual(total_fees_collected, 0) + else: + self.assertClose(fee_pool, total_fees_collected, precision=3) def test_100_percent_withdrawal(self): - self.check_fees_collected(1, [10, 20, 30, 40], [100, 200, 200, 300, 300]) + self.check_fees_collected(1, [100, 200, 200, 300, 300]) def test_50_percent_withdrawal(self): - self.check_fees_collected(.5, [10, 20, 30, 40], [100, 200, 200, 300, 300]) + self.check_fees_collected(.5, [100, 200, 200, 300, 300]) def test_0_percent_withdrawal(self): - self.check_fees_collected(0, [10, 20, 30, 40], [100, 200, 200, 300, 300]) + self.check_fees_collected(0, [100, 200, 200, 300, 300]) # - fees rolling over - def check_fees_rolling_over(self, percentage_havvens, hav_holders, nom_users): + def check_fees_rolling_over(self, percentage_havvens, hav_holders): """ Check that fees roll over multiple fee periods fees are collected correctly by some % of havven holders at the end of all the fee periods rolling over :param percentage_havvens: the percent of havvens being used :param hav_holders: a list (later normalised) of quantities each havven holder will have - :param nom_users: a list of nomin users, and how many nomins each holds """ - addresses = fresh_accounts(len(hav_holders) + len(nom_users) + 1) + addresses = fresh_accounts(len(hav_holders) + 1) # create havven holders, and give their share of havvens sum_vals = sum(hav_holders) hav_holders = [((i / sum_vals) * percentage_havvens) for i in hav_holders] - h_total_supply = self.h_totalSupply() + h_total_supply = self.havven.totalSupply() hav_addr = addresses[:len(hav_holders)] for i in range(len(hav_holders)): # use int to clear float imprecision - self.h_endow(MASTER, hav_addr[i], int(100 * hav_holders[i]) * h_total_supply // 100) - self.assertClose(self.h_balanceOf(hav_addr[i]), h_total_supply * hav_holders[i], precision=5) - - self.assertClose(sum([self.h_balanceOf(addr) for addr in hav_addr]), int(h_total_supply * percentage_havvens), - precision=5) - - # give each nomin holder their share of nomins - nom_addr = addresses[len(hav_holders):-1] - self.give_master_nomins(sum(nom_users) * 2) + self.havven.endow(MASTER, hav_addr[i], int(hav_holders[i] * h_total_supply) // UNIT * UNIT) + self.assertClose(self.havven.balanceOf(hav_addr[i]), h_total_supply * hav_holders[i], precision=5) - for i in range(len(nom_users)): - self.n_transfer(MASTER, nom_addr[i], nom_users[i] * UNIT) - self.assertEqual(self.n_balanceOf(nom_addr[i]), nom_users[i] * UNIT) + self.assertClose( + sum([self.havven.balanceOf(addr) for addr in hav_addr]), + int(h_total_supply * percentage_havvens), + precision=5 + ) - # will receive and send back nomins - receiver = addresses[-1] + self.havven_updatePrice(self.havven.oracle(), UNIT, self.havven.currentTime() + 2) + self.nomin.generateFees(MASTER, 20 * UNIT) - # generate some fees - for addr in nom_addr: - self.n_transfer(addr, receiver, int(((self.n_balanceOf(addr) * UNIT) // (self.n_transferFeeRate() + UNIT)))) - self.n_transfer(receiver, addr, - int(((self.n_balanceOf(receiver) * UNIT) // (self.n_transferFeeRate() + UNIT)))) + for addr in hav_addr: + self.havven.setIssuer(MASTER, addr, True) + self.havven.issueNomins(addr, self.havven.maxIssuableNomins(addr)) for addr in hav_addr: - self.h_withdrawFeeEntitlement(addr) - self.assertEqual(self.n_balanceOf(addr), 0) + self.withdraw_and_validate(addr) + self.assertEqual(self.nomin.balanceOf(addr), self.havven.nominsIssued(addr)) # no fees # roll over 4 more periods, generating more fees for i in range(4): # fast forward to next period - fast_forward(2 * self.h_targetFeePeriodDurationSeconds()) - self.h_checkFeePeriodRollover(DUMMY) - - # generate some more fees - for addr in nom_addr: - self.n_transfer(addr, receiver, - int(((self.n_balanceOf(addr) * UNIT) // (self.n_transferFeeRate() + UNIT)))) - self.n_transfer(receiver, addr, - int(((self.n_balanceOf(receiver) * UNIT) // (self.n_transferFeeRate() + UNIT)))) + self.rollover_and_validate() + self.nomin.generateFees(MASTER, 20 * UNIT) # fast forward to next period - fast_forward(2 * self.h_targetFeePeriodDurationSeconds()) - self.h_checkFeePeriodRollover(DUMMY) - self.assertEqual(self.n_feePool(), self.h_lastFeesCollected()) - inital_pool = self.n_feePool() + self.rollover_and_validate() + + self.assertEqual(self.nomin.feePool(), self.havven.lastFeesCollected()) + self.assertEqual(self.nomin.feePool(), 100 * UNIT) + + inital_pool = self.nomin.feePool() total_fees_collected = 0 + self.havven_updatePrice(self.havven.oracle(), UNIT, self.havven.currentTime() + 1) + for addr in hav_addr: - self.h_withdrawFeeEntitlement(addr) + self.withdraw_and_validate(addr) if percentage_havvens == 0: - self.assertEqual(self.n_balanceOf(addr), 0) + self.assertEqual(self.nomin.balanceOf(addr), 0) else: - self.assertNotEqual(self.n_balanceOf(addr), 0) - total_fees_collected += self.n_balanceOf(addr) + self.assertGreater(self.nomin.balanceOf(addr), self.havven.maxIssuableNomins(addr)) + total_fees_collected += self.nomin.balanceOf(addr) - self.havven.nominsIssued(addr) - self.assertClose(self.n_feePool() + total_fees_collected, inital_pool, precision=1) - - self.assertClose(inital_pool * percentage_havvens, total_fees_collected) + if percentage_havvens == 0: + self.assertEqual(self.nomin.feePool(), inital_pool) + self.assertEqual(total_fees_collected, 0) + else: + self.assertClose(self.nomin.feePool() + total_fees_collected, inital_pool, precision=2) + self.assertClose(inital_pool, total_fees_collected, precision=2) def test_rolling_over_100_percent_withdrawal(self): - self.check_fees_rolling_over(1, [10, 20, 30, 40], [100, 200, 200, 300, 300]) + self.check_fees_rolling_over(1, [100, 200, 200, 300, 300]) def test_rolling_over_50_percent_withdrawal(self): - self.check_fees_rolling_over(.5, [10, 20, 30, 40], [100, 200, 200, 300, 300]) + self.check_fees_rolling_over(.5, [100, 200, 200, 300, 300]) def test_rolling_over_0_percent_withdrawal(self): - self.check_fees_rolling_over(0, [10, 20, 30, 40], [100, 200, 200, 300, 300]) + self.check_fees_rolling_over(0, [100, 200, 200, 300, 300]) # Collecting after transferring havvens # i.e. checking that averages work as intended - def check_transferring_havven_fee_collection(self, h_percent, nom_users): + def check_transferring_havven_fee_collection(self, h_percent, h_price): """ - Check that the average balance calulation actually influences the number of nomins received + Check that the average balance calculation actually influences the number of nomins received (Single user only) :param h_percent: the percent of havvens being used - :param nom_users: a list of nomin users, and how many nomins each holds """ - fee_period_duration = self.h_targetFeePeriodDurationSeconds() - - addresses = fresh_accounts(len(nom_users) + 3) - havven_holder = addresses[0] - h_total_supply = self.h_totalSupply() + fee_period_duration = self.havven.feePeriodDuration() - self.h_endow(MASTER, havven_holder, int(h_total_supply * h_percent)) - self.assertClose(self.h_balanceOf(havven_holder), h_total_supply * h_percent, precision=2) + havven_holder, h_receiver = fresh_accounts(2) - nom_addr = addresses[1:-2] - self.give_master_nomins(sum(nom_users) * 2) + h_total_supply = self.havven.totalSupply() - for i in range(len(nom_users)): - self.n_transfer(MASTER, nom_addr[i], nom_users[i] * UNIT) - self.assertEqual(self.n_balanceOf(nom_addr[i]), nom_users[i] * UNIT) + self.havven.endow(MASTER, havven_holder, int(h_total_supply * h_percent) // UNIT * UNIT) + self.assertClose(self.havven.balanceOf(havven_holder), h_total_supply * h_percent, precision=2) - n_receiver = addresses[-1] - for addr in nom_addr: - self.n_transfer(addr, n_receiver, - int(((self.n_balanceOf(addr) * UNIT) // (self.n_transferFeeRate() + UNIT)))) - self.n_transfer(n_receiver, addr, - int(((self.n_balanceOf(n_receiver) * UNIT) // (self.n_transferFeeRate() + UNIT)))) + self.rollover_and_validate() - # transfer to receiver and back 5 times - # havven balance should look like: + # transfer to receiver and back 6 times + # issued nomin balance should look like: # # | : # | _ _ _ : # ||_|_|_|_|_|_:__ __ _ - h_receiver = addresses[-2] - self.h_transfer(havven_holder, h_receiver, self.h_balanceOf(havven_holder)) - fast_forward(fee_period_duration / 5) - - self.h_transfer(h_receiver, havven_holder, self.h_balanceOf(h_receiver)) - fast_forward(fee_period_duration / 5) - - self.h_transfer(havven_holder, h_receiver, self.h_balanceOf(havven_holder)) - fast_forward(fee_period_duration / 5) - - self.h_transfer(h_receiver, havven_holder, self.h_balanceOf(h_receiver)) - fast_forward(fee_period_duration / 5) - - self.h_transfer(havven_holder, h_receiver, self.h_balanceOf(havven_holder)) - fast_forward(fee_period_duration / 5) # should roll over after this - - self.assertEqual(self.h_balanceOf(havven_holder), 0) - self.assertEqual(self.n_balanceOf(havven_holder), 0) - fee_pool = self.n_feePool() - self.h_checkFeePeriodRollover(DUMMY) - self.assertEqual(self.h_lastFeesCollected(), fee_pool) - - self.h_withdrawFeeEntitlement(havven_holder) - self.h_withdrawFeeEntitlement(h_receiver) - - self.assertClose(self.n_balanceOf(havven_holder), fee_pool * 2 / 5 * h_percent) - - self.assertClose(self.n_balanceOf(h_receiver), fee_pool * 3 / 5 * h_percent) + self.havven.setIssuer(MASTER, havven_holder, True) + self.havven.setIssuer(MASTER, h_receiver, True) + + addrs = [havven_holder, h_receiver] + current_addr = False + for i in range(6): + self.havven_updatePrice(self.havven.oracle(), h_price, self.havven.currentTime() + 1) + self.havven.issueNomins(addrs[current_addr], self.havven.remainingIssuableNomins(addrs[current_addr])) + self.assertClose(self.nomin.totalSupply(), h_price * h_total_supply * h_percent * self.havven.issuanceRatio() // UNIT // UNIT) + fast_forward(fee_period_duration // 6 - 5) + self.havven.burnNomins(addrs[current_addr], self.nomin.balanceOf(addrs[current_addr])) + self.havven.transfer(addrs[current_addr], addrs[not current_addr], self.havven.balanceOf(addrs[current_addr])) + self.assertClose(self.nomin.totalSupply(), 0) + current_addr = not current_addr # switch between accounts + + self.assertEqual(self.havven.balanceOf(h_receiver), 0) + self.assertEqual(self.nomin.balanceOf(havven_holder), 0) + self.assertEqual(self.nomin.balanceOf(h_receiver), 0) + self.nomin.generateFees(MASTER, 100 * UNIT) + fee_pool = self.nomin.feePool() + self.rollover_and_validate(100) + self.assertEqual(self.havven.lastFeesCollected(), fee_pool) + + self.withdraw_and_validate(havven_holder) + self.withdraw_and_validate(h_receiver) + if h_percent == 0: + self.assertEqual(self.nomin.balanceOf(havven_holder), 0) + self.assertEqual(self.nomin.balanceOf(h_receiver), 0) + else: + self.assertClose(self.nomin.balanceOf(havven_holder), fee_pool // 2) + self.assertClose(self.nomin.balanceOf(h_receiver), fee_pool // 2) def test_transferring_havven_100_percent(self): - self.check_transferring_havven_fee_collection(1, [100, 200, 200, 300, 300]) + self.check_transferring_havven_fee_collection(1, UNIT) def test_transferring_havven_50_percent(self): - self.check_transferring_havven_fee_collection(0.5, [100, 200, 200, 300, 300]) + self.check_transferring_havven_fee_collection(0.5, UNIT) + + def test_transferring_havven_1_percent(self): + # full fees should be withdrawable by even with only 1% of havvens issuing + self.check_transferring_havven_fee_collection(0.01, UNIT) def test_transferring_havven_0_percent(self): - self.check_transferring_havven_fee_collection(0, [100, 200, 200, 300, 300]) + self.check_transferring_havven_fee_collection(0, UNIT) - # - fees rolling over - def check_fees_multi_period(self, percentage_havvens, hav_holders, nom_users): + # # - fees rolling over + def check_fees_multi_period(self, percentage_havvens, hav_holders): """ Check that fees over multiple periods are collected correctly (collecting each period) :param percentage_havvens: the percent of havvens being used :param hav_holders: a list (later normalised) of quantities each havven holder will have - :param nom_users: a list of nomin users, and how many nomins each holds """ - addresses = fresh_accounts(len(hav_holders) + len(nom_users) + 1) + addresses = fresh_accounts(len(hav_holders) + 1) - # create normalised havven holders + # create havven holders, and give their share of havvens sum_vals = sum(hav_holders) hav_holders = [((i / sum_vals) * percentage_havvens) for i in hav_holders] - h_total_supply = self.h_totalSupply() + h_total_supply = self.havven.totalSupply() hav_addr = addresses[:len(hav_holders)] for i in range(len(hav_holders)): # use int to clear float imprecision - self.h_endow(MASTER, hav_addr[i], int(100 * hav_holders[i]) * h_total_supply // 100) - self.assertClose(self.h_balanceOf(hav_addr[i]), h_total_supply * hav_holders[i], precision=5) - - self.assertClose(sum([self.h_balanceOf(addr) for addr in hav_addr]), int(h_total_supply * percentage_havvens), - precision=5) - - nom_addr = addresses[len(hav_holders):-1] - self.give_master_nomins(sum(nom_users) * 2) - - for i in range(len(nom_users)): - self.n_transfer(MASTER, nom_addr[i], nom_users[i] * UNIT) - self.assertEqual(self.n_balanceOf(nom_addr[i]), nom_users[i] * UNIT) + self.havven.endow(MASTER, hav_addr[i], int(hav_holders[i] * h_total_supply) // UNIT * UNIT) + self.assertClose(self.havven.balanceOf(hav_addr[i]), h_total_supply * hav_holders[i], precision=5) - # will receive and send back nomins - receiver = addresses[-1] + self.assertClose( + sum([self.havven.balanceOf(addr) for addr in hav_addr]), + int(h_total_supply * percentage_havvens), + precision=5 + ) - # generate some fees - for addr in nom_addr: - self.n_transfer(addr, receiver, int(((self.n_balanceOf(addr) * UNIT) // (self.n_transferFeeRate() + UNIT)))) - self.n_transfer(receiver, addr, - int(((self.n_balanceOf(receiver) * UNIT) // (self.n_transferFeeRate() + UNIT)))) + self.havven_updatePrice(self.havven.oracle(), UNIT, self.havven.currentTime() + 2) + self.nomin.generateFees(MASTER, 20 * UNIT) for addr in hav_addr: - self.h_withdrawFeeEntitlement(addr) - self.assertEqual(self.n_balanceOf(addr), 0) + self.havven.setIssuer(MASTER, addr, True) + self.havven.issueNomins(addr, self.havven.maxIssuableNomins(addr)) + + self.rollover_and_validate() # roll over 4 more periods, generating more fees, and withdrawing them for i in range(4): # fast forward to next period # generate some more fees - for addr in nom_addr: - self.n_transfer(addr, receiver, - int(((self.n_balanceOf(addr) * UNIT) // (self.n_transferFeeRate() + UNIT)))) - self.n_transfer(receiver, addr, - int(((self.n_balanceOf(receiver) * UNIT) // (self.n_transferFeeRate() + UNIT)))) + self.nomin.generateFees(MASTER, 10 * UNIT) - fast_forward(2 * self.h_targetFeePeriodDurationSeconds()) - self.h_checkFeePeriodRollover(DUMMY) + self.rollover_and_validate() # withdraw the fees - inital_pool = self.h_lastFeesCollected() + inital_pool = self.havven.lastFeesCollected() total_fees_collected = 0 for addr in hav_addr: - inital_n = self.n_balanceOf(addr) - self.h_withdrawFeeEntitlement(addr) + inital_n = self.nomin.balanceOf(addr) + self.withdraw_and_validate(addr) if percentage_havvens == 0: - self.assertEqual(self.n_balanceOf(addr), 0) + self.assertEqual(self.nomin.balanceOf(addr), 0) else: - self.assertNotEqual(self.n_balanceOf(addr), 0) - total_fees_collected += self.n_balanceOf(addr) - inital_n - self.assertClose(inital_pool * percentage_havvens, total_fees_collected, precision=5) + self.assertNotEqual(self.nomin.balanceOf(addr), 0) + total_fees_collected += self.nomin.balanceOf(addr) - inital_n + if percentage_havvens == 0: + self.assertEqual(total_fees_collected, 0) + self.assertEqual(inital_pool, self.nomin.feePool()) + else: + self.assertClose(inital_pool, total_fees_collected, precision=3) def test_multi_period_100_percent_withdrawal(self): - self.check_fees_multi_period(1, [10, 20, 30, 40], [100, 200, 200, 300, 300]) + self.check_fees_multi_period(1, [10, 20, 30, 40]) def test_multi_period_50_percent_withdrawal(self): - self.check_fees_multi_period(.5, [10, 20, 30, 40], [100, 200, 200, 300, 300]) + self.check_fees_multi_period(.5, [10, 20, 30, 40]) def test_multi_period_0_percent_withdrawal(self): - self.check_fees_multi_period(0, [10, 20, 30, 40], [100, 200, 200, 300, 300]) + self.check_fees_multi_period(0, [10, 20, 30, 40]) diff --git a/tests/test_FeeToken.py b/tests/test_FeeToken.py new file mode 100644 index 0000000000..c0e43234de --- /dev/null +++ b/tests/test_FeeToken.py @@ -0,0 +1,501 @@ +from utils.deployutils import ( + W3, UNIT, MASTER, DUMMY, fresh_account, fresh_accounts, + attempt_deploy, mine_txs, mine_tx, + take_snapshot, restore_snapshot +) +from utils.testutils import ( + HavvenTestCase, ZERO_ADDRESS, + generate_topic_event_map, get_event_data_from_log +) +from tests.contract_interfaces.fee_token_interface import PublicFeeTokenInterface + + +def setUpModule(): + print("Testing FeeToken...") + print("==============================") + print() + + +def tearDownModule(): + print() + print() + + +class TestFeeToken(HavvenTestCase): + def setUp(self): + self.snapshot = take_snapshot() + + def tearDown(self): + restore_snapshot(self.snapshot) + + @classmethod + def deployContracts(cls): + sources = ["contracts/FeeToken.sol", + "contracts/TokenState.sol", + "tests/contracts/PublicFeeToken.sol"] + + compiled, cls.event_maps = cls.compileAndMapEvents(sources) + + feetoken_abi = compiled['PublicFeeToken']['abi'] + + proxy, _ = attempt_deploy( + compiled, "Proxy", MASTER, [MASTER] + ) + proxied_feetoken = W3.eth.contract(address=proxy.address, abi=feetoken_abi) + + feetoken_event_dict = generate_topic_event_map(feetoken_abi) + feetoken_contract, construction_txr = attempt_deploy( + compiled, "PublicFeeToken", MASTER, + [proxy.address, "Test Fee Token", "FEE", UNIT // 20, MASTER, MASTER] + ) + + feestate, txr = attempt_deploy( + compiled, "TokenState", MASTER, + [MASTER, MASTER] + ) + + mine_txs([ + proxy.functions.setTarget(feetoken_contract.address).transact({'from': MASTER}), + feestate.functions.setBalanceOf(DUMMY, 1000 * UNIT).transact({'from': MASTER}), + feestate.functions.setAssociatedContract(feetoken_contract.address).transact({'from': MASTER}), + feetoken_contract.functions.setTokenState(feestate.address).transact({'from': MASTER})] + ) + + return compiled, proxy, proxied_feetoken, feetoken_contract, feetoken_event_dict, feestate + + @classmethod + def setUpClass(cls): + cls.compiled, cls.proxy, cls.proxied_feetoken, cls.feetoken_contract, cls.feetoken_event_dict, cls.feestate = cls.deployContracts() + cls.event_map = cls.event_maps['FeeToken'] + + cls.initial_beneficiary = DUMMY + cls.fee_authority = fresh_account() + + cls.feetoken = PublicFeeTokenInterface(cls.proxied_feetoken, "FeeToken") + cls.feetoken.setFeeAuthority(MASTER, cls.fee_authority) + + def feetoken_withdrawFees(self, sender, beneficiary, quantity): + return mine_tx(self.feetoken_contract.functions.withdrawFees(beneficiary, quantity).transact({'from': sender}), "withdrawFees", self.feetoken.contract_name) + + def test_constructor(self): + self.assertEqual(self.feetoken.name(), "Test Fee Token") + self.assertEqual(self.feetoken.symbol(), "FEE") + self.assertEqual(self.feetoken.decimals(), 18) + self.assertEqual(self.feetoken.totalSupply(), 0) + self.assertEqual(self.feetoken.transferFeeRate(), UNIT // 20) + self.assertEqual(self.feetoken.feeAuthority(), self.fee_authority) + self.assertEqual(self.feetoken.tokenState(), self.feestate.address) + self.assertEqual(self.feestate.functions.associatedContract().call(), self.feetoken_contract.address) + + def test_provide_tokenstate(self): + feetoken, _ = attempt_deploy(self.compiled, 'FeeToken', + MASTER, + [self.proxy.address, "Test Fee Token", "FEE", 0, + UNIT // 20, self.fee_authority, DUMMY]) + self.assertNotEqual(feetoken.functions.tokenState().call(), ZERO_ADDRESS) + + def test_getSetOwner(self): + owner = self.feetoken.owner() + new_owner = DUMMY + self.assertNotEqual(owner, new_owner) + + # Only the owner must be able to change the new owner. + self.assertReverts(self.feetoken.nominateNewOwner, new_owner, new_owner) + + self.feetoken.nominateNewOwner(owner, new_owner) + self.feetoken.acceptOwnership(new_owner) + self.assertEqual(self.feetoken.owner(), new_owner) + self.feetoken.nominateNewOwner(new_owner, owner) + self.feetoken.acceptOwnership(owner) + + def test_getSetTransferFeeRate(self): + transfer_fee_rate = self.feetoken.transferFeeRate() + new_transfer_fee_rate = transfer_fee_rate + UNIT // 20 + owner = self.feetoken.owner() + fake_owner = DUMMY + self.assertNotEqual(owner, fake_owner) + + # Only the owner is able to set the Transfer Fee Rate. + self.assertReverts(self.feetoken.setTransferFeeRate, fake_owner, new_transfer_fee_rate) + tx_receipt = self.feetoken.setTransferFeeRate(owner, new_transfer_fee_rate) + # Check that event is emitted. + self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], + "TransferFeeRateUpdated") + self.assertEqual(self.feetoken.transferFeeRate(), new_transfer_fee_rate) + + # Maximum fee rate is UNIT /10. + bad_transfer_fee_rate = UNIT + self.assertReverts(self.feetoken.setTransferFeeRate, owner, bad_transfer_fee_rate) + self.assertEqual(self.feetoken.transferFeeRate(), new_transfer_fee_rate) + + def test_getSetFeeAuthority(self): + new_fee_authority = fresh_account() + owner = self.feetoken.owner() + + # Only the owner is able to set the Fee Authority. + self.assertReverts(self.feetoken.setFeeAuthority, new_fee_authority, new_fee_authority) + tx_receipt = self.feetoken.setFeeAuthority(owner, new_fee_authority) + # Check that event is emitted. + self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], + "FeeAuthorityUpdated") + self.assertEqual(self.feetoken.feeAuthority(), new_fee_authority) + + def test_getSetTokenState(self): + _, new_tokenstate = fresh_accounts(2) + owner = self.feetoken.owner() + self.assertNotEqual(new_tokenstate, owner) + + # Only the owner is able to set the Fee Authority. + self.assertReverts(self.feetoken.setTokenState, new_tokenstate, new_tokenstate) + tx_receipt = self.feetoken.setTokenState(owner, new_tokenstate) + # Check that event is emitted. + self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], + "TokenStateUpdated") + self.assertEqual(self.feetoken.tokenState(), new_tokenstate) + + def test_balanceOf(self): + self.assertEqual(self.feetoken.balanceOf(ZERO_ADDRESS), 0) + self.assertEqual(self.feetoken.balanceOf(self.initial_beneficiary), 1000 * UNIT) + self.feetoken.setTokenState(self.feetoken.owner(), ZERO_ADDRESS) + self.assertReverts(self.feetoken.balanceOf, ZERO_ADDRESS) + + def test_allowance(self): + self.assertEqual(self.feetoken.allowance(self.initial_beneficiary, ZERO_ADDRESS), 0) + self.assertEqual(self.feetoken.allowance(ZERO_ADDRESS, self.initial_beneficiary), 0) + self.feetoken.approve(self.initial_beneficiary, ZERO_ADDRESS, 1000) + self.assertEqual(self.feetoken.allowance(self.initial_beneficiary, ZERO_ADDRESS), 1000) + self.assertEqual(self.feetoken.allowance(ZERO_ADDRESS, self.initial_beneficiary), 0) + self.feetoken.setTokenState(self.feetoken.owner(), ZERO_ADDRESS) + self.assertReverts(self.feetoken.allowance, self.initial_beneficiary, ZERO_ADDRESS) + + def test_getTransferFeeIncurred(self): + value = 10 * UNIT + fee = value * self.feetoken.transferFeeRate() // UNIT + self.assertEqual(self.feetoken.transferFeeIncurred(value), fee) + + self.assertEqual(self.feetoken.transferFeeIncurred(0), 0) + + def test_getTransferPlusFee(self): + value = 10 * UNIT + fee = value * self.feetoken.transferFeeRate() // UNIT + total = value + fee + self.assertEqual(self.feetoken.transferPlusFee(value), total) + + self.assertEqual(self.feetoken.transferPlusFee(0), 0) + + def test_amountReceived(self): + value = 10 * UNIT + self.assertEqual(self.feetoken.amountReceived(0), 0) + fee_rate = self.feetoken.transferFeeRate() + self.assertEqual(self.feetoken.amountReceived(value), (UNIT * value) // (UNIT + fee_rate)) + fee_rate = 13 * UNIT // 10000 + self.feetoken.setTransferFeeRate(MASTER, fee_rate) + self.assertEqual(self.feetoken.amountReceived(value), (UNIT * value) // (UNIT + fee_rate)) + + def test_transfer(self): + sender = self.initial_beneficiary + sender_balance = self.feetoken.balanceOf(sender) + + receiver = fresh_account() + receiver_balance = self.feetoken.balanceOf(receiver) + self.assertEqual(receiver_balance, 0) + + total_supply = self.feetoken.totalSupply() + fee_pool = self.feetoken.feePool() + value = 10 * UNIT + amountReceived = self.feetoken.amountReceived(value) + fee = value - amountReceived + + # This should fail because receiver has no tokens + self.assertReverts(self.feetoken.transfer, receiver, sender, value) + + tx_receipt = self.feetoken.transfer(sender, receiver, value) + # Check that events are emitted properly. + self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Transfer") + self.assertEqual(self.feetoken.balanceOf(receiver), receiver_balance + amountReceived) + self.assertEqual(self.feetoken.balanceOf(sender), sender_balance - value) + self.assertEqual(self.feetoken.totalSupply(), total_supply) + self.assertEqual(self.feetoken.feePool(), fee_pool + fee) + + value = 1001 * UNIT + + # This should fail because balance < value + self.assertReverts(self.feetoken.transfer, sender, receiver, value) + + # 0 Value transfers are allowed and incur no fee. + value = 0 + total_supply = self.feetoken.totalSupply() + fee_pool = self.feetoken.feePool() + + tx_receipt = self.feetoken.transfer(sender, receiver, value) + # Check that events are emitted properly. + self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Transfer") + self.assertEqual(self.feetoken.totalSupply(), total_supply) + self.assertEqual(self.feetoken.feePool(), fee_pool) + + # It is also possible to send 0 value transfer from an account with 0 balance + value = 0 + no_tokens = fresh_account() + self.assertEqual(self.feetoken.balanceOf(no_tokens), 0) + fee = self.feetoken.transferFeeIncurred(value) + total_supply = self.feetoken.totalSupply() + fee_pool = self.feetoken.feePool() + + # Disallow transfers to zero, to the contract itself, and to its proxy. + self.assertReverts(self.feetoken.transfer, sender, ZERO_ADDRESS, value) + self.assertReverts(self.feetoken.transfer, sender, self.feetoken_contract.address, value) + self.assertReverts(self.feetoken.transfer, sender, self.proxy.address, value) + + tx_receipt = self.feetoken.transfer(no_tokens, receiver, value) + # Check that events are emitted properly. + self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Transfer") + self.assertEqual(self.feetoken.balanceOf(no_tokens), 0) + self.assertEqual(self.feetoken.totalSupply(), total_supply) + self.assertEqual(self.feetoken.feePool(), fee_pool) + + def test_approve(self): + approver = MASTER + spender = fresh_account() + approval_amount = 1 * UNIT + + tx_receipt = self.feetoken.approve(approver, spender, approval_amount) + # Check that event is emitted properly. + self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Approval") + self.assertEqual(self.feetoken.allowance(approver, spender), approval_amount) + + # Any positive approval amount is valid, even greater than total_supply. + approval_amount = self.feetoken.totalSupply() * 100 + tx_receipt = self.feetoken.approve(approver, spender, approval_amount) + # Check that event is emitted properly. + self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Approval") + self.assertEqual(self.feetoken.allowance(approver, spender), approval_amount) + + def test_transferFrom(self): + approver = self.initial_beneficiary + spender, receiver = fresh_accounts(2) + self.assertNotEqual(approver, spender) + self.assertNotEqual(approver, receiver) + + approver_balance = self.feetoken.balanceOf(approver) + spender_balance = self.feetoken.balanceOf(spender) + receiver_balance = self.feetoken.balanceOf(receiver) + + value = 10 * UNIT + amountReceived = self.feetoken.amountReceived(value) + fee = value - amountReceived + total_supply = self.feetoken.totalSupply() + fee_pool = self.feetoken.feePool() + + # This fails because there has been no approval yet. + self.assertReverts(self.feetoken.transferFrom, spender, approver, receiver, value) + + # Approve amount to transfer. + tx_receipt = self.feetoken.approve(approver, spender, value) + # Check that event is emitted properly. + self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Approval") + self.assertEqual(self.feetoken.allowance(approver, spender), value) + + value = UNIT + amountReceived = self.feetoken.amountReceived(value) + fee = value - amountReceived + tx_receipt = self.feetoken.transferFrom(spender, approver, receiver, value) + + # Check that events are emitted properly. + self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Transfer") + + self.assertEqual(self.feetoken.allowance(approver, spender), 9 * UNIT) + + self.assertEqual(self.feetoken.balanceOf(approver), approver_balance - value) + self.assertEqual(self.feetoken.balanceOf(spender), spender_balance) + self.assertEqual(self.feetoken.balanceOf(receiver), receiver_balance + amountReceived) + self.assertEqual(self.feetoken.totalSupply(), total_supply) + self.assertEqual(self.feetoken.feePool(), fee_pool + fee) + + value = 9 * UNIT + amountReceived = self.feetoken.amountReceived(value) + fee = value - amountReceived + tx_receipt = self.feetoken.transferFrom(spender, approver, receiver, value) + + # Check that events are emitted properly. + self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Transfer") + + self.assertEqual(self.feetoken.allowance(approver, spender), 0) + self.assertEqual(self.feetoken.balanceOf(approver), approver_balance - 10 * UNIT) + self.assertEqual(self.feetoken.balanceOf(spender), spender_balance) + self.assertEqual(self.feetoken.balanceOf(receiver), receiver_balance + self.feetoken.amountReceived(UNIT) + self.feetoken.amountReceived(9 * UNIT)) + self.assertEqual(self.feetoken.totalSupply(), total_supply) + + totalFees = 9 * UNIT - self.feetoken.amountReceived(9 * UNIT) + UNIT - self.feetoken.amountReceived(UNIT) + self.assertEqual(self.feetoken.feePool(), fee_pool + totalFees) + + approver = fresh_account() + # This account has no tokens. + approver_balance = self.feetoken.balanceOf(approver) + self.assertEqual(approver_balance, 0) + + value = 10 * UNIT + tx_receipt = self.feetoken.approve(approver, spender, value) + self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Approval") + + # This should fail because the approver has no tokens. + self.assertReverts(self.feetoken.transferFrom, spender, approver, receiver, 1) + + def test_withdrawFees(self): + receiver, fee_receiver, not_fee_authority = fresh_accounts(3) + self.assertNotEqual(self.fee_authority, not_fee_authority) + self.assertNotEqual(self.fee_authority, receiver) + self.assertNotEqual(self.fee_authority, fee_receiver) + + value = 500 * UNIT + amountReceived = self.feetoken.amountReceived(value) + fee = value - amountReceived + + tx_receipt = self.feetoken.transfer(self.initial_beneficiary, self.fee_authority, value) + # Check that event is emitted properly. + self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Transfer") + self.assertEqual(self.feetoken.balanceOf(self.fee_authority), amountReceived) + + total_supply = self.feetoken.totalSupply() + fee_pool = self.feetoken.feePool() + + fee_authority_balance = self.feetoken.balanceOf(self.fee_authority) + receiver_balance = self.feetoken.balanceOf(receiver) + fee_receiver_balance = self.feetoken.balanceOf(fee_receiver) + + value = self.feetoken.balanceOf(self.fee_authority) + amountReceived = self.feetoken.amountReceived(value) + cumulativeFee = fee + value - amountReceived + fee = value - amountReceived + + tx_receipt = self.feetoken.transfer(self.fee_authority, receiver, fee_authority_balance) + # Check that event is emitted properly. + self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], "Transfer") + + self.assertEqual(self.feetoken.balanceOf(receiver), receiver_balance + amountReceived) + self.assertEqual(self.feetoken.balanceOf(self.fee_authority), 0) + self.assertEqual(self.feetoken.totalSupply(), total_supply) + self.assertEqual(self.feetoken.feePool(), cumulativeFee) + + fee_pool = self.feetoken.feePool() + + # This should fail because only the Fee Authority can withdraw fees + self.assertReverts(self.feetoken_withdrawFees, not_fee_authority, not_fee_authority, fee_pool) + + # Failure due to too-large a withdrawal. + self.assertReverts(self.feetoken_withdrawFees, self.fee_authority, fee_receiver, fee_pool + 1) + + # Partial withdrawal leaves stuff in the pool + tx_receipt = self.feetoken_withdrawFees(self.fee_authority, fee_receiver, fee_pool // 4) + # Check that event is emitted properly. + self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], + "FeesWithdrawn") + self.assertEqual(3 * fee_pool // 4 + 1, self.feetoken.feePool()) + self.assertEqual(self.feetoken.balanceOf(fee_receiver), fee_receiver_balance + fee_pool // 4) + + # Withdraw the rest + tx_receipt = self.feetoken_withdrawFees(self.fee_authority, fee_receiver, 3 * fee_pool // 4) + # Check that event is emitted properly. + self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], + "FeesWithdrawn") + + self.assertEqual(self.feetoken.balanceOf(fee_receiver), fee_receiver_balance + fee_pool - 1) + self.assertEqual(self.feetoken.totalSupply(), total_supply) + self.assertEqual(self.feetoken.feePool(), 1) + + def test_donateToFeePool(self): + donor, pauper = fresh_accounts(2) + self.assertNotEqual(donor, pauper) + + self.assertGreater(self.feetoken.balanceOf(self.initial_beneficiary), 10 * UNIT) + + self.feetoken.transferSenderPaysFee(self.initial_beneficiary, donor, 10 * UNIT) + self.feetoken_withdrawFees(self.fee_authority, self.initial_beneficiary, self.feetoken.feePool()) + + # No donations by people with no money... + self.assertReverts(self.feetoken.donateToFeePool, pauper, 10 * UNIT) + # ...even if they donate nothing. + self.assertReverts(self.feetoken.donateToFeePool, pauper, 0) + + # No donations more than you possess. + self.assertReverts(self.feetoken.donateToFeePool, donor, 11 * UNIT) + + self.assertEqual(self.feetoken.feePool(), 0) + self.assertEqual(self.feetoken.balanceOf(donor), 10 * UNIT) + self.assertTrue(self.feetoken.donateToFeePool(donor, UNIT)) + self.assertEqual(self.feetoken.feePool(), UNIT) + self.assertEqual(self.feetoken.balanceOf(donor), 9 * UNIT) + self.assertTrue(self.feetoken.donateToFeePool(donor, 5 * UNIT)) + self.assertEqual(self.feetoken.feePool(), 6 * UNIT) + self.assertEqual(self.feetoken.balanceOf(donor), 4 * UNIT) + + # And it should emit the right event. + tx_receipt = self.feetoken.donateToFeePool(donor, UNIT) + self.assertEqual(len(tx_receipt.logs), 2) + self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[0])['event'], 'FeesDonated') + self.assertEqual(get_event_data_from_log(self.feetoken_event_dict, tx_receipt.logs[1])['event'], 'Transfer') + + def test_event_Transfer(self): + sender = self.initial_beneficiary + txr = self.feetoken.transfer(sender, MASTER, UNIT) + amountReceived = self.feetoken.amountReceived(UNIT) + fee = UNIT - amountReceived + + self.assertEventEquals( + self.feetoken_event_dict, txr.logs[0], 'Transfer', + fields={'from': sender, 'to': MASTER, 'value': amountReceived}, + location=self.proxy.address + ) + + self.assertEventEquals( + self.feetoken_event_dict, txr.logs[1], 'Transfer', + fields={'from': sender, 'to': self.feetoken_contract.address, 'value': fee}, + location=self.proxy.address + ) + + def test_event_Approval(self): + approver, approvee = fresh_accounts(2) + txr = self.feetoken.approve(approver, approvee, UNIT) + self.assertEventEquals( + self.feetoken_event_dict, txr.logs[0], 'Approval', + fields={'owner': approver, 'spender': approvee, 'value': UNIT}, + location=self.proxy.address + ) + + def test_event_TransferFeeRateUpdated(self): + new_rate = UNIT // 11 + txr = self.feetoken.setTransferFeeRate(MASTER, new_rate) + self.assertEventEquals( + self.feetoken_event_dict, txr.logs[0], 'TransferFeeRateUpdated', + fields={'newFeeRate': new_rate}, + location=self.proxy.address + ) + + def test_event_FeeAuthorityUpdated(self): + new_authority = fresh_account() + txr = self.feetoken.setFeeAuthority(MASTER, new_authority) + self.assertEventEquals( + self.feetoken_event_dict, txr.logs[0], 'FeeAuthorityUpdated', + fields={'newFeeAuthority': new_authority}, + location=self.proxy.address + ) + + def test_event_TokenStateUpdated(self): + new_tokenstate = fresh_account() + txr = self.feetoken.setTokenState(MASTER, new_tokenstate) + self.assertEventEquals( + self.feetoken_event_dict, txr.logs[0], 'TokenStateUpdated', + fields={'newTokenState': new_tokenstate}, + location=self.proxy.address + ) + + def test_event_FeesWithdrawn(self): + beneficiary = fresh_account() + self.feetoken.clearTokens(MASTER, self.feetoken_contract.address) + self.feetoken.giveTokens(MASTER, self.feetoken_contract.address, UNIT) + txr = self.feetoken_withdrawFees(self.feetoken.feeAuthority(), + beneficiary, UNIT) + self.assertEventEquals(self.feetoken_event_dict, + txr.logs[0], "FeesWithdrawn", + {"account": beneficiary, + "value": UNIT}, + self.proxy.address) diff --git a/tests/test_Havven.py b/tests/test_Havven.py index 2e6401b1b5..6b5694ffb2 100644 --- a/tests/test_Havven.py +++ b/tests/test_Havven.py @@ -1,220 +1,155 @@ -import unittest - -from utils.deployutils import attempt, compile_contracts, attempt_deploy, W3, mine_txs, mine_tx, \ - UNIT, MASTER, DUMMY, to_seconds, fast_forward, fresh_account, fresh_accounts, take_snapshot, restore_snapshot -from utils.testutils import assertReverts, block_time, assertClose, generate_topic_event_map, get_event_data_from_log -from utils.testutils import ZERO_ADDRESS - -SOLIDITY_SOURCES = ["tests/contracts/PublicHavven.sol", "contracts/EtherNomin.sol", - "contracts/Court.sol", "contracts/HavvenEscrow.sol", - "contracts/Proxy.sol"] - - -def deploy_public_havven(): - print("Deployment initiated.\n") - - compiled = attempt(compile_contracts, [SOLIDITY_SOURCES], "Compiling contracts... ") - - # Deploy contracts - havven_contract, hvn_txr = attempt_deploy(compiled, 'PublicHavven', - MASTER, [ZERO_ADDRESS, MASTER]) - hvn_block = W3.eth.blockNumber - nomin_contract, nom_txr = attempt_deploy(compiled, 'EtherNomin', - MASTER, - [havven_contract.address, MASTER, MASTER, - 1000 * UNIT, MASTER, ZERO_ADDRESS]) - court_contract, court_txr = attempt_deploy(compiled, 'Court', - MASTER, - [havven_contract.address, nomin_contract.address, - MASTER]) - escrow_contract, escrow_txr = attempt_deploy(compiled, 'HavvenEscrow', - MASTER, - [MASTER, havven_contract.address]) - - # Install proxies - havven_proxy, _ = attempt_deploy(compiled, 'Proxy', - MASTER, [havven_contract.address, MASTER]) - mine_tx(havven_contract.functions.setProxy(havven_proxy.address).transact({'from': MASTER})) - proxy_havven = W3.eth.contract(address=havven_proxy.address, abi=compiled['PublicHavven']['abi']) - - nomin_proxy, _ = attempt_deploy(compiled, 'Proxy', - MASTER, [nomin_contract.address, MASTER]) - mine_tx(nomin_contract.functions.setProxy(nomin_proxy.address).transact({'from': MASTER})) - proxy_nomin = W3.eth.contract(address=nomin_proxy.address, abi=compiled['EtherNomin']['abi']) - - # Hook up each of those contracts to each other - txs = [proxy_havven.functions.setNomin(nomin_contract.address).transact({'from': MASTER}), - proxy_nomin.functions.setCourt(court_contract.address).transact({'from': MASTER})] - attempt(mine_txs, [txs], "Linking contracts... ") - - havven_event_dict = generate_topic_event_map(compiled['PublicHavven']['abi']) - - print("\nDeployment complete.\n") - return havven_contract, nomin_contract, havven_proxy, nomin_proxy, havven_contract, nomin_contract, court_contract, escrow_contract, hvn_block, havven_event_dict +import random + +from utils.deployutils import ( + W3, UNIT, MASTER, DUMMY, + mine_txs, mine_tx, + fresh_accounts, fresh_account, + attempt_deploy, + take_snapshot, restore_snapshot, + fast_forward, to_seconds +) +from utils.testutils import ( + HavvenTestCase, block_time, + get_event_data_from_log, generate_topic_event_map, + ZERO_ADDRESS +) +from tests.contract_interfaces.havven_interface import PublicHavvenInterface +from tests.contract_interfaces.nomin_interface import PublicNominInterface def setUpModule(): print("Testing Havven...") + print("=================") + print() def tearDownModule(): print() + print() -class TestHavven(unittest.TestCase): +class TestHavven(HavvenTestCase): def setUp(self): self.snapshot = take_snapshot() - time_remaining = self.targetFeePeriodDurationSeconds() + self.feePeriodStartTime() - block_time() - fast_forward(time_remaining + 1) - self.recomputeLastAverageBalance(MASTER) def tearDown(self): restore_snapshot(self.snapshot) + @classmethod + def deployContracts(cls): + print("Deployment initiated.\n") + + sources = ["tests/contracts/PublicHavven.sol", "contracts/Nomin.sol", + "contracts/Court.sol", "contracts/HavvenEscrow.sol"] + + compiled, cls.event_maps = cls.compileAndMapEvents(sources) + + # Deploy contracts + havven_proxy, _ = attempt_deploy(compiled, 'Proxy', MASTER, [MASTER]) + nomin_proxy, _ = attempt_deploy(compiled, 'Proxy', MASTER, [MASTER]) + proxied_havven = W3.eth.contract(address=havven_proxy.address, abi=compiled['PublicHavven']['abi']) + proxied_nomin = W3.eth.contract(address=nomin_proxy.address, abi=compiled['Nomin']['abi']) + + tokenstate, _ = attempt_deploy(compiled, 'TokenState', + MASTER, [MASTER, MASTER]) + havven_contract, hvn_txr = attempt_deploy(compiled, 'PublicHavven', MASTER, [havven_proxy.address, tokenstate.address, MASTER, MASTER, UNIT//2]) + hvn_block = W3.eth.blockNumber + nomin_contract, nom_txr = attempt_deploy(compiled, 'Nomin', + MASTER, + [nomin_proxy.address, havven_contract.address, MASTER]) + court_contract, court_txr = attempt_deploy(compiled, 'Court', + MASTER, + [havven_contract.address, nomin_contract.address, + MASTER]) + escrow_contract, escrow_txr = attempt_deploy(compiled, 'HavvenEscrow', + MASTER, + [MASTER, havven_contract.address]) + + # Hook up each of those contracts to each other + mine_txs([ + tokenstate.functions.setBalanceOf(havven_contract.address, 100000000 * UNIT).transact({'from': MASTER}), + tokenstate.functions.setAssociatedContract(havven_contract.address).transact({'from': MASTER}), + havven_proxy.functions.setTarget(havven_contract.address).transact({'from': MASTER}), + nomin_proxy.functions.setTarget(nomin_contract.address).transact({'from': MASTER}), + havven_contract.functions.setNomin(nomin_contract.address).transact({'from': MASTER}), + nomin_contract.functions.setCourt(court_contract.address).transact({'from': MASTER}), + nomin_contract.functions.setHavven(havven_contract.address).transact({'from': MASTER}), + havven_contract.functions.setEscrow(escrow_contract.address).transact({'from': MASTER}) + ]) + + havven_event_dict = generate_topic_event_map(compiled['PublicHavven']['abi']) + + print("\nDeployment complete.\n") + return havven_proxy, proxied_havven, nomin_proxy, proxied_nomin, havven_contract, nomin_contract, court_contract, escrow_contract, hvn_block, havven_event_dict + @classmethod def setUpClass(cls): - cls.assertClose = assertClose - cls.assertReverts = assertReverts - # to avoid overflowing in the negative direction (now - targetFeePeriodDuration * 2) + # to avoid overflowing in the negative direction (now - feePeriodDuration * 2) fast_forward(weeks=102) - cls.havven, cls.nomin, cls.havven_proxy, cls.nomin_proxy, cls.havven_real, cls.nomin_real, cls.court, \ - cls.escrow, cls.construction_block, cls.havven_event_dict = deploy_public_havven() - - # INHERITED - # OWNED - # owner - cls.h_owner = lambda self: self.havven.functions.owner().call() - cls.h_nominateOwner = lambda self, sender, addr: mine_tx( - self.havven.functions.nominateOwner(addr).transact({'from': sender})) - cls.h_acceptOwnership = lambda self, sender: mine_tx( - self.havven.functions.acceptOwnership().transact({'from': sender})) - - # ExternStateProxyToken (transfer/transferFrom are overwritten) - # totalSupply - cls.totalSupply = lambda self: self.havven.functions.totalSupply().call() - cls.name = lambda self: self.havven.functions.name().call() - cls.symbol = lambda self: self.havven.functions.symbol().call() - cls.balanceOf = lambda self, a: self.havven.functions.balanceOf(a).call() - cls.allowance = lambda self, owner, spender: self.havven.functions.allowance(owner, spender).call() - cls.approve = lambda self, sender, spender, val: mine_tx( - self.havven.functions.approve(spender, val).transact({"from": sender})) - - # HAVVEN - # GETTERS - cls.currentBalanceSum = lambda self, addr: self.havven.functions._currentBalanceSum(addr).call() - cls.lastAverageBalance = lambda self, addr: self.havven.functions.lastAverageBalance(addr).call() - cls.penultimateAverageBalance = lambda self, addr: self.havven.functions.penultimateAverageBalance(addr).call() - cls.lastTransferTimestamp = lambda self, addr: self.havven.functions._lastTransferTimestamp(addr).call() - cls.hasWithdrawnLastPeriodFees = lambda self, addr: self.havven.functions._hasWithdrawnLastPeriodFees( - addr).call() - cls.lastAverageBalanceNeedsRecomputation = lambda self, addr: \ - self.havven.functions.lastAverageBalanceNeedsRecomputation(addr).call() - - cls.feePeriodStartTime = lambda self: self.havven.functions.feePeriodStartTime().call() - cls.lastFeePeriodStartTime = lambda self: self.havven.functions._lastFeePeriodStartTime().call() - cls.penultimateFeePeriodStartTime = lambda self: self.havven.functions._penultimateFeePeriodStartTime().call() - cls.targetFeePeriodDurationSeconds = lambda self: self.havven.functions.targetFeePeriodDurationSeconds().call() - cls.MIN_FEE_PERIOD_DURATION_SECONDS = lambda \ - self: self.havven.functions._MIN_FEE_PERIOD_DURATION_SECONDS().call() - cls.MAX_FEE_PERIOD_DURATION_SECONDS = lambda \ - self: self.havven.functions._MAX_FEE_PERIOD_DURATION_SECONDS().call() - cls.lastFeesCollected = lambda self: self.havven.functions.lastFeesCollected().call() - - cls.get_nomin = lambda self: self.havven.functions.nomin().call() - cls.get_escrow = lambda self: self.havven.functions.escrow().call() - - # - # SETTERS - cls.setNomin = lambda self, sender, addr: mine_tx( - self.havven.functions.setNomin(addr).transact({'from': sender})) - cls.setEscrow = lambda self, sender, addr: mine_tx( - self.havven.functions.setEscrow(addr).transact({'from': sender})) - cls.unsetEscrow = lambda self, sender: mine_tx( - self.havven.functions.unsetEscrow().transact({'from': sender})) - cls.setTargetFeePeriodDuration = lambda self, sender, dur: mine_tx( - self.havven.functions.setTargetFeePeriodDuration(dur).transact({'from': sender})) - - # - # FUNCTIONS - cls.endow = lambda self, sender, addr, amt: mine_tx( - self.havven_real.functions.endow(addr, amt).transact({'from': sender})) - cls.transfer = lambda self, sender, addr, amt: mine_tx( - self.havven.functions.transfer(addr, amt).transact({'from': sender})) - cls.transferFrom = lambda self, sender, frm, to, amt: mine_tx( - self.havven.functions.transferFrom(frm, to, amt).transact({'from': sender})) - cls.recomputeLastAverageBalance = lambda self, sender: mine_tx( - self.havven.functions.recomputeLastAverageBalance().transact({'from': sender})) - cls.recomputeAccountLastAverageBalance = lambda self, sender, account: mine_tx( - self.havven.functions.recomputeAccountLastAverageBalance(account).transact({'from': sender})) - cls.rolloverFeePeriod = lambda self, sender: mine_tx( - self.havven.functions.rolloverFeePeriod().transact({'from': sender})) - - # - # INTERNAL - cls.adjustFeeEntitlement = lambda self, sender, acc, p_bal: mine_tx( - self.havven.functions._adjustFeeEntitlement(acc, p_bal).transact({'from': sender})) - # rolloverFee (ltt->last_transfer_time) - cls.rolloverFee = lambda self, sender, acc, ltt, p_bal: mine_tx( - self.havven.functions._rolloverFee(acc, ltt, p_bal).transact({'from': sender})) - - # withdrawFeeEntitlement - cls.withdrawFeeEntitlement = lambda self, sender: mine_tx( - self.havven.functions.withdrawFeeEntitlement().transact({'from': sender})) - - # - # MODIFIERS - # postCheckFeePeriodRollover - cls._checkFeePeriodRollover = lambda self, sender: mine_tx( - self.havven.functions._checkFeePeriodRollover().transact({'from': sender})) - - def start_new_fee_period(self): - time_remaining = self.targetFeePeriodDurationSeconds() + self.feePeriodStartTime() - block_time() - fast_forward(time_remaining + 1) - self._checkFeePeriodRollover(MASTER) + cls.havven_proxy, cls.proxied_havven, cls.nomin_proxy, cls.proxied_nomin, \ + cls.havven_contract, cls.nomin_contract, cls.court_contract, \ + cls.escrow_contract, cls.construction_block, cls.havven_event_dict = cls.deployContracts() + + cls.event_map = cls.event_maps['Havven'] + + cls.havven = PublicHavvenInterface(cls.proxied_havven, "Havven") + cls.nomin = PublicNominInterface(cls.proxied_nomin, "Nomin") + + cls.unproxied_havven = PublicHavvenInterface(cls.havven_contract, "UnproxiedHavven") + + cls.initial_time = cls.havven.lastFeePeriodStartTime() + cls.time_fast_forwarded = 0 + + cls.base_havven_price = UNIT + + cls.sd_duration = 4 * 7 * 24 * 60 * 60 + + def havven_updatePrice(self, sender, price, time): + return mine_tx(self.havven_contract.functions.updatePrice(price, time).transact({'from': sender}), 'updatePrice', 'Havven') ### # Test inherited Owned - Should be the same test_Owned.py ### def test_owner_is_master(self): - self.assertEqual(self.h_owner(), MASTER) + self.assertEqual(self.havven.owner(), MASTER) def test_change_owner(self): - old_owner = self.h_owner() + old_owner = self.havven.owner() new_owner = DUMMY - self.h_nominateOwner(old_owner, new_owner) - self.h_acceptOwnership(new_owner) - self.assertEqual(self.h_owner(), new_owner) + self.havven.nominateNewOwner(old_owner, new_owner) + self.havven.acceptOwnership(new_owner) + self.assertEqual(self.havven.owner(), new_owner) # reset back to old owner - self.h_nominateOwner(new_owner, old_owner) - self.h_acceptOwnership(old_owner) - self.assertEqual(self.h_owner(), old_owner) + self.havven.nominateNewOwner(new_owner, old_owner) + self.havven.acceptOwnership(old_owner) + self.assertEqual(self.havven.owner(), old_owner) def test_change_invalid_owner(self): invalid_account = DUMMY - self.assertReverts(self.h_nominateOwner, invalid_account, invalid_account) + self.assertReverts(self.havven.nominateNewOwner, invalid_account, invalid_account) ### - # Test inherited ExternStateProxyToken + # Test inherited ExternStateToken ### # Constuctor - def test_ExternStateProxyToken_constructor(self): + def test_ExternStateToken_constructor(self): total_supply = 10 ** 8 * UNIT - self.assertEqual(self.name(), "Havven") - self.assertEqual(self.symbol(), "HAV") - self.assertEqual(self.totalSupply(), total_supply) - self.assertEqual(self.balanceOf(self.havven_real.address), total_supply) + self.assertEqual(self.havven.name(), "Havven") + self.assertEqual(self.havven.symbol(), "HAV") + self.assertEqual(self.havven.totalSupply(), total_supply) + self.assertEqual(self.havven.balanceOf(self.havven_contract.address), total_supply) # Approval def test_approve(self): owner = MASTER spender = DUMMY - self.approve(owner, spender, UNIT) - self.assertEqual(self.allowance(owner, spender), UNIT) - self.approve(owner, spender, 0) - self.assertEqual(self.allowance(owner, spender), 0) + self.havven.approve(owner, spender, UNIT) + self.assertEqual(self.havven.allowance(owner, spender), UNIT) + self.havven.approve(owner, spender, 0) + self.assertEqual(self.havven.allowance(owner, spender), 0) # ## @@ -225,253 +160,244 @@ def test_approve(self): # Constructor ### def test_constructor(self): - fee_period = self.targetFeePeriodDurationSeconds() + fee_period = self.havven.feePeriodDuration() self.assertEqual(fee_period, to_seconds(weeks=4)) self.assertGreater(block_time(), 2 * fee_period) - self.assertEqual(self.MIN_FEE_PERIOD_DURATION_SECONDS(), to_seconds(days=1)) - self.assertEqual(self.MAX_FEE_PERIOD_DURATION_SECONDS(), to_seconds(weeks=26)) - self.assertEqual(self.lastFeesCollected(), 0) - self.assertEqual(self.get_nomin(), self.nomin_real.address) - self.assertEqual(self.havven.functions.decimals().call(), 18) + self.assertEqual(self.havven.MIN_FEE_PERIOD_DURATION(), to_seconds(days=1)) + self.assertEqual(self.havven.MAX_FEE_PERIOD_DURATION(), to_seconds(weeks=26)) + self.assertEqual(self.havven.lastFeesCollected(), 0) + self.assertEqual(self.havven.nomin(), self.nomin_contract.address) + self.assertEqual(self.havven.decimals(), 18) ### # Mappings ### # currentBalanceSum def test_currentBalanceSum(self): - """ - Testing the value of currentBalanceSum works as intended, - Further testing involving this and fee collection will be done - in scenario testing - """ - fee_period = self.targetFeePeriodDurationSeconds() + #Testing the value of currentBalanceSum works as intended, + #Further testing involving this and fee collection will be done + #in scenario testing + fee_period = self.havven.feePeriodDuration() delay = int(fee_period / 10) alice = fresh_account() - self.assertEqual(self.balanceOf(alice), 0) + self.assertEqual(self.havven.balanceOf(alice), 0) start_amt = UNIT * 50 - self.endow(MASTER, alice, start_amt) - self.assertEqual(self.balanceOf(alice), start_amt) - self.assertEqual(self.currentBalanceSum(alice), 0) + + self.havven.endow(MASTER, alice, start_amt) + self.havven.setIssuer(MASTER, alice, True) + self.havven_updatePrice(MASTER, UNIT, block_time()+1) + self.havven.setIssuanceRatio(MASTER, UNIT) + self.havven.issueNomins(alice, start_amt) + + self.assertEqual(self.havven.balanceOf(alice), start_amt) + self.assertEqual(self.nomin.balanceOf(alice), start_amt) + self.assertEqual(self.havven.issuanceCurrentBalanceSum(alice), 0) start_time = block_time() fast_forward(delay) - self.adjustFeeEntitlement(alice, alice, self.balanceOf(alice)) + self.havven.recomputeLastAverageBalance(alice, alice) end_time = block_time() balance_sum = (end_time - start_time) * start_amt self.assertEqual( - self.currentBalanceSum(alice), + self.havven.issuanceCurrentBalanceSum(alice), balance_sum ) - self.transfer(alice, self.havven_real.address, start_amt) - self.assertEqual(self.balanceOf(alice), 0) + self.havven.burnNomins(alice, start_amt) + self.assertEqual(self.nomin.balanceOf(alice), 0) fast_forward(delay) - self.adjustFeeEntitlement(alice, alice, self.balanceOf(alice)) + self.havven.recomputeLastAverageBalance(alice, alice) self.assertClose( - self.currentBalanceSum(alice), balance_sum + self.havven.issuanceCurrentBalanceSum(alice), balance_sum ) # lastAverageBalance def test_lastAverageBalance(self): # set the block time to be at least 30seconds away from the end of the fee_period - fee_period = self.targetFeePeriodDurationSeconds() - time_remaining = self.targetFeePeriodDurationSeconds() + self.feePeriodStartTime() - block_time() - if time_remaining < 30: - fast_forward(50) - time_remaining = self.targetFeePeriodDurationSeconds() + self.feePeriodStartTime() - block_time() + fee_period = self.havven.feePeriodDuration() # fast forward next block with some extra padding - delay = time_remaining + 100 + delay = fee_period + 1 + fast_forward(delay) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) alice = fresh_account() - self.assertEqual(self.balanceOf(alice), 0) + self.assertEqual(self.havven.balanceOf(alice), 0) start_amt = UNIT * 50 - tx_receipt = self.endow(MASTER, alice, start_amt) - self.assertEqual(self.balanceOf(alice), start_amt) - self.assertEqual(self.currentBalanceSum(alice), 0) - self.assertEqual(self.lastAverageBalance(alice), 0) - self.assertEqual(self.lastTransferTimestamp(alice), block_time(tx_receipt['blockNumber'])) + self.havven.endow(MASTER, alice, start_amt) + self.havven.setIssuer(MASTER, alice, True) + self.havven_updatePrice(MASTER, UNIT, block_time()+1) + self.havven.setIssuanceRatio(MASTER, UNIT) + tx_receipt = self.havven.issueNomins(alice, start_amt) + + self.assertEqual(self.havven.balanceOf(alice), start_amt) + self.assertEqual(self.havven.issuanceCurrentBalanceSum(alice), 0) + self.assertEqual(self.havven.issuanceLastAverageBalance(alice), 0) + self.assertEqual(self.havven.issuanceLastModified(alice), block_time(tx_receipt['blockNumber'])) fast_forward(delay) - self._checkFeePeriodRollover(DUMMY) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) fast_forward(fee_period // 2) - tx_receipt = self.adjustFeeEntitlement(alice, alice, self.balanceOf(alice)) + tx_receipt = self.havven.recomputeLastAverageBalance(alice, alice) block_number = tx_receipt['blockNumber'] - duration_since_rollover = block_time(block_number) - self.feePeriodStartTime() + duration_since_rollover = block_time(block_number) - self.havven.feePeriodStartTime() balance_sum = duration_since_rollover * start_amt - actual = self.currentBalanceSum(alice) + actual = self.havven.issuanceCurrentBalanceSum(alice) expected = balance_sum self.assertClose( actual, expected ) - time_remaining = self.targetFeePeriodDurationSeconds() + self.feePeriodStartTime() - block_time() + time_remaining = self.havven.feePeriodDuration() + self.havven.feePeriodStartTime() - block_time() fast_forward(time_remaining - 5) - self.transfer(alice, MASTER, start_amt // 2) - time_remaining = self.targetFeePeriodDurationSeconds() + self.feePeriodStartTime() - block_time() + self.havven.burnNomins(alice, start_amt // 2) + time_remaining = self.havven.feePeriodDuration() + self.havven.feePeriodStartTime() - block_time() fast_forward(time_remaining + 10) - actual = self.lastAverageBalance(alice) - expected = (start_amt * delay) // (self.feePeriodStartTime() - self.lastFeePeriodStartTime()) + self.havven.rolloverFeePeriodIfElapsed(alice) + self.havven.recomputeLastAverageBalance(alice, alice) + + actual = self.havven.issuanceLastAverageBalance(alice) + expected = (start_amt * delay) // (self.havven.feePeriodStartTime() - self.havven.lastFeePeriodStartTime()) self.assertClose( actual, expected ) def test_lastAverageBalanceFullPeriod(self): alice = fresh_account() - fee_period = self.targetFeePeriodDurationSeconds() + self.havven.setIssuer(MASTER, alice, True) + fee_period = self.havven.feePeriodDuration() # Alice will initially have 20 havvens - self.endow(MASTER, alice, 20 * UNIT) - self.assertEqual(self.balanceOf(alice), 20 * UNIT) + self.havven.endow(MASTER, alice, 20 * UNIT) + self.havven.setIssuer(MASTER, alice, True) + self.havven_updatePrice(MASTER, UNIT, block_time()+1) + self.havven.setIssuanceRatio(MASTER, UNIT) + self.havven.issueNomins(alice, 20 * UNIT) + + self.assertEqual(self.havven.balanceOf(alice), 20 * UNIT) + self.assertEqual(self.nomin.balanceOf(alice), 20 * UNIT) # Fastforward until just before a fee period rolls over. - time_remaining = self.targetFeePeriodDurationSeconds() + self.feePeriodStartTime() - block_time() + time_remaining = self.havven.feePeriodDuration() + self.havven.feePeriodStartTime() - block_time() fast_forward(time_remaining + 50) - tx_receipt = self.transfer(alice, alice, 0) - self.assertEqual(self.lastTransferTimestamp(alice), block_time(tx_receipt['blockNumber'])) + tx_receipt = self.havven.rolloverFeePeriodIfElapsed(alice) + self.havven_updatePrice(MASTER, UNIT, block_time()) + issue_receipt = self.havven.issueNomins(alice, 0) + + self.assertEqual(self.havven.issuanceLastModified(alice), block_time(issue_receipt['blockNumber'])) event = get_event_data_from_log(self.havven_event_dict, tx_receipt.logs[0]) self.assertEqual(event['event'], 'FeePeriodRollover') # roll over the full period fast_forward(fee_period + 50) - tx_receipt = self.transfer(alice, alice, 0) + tx_receipt = self.havven.rolloverFeePeriodIfElapsed(MASTER) + self.havven_updatePrice(MASTER, UNIT, block_time()+1) + transfer_receipt = self.havven.issueNomins(alice, 0) + event = get_event_data_from_log(self.havven_event_dict, tx_receipt.logs[0]) self.assertEqual(event['event'], 'FeePeriodRollover') - self.assertEqual(self.lastTransferTimestamp(alice), block_time(tx_receipt['blockNumber'])) - self.assertEqual(self.lastAverageBalance(alice), 20 * UNIT) + self.assertEqual(self.havven.issuanceLastModified(alice), block_time(transfer_receipt['blockNumber'])) + self.assertEqual(self.havven.issuanceLastAverageBalance(alice), 20 * UNIT) # Try a half-and-half period - time_remaining = self.targetFeePeriodDurationSeconds() + self.feePeriodStartTime() - block_time() + time_remaining = self.havven.feePeriodDuration() + self.havven.feePeriodStartTime() - block_time() fast_forward(time_remaining + 50) - self.transfer(alice, MASTER, 10 * UNIT) + self.havven.rolloverFeePeriodIfElapsed(MASTER) + self.havven.burnNomins(alice, 10 * UNIT) + + fast_forward(fee_period // 2 + 10) + self.havven.burnNomins(alice, 10 * UNIT) + self.havven.rolloverFeePeriodIfElapsed(MASTER) + + fast_forward(fee_period // 2 + 10) + + tx_receipt = self.havven.rolloverFeePeriodIfElapsed(MASTER) event = get_event_data_from_log(self.havven_event_dict, tx_receipt.logs[0]) self.assertEqual(event['event'], 'FeePeriodRollover') - fast_forward(fee_period // 2) - tx_receipt = self.transfer(alice, MASTER, 10 * UNIT) - fast_forward(fee_period // 2 + 10) - self.recomputeLastAverageBalance(alice) - self.assertClose(self.lastAverageBalance(alice), 5 * UNIT) + + self.havven.rolloverFeePeriodIfElapsed(alice) + self.havven.recomputeLastAverageBalance(alice, alice) + self.assertClose(self.havven.issuanceLastAverageBalance(alice), 5 * UNIT) def test_arithmeticSeriesBalance(self): alice = fresh_account() - fee_period = self.targetFeePeriodDurationSeconds() + fee_period = self.havven.feePeriodDuration() n = 50 - self.endow(MASTER, alice, n * UNIT) - time_remaining = self.targetFeePeriodDurationSeconds() + self.feePeriodStartTime() - block_time() - fast_forward(time_remaining + 5) + self.havven.endow(MASTER, alice, n * UNIT) + self.havven_updatePrice(self.havven.oracle(), UNIT, block_time()) + self.havven.setIssuer(MASTER, alice, True) + self.havven.issueNomins(alice, n * UNIT // 20) + time_remaining = self.havven.feePeriodDuration() + self.havven.feePeriodStartTime() - block_time() + fast_forward(time_remaining + 5 ) + self.havven.rolloverFeePeriodIfElapsed(MASTER) for _ in range(n): - self.transfer(alice, MASTER, UNIT) + self.havven.burnNomins(alice, UNIT // 20) fast_forward(fee_period // n) - self.recomputeLastAverageBalance(alice) - self.assertClose(self.lastAverageBalance(alice), n * (n - 1) * UNIT // (2 * n)) + fast_forward(n) # fast forward allow the rollover to happen + self.havven.rolloverFeePeriodIfElapsed(MASTER) + + self.havven.recomputeLastAverageBalance(alice, alice) + self.assertClose(self.havven.issuanceLastAverageBalance(alice), n * (n - 1) * UNIT // (2 * n * 20), precision=3) def test_averageBalanceSum(self): alice, bob, carol = fresh_accounts(3) - fee_period = self.targetFeePeriodDurationSeconds() - - self.endow(MASTER, alice, UNIT) - - self.start_new_fee_period() - - self.transfer(alice, bob, UNIT // 4) - self.transfer(alice, carol, UNIT // 4) - fast_forward(fee_period // 10) - self.transfer(bob, carol, UNIT // 4) - fast_forward(fee_period // 10) - self.transfer(carol, bob, UNIT // 2) - fast_forward(fee_period // 10) - self.transfer(bob, alice, UNIT // 4) - fast_forward(2 * fee_period // 10) - self.transfer(alice, bob, UNIT // 3) - self.transfer(alice, carol, UNIT // 3) - fast_forward(3 * fee_period // 10) - self.transfer(carol, bob, UNIT // 3) - fast_forward(3 * fee_period // 10) - - self.recomputeLastAverageBalance(alice) - self.recomputeLastAverageBalance(bob) - self.recomputeLastAverageBalance(carol) - - total_average = self.lastAverageBalance(alice) + \ - self.lastAverageBalance(bob) + \ - self.lastAverageBalance(carol) - - self.assertClose(UNIT, total_average) - - # penultimateAverageBalance - def test_penultimateAverageBalance(self): - # start a new fee period - alice = fresh_account() - fee_period = self.targetFeePeriodDurationSeconds() - fast_forward(fee_period * 2) - self._checkFeePeriodRollover(DUMMY) - - # skip to halfway through it - delay = fee_period // 2 - fast_forward(delay) - - self.assertEqual(self.balanceOf(alice), 0) - - start_amt = UNIT * 50 - - self.endow(MASTER, alice, start_amt) - inital_transfer_time = self.lastTransferTimestamp(alice) - self.assertEqual(self.balanceOf(alice), start_amt) - self.assertEqual(self.currentBalanceSum(alice), 0) - self.assertEqual(self.lastAverageBalance(alice), 0) - - # rollover two fee periods without alice doing anything - fast_forward(fee_period * 2) - self._checkFeePeriodRollover(DUMMY) - - fast_forward(fee_period * 2) - self._checkFeePeriodRollover(DUMMY) - - # adjust alice's fee entitlement - self.adjustFeeEntitlement(alice, alice, self.balanceOf(alice)) - - # expected currentBalance sum is balance*(time since start of period) - actual = self.currentBalanceSum(alice) - expected = (block_time() - self.feePeriodStartTime()) * start_amt - self.assertClose( - actual, expected - ) - - last_period_delay = (self.feePeriodStartTime() - self.lastFeePeriodStartTime()) - - actual = self.lastAverageBalance(alice) - expected = (start_amt * last_period_delay) // last_period_delay - self.assertClose( - actual, expected, - msg='last:' - ) - - delay_from_transfer = self.lastFeePeriodStartTime() - inital_transfer_time - penultimate_period_duration = self.lastFeePeriodStartTime() - self.penultimateFeePeriodStartTime() - - actual = self.penultimateAverageBalance(alice) - expected = (start_amt * delay_from_transfer) // penultimate_period_duration - self.assertClose( - actual, expected, - msg='penultimate:' - ) - - # lastTransferTimestamp - tested above - # hasWithdrawnLastPeriodFees - tested in test_FeeCollection.py + fee_period = self.havven.feePeriodDuration() + + self.havven.endow(MASTER, alice, UNIT) + self.havven.endow(MASTER, bob, UNIT) + self.havven.endow(MASTER, carol, UNIT) + + self.havven.setIssuer(MASTER, alice, True) + self.havven.setIssuer(MASTER, bob, True) + self.havven.setIssuer(MASTER, carol, True) + self.havven.setIssuanceRatio(MASTER, UNIT) + + fast_forward(fee_period + 1) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) + + for i in range(10): + self.havven_updatePrice(MASTER, UNIT, block_time() + 1) + a_weight = random.random() + b_weight = random.random() + c_weight = random.random() + tot = a_weight + b_weight + c_weight + + self.havven.issueNomins(alice, max(1, int(UNIT * a_weight / tot))) + self.havven.issueNomins(bob, max(1, int(UNIT * b_weight / tot))) + self.havven.issueNomins(carol, max(1, int(UNIT * c_weight / tot))) + fast_forward(fee_period // 10 - 1) + self.havven.burnNomins(alice, max(1, int(UNIT * a_weight / tot))) + self.havven.burnNomins(bob, max(1, int(UNIT * b_weight / tot))) + self.havven.burnNomins(carol, max(1, int(UNIT * c_weight / tot))) + fast_forward(11) + self.havven.rolloverFeePeriodIfElapsed(MASTER) + + self.havven.recomputeLastAverageBalance(alice, alice) + self.havven.recomputeLastAverageBalance(bob, bob) + self.havven.recomputeLastAverageBalance(carol, carol) + + total_average = self.havven.issuanceLastAverageBalance(alice) + \ + self.havven.issuanceLastAverageBalance(bob) + \ + self.havven.issuanceLastAverageBalance(carol) + + self.assertClose(UNIT, total_average, precision=3) + + # lastModified - tested above + # hasWithdrawnFees - tested in test_FeeCollection.py # lastFeesCollected - tested in test_FeeCollection.py ### # Contract variables ### # feePeriodStartTime - tested above - # targetFeePeriodDurationSeconds - tested above - # MIN_FEE_PERIOD_DURATION_SECONDS - constant, checked in constructor test + # feePeriodDuration - tested above + # MIN_FEE_PERIOD_DURATION - constant, checked in constructor test ### # Functions @@ -480,280 +406,406 @@ def test_penultimateAverageBalance(self): # setNomin def test_setNomin(self): alice = fresh_account() - self.setNomin(MASTER, alice) - self.assertEqual(self.get_nomin(), alice) + self.havven.setNomin(MASTER, alice) + self.assertEqual(self.havven.nomin(), alice) def test_invalidSetNomin(self): alice = fresh_account() - self.assertReverts(self.setNomin, alice, alice) + self.assertReverts(self.havven.setNomin, alice, alice) # setEscrow def test_setEscrow(self): alice = fresh_account() - self.setEscrow(MASTER, alice) - self.assertEqual(self.get_escrow(), alice) + self.havven.setEscrow(MASTER, alice) + self.assertEqual(self.havven.escrow(), alice) def test_invalidSetEscrow(self): alice = fresh_account() - self.assertReverts(self.setEscrow, alice, alice) + self.assertReverts(self.havven.setEscrow, alice, alice) - # setTargetFeePeriod - def test_setTargetFeePeriod(self): - self.setTargetFeePeriodDuration(MASTER, to_seconds(weeks=10)) + # setFeePeriodDuration + def test_setFeePeriodDuration(self): + self.havven.setFeePeriodDuration(MASTER, to_seconds(weeks=10)) self.assertEqual( - self.targetFeePeriodDurationSeconds(), + self.havven.feePeriodDuration(), to_seconds(weeks=10) ) - def test_setTargetFeePeriod_max(self): + def test_setFeePeriodDuration_max(self): sixmonths = 26 * 7 * 24 * 60 * 60 - self.assertReverts(self.setTargetFeePeriodDuration, MASTER, 2 ** 256 - 1) - self.assertReverts(self.setTargetFeePeriodDuration, MASTER, sixmonths + 1) - self.setTargetFeePeriodDuration(MASTER, sixmonths) + self.assertReverts(self.havven.setFeePeriodDuration, MASTER, 2 ** 256 - 1) + self.assertReverts(self.havven.setFeePeriodDuration, MASTER, sixmonths + 1) + self.havven.setFeePeriodDuration(MASTER, sixmonths) self.assertEqual( - self.targetFeePeriodDurationSeconds(), + self.havven.feePeriodDuration(), sixmonths ) - def test_setTargetFeePeriod_minimal(self): - self.setTargetFeePeriodDuration(MASTER, self.MIN_FEE_PERIOD_DURATION_SECONDS()) + def test_setFeePeriodDuration_min(self): + self.havven.setFeePeriodDuration(MASTER, self.havven.MIN_FEE_PERIOD_DURATION()) self.assertEqual( - self.targetFeePeriodDurationSeconds(), - self.MIN_FEE_PERIOD_DURATION_SECONDS() + self.havven.feePeriodDuration(), + self.havven.MIN_FEE_PERIOD_DURATION() ) - def test_setTargetFeePeriod_invalid_below_min(self): - self.assertReverts(self.setTargetFeePeriodDuration, MASTER, self.MIN_FEE_PERIOD_DURATION_SECONDS() - 1) + def test_setFeePeriodDuration_invalid_below_min(self): + self.assertReverts(self.havven.setFeePeriodDuration, MASTER, self.havven.MIN_FEE_PERIOD_DURATION() - 1) - def test_setTargetFeePeriod_invalid_0(self): - self.assertReverts(self.setTargetFeePeriodDuration, MASTER, self.MIN_FEE_PERIOD_DURATION_SECONDS() - 1) + def test_setFeePeriodDuration_invalid_0(self): + self.assertReverts(self.havven.setFeePeriodDuration, MASTER, self.havven.MIN_FEE_PERIOD_DURATION() - 1) # endow def test_endow_valid(self): amount = 50 * UNIT - havven_balance = self.balanceOf(self.havven_real.address) + havven_balance = self.havven.balanceOf(self.havven_contract.address) alice = fresh_account() - self.assertEqual(self.balanceOf(alice), 0) - self.endow(MASTER, alice, amount) - self.assertEqual(self.balanceOf(alice), amount) - self.assertEqual(havven_balance - self.balanceOf(self.havven_real.address), amount) + self.assertEqual(self.havven.balanceOf(alice), 0) + self.havven.endow(MASTER, alice, amount) + self.assertEqual(self.havven.balanceOf(alice), amount) + self.assertEqual(havven_balance - self.havven.balanceOf(self.havven_contract.address), amount) def test_endow_0(self): amount = 0 - havven_balance = self.balanceOf(self.havven_real.address) + havven_balance = self.havven.balanceOf(self.havven_contract.address) alice = fresh_account() - self.assertEqual(self.balanceOf(alice), 0) - self.endow(MASTER, alice, amount) - self.assertEqual(self.balanceOf(alice), amount) - self.assertEqual(havven_balance - self.balanceOf(self.havven_real.address), amount) + self.assertEqual(self.havven.balanceOf(alice), 0) + self.havven.endow(MASTER, alice, amount) + self.assertEqual(self.havven.balanceOf(alice), amount) + self.assertEqual(havven_balance - self.havven.balanceOf(self.havven_contract.address), amount) def test_endow_supply(self): - amount = self.totalSupply() - havven_balance = self.balanceOf(self.havven_real.address) + amount = self.havven.totalSupply() + havven_balance = self.havven.balanceOf(self.havven_contract.address) alice = fresh_account() - self.assertEqual(self.balanceOf(alice), 0) - self.endow(MASTER, alice, amount) - self.assertEqual(self.balanceOf(alice), amount) - self.assertEqual(havven_balance - self.balanceOf(self.havven_real.address), amount) + self.assertEqual(self.havven.balanceOf(alice), 0) + self.havven.endow(MASTER, alice, amount) + self.assertEqual(self.havven.balanceOf(alice), amount) + self.assertEqual(havven_balance - self.havven.balanceOf(self.havven_contract.address), amount) def test_endow_more_than_supply(self): - amount = self.totalSupply() * 2 + amount = self.havven.totalSupply() * 2 alice = fresh_account() - self.assertReverts(self.endow, MASTER, alice, amount) - self.assertEqual(self.balanceOf(alice), 0) + self.assertReverts(self.havven.endow, MASTER, alice, amount) + self.assertEqual(self.havven.balanceOf(alice), 0) def test_endow_invalid_sender(self): amount = 50 * UNIT alice = fresh_account() - self.assertReverts(self.endow, alice, alice, amount) - self.assertEqual(self.balanceOf(alice), 0) + self.assertReverts(self.havven.endow, alice, alice, amount) + self.assertEqual(self.havven.balanceOf(alice), 0) def test_endow_contract_sender(self): amount = 50 * UNIT alice = fresh_account() - self.assertReverts(self.endow, self.havven_real.address, alice, amount) - self.assertEqual(self.balanceOf(alice), 0) + self.assertReverts(self.havven.endow, self.havven.contract.address, alice, amount) + self.assertEqual(self.havven.balanceOf(alice), 0) def test_endow_to_contract(self): amount = 50 * UNIT - self.assertEqual(self.balanceOf(self.havven_real.address), self.totalSupply()) - self.endow(MASTER, self.havven_real.address, amount) - self.assertEqual(self.balanceOf(self.havven_real.address), self.totalSupply()) - # Balance is not lost (still distributable) if sent to the contract. - self.endow(MASTER, self.havven_real.address, amount) - - def test_endow_currentBalanceSum(self): - amount = 50 * UNIT - # Force updates. - self.endow(MASTER, self.havven_real.address, 0) - havven_balanceSum = self.currentBalanceSum(self.havven_real.address) - alice = fresh_account() - fast_forward(seconds=60) - self.endow(MASTER, alice, amount) - self.assertGreater(self.currentBalanceSum(self.havven_real.address), havven_balanceSum) + self.assertEqual(self.havven.balanceOf(self.havven_contract.address), self.havven.totalSupply()) + self.havven.endow(MASTER, self.havven_contract.address, amount) + self.assertEqual(self.havven.balanceOf(self.havven_contract.address), self.havven.totalSupply()) + self.havven.endow(MASTER, self.havven_contract.address, amount) def test_endow_transfers(self): alice = fresh_account() - self.recomputeLastAverageBalance(MASTER) - tx_receipt = self.endow(MASTER, alice, 50 * UNIT) + tx_receipt = self.havven.endow(MASTER, alice, 50 * UNIT) event = get_event_data_from_log(self.havven_event_dict, tx_receipt.logs[0]) self.assertEqual(event['event'], 'Transfer') # transfer def test_transferRollsOver(self): alice = fresh_account() - self.endow(MASTER, alice, 50 * UNIT) - fast_forward(seconds=self.targetFeePeriodDurationSeconds() + 100) - tx_receipt = self.transfer(alice, MASTER, 25 * UNIT) + self.havven.endow(MASTER, alice, 50 * UNIT) + fast_forward(seconds=self.havven.feePeriodDuration() + 100) + self.havven.transfer(alice, MASTER, 25 * UNIT) + tx_receipt = self.havven.rolloverFeePeriodIfElapsed(MASTER) + event = get_event_data_from_log(self.havven_event_dict, tx_receipt.logs[0]) self.assertEqual(event['event'], 'FeePeriodRollover') - # same as test_ExternStateProxyToken + # same as test_ExternStateToken def test_transfer(self): sender, receiver, no_tokens = fresh_accounts(3) - self.endow(MASTER, sender, 50 * UNIT) - sender_balance = self.balanceOf(sender) + self.havven.endow(MASTER, sender, 50 * UNIT) + sender_balance = self.havven.balanceOf(sender) - receiver_balance = self.balanceOf(receiver) + receiver_balance = self.havven.balanceOf(receiver) self.assertEqual(receiver_balance, 0) value = 10 * UNIT - total_supply = self.totalSupply() + total_supply = self.havven.totalSupply() # This should fail because receiver has no tokens - self.assertReverts(self.transfer, receiver, sender, value) + self.assertReverts(self.havven.transfer, receiver, sender, value) - self.transfer(sender, receiver, value) - self.assertEqual(self.balanceOf(receiver), receiver_balance + value) - self.assertEqual(self.balanceOf(sender), sender_balance - value) + self.havven.transfer(sender, receiver, value) + self.assertEqual(self.havven.balanceOf(receiver), receiver_balance + value) + self.assertEqual(self.havven.balanceOf(sender), sender_balance - value) # transfers should leave the supply unchanged - self.assertEqual(self.totalSupply(), total_supply) + self.assertEqual(self.havven.totalSupply(), total_supply) value = 1001 * UNIT # This should fail because balance < value and balance > totalSupply - self.assertReverts(self.transfer, sender, receiver, value) + self.assertReverts(self.havven.transfer, sender, receiver, value) # 0 value transfers are allowed. value = 0 - pre_sender_balance = self.balanceOf(sender) - pre_receiver_balance = self.balanceOf(receiver) - self.transfer(sender, receiver, value) - self.assertEqual(self.balanceOf(receiver), pre_receiver_balance) - self.assertEqual(self.balanceOf(sender), pre_sender_balance) + pre_sender_balance = self.havven.balanceOf(sender) + pre_receiver_balance = self.havven.balanceOf(receiver) + self.havven.transfer(sender, receiver, value) + self.assertEqual(self.havven.balanceOf(receiver), pre_receiver_balance) + self.assertEqual(self.havven.balanceOf(sender), pre_sender_balance) # It is also possible to send 0 value transfer from an account with 0 balance. - self.assertEqual(self.balanceOf(no_tokens), 0) - self.transfer(no_tokens, receiver, value) - self.assertEqual(self.balanceOf(no_tokens), 0) + self.assertEqual(self.havven.balanceOf(no_tokens), 0) + self.havven.transfer(no_tokens, receiver, value) + self.assertEqual(self.havven.balanceOf(no_tokens), 0) # transferFrom def test_transferFromRollsOver(self): alice = fresh_account() - self.endow(MASTER, alice, 50 * UNIT) - self.approve(alice, MASTER, 25 * UNIT) - fast_forward(seconds=self.targetFeePeriodDurationSeconds() + 100) - tx_receipt = self.transferFrom(MASTER, alice, MASTER, 25 * UNIT) + self.havven.endow(MASTER, alice, 50 * UNIT) + self.havven.approve(alice, MASTER, 25 * UNIT) + fast_forward(seconds=self.havven.feePeriodDuration() + 100) + self.havven.transferFrom(MASTER, alice, MASTER, 25 * UNIT) + tx_receipt = self.havven.rolloverFeePeriodIfElapsed(MASTER) + event = get_event_data_from_log(self.havven_event_dict, tx_receipt.logs[0]) self.assertEqual(event['event'], 'FeePeriodRollover') def test_transferFrom(self): approver, spender, receiver, no_tokens = fresh_accounts(4) - self.endow(MASTER, approver, 50 * UNIT) + self.havven.endow(MASTER, approver, 50 * UNIT) - approver_balance = self.balanceOf(approver) - spender_balance = self.balanceOf(spender) - receiver_balance = self.balanceOf(receiver) + approver_balance = self.havven.balanceOf(approver) + spender_balance = self.havven.balanceOf(spender) + receiver_balance = self.havven.balanceOf(receiver) value = 10 * UNIT - total_supply = self.totalSupply() + total_supply = self.havven.totalSupply() # This fails because there has been no approval yet - self.assertReverts(self.transferFrom, spender, approver, receiver, value) + self.assertReverts(self.havven.transferFrom, spender, approver, receiver, value) - self.approve(approver, spender, 2 * value) - self.assertEqual(self.allowance(approver, spender), 2 * value) + self.havven.approve(approver, spender, 2 * value) + self.assertEqual(self.havven.allowance(approver, spender), 2 * value) - self.assertReverts(self.transferFrom, spender, approver, receiver, 2 * value + 1) - self.transferFrom(spender, approver, receiver, value) + self.assertReverts(self.havven.transferFrom, spender, approver, receiver, 2 * value + 1) + self.havven.transferFrom(spender, approver, receiver, value) - self.assertEqual(self.balanceOf(approver), approver_balance - value) - self.assertEqual(self.balanceOf(spender), spender_balance) - self.assertEqual(self.balanceOf(receiver), receiver_balance + value) - self.assertEqual(self.allowance(approver, spender), value) - self.assertEqual(self.totalSupply(), total_supply) + self.assertEqual(self.havven.balanceOf(approver), approver_balance - value) + self.assertEqual(self.havven.balanceOf(spender), spender_balance) + self.assertEqual(self.havven.balanceOf(receiver), receiver_balance + value) + self.assertEqual(self.havven.allowance(approver, spender), value) + self.assertEqual(self.havven.totalSupply(), total_supply) # Empty the account - self.transferFrom(spender, approver, receiver, value) + self.havven.transferFrom(spender, approver, receiver, value) # This account has no tokens - approver_balance = self.balanceOf(no_tokens) + approver_balance = self.havven.balanceOf(no_tokens) self.assertEqual(approver_balance, 0) - self.assertEqual(self.allowance(no_tokens, spender), 0) + self.assertEqual(self.havven.allowance(no_tokens, spender), 0) - self.approve(no_tokens, spender, value) - self.assertEqual(self.allowance(no_tokens, spender), value) + self.havven.approve(no_tokens, spender, value) + self.assertEqual(self.havven.allowance(no_tokens, spender), value) # This should fail because the approver has no tokens. - self.assertReverts(self.transferFrom, spender, no_tokens, receiver, value) + self.assertReverts(self.havven.transferFrom, spender, no_tokens, receiver, value) def test_double_withdraw_fee(self): alice = fresh_account() - self.withdrawFeeEntitlement(alice) - self.assertReverts(self.withdrawFeeEntitlement, alice) + self.havven.withdrawFees(alice) + self.assertReverts(self.havven.withdrawFees, alice) def test_withdraw_multiple_periods(self): alice = fresh_account() - self.withdrawFeeEntitlement(alice) - fast_forward(self.targetFeePeriodDurationSeconds() * 2) - self.rolloverFeePeriod(DUMMY) - self.withdrawFeeEntitlement(alice) - fast_forward(self.targetFeePeriodDurationSeconds() * 2) - self.rolloverFeePeriod(DUMMY) + self.havven.withdrawFees(alice) + fast_forward(self.havven.feePeriodDuration() * 2) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) + self.havven.withdrawFees(alice) + fast_forward(self.havven.feePeriodDuration() * 2) + self.havven.rolloverFeePeriodIfElapsed(DUMMY) # adjustFeeEntitlement - tested above # rolloverFee - tested above, indirectly - # withdrawFeeEntitlement - tested in test_FeeCollection.py + # withdrawFees - tested in test_FeeCollection.py + + def test_selfDestruct(self): + owner = self.havven.owner() + notowner = DUMMY + self.assertNotEqual(owner, notowner) + + # The contract cannot be self-destructed before the SD has been initiated. + self.assertReverts(self.unproxied_havven.selfDestruct, owner) + + tx = self.unproxied_havven.initiateSelfDestruct(owner) + self.assertEventEquals(self.event_map, tx.logs[0], + "SelfDestructInitiated", + {"selfDestructDelay": self.sd_duration}, + location=self.havven_contract.address) + + # Neither owners nor non-owners may not self-destruct before the time has elapsed. + self.assertReverts(self.unproxied_havven.selfDestruct, notowner) + self.assertReverts(self.unproxied_havven.selfDestruct, owner) + fast_forward(seconds=self.sd_duration, days=-1) + self.assertReverts(self.unproxied_havven.selfDestruct, notowner) + self.assertReverts(self.unproxied_havven.selfDestruct, owner) + fast_forward(seconds=10, days=1) + + # Non-owner should not be able to self-destruct even if the time has elapsed. + self.assertReverts(self.unproxied_havven.selfDestruct, notowner) + address = self.unproxied_havven.contract.address + tx = self.unproxied_havven.selfDestruct(owner) + + self.assertEventEquals(self.event_map, tx.logs[0], + "SelfDestructed", + {"beneficiary": owner}, + location=self.havven_contract.address) + # Check contract not exist + self.assertEqual(W3.eth.getCode(address), b'\x00') + ### # Modifiers ### - # postCheckFeePeriodRollover - tested above - def test_checkFeePeriodRollover_escrow_exists(self): - fast_forward(seconds=self.targetFeePeriodDurationSeconds() + 10) + # postrolloverFeePeriodIfElapsed - tested above + def test_rolloverFeePeriodIfElapsed_escrow_exists(self): + fast_forward(seconds=self.havven.feePeriodDuration() + 10) - pre_feePeriodStartTime = self.feePeriodStartTime() + pre_feePeriodStartTime = self.havven.feePeriodStartTime() # This should work fine. - self._checkFeePeriodRollover(MASTER) - self.assertGreater(self.feePeriodStartTime(), pre_feePeriodStartTime) + self.havven.rolloverFeePeriodIfElapsed(MASTER) + self.assertGreater(self.havven.feePeriodStartTime(), pre_feePeriodStartTime) - fast_forward(seconds=self.targetFeePeriodDurationSeconds() + 10) - pre_feePeriodStartTime = self.feePeriodStartTime() + fast_forward(seconds=self.havven.feePeriodDuration() + 10) + pre_feePeriodStartTime = self.havven.feePeriodStartTime() # And so should this - self.setEscrow(MASTER, ZERO_ADDRESS) - self._checkFeePeriodRollover(MASTER) - self.assertGreater(self.feePeriodStartTime(), pre_feePeriodStartTime) + self.havven.setEscrow(MASTER, ZERO_ADDRESS) + self.havven.rolloverFeePeriodIfElapsed(MASTER) + self.assertGreater(self.havven.feePeriodStartTime(), pre_feePeriodStartTime) def test_abuse_havven_balance(self): - """Test whether repeatedly moving havvens between two parties will shift averages upwards""" - alice, bob = fresh_accounts(2) + # Test whether repeatedly moving havvens between two parties will shift averages upwards + alice = fresh_account() amount = UNIT * 100000 + self.havven_updatePrice(MASTER, UNIT, block_time() + 1) + self.havven.setIssuer(MASTER, alice, True) + self.havven.setIssuanceRatio(MASTER, UNIT) a_sum = 0 - b_sum = 0 - self.endow(MASTER, alice, amount) - time = block_time() - self.assertEqual(self.balanceOf(alice), amount) - self.assertEqual(self.currentBalanceSum(alice), 0) + self.havven.endow(MASTER, alice, amount) + self.assertEqual(self.havven.balanceOf(alice), amount) + self.assertEqual(self.havven.issuanceCurrentBalanceSum(alice), 0) for i in range(20): - self.transfer(alice, bob, amount) - a_sum += (block_time() - time) * amount - time = block_time() - self.assertEqual(self.balanceOf(bob), amount) - self.assertEqual(self.currentBalanceSum(alice), a_sum) - self.transfer(bob, alice, amount) - b_sum += (block_time() - time) * amount - time = block_time() - self.assertEqual(self.balanceOf(alice), amount) - self.assertEqual(self.currentBalanceSum(bob), b_sum) + self.havven.issueNomins(alice, amount) + t = block_time() + self.assertEqual(self.nomin.balanceOf(alice), amount) + self.assertEqual(self.havven.issuanceCurrentBalanceSum(alice), a_sum) + self.havven.burnNomins(alice, amount) + a_sum += (block_time() - t) * amount + self.assertEqual(self.nomin.balanceOf(alice), 0) + self.assertEqual(self.havven.issuanceCurrentBalanceSum(alice), a_sum) + + def test_event_PriceUpdated(self): + time = block_time() + tx = self.havven_updatePrice(self.havven.oracle(), 10 * UNIT, time) + self.assertEventEquals(self.event_map, + tx.logs[0], "PriceUpdated", + {"newPrice": 10 * UNIT, + "timestamp": time}, + self.havven_proxy.address) + + def test_event_IssuanceRatioUpdated(self): + new_ratio = UNIT // 12 + tx = self.havven.setIssuanceRatio(MASTER, new_ratio) + self.assertEventEquals(self.event_map, + tx.logs[0], "IssuanceRatioUpdated", + {"newRatio": new_ratio}, + self.havven_proxy.address) + + def test_event_FeePeriodRollover(self): + time = block_time() + fee_period = self.havven.feePeriodDuration() + fast_forward(fee_period + 10) + tx = self.havven.rolloverFeePeriodIfElapsed(MASTER) + time = block_time(tx.blockNumber) + self.assertEventEquals(self.event_map, + tx.logs[0], "FeePeriodRollover", + {"timestamp": time}, + self.havven_proxy.address) + + def test_event_FeePeriodDurationUpdated(self): + new_duration = 19 * 24 * 60 * 60 + tx = self.havven.setFeePeriodDuration(MASTER, new_duration) + self.assertEventEquals(self.event_map, + tx.logs[0], "FeePeriodDurationUpdated", + {"duration": new_duration}, + self.havven_proxy.address) + + def test_event_FeesWithdrawn(self): + issuer = fresh_account() + fee_rate = self.nomin.transferFeeRate() + fee_period = self.havven.feePeriodDuration() + self.havven.endow(MASTER, issuer, 2 * UNIT) + self.havven_updatePrice(self.havven.oracle(), UNIT, block_time()) + self.havven.setIssuanceRatio(MASTER, UNIT) + self.havven.setIssuer(MASTER, issuer, True) + self.havven.issueNomins(issuer, 2 * UNIT) + fast_forward(fee_period + 100) + self.havven.rolloverFeePeriodIfElapsed(MASTER) + self.nomin.transferSenderPaysFee(issuer, issuer, UNIT) + fast_forward(fee_period + 100) + tx = self.havven.withdrawFees(issuer) + self.assertEventEquals(self.event_map, + tx.logs[3], "FeesWithdrawn", + {"account": issuer, + "value": fee_rate}, + self.havven_proxy.address) + + def test_event_OracleUpdated(self): + new_oracle = fresh_account() + self.assertNotEqual(MASTER, new_oracle) + tx = self.havven.setOracle(MASTER, new_oracle) + self.assertEventEquals(self.event_map, + tx.logs[0], "OracleUpdated", + {"newOracle": new_oracle}, + self.havven_proxy.address) + + def test_event_NominUpdated(self): + new_nomin = fresh_account() + self.assertNotEqual(MASTER, new_nomin) + tx = self.havven.setNomin(MASTER, new_nomin) + self.assertEventEquals(self.event_map, + tx.logs[0], "NominUpdated", + {"newNomin": new_nomin}, + self.havven_proxy.address) + + def test_event_EscrowUpdated(self): + new_escrow = fresh_account() + self.assertNotEqual(MASTER, new_escrow) + tx = self.havven.setEscrow(MASTER, new_escrow) + self.assertEventEquals(self.event_map, + tx.logs[0], "EscrowUpdated", + {"newEscrow": new_escrow}, + self.havven_proxy.address) + + def test_event_IssuersUpdated(self): + new_issuer = fresh_account() + self.assertNotEqual(MASTER, new_issuer) + tx = self.havven.setIssuer(MASTER, new_issuer, True) + self.assertEventEquals(self.event_map, + tx.logs[0], "IssuersUpdated", + {"account": new_issuer, + "value": True}, + self.havven_proxy.address) + tx = self.havven.setIssuer(MASTER, new_issuer, False) + self.assertEventEquals(self.event_map, + tx.logs[0], "IssuersUpdated", + {"account": new_issuer, + "value": False}, + self.havven_proxy.address) + diff --git a/tests/test_HavvenEscrow.py b/tests/test_HavvenEscrow.py index 79db1815f4..08166d802b 100644 --- a/tests/test_HavvenEscrow.py +++ b/tests/test_HavvenEscrow.py @@ -1,680 +1,640 @@ -import unittest -import time - -import utils.generalutils -from utils.testutils import assertReverts, assertClose, block_time -from utils.testutils import ZERO_ADDRESS, generate_topic_event_map, get_event_data_from_log - -from utils.deployutils import attempt, compile_contracts, attempt_deploy, W3, mine_txs, mine_tx, \ - UNIT, MASTER, DUMMY, to_seconds, fast_forward, fresh_account, fresh_accounts, take_snapshot, restore_snapshot - -SOLIDITY_SOURCES = ["tests/contracts/PublicHavven.sol", "contracts/EtherNomin.sol", - "contracts/Court.sol", "contracts/HavvenEscrow.sol", - "contracts/Proxy.sol", "tests/contracts/PublicHavvenEscrow.sol"] - - -def deploy_public_havven(): - print("Deployment initiated.\n") - - compiled = attempt(compile_contracts, [SOLIDITY_SOURCES], "Compiling contracts... ") - - # Deploy contracts - havven_contract, hvn_txr = attempt_deploy(compiled, 'PublicHavven', - MASTER, [ZERO_ADDRESS, MASTER]) - hvn_block = W3.eth.blockNumber - nomin_contract, nom_txr = attempt_deploy(compiled, 'EtherNomin', - MASTER, - [havven_contract.address, MASTER, MASTER, - 1000 * UNIT, MASTER, ZERO_ADDRESS]) - court_contract, court_txr = attempt_deploy(compiled, 'Court', - MASTER, - [havven_contract.address, nomin_contract.address, - MASTER]) - escrow_contract, escrow_txr = attempt_deploy(compiled, 'PublicHavvenEscrow', - MASTER, - [MASTER, havven_contract.address]) - - # Install proxies - havven_proxy, _ = attempt_deploy(compiled, 'Proxy', - MASTER, [havven_contract.address, MASTER]) - mine_tx(havven_contract.functions.setProxy(havven_proxy.address).transact({'from': MASTER})) - proxy_havven = W3.eth.contract(address=havven_proxy.address, abi=compiled['PublicHavven']['abi']) - - nomin_proxy, _ = attempt_deploy(compiled, 'Proxy', - MASTER, [nomin_contract.address, MASTER]) - mine_tx(nomin_contract.functions.setProxy(nomin_proxy.address).transact({'from': MASTER})) - proxy_nomin = W3.eth.contract(address=nomin_proxy.address, abi=compiled['EtherNomin']['abi']) - - # Hook up each of those contracts to each other - txs = [proxy_havven.functions.setNomin(nomin_contract.address).transact({'from': MASTER}), - proxy_nomin.functions.setCourt(court_contract.address).transact({'from': MASTER}), - proxy_havven.functions.setEscrow(escrow_contract.address).transact({'from': MASTER})] - attempt(mine_txs, [txs], "Linking contracts... ") +from utils.testutils import ( + HavvenTestCase, ZERO_ADDRESS, block_time, + generate_topic_event_map +) - escrow_event_dict = generate_topic_event_map(compiled['HavvenEscrow']['abi']) +from utils.deployutils import ( + W3, UNIT, MASTER, DUMMY, + mine_tx, mine_txs, + attempt, attempt_deploy, compile_contracts, + to_seconds, fast_forward, + fresh_account, fresh_accounts, + take_snapshot, restore_snapshot +) - print("\nDeployment complete.\n") - return proxy_havven, proxy_nomin, havven_proxy, nomin_proxy, havven_contract, nomin_contract, court_contract, escrow_contract, hvn_block, escrow_event_dict +from tests.contract_interfaces.havven_interface import PublicHavvenInterface +from tests.contract_interfaces.havven_escrow_interface import PublicHavvenEscrowInterface +from tests.contract_interfaces.nomin_interface import PublicNominInterface def setUpModule(): print("Testing HavvenEscrow...") + print("=======================") + print() def tearDownModule(): print() + print() -class TestHavvenEscrow(unittest.TestCase): +class TestHavvenEscrow(HavvenTestCase): def setUp(self): self.snapshot = take_snapshot() - utils.generalutils.time_fast_forwarded = 0 - self.initial_time = round(time.time()) def tearDown(self): restore_snapshot(self.snapshot) - def test_time_elapsed(self): - return utils.generalutils.time_fast_forwarded + (round(time.time()) - self.initial_time) + @classmethod + def deployContracts(cls): + sources = ["tests/contracts/PublicHavven.sol", "tests/contracts/PublicNomin.sol", + "contracts/Court.sol", "contracts/HavvenEscrow.sol", + "tests/contracts/PublicHavvenEscrow.sol", "contracts/Proxy.sol"] + + print("Deployment initiated.\n") - def now_block_time(self): - return block_time() + self.test_time_elapsed() + compiled, cls.event_maps = cls.compileAndMapEvents(sources) + + # Deploy contracts + + havven_proxy, _ = attempt_deploy(compiled, 'Proxy', MASTER, [MASTER]) + nomin_proxy, _ = attempt_deploy(compiled, 'Proxy', MASTER, [MASTER]) + proxied_havven = W3.eth.contract(address=havven_proxy.address, abi=compiled['PublicHavven']['abi']) + proxied_nomin = W3.eth.contract(address=nomin_proxy.address, abi=compiled['PublicNomin']['abi']) + + tokenstate, _ = attempt_deploy(compiled, 'TokenState', + MASTER, [MASTER, MASTER]) + havven_contract, hvn_txr = attempt_deploy(compiled, 'PublicHavven', MASTER, [havven_proxy.address, tokenstate.address, MASTER, MASTER, UNIT//2]) + hvn_block = W3.eth.blockNumber + + nomin_contract, nom_txr = attempt_deploy(compiled, 'PublicNomin', + MASTER, + [nomin_proxy.address, havven_contract.address, MASTER]) + court_contract, court_txr = attempt_deploy(compiled, 'Court', + MASTER, + [havven_contract.address, nomin_contract.address, + MASTER]) + escrow_contract, escrow_txr = attempt_deploy(compiled, 'PublicHavvenEscrow', + MASTER, + [MASTER, havven_contract.address]) + + # Hook up each of those contracts to each other + mine_txs([tokenstate.functions.setBalanceOf(havven_contract.address, 100000000 * UNIT).transact({'from': MASTER}), + tokenstate.functions.setAssociatedContract(havven_contract.address).transact({'from': MASTER}), + havven_proxy.functions.setTarget(havven_contract.address).transact({'from': MASTER}), + nomin_proxy.functions.setTarget(nomin_contract.address).transact({'from': MASTER}), + havven_contract.functions.setNomin(nomin_contract.address).transact({'from': MASTER}), + nomin_contract.functions.setCourt(court_contract.address).transact({'from': MASTER}), + nomin_contract.functions.setHavven(havven_contract.address).transact({'from': MASTER}), + havven_contract.functions.setEscrow(escrow_contract.address).transact({'from': MASTER})]) + + escrow_event_dict = generate_topic_event_map(compiled['HavvenEscrow']['abi']) + + print("\nDeployment complete.\n") + return havven_proxy, proxied_havven, nomin_proxy, proxied_nomin, havven_contract, nomin_contract, court_contract, escrow_contract, hvn_block, escrow_event_dict @classmethod def setUpClass(cls): - cls.assertReverts = assertReverts - cls.assertClose = assertClose - - cls.havven, cls.nomin, cls.havven_proxy, cls.nomin_proxy, cls.havven_real, cls.nomin_real, cls.court, cls.escrow, cls.construction_block, cls.escrow_event_dict = deploy_public_havven() - - cls.initial_time = cls.nomin.functions.lastPriceUpdateTime().call() - - cls.h_totalSupply = lambda self: cls.havven.functions.totalSupply().call() - cls.h_targetFeePeriodDurationSeconds = lambda self: cls.havven.functions.targetFeePeriodDurationSeconds().call() - cls.h_feePeriodStartTime = lambda self: cls.havven.functions.feePeriodStartTime().call() - cls.h_endow = lambda self, sender, receiver, amt: mine_tx( - cls.havven.functions.endow(receiver, amt).transact({'from': sender})) - cls.h_balanceOf = lambda self, account: cls.havven.functions.balanceOf(account).call() - cls.h_transfer = lambda self, sender, receiver, amt: mine_tx( - cls.havven.functions.transfer(receiver, amt).transact({'from': sender})) - cls.h_recomputeLastAverageBalance = lambda self, sender: mine_tx( - cls.havven.functions.recomputeLastAverageBalance().transact({'from': sender})) - cls.h_withdrawFeeEntitlement = lambda self, sender: mine_tx( - cls.havven.functions.withdrawFeeEntitlement().transact({'from': sender})) - - cls.n_updatePrice = lambda self, sender, price, timeSent: mine_tx( - cls.nomin_real.functions.updatePrice(price, timeSent).transact({'from': sender})) - cls.n_setTransferFeeRate = lambda self, sender, rate: mine_tx( - cls.nomin.functions.setTransferFeeRate(rate).transact({'from': sender})) - cls.n_replenishPool = lambda self, sender, quantity, value: mine_tx( - cls.nomin.functions.replenishPool(quantity).transact({'from': sender, 'value': value})) - cls.n_diminishPool = lambda self, sender, quantity: mine_tx( - cls.nomin.functions.diminishPool(quantity).transact({'from': sender})) - cls.n_buy = lambda self, sender, quantity, value: mine_tx( - cls.nomin.functions.buy(quantity).transact({'from': sender, 'value': value})) - cls.n_sell = lambda self, sender, quantity: mine_tx( - cls.nomin.functions.sell(quantity).transact({'from': sender})) - cls.n_purchaseCostEther = lambda self, quantity: cls.nomin.functions.purchaseCostEther(quantity).call() - cls.n_balanceOf = lambda self, account: cls.nomin.functions.balanceOf(account).call() - cls.n_transfer = lambda self, sender, recipient, quantity: mine_tx( - cls.nomin.functions.transfer(recipient, quantity).transact({'from': sender})) - cls.n_feePool = lambda self: cls.nomin.functions.feePool().call() - cls.n_nominPool = lambda self: cls.nomin.functions.nominPool().call() - cls.n_priceToSpend = lambda self, v: cls.nomin.functions.priceToSpend(v).call() - - cls.owner = lambda self: cls.escrow.functions.owner().call() - cls.nominateOwner = lambda self, sender, newOwner: mine_tx( - cls.escrow.functions.nominateOwner(newOwner).transact({'from': sender})) - cls.acceptOwnership = lambda self, sender: mine_tx( - cls.escrow.functions.acceptOwnership().transact({'from': sender})) - - cls.e_havven = lambda self: cls.escrow.functions.havven().call() - cls.vestingSchedules = lambda self, account, index, i: cls.escrow.functions.vestingSchedules(account, index, - i).call() - cls.numVestingEntries = lambda self, account: cls.escrow.functions.numVestingEntries(account).call() - cls.getVestingScheduleEntry = lambda self, account, index: cls.escrow.functions.getVestingScheduleEntry(account, - index).call() - cls.getVestingTime = lambda self, account, index: cls.escrow.functions.getVestingTime(account, index).call() - cls.getVestingQuantity = lambda self, account, index: cls.escrow.functions.getVestingQuantity(account, - index).call() - cls.totalVestedAccountBalance = lambda self, account: cls.escrow.functions.totalVestedAccountBalance( - account).call() - cls.totalVestedBalance = lambda self: cls.escrow.functions.totalVestedBalance().call() - cls.getNextVestingIndex = lambda self, account: cls.escrow.functions.getNextVestingIndex(account).call() - cls.getNextVestingEntry = lambda self, account: cls.escrow.functions.getNextVestingEntry(account).call() - cls.getNextVestingTime = lambda self, account: cls.escrow.functions.getNextVestingTime(account).call() - cls.getNextVestingQuantity = lambda self, account: cls.escrow.functions.getNextVestingQuantity(account).call() - cls.balanceOf = lambda self, account: cls.escrow.functions.balanceOf(account).call() - - cls.setHavven = lambda self, sender, account: mine_tx( - cls.escrow.functions.setHavven(account).transact({'from': sender})) - cls.purgeAccount = lambda self, sender, account: mine_tx( - cls.escrow.functions.purgeAccount(account).transact({'from': sender})) - cls.withdrawHavvens = lambda self, sender, quantity: mine_tx( - cls.escrow.functions.withdrawHavvens(quantity).transact({'from': sender})) - cls.appendVestingEntry = lambda self, sender, account, time, quantity: mine_tx( - cls.escrow.functions.appendVestingEntry(account, time, quantity).transact({'from': sender})) - cls.addVestingSchedule = lambda self, sender, account, times, quantities: mine_tx( - cls.escrow.functions.addVestingSchedule(account, times, quantities).transact({'from': sender})) - cls.addRegularVestingSchedule = lambda self, sender, account, time, quantity, periods: mine_tx( - cls.escrow.functions.addRegularVestingSchedule(account, time, quantity, periods).transact({'from': sender})) - cls.vest = lambda self, sender: mine_tx(cls.escrow.functions.vest().transact({'from': sender})) - - def make_nomin_velocity(self): - # should produce a 36 * UNIT fee pool - buyer = fresh_account() - self.n_updatePrice(MASTER, UNIT, self.now_block_time()) - self.n_setTransferFeeRate(MASTER, UNIT // 100) - self.n_replenishPool(MASTER, 1000 * UNIT, 2000 * UNIT) - self.n_buy(buyer, 1000 * UNIT, self.n_purchaseCostEther(1000 * UNIT)) - for i in range(8): - self.n_transfer(buyer, buyer, (9 - (i + 1)) * 100 * UNIT) - self.n_sell(buyer, self.n_balanceOf(MASTER)) - self.n_diminishPool(MASTER, self.n_nominPool()) + cls.havven_proxy, cls.proxied_havven, cls.nomin_proxy, cls.proxied_nomin, cls.havven_contract, \ + cls.nomin_contract, cls.court, cls.escrow_contract, cls.construction_block, \ + cls.escrow_event_dict = cls.deployContracts() + + cls.event_map = cls.event_maps['HavvenEscrow'] + + cls.havven = PublicHavvenInterface(cls.proxied_havven, "Havven") + cls.nomin = PublicNominInterface(cls.proxied_nomin, "Nomin") + cls.escrow = PublicHavvenEscrowInterface(cls.escrow_contract, "HavvenEscrow") + + def havven_updatePrice(self, sender, price, time): + mine_tx(self.havven_contract.functions.updatePrice(price, time).transact({'from': sender}), 'updatePrice', 'Havven') def test_constructor(self): - self.assertEqual(self.e_havven(), self.havven_real.address) - self.assertEqual(self.owner(), MASTER) - self.assertEqual(self.totalVestedBalance(), 0) + self.assertEqual(self.escrow.havven(), self.havven_contract.address) + self.assertEqual(self.escrow.owner(), MASTER) + self.assertEqual(self.escrow.totalVestedBalance(), 0) def test_vestingTimes(self): alice = fresh_account() time = block_time() times = [time + to_seconds(weeks=i) for i in range(1, 6)] - self.h_endow(MASTER, self.escrow.address, 100 * UNIT) - self.appendVestingEntry(MASTER, alice, times[0], UNIT) - self.assertEqual(self.getVestingTime(alice, 0), times[0]) + self.havven.endow(MASTER, self.escrow.contract.address, 100 * UNIT) + self.escrow.appendVestingEntry(MASTER, alice, times[0], UNIT) + self.assertEqual(self.escrow.getVestingTime(alice, 0), times[0]) for i in range(1, len(times)): - self.appendVestingEntry(MASTER, alice, times[i], UNIT) + self.escrow.appendVestingEntry(MASTER, alice, times[i], UNIT) for i in range(1, len(times)): - self.assertEqual(self.getVestingTime(alice, i), times[i]) + self.assertEqual(self.escrow.getVestingTime(alice, i), times[i]) def test_vestingQuantities(self): alice = fresh_account() time = block_time() times = [time + to_seconds(weeks=i) for i in range(1, 6)] quantities = [UNIT * i for i in range(1, 6)] - self.h_endow(MASTER, self.escrow.address, 100 * UNIT) - self.appendVestingEntry(MASTER, alice, times[0], quantities[0]) - self.assertEqual(self.getVestingQuantity(alice, 0), quantities[0]) + self.havven.endow(MASTER, self.escrow.contract.address, 100 * UNIT) + self.escrow.appendVestingEntry(MASTER, alice, times[0], quantities[0]) + self.assertEqual(self.escrow.getVestingQuantity(alice, 0), quantities[0]) for i in range(1, len(times)): - self.appendVestingEntry(MASTER, alice, times[i], quantities[i]) + self.escrow.appendVestingEntry(MASTER, alice, times[i], quantities[i]) for i in range(1, len(times)): - self.assertEqual(self.getVestingQuantity(alice, i), quantities[i]) + self.assertEqual(self.escrow.getVestingQuantity(alice, i), quantities[i]) def test_vestingSchedules(self): alice = fresh_account() time = block_time() - self.h_endow(MASTER, self.escrow.address, 1500 * UNIT) + self.havven.endow(MASTER, self.escrow.contract.address, 1500 * UNIT) - self.appendVestingEntry(MASTER, alice, time + 1000, UNIT) - self.assertEqual(self.vestingSchedules(alice, 0, 0), time + 1000) - self.assertEqual(self.vestingSchedules(alice, 0, 1), UNIT) - self.appendVestingEntry(MASTER, alice, time + 2000, 2 * UNIT) - self.assertEqual(self.vestingSchedules(alice, 1, 0), time + 2000) - self.assertEqual(self.vestingSchedules(alice, 1, 1), 2 * UNIT) + self.escrow.appendVestingEntry(MASTER, alice, time + 1000, UNIT) + self.assertEqual(self.escrow.vestingSchedules(alice, 0, 0), time + 1000) + self.assertEqual(self.escrow.vestingSchedules(alice, 0, 1), UNIT) + self.escrow.appendVestingEntry(MASTER, alice, time + 2000, 2 * UNIT) + self.assertEqual(self.escrow.vestingSchedules(alice, 1, 0), time + 2000) + self.assertEqual(self.escrow.vestingSchedules(alice, 1, 1), 2 * UNIT) def test_balanceOf(self): alice = fresh_account() time = block_time() - self.h_endow(MASTER, self.escrow.address, 100 * UNIT) - self.assertEqual(self.balanceOf(alice), 0) - self.appendVestingEntry(MASTER, alice, time + 100, UNIT) - self.assertEqual(self.balanceOf(alice), UNIT) + self.havven.endow(MASTER, self.escrow.contract.address, 100 * UNIT) + self.assertEqual(self.escrow.balanceOf(alice), 0) + self.escrow.appendVestingEntry(MASTER, alice, time + 100, UNIT) + self.assertEqual(self.escrow.balanceOf(alice), UNIT) - self.purgeAccount(MASTER, alice) - self.assertEqual(self.balanceOf(alice), 0) + self.escrow.purgeAccount(MASTER, alice) + self.assertEqual(self.escrow.balanceOf(alice), 0) k = 5 for n in [100 * 2 ** i for i in range(k)]: - self.appendVestingEntry(MASTER, alice, time + n, n) + self.escrow.appendVestingEntry(MASTER, alice, time + n, n) - self.assertEqual(self.balanceOf(alice), 100 * (2 ** k - 1)) + self.assertEqual(self.escrow.balanceOf(alice), 100 * (2 ** k - 1)) fast_forward(110) - self.vest(alice) - self.assertEqual(self.balanceOf(alice), 100 * (2 ** k - 1) - 100) + self.escrow.vest(alice) + self.assertEqual(self.escrow.balanceOf(alice), 100 * (2 ** k - 1) - 100) def test_totalVestedAccountBalance(self): alice = fresh_account() time = block_time() - self.h_endow(MASTER, self.escrow.address, 100 * UNIT) - self.assertEqual(self.totalVestedAccountBalance(alice), 0) - self.appendVestingEntry(MASTER, alice, time + 100, UNIT) - self.assertEqual(self.totalVestedAccountBalance(alice), UNIT) + self.havven.endow(MASTER, self.escrow.contract.address, 100 * UNIT) + self.assertEqual(self.escrow.totalVestedAccountBalance(alice), 0) + self.escrow.appendVestingEntry(MASTER, alice, time + 100, UNIT) + self.assertEqual(self.escrow.totalVestedAccountBalance(alice), UNIT) - self.purgeAccount(MASTER, alice) - self.assertEqual(self.totalVestedAccountBalance(alice), 0) + self.escrow.purgeAccount(MASTER, alice) + self.assertEqual(self.escrow.totalVestedAccountBalance(alice), 0) k = 5 for n in [100 * 2 ** i for i in range(k)]: - self.appendVestingEntry(MASTER, alice, time + n, n) + self.escrow.appendVestingEntry(MASTER, alice, time + n, n) - self.assertEqual(self.totalVestedAccountBalance(alice), 100 * (2 ** k - 1)) + self.assertEqual(self.escrow.totalVestedAccountBalance(alice), 100 * (2 ** k - 1)) fast_forward(110) - self.vest(alice) - self.assertEqual(self.totalVestedAccountBalance(alice), 100 * (2 ** k - 1) - 100) + self.escrow.vest(alice) + self.assertEqual(self.escrow.totalVestedAccountBalance(alice), 100 * (2 ** k - 1) - 100) def test_totalVestedBalance(self): alice, bob = fresh_accounts(2) time = block_time() - self.h_endow(MASTER, self.escrow.address, 100 * UNIT) - self.assertEqual(self.totalVestedBalance(), 0) - self.appendVestingEntry(MASTER, bob, time + 100, UNIT) - self.assertEqual(self.totalVestedBalance(), UNIT) + self.havven.endow(MASTER, self.escrow.contract.address, 100 * UNIT) + self.assertEqual(self.escrow.totalVestedBalance(), 0) + self.escrow.appendVestingEntry(MASTER, bob, time + 100, UNIT) + self.assertEqual(self.escrow.totalVestedBalance(), UNIT) - self.appendVestingEntry(MASTER, alice, time + 100, UNIT) - self.assertEqual(self.totalVestedBalance(), 2 * UNIT) + self.escrow.appendVestingEntry(MASTER, alice, time + 100, UNIT) + self.assertEqual(self.escrow.totalVestedBalance(), 2 * UNIT) - self.purgeAccount(MASTER, alice) - self.assertEqual(self.totalVestedBalance(), UNIT) + self.escrow.purgeAccount(MASTER, alice) + self.assertEqual(self.escrow.totalVestedBalance(), UNIT) k = 5 for n in [100 * 2 ** i for i in range(k)]: - self.appendVestingEntry(MASTER, alice, time + n, n) + self.escrow.appendVestingEntry(MASTER, alice, time + n, n) - self.assertEqual(self.totalVestedBalance(), UNIT + 100 * (2 ** k - 1)) + self.assertEqual(self.escrow.totalVestedBalance(), UNIT + 100 * (2 ** k - 1)) fast_forward(110) - self.vest(alice) - self.assertEqual(self.totalVestedBalance(), UNIT + 100 * (2 ** k - 1) - 100) + self.escrow.vest(alice) + self.assertEqual(self.escrow.totalVestedBalance(), UNIT + 100 * (2 ** k - 1) - 100) def test_numVestingEntries(self): alice = fresh_account() time = block_time() times = [time + to_seconds(weeks=i) for i in range(1, 6)] - self.h_endow(MASTER, self.escrow.address, 100 * UNIT) - - self.assertEqual(self.numVestingEntries(alice), 0) - self.appendVestingEntry(MASTER, alice, times[0], UNIT) - self.assertEqual(self.numVestingEntries(alice), 1) - self.appendVestingEntry(MASTER, alice, times[1], UNIT) - self.assertEqual(self.numVestingEntries(alice), 2) - self.appendVestingEntry(MASTER, alice, times[2], UNIT) - self.appendVestingEntry(MASTER, alice, times[3], UNIT) - self.appendVestingEntry(MASTER, alice, times[4], UNIT) - self.assertEqual(self.numVestingEntries(alice), 5) - self.purgeAccount(MASTER, alice) - self.assertEqual(self.numVestingEntries(alice), 0) + self.havven.endow(MASTER, self.escrow.contract.address, 100 * UNIT) + + self.assertEqual(self.escrow.numVestingEntries(alice), 0) + self.escrow.appendVestingEntry(MASTER, alice, times[0], UNIT) + self.assertEqual(self.escrow.numVestingEntries(alice), 1) + self.escrow.appendVestingEntry(MASTER, alice, times[1], UNIT) + self.assertEqual(self.escrow.numVestingEntries(alice), 2) + self.escrow.appendVestingEntry(MASTER, alice, times[2], UNIT) + self.escrow.appendVestingEntry(MASTER, alice, times[3], UNIT) + self.escrow.appendVestingEntry(MASTER, alice, times[4], UNIT) + self.assertEqual(self.escrow.numVestingEntries(alice), 5) + self.escrow.purgeAccount(MASTER, alice) + self.assertEqual(self.escrow.numVestingEntries(alice), 0) + + def test_maxVestingEntries(self): + alice = fresh_account() + time = block_time() + self.havven.endow(MASTER, self.escrow.contract.address, 200 * UNIT) + self.escrow.addRegularVestingSchedule(MASTER, alice, time + to_seconds(weeks=52), 100 * UNIT, 21) + self.assertReverts(self.escrow.appendVestingEntry, MASTER, alice, time + to_seconds(weeks=52, days=1), UNIT) def test_getVestingScheduleEntry(self): alice = fresh_account() time = block_time() - self.h_endow(MASTER, self.escrow.address, 100 * UNIT) - self.appendVestingEntry(MASTER, alice, time + 100, 1); - self.assertEqual(self.getVestingScheduleEntry(alice, 0), [time + 100, 1]) + self.havven.endow(MASTER, self.escrow.contract.address, 100 * UNIT) + self.escrow.appendVestingEntry(MASTER, alice, time + 100, 1); + self.assertEqual(self.escrow.getVestingScheduleEntry(alice, 0), [time + 100, 1]) def test_getNextVestingIndex(self): - self.h_endow(MASTER, self.escrow.address, 100 * UNIT) + self.havven.endow(MASTER, self.escrow.contract.address, 100 * UNIT) alice = fresh_account() time = block_time() times = [time + to_seconds(weeks=i) for i in range(1, 6)] - self.assertEqual(self.getNextVestingIndex(alice), 0) + self.assertEqual(self.escrow.getNextVestingIndex(alice), 0) for i in range(len(times)): - self.appendVestingEntry(MASTER, alice, times[i], UNIT) + self.escrow.appendVestingEntry(MASTER, alice, times[i], UNIT) for i in range(len(times)): fast_forward(to_seconds(weeks=1) + 30) - self.assertEqual(self.getNextVestingIndex(alice), i) - self.vest(alice) - self.assertEqual(self.getNextVestingIndex(alice), i + 1) + self.assertEqual(self.escrow.getNextVestingIndex(alice), i) + self.escrow.vest(alice) + self.assertEqual(self.escrow.getNextVestingIndex(alice), i + 1) def test_getNextVestingEntry(self): - self.h_endow(MASTER, self.escrow.address, 100 * UNIT) + self.havven.endow(MASTER, self.escrow.contract.address, 100 * UNIT) alice = fresh_account() time = block_time() entries = [[time + to_seconds(weeks=i), i * UNIT] for i in range(1, 6)] - self.assertEqual(self.getNextVestingEntry(alice), [0, 0]) + self.assertEqual(self.escrow.getNextVestingEntry(alice), [0, 0]) for i in range(len(entries)): - self.appendVestingEntry(MASTER, alice, entries[i][0], entries[i][1]) + self.escrow.appendVestingEntry(MASTER, alice, entries[i][0], entries[i][1]) for i in range(len(entries)): fast_forward(to_seconds(weeks=1) + 30) - self.assertEqual(self.getNextVestingEntry(alice), entries[i]) - self.vest(alice) - self.assertEqual(self.getNextVestingEntry(alice), [0, 0] if i == len(entries) - 1 else entries[i + 1]) + self.assertEqual(self.escrow.getNextVestingEntry(alice), entries[i]) + self.escrow.vest(alice) + self.assertEqual(self.escrow.getNextVestingEntry(alice), [0, 0] if i == len(entries) - 1 else entries[i + 1]) def test_getNextVestingTime(self): - self.h_endow(MASTER, self.escrow.address, 100 * UNIT) + self.havven.endow(MASTER, self.escrow.contract.address, 100 * UNIT) alice = fresh_account() time = block_time() entries = [[time + to_seconds(weeks=i), i * UNIT] for i in range(1, 6)] - self.assertEqual(self.getNextVestingTime(alice), 0) + self.assertEqual(self.escrow.getNextVestingTime(alice), 0) for i in range(len(entries)): - self.appendVestingEntry(MASTER, alice, entries[i][0], entries[i][1]) + self.escrow.appendVestingEntry(MASTER, alice, entries[i][0], entries[i][1]) for i in range(len(entries)): fast_forward(to_seconds(weeks=1) + 30) - self.assertEqual(self.getNextVestingTime(alice), entries[i][0]) - self.vest(alice) - self.assertEqual(self.getNextVestingTime(alice), 0 if i == len(entries) - 1 else entries[i + 1][0]) + self.assertEqual(self.escrow.getNextVestingTime(alice), entries[i][0]) + self.escrow.vest(alice) + self.assertEqual(self.escrow.getNextVestingTime(alice), 0 if i == len(entries) - 1 else entries[i + 1][0]) def test_getNextVestingQuantity(self): - self.h_endow(MASTER, self.escrow.address, 100 * UNIT) + self.havven.endow(MASTER, self.escrow.contract.address, 100 * UNIT) alice = fresh_account() time = block_time() entries = [[time + to_seconds(weeks=i), i * UNIT] for i in range(1, 6)] - self.assertEqual(self.getNextVestingQuantity(alice), 0) + self.assertEqual(self.escrow.getNextVestingQuantity(alice), 0) for i in range(len(entries)): - self.appendVestingEntry(MASTER, alice, entries[i][0], entries[i][1]) + self.escrow.appendVestingEntry(MASTER, alice, entries[i][0], entries[i][1]) for i in range(len(entries)): fast_forward(to_seconds(weeks=1) + 30) - self.assertEqual(self.getNextVestingQuantity(alice), entries[i][1]) - self.vest(alice) - self.assertEqual(self.getNextVestingQuantity(alice), 0 if i == len(entries) - 1 else entries[i + 1][1]) + self.assertEqual(self.escrow.getNextVestingQuantity(alice), entries[i][1]) + self.escrow.vest(alice) + self.assertEqual(self.escrow.getNextVestingQuantity(alice), 0 if i == len(entries) - 1 else entries[i + 1][1]) def test_setHavven(self): alice = fresh_account() - self.setHavven(MASTER, alice) - self.assertEqual(self.e_havven(), alice) - self.assertReverts(self.setHavven, alice, alice) + self.escrow.setHavven(MASTER, alice) + self.assertEqual(self.escrow.havven(), alice) + self.assertReverts(self.escrow.setHavven, alice, alice) def test_escrowedFees(self): - self.h_endow(MASTER, self.escrow.address, self.h_totalSupply() - 100 * UNIT) - self.h_endow(MASTER, MASTER, 100 * UNIT) - self.appendVestingEntry(MASTER, MASTER, block_time() + to_seconds(weeks=1), self.h_totalSupply() - 100 * UNIT) - self.make_nomin_velocity() + self.havven.endow(MASTER, self.escrow.contract.address, self.havven.totalSupply()) + self.escrow.appendVestingEntry(MASTER, MASTER, block_time() + 100000, self.havven.totalSupply()) + + self.havven_updatePrice(self.havven.oracle(), UNIT, self.havven.currentTime() + 1) + self.havven.setIssuer(MASTER, MASTER, True) + self.havven.issueNomins(MASTER, UNIT) - self.assertClose(self.n_feePool(), 36 * UNIT) + # generate 1 UNIT of fees + self.nomin.donateToFeePool(MASTER, UNIT) + + fees = self.nomin.feePool() + + # Skip a period so we have a full period with no transfers + fast_forward(self.havven.feePeriodDuration() + 100) + self.havven.rolloverFeePeriodIfElapsed(MASTER) + self.havven.recomputeLastAverageBalance(MASTER, MASTER) + # Skip a period so we have a full period with no transfers + fast_forward(self.havven.feePeriodDuration() + 100) + self.havven.rolloverFeePeriodIfElapsed(MASTER) + self.havven.recomputeLastAverageBalance(MASTER, MASTER) - target_period = self.h_targetFeePeriodDurationSeconds() + 1000 - fast_forward(seconds=target_period) + self.assertEqual(fees, self.havven.lastFeesCollected()) - self.h_transfer(MASTER, self.escrow.address, 0) - fast_forward(seconds=target_period) + self.havven.withdrawFees(MASTER) - self.h_withdrawFeeEntitlement(MASTER) - self.assertEqual(self.n_feePool(), 0) - self.assertClose(self.n_balanceOf(MASTER), 36 * UNIT) + self.assertEqual(self.nomin.feePool(), 0) + self.assertEqual(self.nomin.balanceOf(MASTER), fees) def test_withdrawHalfFees(self): - self.h_endow(MASTER, self.escrow.address, self.h_totalSupply()) - self.appendVestingEntry(MASTER, MASTER, block_time() + 100000, self.h_totalSupply() // 2) - self.appendVestingEntry(MASTER, DUMMY, block_time() + 100000, self.h_totalSupply() // 2) - self.make_nomin_velocity() + self.havven.endow(MASTER, self.escrow.contract.address, self.havven.totalSupply()) + self.escrow.appendVestingEntry(MASTER, MASTER, block_time() + 100000, self.havven.totalSupply() // 2) + self.escrow.appendVestingEntry(MASTER, DUMMY, block_time() + 100000, self.havven.totalSupply() // 2) - uncollected = self.n_feePool() - self.assertClose(uncollected, 36 * UNIT) + self.havven_updatePrice(self.havven.oracle(), UNIT, self.havven.currentTime() + 1) + self.havven.setIssuer(MASTER, MASTER, True) + self.havven.issueNomins(MASTER, UNIT) - # Skip a period so we have a full period with no transfers - target_period = self.h_targetFeePeriodDurationSeconds() + 100 - fast_forward(seconds=target_period) + # generate 1 UNIT of fees + self.havven.setIssuer(MASTER, DUMMY, True) + self.havven.issueNomins(DUMMY, UNIT) + self.nomin.donateToFeePool(DUMMY, UNIT) - # Zero value transfer to roll over the fee period - self.h_transfer(MASTER, self.escrow.address, 0) - fast_forward(seconds=target_period) + fees = self.nomin.feePool() + + # Skip a period so we have a full period with no transfers + fast_forward(self.havven.feePeriodDuration() + 100) + self.havven.rolloverFeePeriodIfElapsed(MASTER) + self.havven.recomputeLastAverageBalance(MASTER, MASTER) # Since escrow contract has most of the global supply, and half of the # escrowed balance, they should get half of the fees. - self.h_withdrawFeeEntitlement(MASTER) - self.assertClose(self.n_balanceOf(MASTER), 18 * UNIT) + self.havven.withdrawFees(MASTER) + self.assertClose(self.nomin.balanceOf(MASTER) - UNIT, fees/2) + + self.havven.withdrawFees(DUMMY) + self.assertClose(self.nomin.balanceOf(DUMMY), fees/2) def test_purgeAccount(self): alice = fresh_account() time = block_time() + 100 - self.h_endow(MASTER, self.escrow.address, 1000 * UNIT) - self.appendVestingEntry(MASTER, alice, time, 1000) + self.havven.endow(MASTER, self.escrow.contract.address, 1000 * UNIT) + self.escrow.appendVestingEntry(MASTER, alice, time, 1000) - self.assertReverts(self.purgeAccount, alice, alice); + self.assertReverts(self.escrow.purgeAccount, alice, alice); - self.assertEqual(self.numVestingEntries(alice), 1) - self.assertEqual(self.totalVestedBalance(), 1000) - self.assertEqual(self.totalVestedAccountBalance(alice), 1000) - self.assertEqual(self.getNextVestingIndex(alice), 0) - self.assertEqual(self.getNextVestingTime(alice), time) - self.assertEqual(self.getNextVestingQuantity(alice), 1000) + self.assertEqual(self.escrow.numVestingEntries(alice), 1) + self.assertEqual(self.escrow.totalVestedBalance(), 1000) + self.assertEqual(self.escrow.totalVestedAccountBalance(alice), 1000) + self.assertEqual(self.escrow.getNextVestingIndex(alice), 0) + self.assertEqual(self.escrow.getNextVestingTime(alice), time) + self.assertEqual(self.escrow.getNextVestingQuantity(alice), 1000) - tx_receipt = self.purgeAccount(MASTER, alice) + tx_receipt = self.escrow.purgeAccount(MASTER, alice) - self.assertEqual(self.numVestingEntries(alice), 0) - self.assertEqual(self.totalVestedBalance(), 0) - self.assertEqual(self.totalVestedAccountBalance(alice), 0) - self.assertEqual(self.getNextVestingIndex(alice), 0) - self.assertEqual(self.getNextVestingTime(alice), 0) - self.assertEqual(self.getNextVestingQuantity(alice), 0) + self.assertEqual(self.escrow.numVestingEntries(alice), 0) + self.assertEqual(self.escrow.totalVestedBalance(), 0) + self.assertEqual(self.escrow.totalVestedAccountBalance(alice), 0) + self.assertEqual(self.escrow.getNextVestingIndex(alice), 0) + self.assertEqual(self.escrow.getNextVestingTime(alice), 0) + self.assertEqual(self.escrow.getNextVestingQuantity(alice), 0) + """ def test_withdrawHavvens(self): - self.h_endow(MASTER, self.escrow.address, UNIT) - self.assertEqual(self.h_balanceOf(self.escrow.address), UNIT) + self.havven.endow(MASTER, self.escrow.contract.address, UNIT) + self.assertEqual(self.havven.balanceOf(self.escrow.contract.address), UNIT) - pre_h_balance = self.h_balanceOf(self.havven_real.address) - self.withdrawHavvens(MASTER, UNIT // 2) - self.assertEqual(self.h_balanceOf(self.escrow.address), UNIT // 2) - self.assertEqual(self.h_balanceOf(self.havven_real.address), pre_h_balance + UNIT // 2) + pre_h_balance = self.havven.balanceOf(self.havven_contract.address) + self.escrow.withdrawHavvens(MASTER, UNIT // 2) + self.assertEqual(self.havven.balanceOf(self.escrow.contract.address), UNIT // 2) + self.assertEqual(self.havven.balanceOf(self.havven_contract.address), pre_h_balance + UNIT // 2) + """ def test_appendVestingEntry(self): alice, bob = fresh_accounts(2) escrow_balance = 20 * UNIT amount = 10 - self.h_endow(MASTER, self.escrow.address, escrow_balance) + self.havven.endow(MASTER, self.escrow.contract.address, escrow_balance) time = block_time() # Should not be able to add a vestingEntry > havven.totalSupply() - self.assertReverts(self.appendVestingEntry, MASTER, alice, time + to_seconds(weeks=2), self.h_totalSupply() + 1) + self.assertReverts(self.escrow.appendVestingEntry, MASTER, alice, time + to_seconds(weeks=2), self.havven.totalSupply() + 1) # Should not be able to add a vestingEntry > balanceOf escrow account - self.assertReverts(self.appendVestingEntry, MASTER, alice, time + to_seconds(weeks=2), escrow_balance + 1) + self.assertReverts(self.escrow.appendVestingEntry, MASTER, alice, time + to_seconds(weeks=2), escrow_balance + 1) # Should not be able to vest in the past - self.assertReverts(self.appendVestingEntry, MASTER, alice, 0, UNIT) - self.assertReverts(self.appendVestingEntry, MASTER, alice, time - 1, UNIT) - self.assertReverts(self.appendVestingEntry, MASTER, alice, time, UNIT) + self.assertReverts(self.escrow.appendVestingEntry, MASTER, alice, 0, UNIT) + self.assertReverts(self.escrow.appendVestingEntry, MASTER, alice, time - 1, UNIT) + self.assertReverts(self.escrow.appendVestingEntry, MASTER, alice, time, UNIT) # Vesting quantities should be nonzero - self.assertReverts(self.appendVestingEntry, MASTER, alice, time + to_seconds(weeks=2), 0) + self.assertReverts(self.escrow.appendVestingEntry, MASTER, alice, time + to_seconds(weeks=2), 0) - self.appendVestingEntry(MASTER, alice, time + to_seconds(weeks=2), amount) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 0) + self.escrow.appendVestingEntry(MASTER, alice, time + to_seconds(weeks=2), amount) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 0) fast_forward(weeks=3) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), amount) - self.h_transfer(alice, MASTER, amount) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), amount) + self.havven.transfer(alice, MASTER, amount) time = block_time() t1 = time + to_seconds(weeks=1) t2 = time + to_seconds(weeks=2) - self.appendVestingEntry(MASTER, alice, t1, amount) - self.assertReverts(self.appendVestingEntry, MASTER, alice, time + to_seconds(days=1), amount) - self.assertReverts(self.appendVestingEntry, MASTER, alice, time + to_seconds(weeks=1), amount) - self.appendVestingEntry(MASTER, alice, t2, amount + 1) + self.escrow.appendVestingEntry(MASTER, alice, t1, amount) + self.assertReverts(self.escrow.appendVestingEntry, MASTER, alice, time + to_seconds(days=1), amount) + self.assertReverts(self.escrow.appendVestingEntry, MASTER, alice, time + to_seconds(weeks=1), amount) + self.escrow.appendVestingEntry(MASTER, alice, t2, amount + 1) - self.assertEqual(self.getVestingQuantity(alice, 1), amount) - self.assertEqual(self.getVestingQuantity(alice, 2), amount + 1) + self.assertEqual(self.escrow.getVestingQuantity(alice, 1), amount) + self.assertEqual(self.escrow.getVestingQuantity(alice, 2), amount + 1) - self.assertEqual(self.getVestingTime(alice, 1), t1) - self.assertEqual(self.getVestingTime(alice, 2), t2) - self.assertEqual(self.numVestingEntries(alice), 3) + self.assertEqual(self.escrow.getVestingTime(alice, 1), t1) + self.assertEqual(self.escrow.getVestingTime(alice, 2), t2) + self.assertEqual(self.escrow.numVestingEntries(alice), 3) def test_vest(self): alice = fresh_account() - self.h_endow(MASTER, self.escrow.address, 100 * UNIT) + self.havven.endow(MASTER, self.escrow.contract.address, 100 * UNIT) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 0) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 0) time = block_time() - self.appendVestingEntry(MASTER, alice, time + 100, UNIT) - self.appendVestingEntry(MASTER, alice, time + 200, UNIT) - self.appendVestingEntry(MASTER, alice, time + 300, UNIT) - self.appendVestingEntry(MASTER, alice, time + 400, UNIT) - self.appendVestingEntry(MASTER, alice, time + 500, UNIT) - self.appendVestingEntry(MASTER, alice, time + 600, UNIT) - + self.escrow.appendVestingEntry(MASTER, alice, time + 100, UNIT) + self.escrow.appendVestingEntry(MASTER, alice, time + 200, UNIT) + self.escrow.appendVestingEntry(MASTER, alice, time + 300, UNIT) + self.escrow.appendVestingEntry(MASTER, alice, time + 400, UNIT) + self.escrow.appendVestingEntry(MASTER, alice, time + 500, UNIT) + self.escrow.appendVestingEntry(MASTER, alice, time + 600, UNIT) + + self.assertEqual(self.escrow.balanceOf(alice), 6 * UNIT) fast_forward(105) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), UNIT) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), UNIT) + self.assertEqual(self.escrow.balanceOf(alice), 5 * UNIT) fast_forward(205) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 3 * UNIT) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 3 * UNIT) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 3 * UNIT) + self.assertEqual(self.escrow.balanceOf(alice), 3 * UNIT) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 3 * UNIT) + self.assertEqual(self.escrow.balanceOf(alice), 3 * UNIT) fast_forward(105) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 4 * UNIT) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 4 * UNIT) + self.assertEqual(self.escrow.balanceOf(alice), 2 * UNIT) fast_forward(505) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 6 * UNIT) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 6 * UNIT) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 6 * UNIT) + self.assertEqual(self.escrow.balanceOf(alice), 0 * UNIT) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 6 * UNIT) + self.assertEqual(self.escrow.balanceOf(alice), 0 * UNIT) def test_addVestingSchedule(self): alice, bob, eve = fresh_accounts(3) - self.h_endow(MASTER, self.escrow.address, 1000 * UNIT) + self.havven.endow(MASTER, self.escrow.contract.address, 1000 * UNIT) time = block_time() - self.appendVestingEntry(MASTER, bob, time + 100000, UNIT) + self.escrow.appendVestingEntry(MASTER, bob, time + 100000, UNIT) times = [time + 100, time + 300, time + 400, time + 10000] quantities = [UNIT, 2*UNIT, UNIT, 5*UNIT] - self.addVestingSchedule(MASTER, alice, times, quantities) - self.assertEqual(self.numVestingEntries(alice), 4) + self.escrow.addVestingSchedule(MASTER, alice, times, quantities) + self.assertEqual(self.escrow.numVestingEntries(alice), 4) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 0) - self.assertEqual(self.numVestingEntries(alice), 4) - self.assertEqual(self.totalVestedBalance(), 10*UNIT) - self.assertEqual(self.totalVestedAccountBalance(alice), 9*UNIT) - self.assertEqual(self.getNextVestingIndex(alice), 0) - self.assertEqual(self.getNextVestingTime(alice), times[0]) - self.assertEqual(self.getNextVestingQuantity(alice), quantities[0]) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 0) + self.assertEqual(self.escrow.numVestingEntries(alice), 4) + self.assertEqual(self.escrow.totalVestedBalance(), 10*UNIT) + self.assertEqual(self.escrow.totalVestedAccountBalance(alice), 9*UNIT) + self.assertEqual(self.escrow.getNextVestingIndex(alice), 0) + self.assertEqual(self.escrow.getNextVestingTime(alice), times[0]) + self.assertEqual(self.escrow.getNextVestingQuantity(alice), quantities[0]) fast_forward(110) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), UNIT) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), UNIT) - self.assertEqual(self.numVestingEntries(alice), 4) - self.assertEqual(self.totalVestedAccountBalance(alice), 8*UNIT) - self.assertEqual(self.totalVestedBalance(), 9*UNIT) - self.assertEqual(self.getNextVestingIndex(alice), 1) - self.assertEqual(self.getNextVestingTime(alice), times[1]) - self.assertEqual(self.getNextVestingQuantity(alice), quantities[1]) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), UNIT) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), UNIT) + self.assertEqual(self.escrow.numVestingEntries(alice), 4) + self.assertEqual(self.escrow.totalVestedAccountBalance(alice), 8*UNIT) + self.assertEqual(self.escrow.totalVestedBalance(), 9*UNIT) + self.assertEqual(self.escrow.getNextVestingIndex(alice), 1) + self.assertEqual(self.escrow.getNextVestingTime(alice), times[1]) + self.assertEqual(self.escrow.getNextVestingQuantity(alice), quantities[1]) fast_forward(220) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 3*UNIT) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 3*UNIT) - self.assertEqual(self.numVestingEntries(alice), 4) - self.assertEqual(self.totalVestedAccountBalance(alice), 6*UNIT) - self.assertEqual(self.totalVestedBalance(), 7*UNIT) - self.assertEqual(self.getNextVestingIndex(alice), 2) - self.assertEqual(self.getNextVestingTime(alice), times[2]) - self.assertEqual(self.getNextVestingQuantity(alice), quantities[2]) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 3*UNIT) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 3*UNIT) + self.assertEqual(self.escrow.numVestingEntries(alice), 4) + self.assertEqual(self.escrow.totalVestedAccountBalance(alice), 6*UNIT) + self.assertEqual(self.escrow.totalVestedBalance(), 7*UNIT) + self.assertEqual(self.escrow.getNextVestingIndex(alice), 2) + self.assertEqual(self.escrow.getNextVestingTime(alice), times[2]) + self.assertEqual(self.escrow.getNextVestingQuantity(alice), quantities[2]) fast_forward(110) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 4*UNIT) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 4*UNIT) - self.assertEqual(self.numVestingEntries(alice), 4) - self.assertEqual(self.totalVestedAccountBalance(alice), 5*UNIT) - self.assertEqual(self.totalVestedBalance(), 6*UNIT) - self.assertEqual(self.getNextVestingIndex(alice), 3) - self.assertEqual(self.getNextVestingTime(alice), times[3]) - self.assertEqual(self.getNextVestingQuantity(alice), quantities[3]) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 4*UNIT) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 4*UNIT) + self.assertEqual(self.escrow.numVestingEntries(alice), 4) + self.assertEqual(self.escrow.totalVestedAccountBalance(alice), 5*UNIT) + self.assertEqual(self.escrow.totalVestedBalance(), 6*UNIT) + self.assertEqual(self.escrow.getNextVestingIndex(alice), 3) + self.assertEqual(self.escrow.getNextVestingTime(alice), times[3]) + self.assertEqual(self.escrow.getNextVestingQuantity(alice), quantities[3]) fast_forward(10000) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 9*UNIT) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 9*UNIT) - self.assertEqual(self.numVestingEntries(alice), 4) - self.assertEqual(self.totalVestedAccountBalance(alice), 0) - self.assertEqual(self.totalVestedBalance(), UNIT) - self.assertEqual(self.getNextVestingIndex(alice), 4) - self.assertEqual(self.getNextVestingTime(alice), 0) - self.assertEqual(self.getNextVestingQuantity(alice), 0) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 9*UNIT) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 9*UNIT) + self.assertEqual(self.escrow.numVestingEntries(alice), 4) + self.assertEqual(self.escrow.totalVestedAccountBalance(alice), 0) + self.assertEqual(self.escrow.totalVestedBalance(), UNIT) + self.assertEqual(self.escrow.getNextVestingIndex(alice), 4) + self.assertEqual(self.escrow.getNextVestingTime(alice), 0) + self.assertEqual(self.escrow.getNextVestingQuantity(alice), 0) time = block_time() # Bad (zero) quantities - self.assertReverts(self.addVestingSchedule, MASTER, eve, [time + 1000, time + 2000], [0, UNIT]) - self.assertReverts(self.addVestingSchedule, MASTER, eve, [time + 1000, time + 2000], [UNIT, 0]) + self.assertReverts(self.escrow.addVestingSchedule, MASTER, eve, [time + 1000, time + 2000], [0, UNIT]) + self.assertReverts(self.escrow.addVestingSchedule, MASTER, eve, [time + 1000, time + 2000], [UNIT, 0]) # Bad times - self.assertReverts(self.addVestingSchedule, MASTER, eve, [time - 1000, time + 2000], [UNIT, UNIT]) - self.assertReverts(self.addVestingSchedule, MASTER, eve, [time + 1000, time + 500], [UNIT, UNIT]) + self.assertReverts(self.escrow.addVestingSchedule, MASTER, eve, [time - 1000, time + 2000], [UNIT, UNIT]) + self.assertReverts(self.escrow.addVestingSchedule, MASTER, eve, [time + 1000, time + 500], [UNIT, UNIT]) def test_addRegularVestingSchedule(self): alice, bob, carol, tim, pim = fresh_accounts(5) - self.h_endow(MASTER, self.escrow.address, 1000 * UNIT) + self.havven.endow(MASTER, self.escrow.contract.address, 1000 * UNIT) time = block_time() - self.addRegularVestingSchedule(MASTER, alice, time + to_seconds(weeks=52), 100 * UNIT, 4) - self.assertEqual(self.numVestingEntries(alice), 4) + self.escrow.addRegularVestingSchedule(MASTER, alice, time + to_seconds(weeks=52), 100 * UNIT, 4) + self.assertEqual(self.escrow.numVestingEntries(alice), 4) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 0) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 0) fast_forward(to_seconds(weeks=13) + 10) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 25 * UNIT) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 25 * UNIT) fast_forward(to_seconds(weeks=13) + 10) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 50 * UNIT) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 50 * UNIT) fast_forward(to_seconds(weeks=13) + 10) - def test_addRegularVestingSchedule(self): + def test_addRegularVestingSchedule2(self): alice, bob, carol, tim, pim = fresh_accounts(5) - self.h_endow(MASTER, self.escrow.address, 1000 * UNIT) + self.havven.endow(MASTER, self.escrow.contract.address, 1000 * UNIT) time = block_time() - self.addRegularVestingSchedule(MASTER, alice, time + to_seconds(weeks=52), 100 * UNIT, 4) - self.assertEqual(self.numVestingEntries(alice), 4) + self.escrow.addRegularVestingSchedule(MASTER, alice, time + to_seconds(weeks=52), 100 * UNIT, 4) + self.assertEqual(self.escrow.numVestingEntries(alice), 4) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 0) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 0) fast_forward(to_seconds(weeks=13) + 10) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 25 * UNIT) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 25 * UNIT) fast_forward(to_seconds(weeks=13) + 10) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 50 * UNIT) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 50 * UNIT) fast_forward(to_seconds(weeks=13) + 10) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 75 * UNIT) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 75 * UNIT) fast_forward(to_seconds(weeks=13) + 10) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 100 * UNIT) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 100 * UNIT) fast_forward(to_seconds(weeks=13) + 10) - self.vest(alice) - self.assertEqual(self.h_balanceOf(alice), 100 * UNIT) + self.escrow.vest(alice) + self.assertEqual(self.havven.balanceOf(alice), 100 * UNIT) time = block_time() + 10000000 bob_periods = 7 - self.addRegularVestingSchedule(MASTER, bob, time, UNIT, 7) + self.escrow.addRegularVestingSchedule(MASTER, bob, time, UNIT, 7) - q = sum(self.getVestingQuantity(bob, i) for i in range(bob_periods)) + q = sum(self.escrow.getVestingQuantity(bob, i) for i in range(bob_periods)) self.assertEqual(q, UNIT) - self.assertEqual(self.getVestingTime(bob, bob_periods - 1), time, UNIT) + self.assertEqual(self.escrow.getVestingTime(bob, bob_periods - 1), time, UNIT) - self.assertReverts(self.addRegularVestingSchedule, MASTER, carol, block_time() - 1, UNIT, 5) - self.assertReverts(self.addRegularVestingSchedule, MASTER, carol, 0, UNIT, 5) - self.assertReverts(self.addRegularVestingSchedule, MASTER, carol, block_time() + 100000, UNIT, 0) + self.assertReverts(self.escrow.addRegularVestingSchedule, MASTER, carol, block_time() - 1, UNIT, 5) + self.assertReverts(self.escrow.addRegularVestingSchedule, MASTER, carol, 0, UNIT, 5) + self.assertReverts(self.escrow.addRegularVestingSchedule, MASTER, carol, block_time() + 100000, UNIT, 0) time = block_time() + 10000 - self.appendVestingEntry(MASTER, tim, time, UNIT) - self.addRegularVestingSchedule(MASTER, pim, time, UNIT, 1) + self.escrow.appendVestingEntry(MASTER, tim, time, UNIT) + self.escrow.addRegularVestingSchedule(MASTER, pim, time, UNIT, 1) - self.assertEqual(self.numVestingEntries(tim), self.numVestingEntries(pim)) - self.assertEqual(self.getVestingTime(tim, 0), self.getVestingTime(pim, 0)) - self.assertEqual(self.getVestingQuantity(tim, 0), self.getVestingQuantity(pim, 0)) + self.assertEqual(self.escrow.numVestingEntries(tim), self.escrow.numVestingEntries(pim)) + self.assertEqual(self.escrow.getVestingTime(tim, 0), self.escrow.getVestingTime(pim, 0)) + self.assertEqual(self.escrow.getVestingQuantity(tim, 0), self.escrow.getVestingQuantity(pim, 0)) diff --git a/tests/test_Issuance.py b/tests/test_Issuance.py new file mode 100644 index 0000000000..7ef6bf6ad0 --- /dev/null +++ b/tests/test_Issuance.py @@ -0,0 +1,177 @@ +from utils.deployutils import ( + W3, MASTER, UNIT, + attempt, attempt_deploy, compile_contracts, + mine_txs, mine_tx, + fast_forward, fresh_account, + take_snapshot, restore_snapshot +) +from utils.testutils import HavvenTestCase, ZERO_ADDRESS, block_time +from tests.contract_interfaces.havven_interface import PublicHavvenInterface +from tests.contract_interfaces.nomin_interface import PublicNominInterface +from tests.contract_interfaces.court_interface import FakeCourtInterface +from tests.contract_interfaces.havven_escrow_interface import PublicHavvenEscrowInterface + + +def setUpModule(): + print("Testing Issuance...") + print("===================") + print() + + +def tearDownModule(): + print() + print() + + +class TestIssuance(HavvenTestCase): + def setUp(self): + self.snapshot = take_snapshot() + + def tearDown(self): + restore_snapshot(self.snapshot) + + @classmethod + def deployContracts(cls): + sources = ["contracts/Havven.sol", "tests/contracts/PublicHavven.sol", "tests/contracts/PublicNomin.sol", + "tests/contracts/FakeCourt.sol", "tests/contracts/PublicHavvenEscrow.sol"] + print("Deployment initiated.\n") + + compiled, cls.event_maps = cls.compileAndMapEvents(sources) + cls.event_map = cls.event_maps['Havven'] + + # Deploy contracts + + havven_proxy, _ = attempt_deploy(compiled, 'Proxy', MASTER, [MASTER]) + nomin_proxy, _ = attempt_deploy(compiled, 'Proxy', MASTER, [MASTER]) + proxied_havven = W3.eth.contract(address=havven_proxy.address, abi=compiled['PublicHavven']['abi']) + proxied_nomin = W3.eth.contract(address=nomin_proxy.address, abi=compiled['PublicNomin']['abi']) + + tokenstate, _ = attempt_deploy(compiled, 'TokenState', + MASTER, [MASTER, MASTER]) + havven_contract, hvn_txr = attempt_deploy(compiled, 'PublicHavven', MASTER, + [havven_proxy.address, tokenstate.address, MASTER, MASTER, UNIT//2]) + + nomin_contract, nom_txr = attempt_deploy(compiled, 'PublicNomin', + MASTER, + [nomin_proxy.address, havven_contract.address, MASTER]) + court_contract, court_txr = attempt_deploy(compiled, 'FakeCourt', + MASTER, + [havven_contract.address, nomin_contract.address, + MASTER]) + escrow_contract, escrow_txr = attempt_deploy(compiled, 'PublicHavvenEscrow', + MASTER, + [MASTER, havven_contract.address]) + + # Hook up each of those contracts to each other + mine_txs([tokenstate.functions.setBalanceOf(havven_contract.address, 100000000 * UNIT).transact({'from': MASTER}), + tokenstate.functions.setAssociatedContract(havven_contract.address).transact({'from': MASTER}), + havven_proxy.functions.setTarget(havven_contract.address).transact({'from': MASTER}), + nomin_proxy.functions.setTarget(nomin_contract.address).transact({'from': MASTER}), + havven_contract.functions.setNomin(nomin_contract.address).transact({'from': MASTER}), + nomin_contract.functions.setCourt(court_contract.address).transact({'from': MASTER}), + nomin_contract.functions.setHavven(havven_contract.address).transact({'from': MASTER}), + havven_contract.functions.setEscrow(escrow_contract.address).transact({'from': MASTER})]) + + print("\nDeployment complete.\n") + return havven_proxy, proxied_havven, nomin_proxy, proxied_nomin, havven_contract, nomin_contract, court_contract, escrow_contract + + @classmethod + def setUpClass(cls): + cls.havven_proxy, cls.proxied_havven, cls.nomin_proxy, cls.proxied_nomin, cls.havven_contract, cls.nomin_contract, cls.fake_court_contract, cls.escrow_contract = cls.deployContracts() + + cls.havven = PublicHavvenInterface(cls.proxied_havven, "Havven") + cls.nomin = PublicNominInterface(cls.proxied_nomin, "Nomin") + cls.escrow = PublicHavvenEscrowInterface(cls.escrow_contract, "HavvenEscrow") + + fast_forward(weeks=102) + + cls.fake_court = FakeCourtInterface(cls.fake_court_contract, "FakeCourt") + cls.fake_court.setNomin(MASTER, cls.nomin_contract.address) + + def havven_updatePrice(self, sender, price, time): + mine_tx(self.havven_contract.functions.updatePrice(price, time).transact({'from': sender}), 'updatePrice', 'Havven') + + def test_issue(self): + self.havven.endow(MASTER, MASTER, 1000 * UNIT) + self.havven.setIssuer(MASTER, MASTER, True) + + self.assertEqual(self.havven.balanceOf(MASTER), 1000 * UNIT) + + self.havven_updatePrice(self.havven.oracle(), UNIT, self.havven.currentTime() + 1) + self.havven.issueNomins(MASTER, 5 * UNIT) + + self.assertEqual(self.nomin_contract.functions.balanceOf(MASTER).call(), 5 * UNIT) + + def test_issue_against_escrowed(self): + alice = fresh_account() + self.havven.endow(MASTER, self.escrow.contract.address, self.havven.totalSupply()) + self.escrow.appendVestingEntry(MASTER, alice, block_time() + 100000, self.havven.totalSupply() // 2) + + self.havven.setIssuer(MASTER, alice, True) + self.havven_updatePrice(self.havven.oracle(), UNIT, self.havven.currentTime() + 1) + self.havven.issueNomins(alice, 100 * UNIT) + + self.assertEqual(self.havven.balanceOf(alice), 0) + self.assertEqual(self.nomin.balanceOf(alice), 100 * UNIT) + self.assertClose(self.havven.availableHavvens(alice) + 100 * UNIT / (self.havven.issuanceRatio() / UNIT), self.havven.totalSupply() // 2) + + def test_issuance_price_shift(self): + alice = fresh_account() + self.havven.endow(MASTER, alice, 1000 * UNIT) + + self.havven.setIssuer(MASTER, alice, True) + self.havven_updatePrice(self.havven.oracle(), UNIT, self.havven.currentTime() + 1) + self.havven.issueNomins(alice, 10 * UNIT) + self.assertEqual(self.havven.availableHavvens(alice), 800 * UNIT) + fast_forward(2) + self.havven_updatePrice(self.havven.oracle(), 100 * UNIT, self.havven.currentTime() + 1) + self.assertEqual(self.havven.availableHavvens(alice), 998 * UNIT) + fast_forward(2) + self.havven_updatePrice(self.havven.oracle(), int(0.01 * UNIT), self.havven.currentTime() + 1) + self.assertEqual(self.havven.availableHavvens(alice), 0) + + self.assertReverts(self.havven.transfer, alice, MASTER, 1) + + fast_forward(2) + self.havven_updatePrice(self.havven.oracle(), 1 * UNIT, self.havven.currentTime() + 1) + self.havven.transfer(alice, MASTER, 800 * UNIT) + self.assertReverts(self.havven.transfer, alice, MASTER, 200 * UNIT) + self.havven.burnNomins(alice, 10 * UNIT) + self.havven.transfer(alice, MASTER, 200 * UNIT) + self.assertEqual(self.nomin.balanceOf(alice), 0) + self.assertEqual(self.nomin.balanceOf(MASTER), 0) + self.assertEqual(self.havven.balanceOf(MASTER), 1000 * UNIT) + self.assertEqual(self.havven.balanceOf(alice), 0) + self.assertEqual(self.havven.nominsIssued(alice), 0) + + def test_issue_revert_conditions(self): + alice = fresh_account() + self.havven.endow(MASTER, alice, 1000 * UNIT) + self.havven_updatePrice(self.havven.oracle(), UNIT, self.havven.currentTime() + 1) + self.assertReverts(self.havven.issueNomins, alice, 10 * UNIT) # reverts, as not an issuer + self.havven.setIssuer(MASTER, alice, True) + fast_forward(days=1) # fast forward to make price stale + self.assertReverts(self.havven.issueNomins, alice, 10 * UNIT) # reverts, as price is stale + self.havven_updatePrice(self.havven.oracle(), UNIT, self.havven.currentTime() + 1) + self.assertReverts(self.havven.issueNomins, alice, 1000 * UNIT) # reverts, as too many nomins being issued + self.havven.setIssuanceRatio(MASTER, 0) + self.assertReverts(self.havven.issueNomins, alice, 10 * UNIT) # reverts, as CMAX too low (0) + self.havven.setIssuanceRatio(MASTER, int(0.05 * UNIT)) + self.havven.issueNomins(alice, self.havven.maxIssuableNomins(alice)) + self.assertEqual(self.havven.nominsIssued(alice), 50 * UNIT) + self.assertReverts(self.havven.issueNomins, alice, self.havven.maxIssuableNomins(alice)) + self.assertEqual(self.havven.remainingIssuableNomins(alice), 0) + self.havven.issueNomins(alice, self.havven.remainingIssuableNomins(alice)) + + def test_burn(self): + alice = fresh_account() + self.havven.endow(MASTER, alice, 1000 * UNIT) + self.havven.setIssuer(MASTER, alice, True) + self.havven_updatePrice(self.havven.oracle(), UNIT, self.havven.currentTime() + 1) + self.havven.issueNomins(alice, 50 * UNIT) + for i in range(50): + self.havven.burnNomins(alice, 1 * UNIT) + self.assertEqual(self.havven.nominsIssued(alice), 0) + self.assertEqual(self.nomin.balanceOf(alice), 0) + + diff --git a/tests/test_IssuanceController.py b/tests/test_IssuanceController.py new file mode 100644 index 0000000000..9c28fe1c16 --- /dev/null +++ b/tests/test_IssuanceController.py @@ -0,0 +1,575 @@ +from utils.deployutils import ( + W3, UNIT, MASTER, DUMMY, + fresh_account, fresh_accounts, + mine_tx, attempt_deploy, mine_txs, + take_snapshot, restore_snapshot, fast_forward +) +from utils.testutils import ( + HavvenTestCase, ZERO_ADDRESS, block_time, get_eth_balance +) +from tests.contract_interfaces.nomin_interface import PublicNominInterface +from tests.contract_interfaces.havven_interface import PublicHavvenInterface +from tests.contract_interfaces.issuanceController_interface import IssuanceControllerInterface + +def setUpModule(): + print("Testing IssuanceController...") + print("================") + print() + + +def tearDownModule(): + print() + print() + + +class TestIssuanceController(HavvenTestCase): + def setUp(self): + self.snapshot = take_snapshot() + + def tearDown(self): + restore_snapshot(self.snapshot) + + @classmethod + def deployContracts(cls): + sources = [ + "tests/contracts/PublicHavven.sol", + "tests/contracts/PublicNomin.sol", + "contracts/IssuanceController.sol" + ] + + compiled, cls.event_maps = cls.compileAndMapEvents(sources) + nomin_abi = compiled['PublicNomin']['abi'] + havven_abi = compiled['PublicHavven']['abi'] + issuance_controller_abi = compiled['IssuanceController']['abi'] + + havven_proxy, _ = attempt_deploy(compiled, 'Proxy', MASTER, [MASTER]) + nomin_proxy, _ = attempt_deploy(compiled, 'Proxy', MASTER, [MASTER]) + proxied_havven = W3.eth.contract(address=havven_proxy.address, abi=havven_abi) + proxied_nomin = W3.eth.contract(address=nomin_proxy.address, abi=nomin_abi) + + tokenstate, _ = attempt_deploy(compiled, 'TokenState', + MASTER, [MASTER, MASTER]) + havven_contract, hvn_txr = attempt_deploy( + compiled, 'PublicHavven', MASTER, [havven_proxy.address, tokenstate.address, MASTER, MASTER, UNIT//2] + ) + nomin_contract, nom_txr = attempt_deploy( + compiled, 'PublicNomin', MASTER, [nomin_proxy.address, havven_contract.address, MASTER] + ) + + mine_txs([ + tokenstate.functions.setBalanceOf(havven_contract.address, 100000000 * UNIT).transact({'from': MASTER}), + tokenstate.functions.setAssociatedContract(havven_contract.address).transact({'from': MASTER}), + havven_proxy.functions.setTarget(havven_contract.address).transact({'from': MASTER}), + nomin_proxy.functions.setTarget(nomin_contract.address).transact({'from': MASTER}), + havven_contract.functions.setNomin(nomin_contract.address).transact({'from': MASTER}), + ]) + + issuanceControllerContract, _ = attempt_deploy( + compiled, 'IssuanceController', MASTER, + [ + cls.contractOwner, + cls.fundsWallet, + havven_contract.address, + nomin_contract.address, + cls.oracleAddress, + cls.usdToEthPrice, + cls.usdToHavPrice + ] + ) + + return havven_proxy, proxied_havven, nomin_proxy, proxied_nomin, havven_contract, nomin_contract, nomin_abi, issuanceControllerContract + + @classmethod + def setUpClass(cls): + addresses = fresh_accounts(6) + cls.participantAddresses = addresses[2:] + cls.contractOwner = MASTER + cls.oracleAddress = addresses[0] + cls.fundsWallet = addresses[1] + cls.usdToEthPrice = 500 * (10 ** 18) + cls.usdToHavPrice = int(0.65 * (10 ** 18)) + cls.priceStalePeriod = 3 * 60 * 60 + cls.havven_proxy, cls.proxied_havven, cls.nomin_proxy, cls.proxied_nomin, cls.havven_contract, cls.nomin_contract, cls.nomin_abi, cls.issuanceControllerContract = cls.deployContracts() + cls.issuanceController = IssuanceControllerInterface(cls.issuanceControllerContract, "IssuanceController") + cls.havven = PublicHavvenInterface(cls.havven_contract, "Havven") + cls.nomin = PublicNominInterface(cls.nomin_contract, "Nomin") + cls.issuanceControllerEventDict = cls.event_maps['IssuanceController'] + fast_forward(1) # Give the contract constructor a second between its execution and execution of the other functions. + + def test_constructor(self): + self.assertEqual(self.issuanceController.owner(), self.contractOwner) + self.assertEqual(self.issuanceController.fundsWallet(), self.fundsWallet) + self.assertEqual(self.issuanceController.oracle(), self.oracleAddress) + self.assertEqual(self.issuanceController.usdToEthPrice(), self.usdToEthPrice) + self.assertEqual(self.issuanceController.usdToHavPrice(), self.usdToHavPrice) + self.assertEqual(self.issuanceController.havven(), self.havven_contract.address) + self.assertEqual(self.issuanceController.nomin(), self.nomin_contract.address) + + # Oracle address setter and getter tests + + def test_getOracleAddress(self): + oracleAddress = self.issuanceController.oracle() + self.assertEqual(oracleAddress, self.oracleAddress) + + def test_setOracleAddress(self): + newOracleAddress = self.participantAddresses[0] + self.issuanceController.setOracle(self.contractOwner, newOracleAddress) + oracleAddressToCheck = self.issuanceController.oracle() + self.assertEqual(newOracleAddress, oracleAddressToCheck) + + def test_cannotSetOracleIfUnauthorised(self): + newOracleAddress, notOwner = self.participantAddresses[0:2] + originalOracleAddress = self.issuanceController.oracle() + self.assertReverts(self.issuanceController.setOracle, notOwner, newOracleAddress) + oracleAddressToCheck = self.issuanceController.oracle() + self.assertEqual(oracleAddressToCheck, originalOracleAddress) + + def test_OracleEvent(self): + newOracleAddress = self.participantAddresses[0] + txr = self.issuanceController.setOracle(self.contractOwner, newOracleAddress) + self.assertEventEquals( + self.issuanceControllerEventDict, txr.logs[0], 'OracleUpdated', + fields={'newOracle': newOracleAddress}, + location=self.issuanceControllerContract.address + ) + + # Funds Wallet setter and getter tests + + def test_getFundsWalletAddress(self): + fundsWalletAddress = self.issuanceController.fundsWallet() + self.assertEqual(fundsWalletAddress, self.fundsWallet) + + def test_setFundsWalletAddress(self): + newFundsWalletAddress = self.participantAddresses[0] + self.issuanceController.setFundsWallet(self.contractOwner, newFundsWalletAddress) + fundsWalletAddressToCheck = self.issuanceController.fundsWallet() + self.assertEqual(newFundsWalletAddress, fundsWalletAddressToCheck) + + def test_cannotSetFundsWalletUnauthorised(self): + newFundsWalletAddress, notOwner = self.participantAddresses[0:2] + originalFundsWalletAddress = self.issuanceController.fundsWallet() + self.assertReverts(self.issuanceController.setFundsWallet, notOwner, newFundsWalletAddress) + fundsWalletAddressToCheck = self.issuanceController.fundsWallet() + self.assertEqual(fundsWalletAddressToCheck, originalFundsWalletAddress) + + def test_FundsWalletEvent(self): + newFundsWalletAddress = self.participantAddresses[0] + txr = self.issuanceController.setFundsWallet(self.contractOwner, newFundsWalletAddress) + self.assertEventEquals( + self.issuanceControllerEventDict, txr.logs[0], 'FundsWalletUpdated', + fields={'newFundsWallet': newFundsWalletAddress}, + location=self.issuanceControllerContract.address + ) + + # Nomin contract address setter and getter tests + + def test_getNominAddress(self): + nominAddress = self.issuanceController.nomin() + self.assertEqual(nominAddress, self.nomin_contract.address) + + def test_setNominAddress(self): + newNominAddress = self.participantAddresses[0] + self.issuanceController.setNomin(self.contractOwner, newNominAddress) + nominAddressToCheck = self.issuanceController.nomin() + self.assertEqual(newNominAddress, nominAddressToCheck) + + def test_cannotSetNominIfUnauthorised(self): + newNominAddress, notOwner = self.participantAddresses[0:2] + originalNominAddress = self.issuanceController.nomin() + self.assertReverts(self.issuanceController.setNomin, notOwner, newNominAddress) + nominAddressToCheck = self.issuanceController.nomin() + self.assertEqual(nominAddressToCheck, originalNominAddress) + + def test_NominUpdatedEvent(self): + newNominAddress = self.participantAddresses[0] + txr = self.issuanceController.setNomin(self.contractOwner, newNominAddress) + self.assertEventEquals( + self.issuanceControllerEventDict, txr.logs[0], 'NominUpdated', + fields={'newNominContract': newNominAddress}, + location=self.issuanceControllerContract.address + ) + + # Havven contract address setter and getter tests + + def test_getHavvenAddress(self): + havvenAddress = self.issuanceController.havven() + self.assertEqual(havvenAddress, self.havven_contract.address) + + def test_setHavvenAddress(self): + newHavvenAddress = self.participantAddresses[0] + self.issuanceController.setHavven(self.contractOwner, newHavvenAddress) + havvenAddressToCheck = self.issuanceController.havven() + self.assertEqual(newHavvenAddress, havvenAddressToCheck) + + def test_cannotSetHavvenIfUnauthorised(self): + newHavvenAddress, notOwner = self.participantAddresses[0:2] + originalHavvenAddress = self.issuanceController.havven() + self.assertReverts(self.issuanceController.setHavven, notOwner, newHavvenAddress) + havvenAddressToCheck = self.issuanceController.havven() + self.assertEqual(havvenAddressToCheck, originalHavvenAddress) + + def test_HavvenUpdatedEvent(self): + newHavvenAddress = self.participantAddresses[0] + txr = self.issuanceController.setHavven(self.contractOwner, newHavvenAddress) + self.assertEventEquals( + self.issuanceControllerEventDict, txr.logs[0], 'HavvenUpdated', + fields={'newHavvenContract': newHavvenAddress}, + location=self.issuanceControllerContract.address + ) + + # Price stale period setter and getter tests + + def test_getPriceStalePeriod(self): + priceStalePeriod = self.issuanceController.priceStalePeriod() + self.assertEqual(priceStalePeriod, self.priceStalePeriod) + + def test_setPriceStalePeriod(self): + newPriceStalePeriod = 400 * 60 * 60 + self.issuanceController.setPriceStalePeriod(self.contractOwner, newPriceStalePeriod) + priceStalePeriodToCheck = self.issuanceController.priceStalePeriod() + self.assertEqual(newPriceStalePeriod, priceStalePeriodToCheck) + + def test_cannotSetPriceStalePeriodIfUnauthorised(self): + notOwner = self.participantAddresses[0] + originalPriceStalePeriod = self.issuanceController.priceStalePeriod() + newPriceStalePeriod = originalPriceStalePeriod + 100 + self.assertReverts(self.issuanceController.setPriceStalePeriod, notOwner, newPriceStalePeriod) + priceStalePeriodToCheck = self.issuanceController.priceStalePeriod() + self.assertEqual(originalPriceStalePeriod, priceStalePeriodToCheck) + + # Update prices (aka exchange rate) setter and getter tests + + def test_updatePrices(self): + newEthPrice = self.usdToEthPrice + 100 + newHavPrice = self.usdToHavPrice + 100 + timeSent = block_time() + self.issuanceController.updatePrices(self.oracleAddress, newEthPrice, newHavPrice, timeSent) + self.assertEqual(self.issuanceController.usdToEthPrice(), newEthPrice) + self.assertEqual(self.issuanceController.usdToHavPrice(), newHavPrice) + self.assertEqual(self.issuanceController.lastPriceUpdateTime(), timeSent) + + def test_updatePricesTooEarly(self): + timeSent = block_time() - 120 + self.assertReverts(self.issuanceController.updatePrices, self.oracleAddress, self.usdToEthPrice, self.usdToHavPrice, timeSent) + + def test_updatePricesTooLate(self): + ORACLE_FUTURE_LIMIT = 10 * 60 + timeSent = block_time() + ORACLE_FUTURE_LIMIT + 60 + self.assertReverts(self.issuanceController.updatePrices, self.oracleAddress, self.usdToEthPrice, self.usdToHavPrice, timeSent) + + def test_cannotUpdatePricesIfOwner(self): + self.assertReverts(self.issuanceController.updatePrices, self.contractOwner, self.usdToEthPrice, self.usdToHavPrice, block_time()) + + def test_cannotUpdatePricesIfUnauthorised(self): + randomUser = self.participantAddresses[0] + self.assertReverts(self.issuanceController.updatePrices, randomUser, self.usdToEthPrice, self.usdToHavPrice, block_time()) + + def test_updatePricesEvents(self): + timeSent = block_time() + txr = self.issuanceController.updatePrices(self.oracleAddress, self.usdToEthPrice, self.usdToHavPrice, timeSent) + self.assertEventEquals( + self.issuanceControllerEventDict, txr.logs[0], 'PricesUpdated', + fields={'newEthPrice': self.usdToEthPrice, 'newHavvenPrice': self.usdToHavPrice, 'timeSent': timeSent}, + location=self.issuanceControllerContract.address + ) + + # Exchange ETH for nUSD tests + + def test_cannotExchangeEtherForNominsIfPriceStale(self): + amount = 10 * UNIT + nominsBalance = (amount * self.usdToEthPrice) // UNIT + base = self.nomin.amountReceived(nominsBalance) + self.issuanceController.setPriceStalePeriod(self.contractOwner, 1) + timeSent = block_time() + self.issuanceController.updatePrices(self.oracleAddress, self.usdToEthPrice, self.usdToHavPrice, timeSent) + exchanger = self.participantAddresses[0] + startingFundsWalletEthBalance = get_eth_balance(self.fundsWallet) + fast_forward(2) # Wait so the lastPriceUpdateTime is different to now + + # Set up the contract so it contains some nomins for folks to convert Ether for + self.nomin.giveNomins(MASTER, self.issuanceControllerContract.address, nominsBalance) + self.assertEqual(self.nomin.balanceOf(self.issuanceControllerContract.address), nominsBalance) + + # Attmpt transfer + self.assertReverts(self.issuanceController.exchangeEtherForNomins, exchanger, amount) + endingFundsWalletEthBalance = get_eth_balance(self.fundsWallet) + self.assertEqual(self.nomin.balanceOf(self.issuanceControllerContract.address), nominsBalance) + self.assertEqual(self.nomin.balanceOf(exchanger), 0) + self.assertEqual(self.nomin.feePool(), 0) + self.assertEqual(startingFundsWalletEthBalance, endingFundsWalletEthBalance) + + def test_cannotExchangeEtherForNominsIfPaused(self): + amount = 10 * UNIT + nominsBalance = (amount * self.usdToEthPrice) // UNIT + base = self.nomin.amountReceived(nominsBalance) + self.issuanceController.setPaused(self.contractOwner, True) + exchanger = self.participantAddresses[0] + startingFundsWalletEthBalance = get_eth_balance(self.fundsWallet) + + # Set up the contract so it contains some nomins for folks to convert Ether for + self.nomin.giveNomins(MASTER, self.issuanceControllerContract.address, nominsBalance) + self.assertEqual(self.nomin.balanceOf(self.issuanceControllerContract.address), nominsBalance) + + # Attmpt transfer + self.assertReverts(self.issuanceController.exchangeEtherForNomins, exchanger, amount) + endingFundsWalletEthBalance = get_eth_balance(self.fundsWallet) + self.assertEqual(self.nomin.balanceOf(self.issuanceControllerContract.address), nominsBalance) + self.assertEqual(self.nomin.balanceOf(exchanger), 0) + self.assertEqual(self.nomin.feePool(), 0) + self.assertEqual(startingFundsWalletEthBalance, endingFundsWalletEthBalance) + + def test_exchangeForSomeNomins(self): + amount = 3 * UNIT + ethToSend = 1 * UNIT + nominsBalance = (amount * self.usdToEthPrice) // UNIT + nominsToSend = (ethToSend * self.usdToEthPrice) // UNIT + baseToSend = self.nomin.amountReceived(nominsToSend) + feesToPayInNomins = nominsToSend - baseToSend + exchanger = self.participantAddresses[0] + startingFundsWalletEthBalance = get_eth_balance(self.fundsWallet) + + # Set up the contract so it contains some nomins for folks to convert Ether for + self.nomin.giveNomins(MASTER, self.issuanceControllerContract.address, nominsBalance) + self.assertEqual(self.nomin.balanceOf(self.issuanceControllerContract.address), nominsBalance) + + # Transfer the amount to the receiver + txr = self.issuanceController.exchangeEtherForNomins(exchanger, ethToSend) + + # Ensure the result of the transfer is correct. + endingFundsWalletEthBalance = get_eth_balance(self.fundsWallet) + self.assertEqual(self.nomin.balanceOf(self.issuanceControllerContract.address), nominsBalance - nominsToSend) + self.assertEqual(self.nomin.balanceOf(exchanger), baseToSend) + self.assertEqual(self.nomin.feePool(), feesToPayInNomins) + self.assertEqual(startingFundsWalletEthBalance + ethToSend, endingFundsWalletEthBalance) + + def test_exchangeForAllNomins(self): + amount = 10 * UNIT + nominsBalance = (amount * self.usdToEthPrice) // UNIT + base = self.nomin.amountReceived(nominsBalance) + feesToPayInNomins = nominsBalance - base + exchanger = self.participantAddresses[0] + startingFundsWalletEthBalance = get_eth_balance(self.fundsWallet) + + # Set up the contract so it contains some nomins for folks to convert Ether for + self.nomin.giveNomins(MASTER, self.issuanceControllerContract.address, nominsBalance) + self.assertEqual(self.nomin.balanceOf(self.issuanceControllerContract.address), nominsBalance) + + # Transfer the amount to the receiver + txr = self.issuanceController.exchangeEtherForNomins(exchanger, amount) + + # Ensure the result of the transfer is correct. + endingFundsWalletEthBalance = get_eth_balance(self.fundsWallet) + self.assertEqual(self.nomin.balanceOf(self.issuanceControllerContract.address), 0) + self.assertEqual(self.nomin.balanceOf(exchanger), base) + self.assertEqual(self.nomin.feePool(), feesToPayInNomins) + self.assertEqual(startingFundsWalletEthBalance + amount, endingFundsWalletEthBalance) + + def test_exchangeForZeroEth(self): + amount = 3 * UNIT + ethToSend = 0 * UNIT + nominsBalance = (amount * self.usdToEthPrice) // UNIT + nominsToSend = (ethToSend * self.usdToEthPrice) // UNIT + baseToSend = 0 + feesToPayInNomins = 0 + exchanger = self.participantAddresses[0] + startingFundsWalletEthBalance = get_eth_balance(self.fundsWallet) + + # Set up the contract so it contains some nomins for folks to convert Ether for + self.nomin.giveNomins(MASTER, self.issuanceControllerContract.address, nominsBalance) + self.assertEqual(self.nomin.balanceOf(self.issuanceControllerContract.address), nominsBalance) + + # Transfer the amount to the receiver + txr = self.issuanceController.exchangeEtherForNomins(exchanger, ethToSend) + + # Ensure the result of the transfer is correct. + endingFundsWalletEthBalance = get_eth_balance(self.fundsWallet) + self.assertEqual(self.nomin.balanceOf(self.issuanceControllerContract.address), nominsBalance) + self.assertEqual(self.nomin.balanceOf(exchanger), 0) + self.assertEqual(self.nomin.feePool(), 0) + self.assertEqual(startingFundsWalletEthBalance, endingFundsWalletEthBalance) + + def test_exchangeFailsNotEnoughNomins(self): + amount = 10 * UNIT + nominsBalance = (int(amount / 2) * self.usdToEthPrice) // UNIT + base = self.nomin.amountReceived(nominsBalance) + feesToPayInNomins = nominsBalance - base + exchanger = self.participantAddresses[0] + startingFundsWalletEthBalance = get_eth_balance(self.fundsWallet) + + # Set up the contract so it contains some nomins for folks to convert Ether for + self.nomin.giveNomins(MASTER, self.issuanceControllerContract.address, nominsBalance) + self.assertEqual(self.nomin.balanceOf(self.issuanceControllerContract.address), nominsBalance) + + # Ensure the transfer fails due to there not being enough nomins in the contract + self.assertReverts(self.issuanceController.exchangeEtherForNomins, exchanger, amount) + + # Ensure the result of the transfer is correct. + endingFundsWalletEthBalance = get_eth_balance(self.fundsWallet) + self.assertEqual(self.nomin.balanceOf(self.issuanceControllerContract.address), nominsBalance) + self.assertEqual(self.nomin.balanceOf(exchanger), 0) + self.assertEqual(self.nomin.feePool(), 0) + self.assertEqual(startingFundsWalletEthBalance, endingFundsWalletEthBalance) + + # Exchange nUSD for HAV tests + + def test_cannotExchangeNominsForHavvensIfPriceStale(self): + exchanger = self.participantAddresses[0] + nominsToTransfer = 20 * UNIT + self.issuanceController.setPriceStalePeriod(self.contractOwner, 1) + + # Set up the contract so it contains some nomins and havvens + self.nomin.giveNomins(MASTER, exchanger, nominsToTransfer) + self.assertEqual(self.nomin.balanceOf(exchanger), nominsToTransfer) + self.havven.endow(MASTER, self.issuanceControllerContract.address, 1000 * UNIT) + self.assertEqual(self.havven.balanceOf(self.issuanceControllerContract.address), 1000 * UNIT) + + timeSent = block_time() + self.issuanceController.updatePrices(self.oracleAddress, self.usdToEthPrice, self.usdToHavPrice, timeSent) + fast_forward(2) + + # Attempt transfer + self.nomin.approve(exchanger, self.issuanceControllerContract.address, nominsToTransfer) + self.assertReverts(self.issuanceController.exchangeNominsForHavvens, exchanger, nominsToTransfer) + self.assertEqual(self.nomin.balanceOf(exchanger), nominsToTransfer) + self.assertEqual(self.havven.balanceOf(self.issuanceControllerContract.address), 1000 * UNIT) + + def test_cannotExchangeNominsForHavvensIfPaused(self): + exchanger = self.participantAddresses[0] + nominsToTransfer = 20 * UNIT + + # Set up the contract so it contains some nomins and havvens + self.nomin.giveNomins(MASTER, exchanger, nominsToTransfer) + self.assertEqual(self.nomin.balanceOf(exchanger), nominsToTransfer) + self.havven.endow(MASTER, self.issuanceControllerContract.address, 1000 * UNIT) + self.assertEqual(self.havven.balanceOf(self.issuanceControllerContract.address), 1000 * UNIT) + + # Pause the contract + self.issuanceController.setPaused(self.contractOwner, True) + + # Attempt transfer + self.nomin.approve(exchanger, self.issuanceControllerContract.address, nominsToTransfer) + self.assertReverts(self.issuanceController.exchangeNominsForHavvens, exchanger, nominsToTransfer) + self.assertEqual(self.nomin.balanceOf(exchanger), nominsToTransfer) + self.assertEqual(self.havven.balanceOf(self.issuanceControllerContract.address), 1000 * UNIT) + + def test_exchangeForSomeHavvens(self): + exchanger = self.participantAddresses[0] + nominsToSend = 5 * UNIT + nominsReceived = self.nomin.amountReceived(nominsToSend) + havBalance = nominsReceived * UNIT // self.usdToHavPrice + + # Set up the contract so it contains some nomins and havvens + self.nomin.giveNomins(MASTER, exchanger, nominsToSend) + self.assertEqual(self.nomin.balanceOf(exchanger), nominsToSend) + self.havven.endow(MASTER, self.issuanceControllerContract.address, 1000 * UNIT) + self.assertEqual(self.havven.balanceOf(self.issuanceControllerContract.address), 1000 * UNIT) + + # Attempt transfer + self.nomin.approve(exchanger, self.issuanceControllerContract.address, nominsToSend) + self.issuanceController.exchangeNominsForHavvens(exchanger, nominsToSend) + + # Ensure the result of the transfer is correct. + self.assertEqual(self.nomin.balanceOf(exchanger), 0) + self.assertEqual(self.nomin.balanceOf(self.issuanceControllerContract.address), nominsReceived) + self.assertEqual(self.nomin.feePool(), nominsToSend - nominsReceived) + self.assertEqual(self.havven.balanceOf(exchanger), havBalance) + self.assertEqual(self.havven.balanceOf(self.issuanceControllerContract.address), 1000 * UNIT - havBalance) + + def test_exchangeForAllHavvens(self): + nominsToSend = 7 * UNIT + nominsAfterFees = self.nomin.amountReceived(nominsToSend) + havvensBalance = 1000 * UNIT + feesToPayInNomins = nominsToSend - nominsAfterFees + exchanger = self.participantAddresses[0] + havvensReceived = self.issuanceController.havvensReceivedForNomins(nominsToSend) + + # Set up exchanger with some nomins + self.nomin.giveNomins(MASTER, exchanger, nominsToSend) + self.assertEqual(self.nomin.balanceOf(exchanger), nominsToSend) + + # Set up the contract so it contains some havvens for folks to convert Nomins for + self.havven.endow(MASTER, self.issuanceControllerContract.address, havvensBalance) + self.assertEqual(self.havven.balanceOf(self.issuanceControllerContract.address), havvensBalance) + + # Allow the contract to work on the exchanger's behalf + self.nomin.approve(exchanger, self.issuanceControllerContract.address, nominsToSend) + + # Transfer the amount to the receiver + self.issuanceController.exchangeNominsForHavvens(exchanger, nominsToSend) + + self.assertEqual(self.havven.balanceOf(self.issuanceControllerContract.address), havvensBalance - havvensReceived) + self.assertEqual(self.havven.balanceOf(exchanger), havvensReceived) + self.assertEqual(self.nomin.balanceOf(exchanger), 0) + + # Exchange rate tests + + def test_havvenExchangeRate(self): + havvenPrice = int(0.5 * UNIT) # 50 cent havvens + self.issuanceController.updatePrices(self.oracleAddress, 0, havvenPrice, block_time()) + + nominsToSend = 10 * UNIT + nominsAfterFees = self.nomin.amountReceived(nominsToSend) + havvensReceived = self.issuanceController.havvensReceivedForNomins(nominsToSend) + + self.assertEqual(havvensReceived, nominsAfterFees * UNIT // havvenPrice) + + def test_nominExchangeRate(self): + ethPrice = 500 * UNIT # $500 ETH + self.issuanceController.updatePrices(self.oracleAddress, ethPrice, 0, block_time()) + + ethToSend = UNIT + nominsSent = ethToSend * ethPrice // UNIT + nominsReceived = self.nomin.amountReceived(nominsSent) + + self.assertEqual(self.issuanceController.nominsReceivedForEther(ethToSend), nominsReceived) + + # Withdraw havvens tests + + def test_withdrawHavvens(self): + amount = 10 * UNIT + + # Set up the contract so it contains some havvens + self.havven.endow(MASTER, self.issuanceControllerContract.address, amount) + self.assertEqual(self.havven.balanceOf(self.issuanceControllerContract.address), amount) + + # Withdraw the Havvens and ensure we've received the endowment. + self.issuanceController.withdrawHavvens(self.contractOwner, amount) + self.assertEqual(self.havven.balanceOf(self.issuanceControllerContract.address), 0) + self.assertEqual(self.havven.balanceOf(self.contractOwner), amount) + + def test_cannotWithdrawHavvensIfUnauthorised(self): + amount = 10 * UNIT + + # Set up the contract so it contains some havvens + self.havven.endow(MASTER, self.issuanceControllerContract.address, amount) + self.assertEqual(self.havven.balanceOf(self.issuanceControllerContract.address), amount) + + notOwner = self.participantAddresses[2] + self.assertReverts(self.issuanceController.withdrawHavvens, notOwner, amount) + self.assertEqual(self.havven.balanceOf(self.issuanceControllerContract.address), amount) + + # Withdraw nomins tests + + def test_withdrawNomins(self): + amount = 10 * UNIT + amountReceived = self.nomin.amountReceived(amount) + + # Set up the contract so it contains some nomins + self.nomin.giveNomins(MASTER, self.issuanceControllerContract.address, amount) + self.assertEqual(self.nomin.balanceOf(self.issuanceControllerContract.address), amount) + + # Withdraw the nomins and ensure we've received the endowment. + self.issuanceController.withdrawNomins(self.contractOwner, amount) + self.assertEqual(self.nomin.balanceOf(self.issuanceControllerContract.address), 0) + self.assertEqual(self.nomin.balanceOf(self.contractOwner), amountReceived) + + def test_cannotWithdrawNominsIfUnauthorised(self): + amount = 10 * UNIT + + # Set up the contract so it contains some nomins + self.nomin.giveNomins(MASTER, self.issuanceControllerContract.address, amount) + self.assertEqual(self.nomin.balanceOf(self.issuanceControllerContract.address), amount) + + notOwner = self.participantAddresses[2] + self.assertReverts(self.issuanceController.withdrawNomins, notOwner, amount) + self.assertEqual(self.nomin.balanceOf(self.issuanceControllerContract.address), amount) diff --git a/tests/test_LimitedSetup.py b/tests/test_LimitedSetup.py index cde51c48d2..9c85ee11da 100644 --- a/tests/test_LimitedSetup.py +++ b/tests/test_LimitedSetup.py @@ -1,27 +1,23 @@ -import unittest - -from utils.deployutils import compile_contracts, attempt_deploy, MASTER, fast_forward -from utils.testutils import assertReverts, block_time +from utils.deployutils import MASTER, compile_contracts, attempt_deploy, fast_forward +from utils.testutils import HavvenTestCase, block_time from utils.generalutils import to_seconds -SETUP_SOURCE = "tests/contracts/OneWeekSetup.sol" - - def setUpModule(): print("Testing LimitedSetup...") + print("=======================") + print() def tearDownModule(): print() + print() -class TestLimitedSetup(unittest.TestCase): +class TestLimitedSetup(HavvenTestCase): @classmethod def setUpClass(cls): - cls.assertReverts = assertReverts - - compiled = compile_contracts([SETUP_SOURCE], + compiled = compile_contracts(["tests/contracts/OneWeekSetup.sol"], remappings=['""=contracts']) cls.setup, txr = attempt_deploy(compiled, 'OneWeekSetup', MASTER, []) cls.contractConstructionTime = block_time(txr.blockNumber) @@ -42,6 +38,3 @@ def test_setupFunc(self): def test_setupDuration(self): self.assertEqual(self.contractConstructionTime + to_seconds(weeks=1), self.setupExpiryTime()) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_Nomin.py b/tests/test_Nomin.py new file mode 100644 index 0000000000..bff2fa7e43 --- /dev/null +++ b/tests/test_Nomin.py @@ -0,0 +1,816 @@ +from utils.deployutils import ( + W3, UNIT, MASTER, DUMMY, + fresh_account, fresh_accounts, + mine_tx, attempt_deploy, mine_txs, + take_snapshot, restore_snapshot, fast_forward +) +from utils.testutils import ( + HavvenTestCase, ZERO_ADDRESS, +) +from tests.contract_interfaces.nomin_interface import PublicNominInterface +from tests.contract_interfaces.havven_interface import HavvenInterface +from tests.contract_interfaces.court_interface import FakeCourtInterface + + +def setUpModule(): + print("Testing Nomin...") + print("================") + print() + + +def tearDownModule(): + print() + print() + + +class TestNomin(HavvenTestCase): + def setUp(self): + self.snapshot = take_snapshot() + + def tearDown(self): + restore_snapshot(self.snapshot) + + @classmethod + def deployContracts(cls): + sources = ["tests/contracts/PublicNomin.sol", "tests/contracts/FakeCourt.sol", "contracts/Havven.sol"] + + compiled, cls.event_maps = cls.compileAndMapEvents(sources) + + havven_proxy, _ = attempt_deploy(compiled, 'Proxy', MASTER, [MASTER]) + nomin_proxy, _ = attempt_deploy(compiled, 'Proxy', MASTER, [MASTER]) + proxied_havven = W3.eth.contract(address=havven_proxy.address, abi=compiled['Havven']['abi']) + proxied_nomin = W3.eth.contract(address=nomin_proxy.address, abi=compiled['PublicNomin']['abi']) + + nomin_contract, _ = attempt_deploy( + compiled, 'PublicNomin', MASTER, [nomin_proxy.address, MASTER, MASTER] + ) + + havven_contract, _ = attempt_deploy( + compiled, "Havven", MASTER, [havven_proxy.address, ZERO_ADDRESS, MASTER, MASTER, UNIT//2] + ) + + fake_court, _ = attempt_deploy(compiled, 'FakeCourt', MASTER, []) + + mine_txs([ + havven_proxy.functions.setTarget(havven_contract.address).transact({'from': MASTER}), + nomin_proxy.functions.setTarget(nomin_contract.address).transact({'from': MASTER}), + havven_contract.functions.setNomin(nomin_contract.address).transact({'from': MASTER}), + nomin_contract.functions.setCourt(fake_court.address).transact({'from': MASTER}), + nomin_contract.functions.setHavven(havven_contract.address).transact({'from': MASTER}) + ]) + + return havven_proxy, proxied_havven, nomin_proxy, proxied_nomin, nomin_contract, havven_contract, fake_court + + @classmethod + def setUpClass(cls): + cls.havven_proxy, cls.proxied_havven, cls.nomin_proxy, cls.proxied_nomin, cls.nomin_contract, cls.havven_contract, cls.fake_court_contract = cls.deployContracts() + + cls.nomin_event_dict = cls.event_maps['Nomin'] + + cls.nomin = PublicNominInterface(cls.proxied_nomin, "Nomin") + cls.havven = HavvenInterface(cls.proxied_havven, "Havven") + + cls.unproxied_nomin = PublicNominInterface(cls.nomin_contract, "UnproxiedNomin") + + cls.fake_court = FakeCourtInterface(cls.fake_court_contract, "FakeCourt") + + cls.fake_court.setNomin(MASTER, cls.nomin_contract.address) + + cls.nomin.setFeeAuthority(MASTER, cls.havven_contract.address) + + cls.sd_duration = 4 * 7 * 24 * 60 * 60 + + def test_constructor(self): + # Nomin-specific members + self.assertEqual(self.nomin.owner(), MASTER) + self.assertTrue(self.nomin.frozen(self.nomin_contract.address)) + + # FeeToken members + self.assertEqual(self.nomin.name(), "Nomin USD") + self.assertEqual(self.nomin.symbol(), "nUSD") + self.assertEqual(self.nomin.totalSupply(), 0) + self.assertEqual(self.nomin.balanceOf(MASTER), 0) + self.assertEqual(self.nomin.transferFeeRate(), 15 * UNIT // 10000) + self.assertEqual(self.nomin.feeAuthority(), self.nomin.havven()) + self.assertEqual(self.nomin.decimals(), 18) + + def test_setOwner(self): + pre_owner = self.nomin.owner() + new_owner = DUMMY + + # Only the owner must be able to set the owner. + self.assertReverts(self.nomin.nominateNewOwner, new_owner, new_owner) + txr = mine_tx(self.nomin_contract.functions.nominateNewOwner(new_owner).transact({'from': pre_owner}), 'nominateNewOwner', 'Nomin') + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'OwnerNominated', + fields={'newOwner': new_owner}, + location=self.nomin_contract.address + ) + txr = mine_tx(self.nomin_contract.functions.acceptOwnership().transact({'from': new_owner}), 'acceptOwnership', 'Nomin') + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'OwnerChanged', + fields={'oldOwner': pre_owner, 'newOwner': new_owner}, + location=self.nomin_contract.address + ) + self.assertEqual(self.nomin_contract.functions.owner().call(), new_owner) + + def test_setCourt(self): + new_court = DUMMY + old_court = self.nomin.court() + + # Only the owner must be able to set the court. + txr = self.nomin.setCourt(self.nomin.owner(), new_court) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'CourtUpdated', + fields={'newCourt': new_court}, + location=self.nomin_proxy.address + ) + self.assertEqual(self.nomin.court(), new_court) + self.assertReverts(self.nomin.setCourt, DUMMY, new_court) + self.nomin.setCourt(self.nomin.owner(), old_court) + + def test_setHavven(self): + new_havven = DUMMY + old_havven = self.nomin.havven() + + # Only the owner must be able to set the court. + txr = self.nomin.setHavven(self.nomin.owner(), new_havven) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'FeeAuthorityUpdated', + fields={'newFeeAuthority': new_havven}, + location=self.nomin_proxy.address + ) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[1], 'HavvenUpdated', + fields={'newHavven': new_havven}, + location=self.nomin_proxy.address + ) + self.assertEqual(self.nomin.havven(), new_havven) + self.assertReverts(self.nomin.setHavven, DUMMY, old_havven) + self.nomin.setHavven(self.nomin.owner(), old_havven) + + def test_ensureCompleteTransfer(self): + amount = 10 * UNIT + amountReceived = self.nomin.amountReceived(amount) + fee = amount - amountReceived + sender = fresh_account() + receiver = fresh_account() + + # Give them the nomins to start the test + self.nomin.giveNomins(MASTER, sender, amount) + self.assertEqual(self.nomin.balanceOf(sender), amount) + + # Transfer the amount to another account + txr = self.nomin.transfer(sender, receiver, amount) + + # Ensure the result of the transfer is correct. + self.assertEqual(self.nomin.balanceOf(sender), 0) + self.assertEqual(self.nomin.balanceOf(receiver), amount - fee) + self.assertEqual(self.nomin.feePool(), fee) + + def test_transferEventEmits(self): + amount = 5 * UNIT + sender = fresh_account() + receiver = fresh_account() + + # Give them the nomins to start the test + self.nomin.giveNomins(MASTER, sender, amount) + self.assertEqual(self.nomin.balanceOf(sender), amount) + + # Transfer the amount to another account + txr = self.nomin.transfer(sender, receiver, amount) + amountReceived = self.nomin.amountReceived(amount) + fee = amount - amountReceived + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'Transfer', + fields={'from': sender, 'to': receiver, 'value': amountReceived}, + location=self.nomin_proxy.address + ) + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[1], 'Transfer', + fields={ + 'from': sender, + 'to': self.nomin_contract.address, + 'value': fee + }, + location=self.nomin_proxy.address + ) + + def test_transfer(self): + target = fresh_account() + + self.nomin.giveNomins(MASTER, MASTER, 10 * UNIT) + self.assertEqual(self.nomin.balanceOf(MASTER), 10 * UNIT) + self.assertEqual(self.nomin.balanceOf(target), 0) + + # Should be impossible to transfer to the nomin contract itself. + self.assertReverts(self.nomin.transfer, MASTER, self.nomin_contract.address, UNIT) + + txr = self.nomin.transfer(MASTER, target, 5 * UNIT) + amountReceived = self.nomin.amountReceived(5 * UNIT) + fee = 5 * UNIT - amountReceived + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'Transfer', + fields={'from': MASTER, 'to': target, 'value': amountReceived}, + location=self.nomin_proxy.address + ) + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[1], 'Transfer', + fields={ + 'from': MASTER, + 'to': self.nomin_contract.address, + 'value': fee + }, + location=self.nomin_proxy.address + ) + + self.assertEqual(self.nomin.balanceOf(MASTER), 5 * UNIT) + self.assertEqual(self.nomin.balanceOf(target), amountReceived) + self.assertEqual(self.nomin.feePool(), fee) + + txr = self.nomin.debugFreezeAccount(MASTER, target) + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'AccountFrozen', + fields={'target': target, 'balance': amountReceived}, + location=self.nomin_proxy.address + ) + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[1], 'Transfer', + fields={ + 'from': target, + 'to': self.nomin_contract.address, + 'value': amountReceived + }, + location=self.nomin_proxy.address + ) + + self.assertEqual(self.nomin.balanceOf(target), 0) + + self.assertReverts(self.nomin.transfer, MASTER, target, UNIT) + self.assertReverts(self.nomin.transfer, target, MASTER, UNIT) + + txr = self.nomin.unfreezeAccount(MASTER, target) + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'AccountUnfrozen', + fields={'target': target}, + location=self.nomin_proxy.address + ) + self.assertEqual(self.nomin.balanceOf(target), 0) + + txr = self.nomin.transfer(MASTER, target, 5 * UNIT) + amountReceived = self.nomin.amountReceived(5 * UNIT) + fee = 5 * UNIT - amountReceived + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'Transfer', + fields={'from': MASTER, 'to': target, 'value': amountReceived}, + location=self.nomin_proxy.address + ) + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[1], 'Transfer', + fields={ + 'from': MASTER, + 'to': self.nomin_contract.address, + 'value': fee + }, + location=self.nomin_proxy.address + ) + + self.assertEqual(self.nomin.balanceOf(target), amountReceived) + self.assertEqual(self.nomin.balanceOf(MASTER), 0) # assert MASTER has nothing left + + def test_transferFrom(self): + target = fresh_account() + + self.nomin.giveNomins(MASTER, MASTER, 10 * UNIT) + + # Unauthorized transfers should not work + self.assertReverts(self.nomin.transferFrom, DUMMY, MASTER, target, UNIT) + + # Neither should transfers that are too large for the allowance. + txr = self.nomin.approve(MASTER, DUMMY, UNIT) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'Approval', + fields={'owner': MASTER, 'spender': DUMMY, 'value': UNIT}, + location=self.nomin_proxy.address + ) + + self.assertReverts(self.nomin.transferFrom, DUMMY, MASTER, target, 2 * UNIT) + + txr = self.nomin.approve(MASTER, DUMMY, 10000 * UNIT) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'Approval', + fields={'owner': MASTER, 'spender': DUMMY, 'value': 10000 * UNIT}, + location=self.nomin_proxy.address + ) + + self.assertEqual(self.nomin.balanceOf(MASTER), 10 * UNIT) + self.assertEqual(self.nomin.balanceOf(target), 0) + + # Should be impossible to transfer to the nomin contract itself. + self.assertReverts(self.nomin.transferFrom, DUMMY, MASTER, self.nomin_contract.address, UNIT) + + txr = self.nomin.transferFrom(DUMMY, MASTER, target, 5 * UNIT) + + amountReceived = self.nomin.amountReceived(5 * UNIT) + fee = 5 * UNIT - amountReceived + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'Transfer', + fields={'from': MASTER, 'to': target, 'value': amountReceived }, + location=self.nomin_proxy.address + ) + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[1], 'Transfer', + fields={ + 'from': MASTER, + 'to': self.nomin_contract.address, + 'value': fee + }, + location=self.nomin_proxy.address + ) + + self.assertEqual(self.nomin.balanceOf(MASTER), 5 * UNIT) + self.assertEqual(self.nomin.balanceOf(target), amountReceived) + self.assertEqual(self.nomin.feePool(), fee) + + txr = self.nomin.debugFreezeAccount(MASTER, target) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'AccountFrozen', + fields={'target': target, 'balance': self.nomin.amountReceived(5 * UNIT)}, + location=self.nomin_proxy.address + ) + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[1], 'Transfer', + fields={ + 'from': target, + 'to': self.nomin_contract.address, + 'value': self.nomin.amountReceived(5 * UNIT) + }, + location=self.nomin_proxy.address + ) + + self.assertReverts(self.nomin.transferFrom, DUMMY, MASTER, target, UNIT) + self.assertReverts(self.nomin.transferFrom, DUMMY, target, MASTER, UNIT) + + txr = self.nomin.unfreezeAccount(MASTER, target) + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'AccountUnfrozen', + fields={'target': target}, + location=self.nomin_proxy.address + ) + + txr = self.nomin.transferFrom(DUMMY, MASTER, target, 5 * UNIT) + amountReceived = self.nomin.amountReceived(5 * UNIT) + fee = 5 * UNIT - amountReceived + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'Transfer', + fields={'from': MASTER, 'to': target, 'value': amountReceived}, + location=self.nomin_proxy.address + ) + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[1], 'Transfer', + fields={ + 'from': MASTER, + 'to': self.nomin_contract.address, + 'value': fee + }, + location=self.nomin_proxy.address + ) + + self.assertEqual(self.nomin.balanceOf(target), amountReceived) + self.assertEqual(self.nomin.balanceOf(MASTER), 0) # assert MASTER has nothing left + + def test_transferSenderPaysFee(self): + target = fresh_account() + + self.nomin.giveNomins(MASTER, MASTER, 10 * UNIT) + self.assertEqual(self.nomin.balanceOf(MASTER), 10 * UNIT) + self.assertEqual(self.nomin.balanceOf(target), 0) + + # Should be impossible to transfer to the nomin contract itself. + self.assertReverts(self.nomin.transfer, MASTER, self.nomin_contract.address, UNIT) + + txr = self.nomin.transferSenderPaysFee(MASTER, target, 5 * UNIT) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'Transfer', + fields={'from': MASTER, 'to': target, 'value': 5 * UNIT}, + location=self.nomin_proxy.address + ) + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[1], 'Transfer', + fields={ + 'from': MASTER, + 'to': self.nomin_contract.address, + 'value': self.nomin.transferFeeIncurred(5 * UNIT) + }, + location=self.nomin_proxy.address + ) + + self.assertEqual(self.nomin.balanceOf(MASTER), 5 * UNIT - self.nomin.transferFeeIncurred(5 * UNIT)) + self.assertEqual(self.nomin.balanceOf(target), 5 * UNIT) + self.assertEqual(self.nomin.feePool(), self.nomin.transferFeeIncurred(5 * UNIT)) + + txr = self.nomin.debugFreezeAccount(MASTER, target) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'AccountFrozen', + fields={'target': target, 'balance': 5 * UNIT}, + location=self.nomin_proxy.address + ) + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[1], 'Transfer', + fields={ + 'from': target, + 'to': self.nomin_contract.address, + 'value': 5 * UNIT + }, + location=self.nomin_proxy.address + ) + + self.assertEqual(self.nomin.balanceOf(target), 0) + + self.assertReverts(self.nomin.transfer, MASTER, target, UNIT) + self.assertReverts(self.nomin.transfer, target, MASTER, UNIT) + + txr = self.nomin.unfreezeAccount(MASTER, target) + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'AccountUnfrozen', + fields={'target': target}, + location=self.nomin_proxy.address + ) + self.assertEqual(self.nomin.balanceOf(target), 0) + + old_bal = self.nomin.balanceOf(MASTER) + + txr = self.nomin.transferSenderPaysFee(MASTER, target, self.nomin.amountReceived(old_bal)) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'Transfer', + fields={'from': MASTER, 'to': target, 'value': self.nomin.amountReceived(old_bal)}, + location=self.nomin_proxy.address + ) + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[1], 'Transfer', + fields={ + 'from': MASTER, + 'to': self.nomin_contract.address, + 'value': self.nomin.transferFeeIncurred(self.nomin.amountReceived(old_bal)) + }, + location=self.nomin_proxy.address + ) + + self.assertEqual(self.nomin.balanceOf(target), self.nomin.amountReceived(old_bal)) + self.assertLess(self.nomin.balanceOf(MASTER), 2) # assert MASTER only has the tiniest bit of change + + def test_transferFromSenderPaysFee(self): + target = fresh_account() + + self.nomin.giveNomins(MASTER, MASTER, 10 * UNIT) + + # Unauthorized transfers should not work + self.assertReverts(self.nomin.transferFrom, DUMMY, MASTER, target, UNIT) + + # Neither should transfers that are too large for the allowance. + self.nomin.approve(MASTER, DUMMY, UNIT) + self.assertReverts(self.nomin.transferFrom, DUMMY, MASTER, target, 2 * UNIT) + + self.nomin.approve(MASTER, DUMMY, 10000 * UNIT) + + self.assertEqual(self.nomin.balanceOf(MASTER), 10 * UNIT) + self.assertEqual(self.nomin.balanceOf(target), 0) + + # Should be impossible to transfer to the nomin contract itself. + self.assertReverts(self.nomin.transferFrom, DUMMY, MASTER, self.nomin_contract.address, UNIT) + + self.nomin.transferFromSenderPaysFee(DUMMY, MASTER, target, 5 * UNIT) + + self.assertClose(self.nomin.balanceOf(MASTER), 5 * UNIT - self.nomin.transferFeeIncurred(5 * UNIT)) + self.assertEqual(self.nomin.balanceOf(target), 5 * UNIT) + self.assertEqual(self.nomin.feePool(), self.nomin.transferFeeIncurred(5 * UNIT)) + + self.nomin.debugFreezeAccount(MASTER, target) + + self.assertReverts(self.nomin.transferFrom, DUMMY, MASTER, target, UNIT) + self.assertReverts(self.nomin.transferFrom, DUMMY, target, MASTER, UNIT) + + txr = self.nomin.unfreezeAccount(MASTER, target) + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'AccountUnfrozen', + fields={'target': target}, + location=self.nomin_proxy.address + ) + + old_bal = self.nomin.balanceOf(MASTER) + + self.nomin.transferFromSenderPaysFee(DUMMY, MASTER, target, self.nomin.amountReceived(old_bal)) + + self.assertEqual(self.nomin.balanceOf(target), self.nomin.amountReceived(old_bal)) + self.assertLess(self.nomin.balanceOf(MASTER), 3) # assert MASTER only has the tiniest bit of change + + def test_freezeAndConfiscate(self): + target = W3.eth.accounts[2] + + self.assertEqual(self.nomin.court(), self.fake_court.contract.address) + + self.nomin.giveNomins(MASTER, target, 10 * UNIT) + + # The target must have some nomins. + self.assertEqual(self.nomin.balanceOf(target), 10 * UNIT) + + motion_id = 1 + self.fake_court.setTargetMotionID(MASTER, target, motion_id) + + # Attempt to confiscate even though the conditions are not met. + self.fake_court.setConfirming(MASTER, motion_id, False) + self.fake_court.setVotePasses(MASTER, motion_id, False) + self.assertReverts(self.fake_court.freezeAndConfiscate, MASTER, target) + + self.fake_court.setConfirming(MASTER, motion_id, True) + self.fake_court.setVotePasses(MASTER, motion_id, False) + self.assertReverts(self.fake_court.freezeAndConfiscate, MASTER, target) + + self.fake_court.setConfirming(MASTER, motion_id, False) + self.fake_court.setVotePasses(MASTER, motion_id, True) + self.assertReverts(self.fake_court.freezeAndConfiscate, MASTER, target) + + # Set up the target balance to be confiscatable. + self.fake_court.setConfirming(MASTER, motion_id, True) + self.fake_court.setVotePasses(MASTER, motion_id, True) + + # Only the court should be able to confiscate balances. + self.assertReverts(self.nomin.freezeAndConfiscate, MASTER, target) + + # Actually confiscate the balance. + pre_fee_pool = self.nomin.feePool() + pre_balance = self.nomin.balanceOf(target) + self.fake_court.freezeAndConfiscate(MASTER, target) + self.assertEqual(self.nomin.balanceOf(target), 0) + self.assertEqual(self.nomin.feePool(), pre_fee_pool + pre_balance) + self.assertTrue(self.nomin.frozen(target)) + + def test_unfreezeAccount(self): + target = fresh_account() + + # The nomin contract itself should not be unfreezable. + self.assertReverts(self.nomin.unfreezeAccount, MASTER, self.nomin_contract.address) + self.assertTrue(self.nomin.frozen(self.nomin_contract.address)) + + # Unfreezing non-frozen accounts should not do anything. + self.assertFalse(self.nomin.frozen(target)) + self.assertReverts(self.nomin.unfreezeAccount, MASTER, target) + self.assertFalse(self.nomin.frozen(target)) + + self.nomin.debugFreezeAccount(MASTER, target) + self.assertTrue(self.nomin.frozen(target)) + + # Only the owner should be able to unfreeze an account. + self.assertReverts(self.nomin.unfreezeAccount, target, target) + + txr = self.nomin.unfreezeAccount(MASTER, target) + + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'AccountUnfrozen', + fields={'target': target}, + location=self.nomin_proxy.address + ) + + self.assertFalse(self.nomin.frozen(target)) + + def test_issue_burn(self): + havven, acc1, acc2 = fresh_accounts(3) + self.nomin.setHavven(MASTER, havven) + + # not even the owner can issue, only the havven contract + self.assertReverts(self.nomin.issue, MASTER, acc1, 100 * UNIT) + + txr = self.nomin.publicIssue(havven, acc1, 100 * UNIT) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'Transfer', + fields={'from': ZERO_ADDRESS, 'to': acc1, 'value': 100 * UNIT}, + location=self.nomin_proxy.address + ) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[1], 'Issued', + fields={'account': acc1, 'amount': 100 * UNIT}, + location=self.nomin_proxy.address + ) + + self.assertEqual(self.nomin.balanceOf(acc1), 100 * UNIT) + self.assertEqual(self.nomin.totalSupply(), 100 * UNIT) + + txr = self.nomin.publicIssue(havven, acc2, 200 * UNIT) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'Transfer', + fields={'from': ZERO_ADDRESS, 'to': acc2, 'value': 200 * UNIT}, + location=self.nomin_proxy.address + ) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[1], 'Issued', + fields={'account': acc2, 'amount': 200 * UNIT}, + location=self.nomin_proxy.address + ) + + self.assertEqual(self.nomin.balanceOf(acc2), 200 * UNIT) + self.assertEqual(self.nomin.totalSupply(), 300 * UNIT) + + self.nomin.transfer(acc1, acc2, 50 * UNIT) + self.assertNotEqual(self.nomin.totalSupply(), self.nomin.balanceOf(acc1) + self.nomin.balanceOf(acc2)) + self.assertEqual(self.nomin.totalSupply(), self.nomin.balanceOf(acc1) + self.nomin.balanceOf(acc2) + self.nomin.feePool()) + + acc1_bal = self.nomin.balanceOf(acc1) + # not even the owner can burn... + self.assertReverts(self.nomin.burn, MASTER, acc1, acc1_bal) + + txr = self.nomin.publicBurn(havven, acc1, acc1_bal) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'Transfer', + fields={'from': acc1, 'to': ZERO_ADDRESS, 'value': acc1_bal}, + location=self.nomin_proxy.address + ) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[1], 'Burned', + fields={'account': acc1, 'amount': acc1_bal}, + location=self.nomin_proxy.address + ) + + self.assertEqual(self.nomin.totalSupply(), self.nomin.balanceOf(acc2) + self.nomin.feePool()) + + # burning more than issued is allowed, as that logic is controlled in the havven contract + self.nomin.publicBurn(havven, acc2, self.nomin.balanceOf(acc2)) + + self.assertEqual(self.nomin.balanceOf(acc1), self.nomin.balanceOf(acc2), 0) + + def test_edge_issue_burn(self): + havven, acc1, acc2 = fresh_accounts(3) + self.nomin.setHavven(MASTER, havven) + + max_int = 2**256 - 1 + txr = self.nomin.publicIssue(havven, acc1, 100 * UNIT) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'Transfer', + fields={'from': ZERO_ADDRESS, 'to': acc1, 'value': 100 * UNIT}, + location=self.nomin_proxy.address + ) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[1], 'Issued', + fields={'account': acc1, 'amount': 100 * UNIT}, + location=self.nomin_proxy.address + ) + self.assertReverts(self.nomin.publicIssue, havven, acc1, max_int) + self.assertReverts(self.nomin.publicIssue, havven, acc2, max_int) + # there shouldn't be a way to burn towards a larger value by overflowing + self.assertReverts(self.nomin.publicBurn, havven, acc1, max_int) + txr = self.nomin.publicBurn(havven, acc1, 100 * UNIT) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'Transfer', + fields={'from': acc1, 'to': ZERO_ADDRESS, 'value': 100 * UNIT}, + location=self.nomin_proxy.address + ) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[1], 'Burned', + fields={'account': acc1, 'amount': 100 * UNIT}, + location=self.nomin_proxy.address + ) + + # as long as no nomins exist, its a valid action + txr = self.nomin.publicIssue(havven, acc2, max_int) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'Transfer', + fields={'from': ZERO_ADDRESS, 'to': acc2, 'value': max_int}, + location=self.nomin_proxy.address + ) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[1], 'Issued', + fields={'account': acc2, 'amount': max_int}, + location=self.nomin_proxy.address + ) + + txr = self.nomin.publicBurn(havven, acc2, max_int) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'Transfer', + fields={'from': acc2, 'to': ZERO_ADDRESS, 'value': max_int}, + location=self.nomin_proxy.address + ) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[1], 'Burned', + fields={'account': acc2, 'amount': max_int}, + location=self.nomin_proxy.address + ) + + def test_selfDestruct(self): + owner = self.nomin.owner() + notowner = DUMMY + self.assertNotEqual(owner, notowner) + + # The contract cannot be self-destructed before the SD has been initiated. + self.assertReverts(self.unproxied_nomin.selfDestruct, owner) + + tx = self.unproxied_nomin.initiateSelfDestruct(owner) + self.assertEventEquals(self.nomin_event_dict, tx.logs[0], + "SelfDestructInitiated", + {"selfDestructDelay": self.sd_duration}, + location=self.nomin_contract.address) + + # Neither owners nor non-owners may not self-destruct before the time has elapsed. + self.assertReverts(self.unproxied_nomin.selfDestruct, notowner) + self.assertReverts(self.unproxied_nomin.selfDestruct, owner) + fast_forward(seconds=self.sd_duration, days=-1) + self.assertReverts(self.unproxied_nomin.selfDestruct, notowner) + self.assertReverts(self.unproxied_nomin.selfDestruct, owner) + fast_forward(seconds=10, days=1) + + # Non-owner should not be able to self-destruct even if the time has elapsed. + self.assertReverts(self.unproxied_nomin.selfDestruct, notowner) + address = self.unproxied_nomin.contract.address + tx = self.unproxied_nomin.selfDestruct(owner) + + self.assertEventEquals(self.nomin_event_dict, tx.logs[0], + "SelfDestructed", + {"beneficiary": owner}, + location=self.nomin_contract.address) + # Check contract not exist + self.assertEqual(W3.eth.getCode(address), b'\x00') + + def test_event_CourtUpdated(self): + new_court = fresh_account() + tx = self.nomin.setCourt(MASTER, new_court) + self.assertEventEquals(self.nomin_event_dict, + tx.logs[0], "CourtUpdated", + {"newCourt": new_court}, + self.nomin_proxy.address) + + def test_event_HavvenUpdated(self): + new_havven = fresh_account() + tx = self.nomin.setHavven(MASTER, new_havven) + self.assertEventEquals(self.nomin_event_dict, + tx.logs[1], "HavvenUpdated", + {"newHavven": new_havven}, + self.nomin_proxy.address) + + def test_event_AccountFrozen(self): + target = fresh_account() + self.nomin.clearNomins(MASTER, target) + self.nomin.giveNomins(MASTER, target, 5 * UNIT) + motion_id = 1 + self.fake_court.setTargetMotionID(MASTER, target, motion_id) + self.fake_court.setConfirming(MASTER, motion_id, True) + self.fake_court.setVotePasses(MASTER, motion_id, True) + self.assertEqual(self.nomin.balanceOf(target), 5 * UNIT) + txr = self.fake_court.freezeAndConfiscate(MASTER, target) + self.assertEqual(self.nomin.balanceOf(target), 0) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'AccountFrozen', + fields={'target': target, 'balance': 5 * UNIT}, + location=self.nomin_proxy.address + ) + + def test_event_AccountUnfrozen(self): + target = fresh_account() + self.nomin.debugFreezeAccount(MASTER, target) + txr = self.nomin.unfreezeAccount(MASTER, target) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[0], 'AccountUnfrozen', + fields={'target': target}, + location=self.nomin_proxy.address + ) + + def test_event_Issued(self): + issuer = fresh_account() + self.nomin.setHavven(MASTER, MASTER) + txr = self.nomin.publicIssue(MASTER, issuer, UNIT) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[1], 'Issued', + fields={'account': issuer, + 'amount': UNIT}, + location=self.nomin_proxy.address + ) + + def test_event_Burned(self): + burner = fresh_account() + self.nomin.setHavven(MASTER, MASTER) + self.nomin.publicIssue(MASTER, burner, UNIT) + txr = self.nomin.publicBurn(MASTER, burner, UNIT) + self.assertEventEquals( + self.nomin_event_dict, txr.logs[1], 'Burned', + fields={'account': burner, + 'amount': UNIT}, + location=self.nomin_proxy.address + ) + diff --git a/tests/test_Owned.py b/tests/test_Owned.py index 4b17033f13..8da3af3aea 100644 --- a/tests/test_Owned.py +++ b/tests/test_Owned.py @@ -1,22 +1,28 @@ -import unittest +from utils.deployutils import ( + MASTER, DUMMY, + attempt_deploy, + take_snapshot, restore_snapshot +) +from utils.testutils import ( + HavvenTestCase, ZERO_ADDRESS +) +from tests.contract_interfaces.owned_interface import OwnedInterface -from utils.deployutils import compile_contracts, attempt_deploy, mine_tx, MASTER, DUMMY -from utils.deployutils import take_snapshot, restore_snapshot -from utils.testutils import assertReverts, ZERO_ADDRESS -from utils.testutils import generate_topic_event_map, get_event_data_from_log -OWNED_SOURCE = "contracts/Owned.sol" def setUpModule(): print("Testing Owned...") + print("================") + print() def tearDownModule(): print() + print() -class TestOwned(unittest.TestCase): +class TestOwned(HavvenTestCase): def setUp(self): self.snapshot = take_snapshot() @@ -25,59 +31,76 @@ def tearDown(self): @classmethod def setUpClass(cls): - cls.assertReverts = assertReverts + sources = ["contracts/Owned.sol"] - compiled = compile_contracts([OWNED_SOURCE]) - cls.owned, txr = attempt_deploy(compiled, 'Owned', MASTER, [MASTER]) + cls.compiled, cls.event_maps = cls.compileAndMapEvents(sources) + cls.event_map = cls.event_maps['Owned'] - cls.owner = lambda self: cls.owned.functions.owner().call() - cls.nominatedOwner = lambda self: cls.owned.functions.nominatedOwner().call() - cls.nominateOwner = lambda self, sender, newOwner: mine_tx( - cls.owned.functions.nominateOwner(newOwner).transact({'from': sender})) - cls.acceptOwnership = lambda self, sender: mine_tx( - cls.owned.functions.acceptOwnership().transact({'from': sender})) + cls.owned_contract, cls.deploy_tx = attempt_deploy(cls.compiled, 'Owned', MASTER, [MASTER]) + cls.owned = OwnedInterface(cls.owned_contract, "Owned") - cls.owned_event_map = generate_topic_event_map(compiled['Owned']['abi']) - - def test_owner_is_master(self): - self.assertEqual(self.owner(), MASTER) + def test_constructor(self): + self.assertEqual(self.owned.owner(), MASTER) + self.assertEqual(self.owned.nominatedOwner(), ZERO_ADDRESS) + self.assertEventEquals(self.event_map, self.deploy_tx.logs[0], + "OwnerChanged", + {"oldOwner": ZERO_ADDRESS, + "newOwner": MASTER}, + location=self.owned_contract.address) def test_change_owner(self): - old_owner = self.owner() + old_owner = self.owned.owner() new_owner = DUMMY - - self.assertReverts(self.nominateOwner, new_owner, old_owner) - nominated_tx = self.nominateOwner(old_owner, new_owner) - event_data = get_event_data_from_log(self.owned_event_map, nominated_tx.logs[0]) - self.assertEqual(event_data['event'], "OwnerNominated") - self.assertEqual(event_data['args']['newOwner'], new_owner) - - self.assertEqual(self.owner(), old_owner) - self.assertEqual(self.nominatedOwner(), new_owner) - self.assertReverts(self.nominateOwner, new_owner, old_owner) - accepted_tx = self.acceptOwnership(new_owner) - event_data = get_event_data_from_log(self.owned_event_map, accepted_tx.logs[0]) - self.assertEqual(event_data['event'], "OwnerChanged") - self.assertEqual(event_data['args']['oldOwner'], old_owner) - self.assertEqual(event_data['args']['newOwner'], new_owner) - - self.assertEqual(self.nominatedOwner(), ZERO_ADDRESS) - self.assertEqual(self.owner(), new_owner) - self.assertReverts(self.nominateOwner, old_owner, new_owner) - - self.nominateOwner(new_owner, old_owner) - self.acceptOwnership(old_owner) - self.assertEqual(self.owner(), old_owner) + self.assertNotEqual(old_owner, new_owner) + + # Only the owner may nominate a new owner. + self.assertReverts(self.owned.nominateNewOwner, new_owner, old_owner) + + # Nominate new owner and ensure event emitted properly. + nominated_tx = self.owned.nominateNewOwner(old_owner, new_owner) + self.assertEventEquals(self.event_map, nominated_tx.logs[0], + "OwnerNominated", + {"newOwner": new_owner}, + location=self.owned_contract.address) + + # Ensure owner unchanged, nominated owner was set properly. + self.assertEqual(self.owned.owner(), old_owner) + self.assertEqual(self.owned.nominatedOwner(), new_owner) + + # Ensure only the nominated owner can accept the ownership. + self.assertReverts(self.owned.acceptOwnership, old_owner) + # But the nominee gains no other privileges. + self.assertReverts(self.owned.nominateNewOwner, new_owner, old_owner) + + # Accept ownership and ensure event emitted properly. + accepted_tx = self.owned.acceptOwnership(new_owner) + self.assertEventEquals(self.event_map, accepted_tx.logs[0], + "OwnerChanged", + {"oldOwner": old_owner, + "newOwner": new_owner}, + location=self.owned_contract.address) + + # Ensure owner changed, nominated owner reset to zero. + self.assertEqual(self.owned.nominatedOwner(), ZERO_ADDRESS) + self.assertEqual(self.owned.owner(), new_owner) + + # The old owner may no longer nominate a new owner. + self.assertReverts(self.owned.nominateNewOwner, old_owner, new_owner) + + # Go backwards. + self.owned.nominateNewOwner(new_owner, old_owner) + self.owned.acceptOwnership(old_owner) + self.assertEqual(self.owned.owner(), old_owner) def test_change_invalid_owner(self): invalid_account = DUMMY - self.assertReverts(self.nominateOwner, invalid_account, invalid_account) + self.assertReverts(self.owned.nominateNewOwner, invalid_account, invalid_account) def test_undo_change_owner(self): - old_owner = self.owner() + old_owner = self.owned.owner() new_owner = DUMMY - self.assertReverts(self.nominateOwner, new_owner, old_owner) - self.nominateOwner(old_owner, new_owner) - self.nominateOwner(old_owner, ZERO_ADDRESS) - self.assertReverts(self.acceptOwnership, new_owner) + self.assertReverts(self.owned.nominateNewOwner, new_owner, old_owner) + self.owned.nominateNewOwner(old_owner, new_owner) + self.owned.nominateNewOwner(old_owner, ZERO_ADDRESS) + self.assertReverts(self.owned.acceptOwnership, new_owner) diff --git a/tests/test_Pausable.py b/tests/test_Pausable.py new file mode 100644 index 0000000000..ee4ff206af --- /dev/null +++ b/tests/test_Pausable.py @@ -0,0 +1,130 @@ +from time import sleep +from utils.deployutils import ( + W3, UNIT, MASTER, DUMMY, + fresh_account, fresh_accounts, + attempt_deploy, + take_snapshot, restore_snapshot +) +from utils.testutils import ( + HavvenTestCase, + block_time +) +from tests.contract_interfaces.pausable_interface import PausableInterface + +def setUpModule(): + print("Testing Pausable...") + print("================") + print() + + +def tearDownModule(): + print() + print() + + +class TestPausable(HavvenTestCase): + def setUp(self): + self.snapshot = take_snapshot() + + def tearDown(self): + restore_snapshot(self.snapshot) + + @classmethod + def deployContracts(cls): + sources = [ + "contracts/Pausable.sol", + "tests/contracts/TestablePausable.sol" + ] + + compiled, cls.event_maps = cls.compileAndMapEvents(sources) + pausableContract, _ = attempt_deploy(compiled, 'TestablePausable', MASTER, [cls.contractOwner]) + return pausableContract + + @classmethod + def setUpClass(cls): + addresses = fresh_accounts(6) + cls.participantAddresses = addresses[0:] + cls.contractOwner = MASTER + cls.pausableContract = cls.deployContracts() + cls.pausable = PausableInterface(cls.pausableContract, "TestablePausable") + cls.pausableEventDict = cls.event_maps['TestablePausable'] + + + def test_constructor(self): + self.assertEqual(self.pausable.owner(), self.contractOwner) + self.assertEqual(self.pausable.lastPauseTime(), 0) + self.assertEqual(self.pausable.paused(), False) + + def test_setPauseToggles(self): + initialPausedState = self.pausable.paused() + self.pausable.setPaused(self.contractOwner, not initialPausedState) + currentlyPausedState = self.pausable.paused() + self.assertEqual(not initialPausedState, currentlyPausedState) + + def test_setPauseIgnoresSame(self): + initialPausedState = self.pausable.paused() + initialLastPauseTime = self.pausable.lastPauseTime() + + # Try to set it to the same value + txr = self.pausable.setPaused(self.contractOwner, initialPausedState) + + # Ensure no event was emitted and the state is the same. + self.assertEqual(len(txr.logs), 0) + currentlyPausedState = self.pausable.paused() + self.assertEqual(initialPausedState, currentlyPausedState) + currentLastPausedTime = self.pausable.lastPauseTime() + self.assertEqual(initialLastPauseTime, currentLastPausedTime) + + def test_cannotSetPausedIfUnauthorised(self): + unauthorisedAddress = fresh_accounts(1)[0] + initialPausedState = self.pausable.paused() + self.assertReverts(self.pausable.setPaused, unauthorisedAddress, not initialPausedState) + + def test_setPauseUpdatesTime(self): + self.pausable.setPaused(self.contractOwner, True) + initialLastPauseTime = self.pausable.lastPauseTime() + sleep(1) # Wait one second so the lastPauseTime is different to now + self.pausable.setPaused(self.contractOwner, False) + afterFalseLastPausedTime = self.pausable.lastPauseTime() + self.assertEqual(initialLastPauseTime, afterFalseLastPausedTime) + txr = self.pausable.setPaused(self.contractOwner, True) + finalLastPausedTime = self.pausable.lastPauseTime() + self.assertEqual(finalLastPausedTime, block_time(txr.blockNumber)) + self.assertNotEqual(finalLastPausedTime, afterFalseLastPausedTime) + + def test_pausingEmitsEvent(self): + initialPausedState = self.pausable.paused() + newState = not initialPausedState + txn1 = self.pausable.setPaused(self.contractOwner, newState) + self.assertEventEquals( + self.pausableEventDict, txn1.logs[0], 'PauseChanged', + fields={'isPaused': newState}, + location=self.pausableContract.address + ) + finalState = not newState + txn2 = self.pausable.setPaused(self.contractOwner, finalState) + self.assertEventEquals( + self.pausableEventDict, txn2.logs[0], 'PauseChanged', + fields={'isPaused': finalState}, + location=self.pausableContract.address + ) + + def test_notPausedModifier(self): + originalValue = self.pausable.getSomeValue() + newValue = originalValue + 123 + self.pausable.setSomeValue(self.contractOwner, newValue) + updatedValue = self.pausable.getSomeValue() + self.assertEqual(newValue, updatedValue) + + # Pause and ensure it doesn't work + self.pausable.setPaused(self.contractOwner, True) + self.assertReverts(self.pausable.setSomeValue, self.contractOwner, originalValue) + updatedValue = self.pausable.getSomeValue() + self.assertNotEqual(originalValue, updatedValue) + + # Unpause and ensure it works again + self.pausable.setPaused(self.contractOwner, False) + newValue = originalValue + 1234 + self.pausable.setSomeValue(self.contractOwner, newValue) + updatedValue = self.pausable.getSomeValue() + self.assertEqual(newValue, updatedValue) diff --git a/tests/test_Proxy.py b/tests/test_Proxy.py index 88c9392a8b..3ec0589255 100644 --- a/tests/test_Proxy.py +++ b/tests/test_Proxy.py @@ -1,2029 +1,137 @@ -import unittest -import time - -import utils.generalutils -from utils.deployutils import W3, UNIT, MASTER, DUMMY, ETHER -from utils.deployutils import compile_contracts, attempt, attempt_deploy, mine_tx, mine_txs -from utils.deployutils import fresh_account, fresh_accounts -from utils.deployutils import take_snapshot, restore_snapshot, fast_forward, to_seconds -from utils.testutils import assertReverts, assertClose, block_time, send_value, get_eth_balance -from utils.testutils import generate_topic_event_map, get_event_data_from_log -from utils.testutils import ZERO_ADDRESS - - -ETHERNOMIN_SOURCE = "tests/contracts/PublicEtherNomin.sol" -FAKECOURT_SOURCE = "tests/contracts/FakeCourt.sol" -PROXY_SOURCE = "contracts/Proxy.sol" - -SOLIDITY_SOURCES = ["tests/contracts/PublicHavven.sol", "contracts/EtherNomin.sol", - "contracts/Court.sol", "contracts/HavvenEscrow.sol", - "contracts/Proxy.sol"] - - - - -def deploy_public_havven(): - print("Deployment initiated.\n") - - compiled = attempt(compile_contracts, [SOLIDITY_SOURCES], "Compiling contracts... ") - - # Deploy contracts - havven_contract, hvn_txr = attempt_deploy(compiled, 'PublicHavven', - MASTER, [ZERO_ADDRESS, MASTER]) - hvn_block = W3.eth.blockNumber - nomin_contract, nom_txr = attempt_deploy(compiled, 'EtherNomin', - MASTER, - [havven_contract.address, MASTER, MASTER, - 1000 * UNIT, MASTER, ZERO_ADDRESS]) - court_contract, court_txr = attempt_deploy(compiled, 'Court', - MASTER, - [havven_contract.address, nomin_contract.address, - MASTER]) - escrow_contract, escrow_txr = attempt_deploy(compiled, 'HavvenEscrow', - MASTER, - [MASTER, havven_contract.address]) - - # Install proxies - havven_proxy, _ = attempt_deploy(compiled, 'Proxy', - MASTER, [havven_contract.address, MASTER]) - mine_tx(havven_contract.functions.setProxy(havven_proxy.address).transact({'from': MASTER})) - proxy_havven = W3.eth.contract(address=havven_proxy.address, abi=compiled['PublicHavven']['abi']) - - nomin_proxy, _ = attempt_deploy(compiled, 'Proxy', - MASTER, [nomin_contract.address, MASTER]) - mine_tx(nomin_contract.functions.setProxy(nomin_proxy.address).transact({'from': MASTER})) - proxy_nomin = W3.eth.contract(address=nomin_proxy.address, abi=compiled['EtherNomin']['abi']) - - # Hook up each of those contracts to each other - txs = [proxy_havven.functions.setNomin(nomin_contract.address).transact({'from': MASTER}), - proxy_nomin.functions.setCourt(court_contract.address).transact({'from': MASTER})] - attempt(mine_txs, [txs], "Linking contracts... ") - - havven_event_dict = generate_topic_event_map(compiled['PublicHavven']['abi']) - - print("\nDeployment complete.\n") - return proxy_havven, proxy_nomin, havven_proxy, nomin_proxy, havven_contract, nomin_contract, court_contract, escrow_contract, hvn_block, havven_event_dict +from utils.deployutils import ( + W3, UNIT, MASTER, DUMMY, fresh_account, fresh_accounts, + compile_contracts, attempt_deploy, mine_txs, + take_snapshot, restore_snapshot +) +from utils.testutils import ( + HavvenTestCase, ZERO_ADDRESS, + generate_topic_event_map, get_event_data_from_log +) +from tests.contract_interfaces.fee_token_interface import FeeTokenInterface +from tests.contract_interfaces.proxy_interface import ProxyInterface +from tests.contract_interfaces.token_state_interface import TokenStateInterface def setUpModule(): print("Testing Proxy...") + print("================") + print() def tearDownModule(): print() + print() -class TestHavven(unittest.TestCase): +class TestFeeToken(HavvenTestCase): def setUp(self): self.snapshot = take_snapshot() - time_remaining = self.targetFeePeriodDurationSeconds() + self.feePeriodStartTime() - block_time() - fast_forward(time_remaining + 1) - self.recomputeLastAverageBalance(MASTER) def tearDown(self): restore_snapshot(self.snapshot) - @classmethod - def setUpClass(cls): - cls.assertClose = assertClose - cls.assertReverts = assertReverts - # to avoid overflowing in the negative direction (now - targetFeePeriodDuration * 2) - fast_forward(weeks=102) - - cls.havven, cls.nomin, cls.havven_proxy, cls.nomin_proxy, cls.havven_real, cls.nomin_real, cls.court, \ - cls.escrow, cls.construction_block, cls.havven_event_dict = deploy_public_havven() - - # INHERITED - # OWNED - # owner - cls.h_owner = lambda self: self.havven.functions.owner().call() - cls.h_nominateOwner = lambda self, sender, addr: mine_tx( - self.havven.functions.nominateOwner(addr).transact({'from': sender})) - cls.h_acceptOwnership = lambda self, sender: mine_tx( - self.havven.functions.acceptOwnership().transact({'from': sender})) - - # ExternStateProxyToken (transfer/transferFrom are overwritten) - # totalSupply - cls.totalSupply = lambda self: self.havven.functions.totalSupply().call() - cls.name = lambda self: self.havven.functions.name().call() - cls.symbol = lambda self: self.havven.functions.symbol().call() - cls.balanceOf = lambda self, a: self.havven.functions.balanceOf(a).call() - cls.allowance = lambda self, owner, spender: self.havven.functions.allowance(owner, spender).call() - cls.approve = lambda self, sender, spender, val: mine_tx( - self.havven.functions.approve(spender, val).transact({"from": sender})) - - # HAVVEN - # GETTERS - cls.currentBalanceSum = lambda self, addr: self.havven.functions._currentBalanceSum(addr).call() - cls.lastAverageBalance = lambda self, addr: self.havven.functions.lastAverageBalance(addr).call() - cls.penultimateAverageBalance = lambda self, addr: self.havven.functions.penultimateAverageBalance(addr).call() - cls.lastTransferTimestamp = lambda self, addr: self.havven.functions._lastTransferTimestamp(addr).call() - cls.hasWithdrawnLastPeriodFees = lambda self, addr: self.havven.functions._hasWithdrawnLastPeriodFees( - addr).call() - cls.lastAverageBalanceNeedsRecomputation = lambda self, addr: \ - self.havven.functions.lastAverageBalanceNeedsRecomputation(addr).call() - - cls.feePeriodStartTime = lambda self: self.havven.functions.feePeriodStartTime().call() - cls.lastFeePeriodStartTime = lambda self: self.havven.functions._lastFeePeriodStartTime().call() - cls.penultimateFeePeriodStartTime = lambda self: self.havven.functions._penultimateFeePeriodStartTime().call() - cls.targetFeePeriodDurationSeconds = lambda self: self.havven.functions.targetFeePeriodDurationSeconds().call() - cls.MIN_FEE_PERIOD_DURATION_SECONDS = lambda \ - self: self.havven.functions._MIN_FEE_PERIOD_DURATION_SECONDS().call() - cls.MAX_FEE_PERIOD_DURATION_SECONDS = lambda \ - self: self.havven.functions._MAX_FEE_PERIOD_DURATION_SECONDS().call() - cls.lastFeesCollected = lambda self: self.havven.functions.lastFeesCollected().call() + @staticmethod + def deployContracts(): + sources = ["tests/contracts/PublicFeeToken.sol", + "contracts/FeeToken.sol", + "contracts/TokenState.sol"] + compiled = compile_contracts(sources, remappings=['""=contracts']) + feetoken_abi = compiled['PublicFeeToken']['abi'] - cls.get_nomin = lambda self: self.havven.functions.nomin().call() - cls.get_escrow = lambda self: self.havven.functions.escrow().call() - - # - # SETTERS - cls.setNomin = lambda self, sender, addr: mine_tx( - self.havven.functions.setNomin(addr).transact({'from': sender})) - cls.setEscrow = lambda self, sender, addr: mine_tx( - self.havven.functions.setEscrow(addr).transact({'from': sender})) - cls.unsetEscrow = lambda self, sender: mine_tx( - self.havven.functions.unsetEscrow().transact({'from': sender})) - cls.setTargetFeePeriodDuration = lambda self, sender, dur: mine_tx( - self.havven.functions.setTargetFeePeriodDuration(dur).transact({'from': sender})) - - # - # FUNCTIONS - cls.endow = lambda self, sender, addr, amt: mine_tx( - self.havven_real.functions.endow(addr, amt).transact({'from': sender})) - cls.transfer = lambda self, sender, addr, amt: mine_tx( - self.havven.functions.transfer(addr, amt).transact({'from': sender})) - cls.transferFrom = lambda self, sender, frm, to, amt: mine_tx( - self.havven.functions.transferFrom(frm, to, amt).transact({'from': sender})) - cls.recomputeLastAverageBalance = lambda self, sender: mine_tx( - self.havven.functions.recomputeLastAverageBalance().transact({'from': sender})) - cls.recomputeAccountLastAverageBalance = lambda self, sender, account: mine_tx( - self.havven.functions.recomputeAccountLastAverageBalance(account).transact({'from': sender})) - cls.rolloverFeePeriod = lambda self, sender: mine_tx( - self.havven.functions.rolloverFeePeriod().transact({'from': sender})) - - # - # INTERNAL - cls.adjustFeeEntitlement = lambda self, sender, acc, p_bal: mine_tx( - self.havven.functions._adjustFeeEntitlement(acc, p_bal).transact({'from': sender})) - # rolloverFee (ltt->last_transfer_time) - cls.rolloverFee = lambda self, sender, acc, ltt, p_bal: mine_tx( - self.havven.functions._rolloverFee(acc, ltt, p_bal).transact({'from': sender})) - - # withdrawFeeEntitlement - cls.withdrawFeeEntitlement = lambda self, sender: mine_tx( - self.havven.functions.withdrawFeeEntitlement().transact({'from': sender})) - - # - # MODIFIERS - # postCheckFeePeriodRollover - cls._checkFeePeriodRollover = lambda self, sender: mine_tx( - self.havven.functions._checkFeePeriodRollover().transact({'from': sender})) - - def start_new_fee_period(self): - time_remaining = self.targetFeePeriodDurationSeconds() + self.feePeriodStartTime() - block_time() - fast_forward(time_remaining + 1) - self._checkFeePeriodRollover(MASTER) - - ### - # Test inherited Owned - Should be the same test_Owned.py - ### - def test_owner_is_master(self): - self.assertEqual(self.h_owner(), MASTER) - - def test_change_owner(self): - old_owner = self.h_owner() - new_owner = DUMMY - - self.h_nominateOwner(old_owner, new_owner) - self.h_acceptOwnership(new_owner) - self.assertEqual(self.h_owner(), new_owner) - - # reset back to old owner - self.h_nominateOwner(new_owner, old_owner) - self.h_acceptOwnership(old_owner) - self.assertEqual(self.h_owner(), old_owner) - - def test_change_invalid_owner(self): - invalid_account = DUMMY - self.assertReverts(self.h_nominateOwner, invalid_account, invalid_account) - - ### - # Test inherited ExternStateProxyToken - ### - # Constuctor - def test_ExternStateProxyToken_constructor(self): - total_supply = 10 ** 8 * UNIT - self.assertEqual(self.name(), "Havven") - self.assertEqual(self.symbol(), "HAV") - self.assertEqual(self.totalSupply(), total_supply) - self.assertEqual(self.balanceOf(self.havven_real.address), total_supply) - - # Approval - def test_approve(self): - owner = MASTER - spender = DUMMY - self.approve(owner, spender, UNIT) - self.assertEqual(self.allowance(owner, spender), UNIT) - self.approve(owner, spender, 0) - self.assertEqual(self.allowance(owner, spender), 0) - - # - ## - ### - # Test Havven - ### - ### - # Constructor - ### - def test_constructor(self): - fee_period = self.targetFeePeriodDurationSeconds() - self.assertEqual(fee_period, to_seconds(weeks=4)) - self.assertGreater(block_time(), 2 * fee_period) - self.assertEqual(self.MIN_FEE_PERIOD_DURATION_SECONDS(), to_seconds(days=1)) - self.assertEqual(self.MAX_FEE_PERIOD_DURATION_SECONDS(), to_seconds(weeks=26)) - self.assertEqual(self.lastFeesCollected(), 0) - self.assertEqual(self.get_nomin(), self.nomin_real.address) - self.assertEqual(self.havven.functions.decimals().call(), 18) - - ### - # Mappings - ### - # currentBalanceSum - def test_currentBalanceSum(self): - """ - Testing the value of currentBalanceSum works as intended, - Further testing involving this and fee collection will be done - in scenario testing - """ - fee_period = self.targetFeePeriodDurationSeconds() - delay = int(fee_period / 10) - alice = fresh_account() - self.assertEqual(self.balanceOf(alice), 0) - - start_amt = UNIT * 50 - self.endow(MASTER, alice, start_amt) - self.assertEqual(self.balanceOf(alice), start_amt) - self.assertEqual(self.currentBalanceSum(alice), 0) - start_time = block_time() - fast_forward(delay) - self.adjustFeeEntitlement(alice, alice, self.balanceOf(alice)) - end_time = block_time() - balance_sum = (end_time - start_time) * start_amt - self.assertEqual( - self.currentBalanceSum(alice), - balance_sum - ) - self.transfer(alice, self.havven_real.address, start_amt) - self.assertEqual(self.balanceOf(alice), 0) - fast_forward(delay) - self.adjustFeeEntitlement(alice, alice, self.balanceOf(alice)) - self.assertClose( - self.currentBalanceSum(alice), balance_sum + proxy, _ = attempt_deploy( + compiled, "Proxy", MASTER, [MASTER] ) + proxied_feetoken = W3.eth.contract(address=proxy.address, abi=feetoken_abi) - # lastAverageBalance - def test_lastAverageBalance(self): - # set the block time to be at least 30seconds away from the end of the fee_period - fee_period = self.targetFeePeriodDurationSeconds() - time_remaining = self.targetFeePeriodDurationSeconds() + self.feePeriodStartTime() - block_time() - if time_remaining < 30: - fast_forward(50) - time_remaining = self.targetFeePeriodDurationSeconds() + self.feePeriodStartTime() - block_time() - - # fast forward next block with some extra padding - delay = time_remaining + 100 - alice = fresh_account() - self.assertEqual(self.balanceOf(alice), 0) - - start_amt = UNIT * 50 - - tx_receipt = self.endow(MASTER, alice, start_amt) - self.assertEqual(self.balanceOf(alice), start_amt) - self.assertEqual(self.currentBalanceSum(alice), 0) - self.assertEqual(self.lastAverageBalance(alice), 0) - self.assertEqual(self.lastTransferTimestamp(alice), block_time(tx_receipt['blockNumber'])) - fast_forward(delay) - self._checkFeePeriodRollover(DUMMY) - fast_forward(fee_period // 2) - - tx_receipt = self.adjustFeeEntitlement(alice, alice, self.balanceOf(alice)) - block_number = tx_receipt['blockNumber'] - - duration_since_rollover = block_time(block_number) - self.feePeriodStartTime() - balance_sum = duration_since_rollover * start_amt - - actual = self.currentBalanceSum(alice) - expected = balance_sum - self.assertClose( - actual, expected - ) - - time_remaining = self.targetFeePeriodDurationSeconds() + self.feePeriodStartTime() - block_time() - fast_forward(time_remaining - 5) - self.transfer(alice, MASTER, start_amt // 2) - time_remaining = self.targetFeePeriodDurationSeconds() + self.feePeriodStartTime() - block_time() - fast_forward(time_remaining + 10) - - actual = self.lastAverageBalance(alice) - expected = (start_amt * delay) // (self.feePeriodStartTime() - self.lastFeePeriodStartTime()) - self.assertClose( - actual, expected - ) - - def test_lastAverageBalanceFullPeriod(self): - alice = fresh_account() - fee_period = self.targetFeePeriodDurationSeconds() - - # Alice will initially have 20 havvens - self.endow(MASTER, alice, 20 * UNIT) - self.assertEqual(self.balanceOf(alice), 20 * UNIT) - - # Fastforward until just before a fee period rolls over. - time_remaining = self.targetFeePeriodDurationSeconds() + self.feePeriodStartTime() - block_time() - fast_forward(time_remaining + 50) - tx_receipt = self.transfer(alice, alice, 0) - self.assertEqual(self.lastTransferTimestamp(alice), block_time(tx_receipt['blockNumber'])) - event = get_event_data_from_log(self.havven_event_dict, tx_receipt.logs[0]) - self.assertEqual(event['event'], 'FeePeriodRollover') - - # roll over the full period - fast_forward(fee_period + 50) - tx_receipt = self.transfer(alice, alice, 0) - event = get_event_data_from_log(self.havven_event_dict, tx_receipt.logs[0]) - self.assertEqual(event['event'], 'FeePeriodRollover') - self.assertEqual(self.lastTransferTimestamp(alice), block_time(tx_receipt['blockNumber'])) - self.assertEqual(self.lastAverageBalance(alice), 20 * UNIT) - - # Try a half-and-half period - time_remaining = self.targetFeePeriodDurationSeconds() + self.feePeriodStartTime() - block_time() - fast_forward(time_remaining + 50) - self.transfer(alice, MASTER, 10 * UNIT) - event = get_event_data_from_log(self.havven_event_dict, tx_receipt.logs[0]) - self.assertEqual(event['event'], 'FeePeriodRollover') - fast_forward(fee_period // 2) - tx_receipt = self.transfer(alice, MASTER, 10 * UNIT) - fast_forward(fee_period // 2 + 10) - self.recomputeLastAverageBalance(alice) - self.assertClose(self.lastAverageBalance(alice), 5 * UNIT) - - def test_arithmeticSeriesBalance(self): - alice = fresh_account() - fee_period = self.targetFeePeriodDurationSeconds() - n = 50 - - self.endow(MASTER, alice, n * UNIT) - time_remaining = self.targetFeePeriodDurationSeconds() + self.feePeriodStartTime() - block_time() - fast_forward(time_remaining + 5) - - for _ in range(n): - self.transfer(alice, MASTER, UNIT) - fast_forward(fee_period // n) - - self.recomputeLastAverageBalance(alice) - self.assertClose(self.lastAverageBalance(alice), n * (n - 1) * UNIT // (2 * n)) - - def test_averageBalanceSum(self): - alice, bob, carol = fresh_accounts(3) - fee_period = self.targetFeePeriodDurationSeconds() - - self.endow(MASTER, alice, UNIT) - - self.start_new_fee_period() - - self.transfer(alice, bob, UNIT // 4) - self.transfer(alice, carol, UNIT // 4) - fast_forward(fee_period // 10) - self.transfer(bob, carol, UNIT // 4) - fast_forward(fee_period // 10) - self.transfer(carol, bob, UNIT // 2) - fast_forward(fee_period // 10) - self.transfer(bob, alice, UNIT // 4) - fast_forward(2 * fee_period // 10) - self.transfer(alice, bob, UNIT // 3) - self.transfer(alice, carol, UNIT // 3) - fast_forward(3 * fee_period // 10) - self.transfer(carol, bob, UNIT // 3) - fast_forward(3 * fee_period // 10) - - self.recomputeLastAverageBalance(alice) - self.recomputeLastAverageBalance(bob) - self.recomputeLastAverageBalance(carol) - - total_average = self.lastAverageBalance(alice) + \ - self.lastAverageBalance(bob) + \ - self.lastAverageBalance(carol) - - self.assertClose(UNIT, total_average) - - # penultimateAverageBalance - def test_penultimateAverageBalance(self): - # start a new fee period - alice = fresh_account() - fee_period = self.targetFeePeriodDurationSeconds() - fast_forward(fee_period * 2) - self._checkFeePeriodRollover(DUMMY) - - # skip to halfway through it - delay = fee_period // 2 - fast_forward(delay) - - self.assertEqual(self.balanceOf(alice), 0) - - start_amt = UNIT * 50 - - self.endow(MASTER, alice, start_amt) - inital_transfer_time = self.lastTransferTimestamp(alice) - self.assertEqual(self.balanceOf(alice), start_amt) - self.assertEqual(self.currentBalanceSum(alice), 0) - self.assertEqual(self.lastAverageBalance(alice), 0) - - # rollover two fee periods without alice doing anything - fast_forward(fee_period * 2) - self._checkFeePeriodRollover(DUMMY) - - fast_forward(fee_period * 2) - self._checkFeePeriodRollover(DUMMY) - - # adjust alice's fee entitlement - self.adjustFeeEntitlement(alice, alice, self.balanceOf(alice)) - - # expected currentBalance sum is balance*(time since start of period) - actual = self.currentBalanceSum(alice) - expected = (block_time() - self.feePeriodStartTime()) * start_amt - self.assertClose( - actual, expected + feetoken_event_dict = generate_topic_event_map(feetoken_abi) + feetoken_contract_1, construction_txr_1 = attempt_deploy( + compiled, "PublicFeeToken", MASTER, + [proxy.address, "Test Fee Token", "FEE", UNIT // 20, MASTER, MASTER] ) - last_period_delay = (self.feePeriodStartTime() - self.lastFeePeriodStartTime()) - - actual = self.lastAverageBalance(alice) - expected = (start_amt * last_period_delay) // last_period_delay - self.assertClose( - actual, expected, - msg='last:' + feetoken_contract_2, construction_txr_2 = attempt_deploy( + compiled, "PublicFeeToken", MASTER, + [proxy.address, "Test Fee Token 2", "FEE", UNIT // 20, MASTER, MASTER] ) - delay_from_transfer = self.lastFeePeriodStartTime() - inital_transfer_time - penultimate_period_duration = self.lastFeePeriodStartTime() - self.penultimateFeePeriodStartTime() - - actual = self.penultimateAverageBalance(alice) - expected = (start_amt * delay_from_transfer) // penultimate_period_duration - self.assertClose( - actual, expected, - msg='penultimate:' + feestate, txr = attempt_deploy( + compiled, "TokenState", MASTER, + [MASTER, MASTER] ) - # lastTransferTimestamp - tested above - # hasWithdrawnLastPeriodFees - tested in test_FeeCollection.py - # lastFeesCollected - tested in test_FeeCollection.py - - ### - # Contract variables - ### - # feePeriodStartTime - tested above - # targetFeePeriodDurationSeconds - tested above - # MIN_FEE_PERIOD_DURATION_SECONDS - constant, checked in constructor test - - ### - # Functions - ### - - # setNomin - def test_setNomin(self): - alice = fresh_account() - self.setNomin(MASTER, alice) - self.assertEqual(self.get_nomin(), alice) - - def test_invalidSetNomin(self): - alice = fresh_account() - self.assertReverts(self.setNomin, alice, alice) - - # setEscrow - def test_setEscrow(self): - alice = fresh_account() - self.setEscrow(MASTER, alice) - self.assertEqual(self.get_escrow(), alice) - - def test_invalidSetEscrow(self): - alice = fresh_account() - self.assertReverts(self.setEscrow, alice, alice) - - # setTargetFeePeriod - def test_setTargetFeePeriod(self): - self.setTargetFeePeriodDuration(MASTER, to_seconds(weeks=10)) - self.assertEqual( - self.targetFeePeriodDurationSeconds(), - to_seconds(weeks=10) - ) - - def test_setTargetFeePeriod_max(self): - sixmonths = 26 * 7 * 24 * 60 * 60 - self.assertReverts(self.setTargetFeePeriodDuration, MASTER, 2 ** 256 - 1) - self.assertReverts(self.setTargetFeePeriodDuration, MASTER, sixmonths + 1) - self.setTargetFeePeriodDuration(MASTER, sixmonths) - self.assertEqual( - self.targetFeePeriodDurationSeconds(), - sixmonths + mine_txs([ + proxy.functions.setTarget(feetoken_contract_1.address).transact({'from': MASTER}), + feestate.functions.setBalanceOf(DUMMY, 1000 * UNIT).transact({'from': MASTER}), + feestate.functions.setAssociatedContract(feetoken_contract_1.address).transact({'from': MASTER}), + feetoken_contract_1.functions.setTokenState(feestate.address).transact({'from': MASTER})] ) - def test_setTargetFeePeriod_minimal(self): - self.setTargetFeePeriodDuration(MASTER, self.MIN_FEE_PERIOD_DURATION_SECONDS()) - self.assertEqual( - self.targetFeePeriodDurationSeconds(), - self.MIN_FEE_PERIOD_DURATION_SECONDS() - ) - - def test_setTargetFeePeriod_invalid_below_min(self): - self.assertReverts(self.setTargetFeePeriodDuration, MASTER, self.MIN_FEE_PERIOD_DURATION_SECONDS() - 1) - - def test_setTargetFeePeriod_invalid_0(self): - self.assertReverts(self.setTargetFeePeriodDuration, MASTER, self.MIN_FEE_PERIOD_DURATION_SECONDS() - 1) - - # endow - def test_endow_valid(self): - amount = 50 * UNIT - havven_balance = self.balanceOf(self.havven_real.address) - alice = fresh_account() - self.assertEqual(self.balanceOf(alice), 0) - self.endow(MASTER, alice, amount) - self.assertEqual(self.balanceOf(alice), amount) - self.assertEqual(havven_balance - self.balanceOf(self.havven_real.address), amount) - - def test_endow_0(self): - amount = 0 - havven_balance = self.balanceOf(self.havven_real.address) - alice = fresh_account() - self.assertEqual(self.balanceOf(alice), 0) - self.endow(MASTER, alice, amount) - self.assertEqual(self.balanceOf(alice), amount) - self.assertEqual(havven_balance - self.balanceOf(self.havven_real.address), amount) - - def test_endow_supply(self): - amount = self.totalSupply() - havven_balance = self.balanceOf(self.havven_real.address) - alice = fresh_account() - self.assertEqual(self.balanceOf(alice), 0) - self.endow(MASTER, alice, amount) - self.assertEqual(self.balanceOf(alice), amount) - self.assertEqual(havven_balance - self.balanceOf(self.havven_real.address), amount) - - def test_endow_more_than_supply(self): - amount = self.totalSupply() * 2 - alice = fresh_account() - self.assertReverts(self.endow, MASTER, alice, amount) - self.assertEqual(self.balanceOf(alice), 0) - - def test_endow_invalid_sender(self): - amount = 50 * UNIT - alice = fresh_account() - self.assertReverts(self.endow, alice, alice, amount) - self.assertEqual(self.balanceOf(alice), 0) - - def test_endow_contract_sender(self): - amount = 50 * UNIT - alice = fresh_account() - self.assertReverts(self.endow, self.havven_real.address, alice, amount) - self.assertEqual(self.balanceOf(alice), 0) - - def test_endow_to_contract(self): - amount = 50 * UNIT - self.assertEqual(self.balanceOf(self.havven_real.address), self.totalSupply()) - self.endow(MASTER, self.havven_real.address, amount) - self.assertEqual(self.balanceOf(self.havven_real.address), self.totalSupply()) - # Balance is not lost (still distributable) if sent to the contract. - self.endow(MASTER, self.havven_real.address, amount) - - def test_endow_currentBalanceSum(self): - amount = 50 * UNIT - # Force updates. - self.endow(MASTER, self.havven_real.address, 0) - havven_balanceSum = self.currentBalanceSum(self.havven_real.address) - alice = fresh_account() - fast_forward(seconds=60) - self.endow(MASTER, alice, amount) - self.assertGreater(self.currentBalanceSum(self.havven_real.address), havven_balanceSum) - - def test_endow_transfers(self): - alice = fresh_account() - self.recomputeLastAverageBalance(MASTER) - tx_receipt = self.endow(MASTER, alice, 50 * UNIT) - event = get_event_data_from_log(self.havven_event_dict, tx_receipt.logs[0]) - self.assertEqual(event['event'], 'Transfer') - - # transfer - def test_transferRollsOver(self): - alice = fresh_account() - self.endow(MASTER, alice, 50 * UNIT) - fast_forward(seconds=self.targetFeePeriodDurationSeconds() + 100) - tx_receipt = self.transfer(alice, MASTER, 25 * UNIT) - event = get_event_data_from_log(self.havven_event_dict, tx_receipt.logs[0]) - self.assertEqual(event['event'], 'FeePeriodRollover') - - # same as test_ExternStateProxyToken - def test_transfer(self): - sender, receiver, no_tokens = fresh_accounts(3) - self.endow(MASTER, sender, 50 * UNIT) - sender_balance = self.balanceOf(sender) - - receiver_balance = self.balanceOf(receiver) - self.assertEqual(receiver_balance, 0) - - value = 10 * UNIT - total_supply = self.totalSupply() - - # This should fail because receiver has no tokens - self.assertReverts(self.transfer, receiver, sender, value) - - self.transfer(sender, receiver, value) - self.assertEqual(self.balanceOf(receiver), receiver_balance + value) - self.assertEqual(self.balanceOf(sender), sender_balance - value) - - # transfers should leave the supply unchanged - self.assertEqual(self.totalSupply(), total_supply) - - value = 1001 * UNIT - # This should fail because balance < value and balance > totalSupply - self.assertReverts(self.transfer, sender, receiver, value) - - # 0 value transfers are allowed. - value = 0 - pre_sender_balance = self.balanceOf(sender) - pre_receiver_balance = self.balanceOf(receiver) - self.transfer(sender, receiver, value) - self.assertEqual(self.balanceOf(receiver), pre_receiver_balance) - self.assertEqual(self.balanceOf(sender), pre_sender_balance) - - # It is also possible to send 0 value transfer from an account with 0 balance. - self.assertEqual(self.balanceOf(no_tokens), 0) - self.transfer(no_tokens, receiver, value) - self.assertEqual(self.balanceOf(no_tokens), 0) - - # transferFrom - def test_transferFromRollsOver(self): - alice = fresh_account() - self.endow(MASTER, alice, 50 * UNIT) - self.approve(alice, MASTER, 25 * UNIT) - fast_forward(seconds=self.targetFeePeriodDurationSeconds() + 100) - tx_receipt = self.transferFrom(MASTER, alice, MASTER, 25 * UNIT) - event = get_event_data_from_log(self.havven_event_dict, tx_receipt.logs[0]) - self.assertEqual(event['event'], 'FeePeriodRollover') - - def test_transferFrom(self): - approver, spender, receiver, no_tokens = fresh_accounts(4) - - self.endow(MASTER, approver, 50 * UNIT) - - approver_balance = self.balanceOf(approver) - spender_balance = self.balanceOf(spender) - receiver_balance = self.balanceOf(receiver) - - value = 10 * UNIT - total_supply = self.totalSupply() - - # This fails because there has been no approval yet - self.assertReverts(self.transferFrom, spender, approver, receiver, value) - - self.approve(approver, spender, 2 * value) - self.assertEqual(self.allowance(approver, spender), 2 * value) - - self.assertReverts(self.transferFrom, spender, approver, receiver, 2 * value + 1) - self.transferFrom(spender, approver, receiver, value) - - self.assertEqual(self.balanceOf(approver), approver_balance - value) - self.assertEqual(self.balanceOf(spender), spender_balance) - self.assertEqual(self.balanceOf(receiver), receiver_balance + value) - self.assertEqual(self.allowance(approver, spender), value) - self.assertEqual(self.totalSupply(), total_supply) - - # Empty the account - self.transferFrom(spender, approver, receiver, value) - - # This account has no tokens - approver_balance = self.balanceOf(no_tokens) - self.assertEqual(approver_balance, 0) - self.assertEqual(self.allowance(no_tokens, spender), 0) - - self.approve(no_tokens, spender, value) - self.assertEqual(self.allowance(no_tokens, spender), value) - - # This should fail because the approver has no tokens. - self.assertReverts(self.transferFrom, spender, no_tokens, receiver, value) - - def test_double_withdraw_fee(self): - alice = fresh_account() - self.withdrawFeeEntitlement(alice) - self.assertReverts(self.withdrawFeeEntitlement, alice) - - def test_withdraw_multiple_periods(self): - alice = fresh_account() - self.withdrawFeeEntitlement(alice) - fast_forward(self.targetFeePeriodDurationSeconds() * 2) - self.rolloverFeePeriod(DUMMY) - self.withdrawFeeEntitlement(alice) - fast_forward(self.targetFeePeriodDurationSeconds() * 2) - self.rolloverFeePeriod(DUMMY) - - # adjustFeeEntitlement - tested above - # rolloverFee - tested above, indirectly - - # withdrawFeeEntitlement - tested in test_FeeCollection.py - - ### - # Modifiers - ### - # postCheckFeePeriodRollover - tested above - def test_checkFeePeriodRollover_escrow_exists(self): - fast_forward(seconds=self.targetFeePeriodDurationSeconds() + 10) - - pre_feePeriodStartTime = self.feePeriodStartTime() - # This should work fine. - self._checkFeePeriodRollover(MASTER) - self.assertGreater(self.feePeriodStartTime(), pre_feePeriodStartTime) - - fast_forward(seconds=self.targetFeePeriodDurationSeconds() + 10) - pre_feePeriodStartTime = self.feePeriodStartTime() - # And so should this - self.setEscrow(MASTER, ZERO_ADDRESS) - self._checkFeePeriodRollover(MASTER) - self.assertGreater(self.feePeriodStartTime(), pre_feePeriodStartTime) - - def test_abuse_havven_balance(self): - """Test whether repeatedly moving havvens between two parties will shift averages upwards""" - alice, bob = fresh_accounts(2) - amount = UNIT * 100000 - a_sum = 0 - b_sum = 0 - self.endow(MASTER, alice, amount) - time = block_time() - self.assertEqual(self.balanceOf(alice), amount) - self.assertEqual(self.currentBalanceSum(alice), 0) - for i in range(20): - self.transfer(alice, bob, amount) - a_sum += (block_time() - time) * amount - time = block_time() - self.assertEqual(self.balanceOf(bob), amount) - self.assertEqual(self.currentBalanceSum(alice), a_sum) - self.transfer(bob, alice, amount) - b_sum += (block_time() - time) * amount - time = block_time() - self.assertEqual(self.balanceOf(alice), amount) - self.assertEqual(self.currentBalanceSum(bob), b_sum) - - - - - - -class TestEtherNomin(unittest.TestCase): - def setUp(self): - self.snapshot = take_snapshot() - utils.generalutils.time_fast_forwarded = 0 - self.initial_time = round(time.time()) - # Reset the price at the start of tests so that it's never stale. - self.updatePrice(self.oracle(), self.etherPrice(), self.now_block_time() + 1) - fast_forward(2) - # Reset the liquidation timestamp so that it's never active. - owner = self.owner() - self.forceLiquidation(owner) - self.terminateLiquidation(owner) - - def tearDown(self): - restore_snapshot(self.snapshot) - - def test_time_elapsed(self): - return utils.generalutils.time_fast_forwarded + (round(time.time()) - self.initial_time) - - def now_block_time(self): - return block_time() + self.test_time_elapsed() + return compiled, proxy, proxied_feetoken, feetoken_contract_1, feetoken_contract_2, feetoken_event_dict, feestate @classmethod def setUpClass(cls): - cls.assertReverts = assertReverts - - compiled = compile_contracts([ETHERNOMIN_SOURCE, FAKECOURT_SOURCE, PROXY_SOURCE], - remappings=['""=contracts']) - cls.nomin_abi = compiled['PublicEtherNomin']['abi'] - cls.nomin_event_dict = generate_topic_event_map(cls.nomin_abi) - - cls.nomin_havven = W3.eth.accounts[1] - cls.nomin_oracle = W3.eth.accounts[2] - cls.nomin_beneficiary = W3.eth.accounts[3] - cls.nomin_owner = W3.eth.accounts[0] - - cls.nomin_real, cls.construction_txr = attempt_deploy(compiled, 'PublicEtherNomin', MASTER, - [cls.nomin_havven, cls.nomin_oracle, - cls.nomin_beneficiary, - 1000 * UNIT, cls.nomin_owner, ZERO_ADDRESS]) - cls.construction_price_time = cls.nomin_real.functions.lastPriceUpdateTime().call() - cls.initial_time = cls.construction_price_time - - cls.fake_court, _ = attempt_deploy(compiled, 'FakeCourt', MASTER, []) + cls.compiled, cls.proxy, cls.proxied_feetoken, cls.feetoken_contract_1, cls.feetoken_contract_2, cls.feetoken_event_dict, cls.feestate = cls.deployContracts() - cls.fake_court.setNomin = lambda sender, new_nomin: mine_tx( - cls.fake_court.functions.setNomin(new_nomin).transact({'from': sender})) - cls.fake_court.setConfirming = lambda sender, target, status: mine_tx( - cls.fake_court.functions.setConfirming(target, status).transact({'from': sender})) - cls.fake_court.setVotePasses = lambda sender, target, status: mine_tx( - cls.fake_court.functions.setVotePasses(target, status).transact({'from': sender})) - cls.fake_court.setTargetMotionID = lambda sender, target, motion_id: mine_tx( - cls.fake_court.functions.setTargetMotionID(target, motion_id).transact({'from': sender})) - cls.fake_court.confiscateBalance = lambda sender, target: mine_tx( - cls.fake_court.functions.confiscateBalance(target).transact({'from': sender})) - cls.fake_court.setNomin(W3.eth.accounts[0], cls.nomin_real.address) + cls.initial_beneficiary = DUMMY + cls.fee_authority = fresh_account() - cls.nomin_proxy, _ = attempt_deploy(compiled, 'Proxy', - MASTER, [cls.nomin_real.address, cls.nomin_owner]) - mine_tx(cls.nomin_real.functions.setProxy(cls.nomin_proxy.address).transact({'from': cls.nomin_owner})) - cls.nomin = W3.eth.contract(address=cls.nomin_proxy.address, abi=compiled['PublicEtherNomin']['abi']) + cls.feetoken = FeeTokenInterface(cls.proxied_feetoken, "FeeToken") + cls.proxy = ProxyInterface(cls.proxy, "Proxy") + cls.feestate = TokenStateInterface(cls.feestate, "TokenState") - mine_tx(cls.nomin_real.functions.setCourt(cls.fake_court.address).transact({'from': cls.nomin_owner})) - - cls.owner = lambda self: cls.nomin.functions.owner().call() - cls.oracle = lambda self: cls.nomin.functions.oracle().call() - cls.court = lambda self: cls.nomin.functions.court().call() - cls.beneficiary = lambda self: cls.nomin.functions.beneficiary().call() - cls.nominPool = lambda self: cls.nomin.functions.nominPool().call() - cls.poolFeeRate = lambda self: cls.nomin.functions.poolFeeRate().call() - cls.liquidationPeriod = lambda self: cls.nomin.functions.liquidationPeriod().call() - cls.liquidationTimestamp = lambda self: cls.nomin.functions.liquidationTimestamp().call() - cls.etherPrice = lambda self: cls.nomin.functions.etherPrice().call() - cls.frozen = lambda self, address: cls.nomin.functions.frozen(address).call() - cls.lastPriceUpdateTime = lambda self: cls.nomin.functions.lastPriceUpdateTime().call() - cls.stalePeriod = lambda self: cls.nomin.functions.stalePeriod().call() - - cls.nominateOwner = lambda self, sender, address: mine_tx( - cls.nomin.functions.nominateOwner(address).transact({'from': sender})) - cls.acceptOwnership = lambda self, sender: mine_tx( - cls.nomin.functions.acceptOwnership().transact({'from': sender})) - cls.setOracle = lambda self, sender, address: mine_tx( - cls.nomin.functions.setOracle(address).transact({'from': sender})) - cls.setCourt = lambda self, sender, address: mine_tx( - cls.nomin.functions.setCourt(address).transact({'from': sender})) - cls.setBeneficiary = lambda self, sender, address: mine_tx( - cls.nomin.functions.setBeneficiary(address).transact({'from': sender})) - cls.setPoolFeeRate = lambda self, sender, rate: mine_tx( - cls.nomin.functions.setPoolFeeRate(rate).transact({'from': sender})) - cls.updatePrice = lambda self, sender, price, timeSent: mine_tx( - cls.nomin_real.functions.updatePrice(price, timeSent).transact({'from': sender})) - cls.setStalePeriod = lambda self, sender, period: mine_tx( - cls.nomin.functions.setStalePeriod(period).transact({'from': sender})) - - cls.fiatValue = lambda self, eth: cls.nomin.functions.fiatValue(eth).call() - cls.fiatBalance = lambda self: cls.nomin.functions.fiatBalance().call() - cls.collateralisationRatio = lambda self: cls.nomin.functions.collateralisationRatio().call() - cls.etherValue = lambda self, fiat: cls.nomin.functions.etherValue(fiat).call() - cls.etherValueAllowStale = lambda self, fiat: cls.nomin.functions.publicEtherValueAllowStale(fiat).call() - cls.poolFeeIncurred = lambda self, n: cls.nomin.functions.poolFeeIncurred(n).call() - cls.purchaseCostFiat = lambda self, n: cls.nomin.functions.purchaseCostFiat(n).call() - cls.purchaseCostEther = lambda self, n: cls.nomin.functions.purchaseCostEther(n).call() - cls.saleProceedsFiat = lambda self, n: cls.nomin.functions.saleProceedsFiat(n).call() - cls.saleProceedsEther = lambda self, n: cls.nomin.functions.saleProceedsEther(n).call() - cls.saleProceedsEtherAllowStale = lambda self, n: cls.nomin.functions.publicSaleProceedsEtherAllowStale( - n).call() - cls.priceIsStale = lambda self: cls.nomin.functions.priceIsStale().call() - cls.isLiquidating = lambda self: cls.nomin.functions.isLiquidating().call() - cls.canSelfDestruct = lambda self: cls.nomin.functions.canSelfDestruct().call() - - cls.transferPlusFee = lambda self, value: cls.nomin.functions.transferPlusFee(value).call() - cls.transfer = lambda self, sender, recipient, value: mine_tx( - cls.nomin.functions.transfer(recipient, value).transact({'from': sender})) - cls.transferFrom = lambda self, sender, fromAccount, to, value: mine_tx( - cls.nomin.functions.transferFrom(fromAccount, to, value).transact({'from': sender})) - cls.approve = lambda self, sender, spender, value: mine_tx( - cls.nomin.functions.approve(spender, value).transact({'from': sender})) - cls.replenishPool = lambda self, sender, n, value: mine_tx( - cls.nomin.functions.replenishPool(n).transact({'from': sender, 'value': value})) - cls.diminishPool = lambda self, sender, n: mine_tx(cls.nomin.functions.diminishPool(n).transact({'from': sender})) - cls.buy = lambda self, sender, n, value: mine_tx( - cls.nomin.functions.buy(n).transact({'from': sender, 'value': value})) - cls.sell = lambda self, sender, n: mine_tx( - cls.nomin.functions.sell(n).transact({'from': sender, 'gasPrice': 10})) - - cls.forceLiquidation = lambda self, sender: mine_tx( - cls.nomin.functions.forceLiquidation().transact({'from': sender})) - cls.liquidate = lambda self, sender: mine_tx(cls.nomin.functions.liquidate().transact({'from': sender})) - cls.extendLiquidationPeriod = lambda self, sender, extension: mine_tx( - cls.nomin.functions.extendLiquidationPeriod(extension).transact({'from': sender})) - cls.terminateLiquidation = lambda self, sender: mine_tx( - cls.nomin.functions.terminateLiquidation().transact({'from': sender})) - cls.selfDestruct = lambda self, sender: mine_tx(cls.nomin.functions.selfDestruct().transact({'from': sender})) - - cls.confiscateBalance = lambda self, sender, target: mine_tx( - cls.nomin.functions.confiscateBalance(target).transact({'from': sender})) - cls.unfreezeAccount = lambda self, sender, target: mine_tx( - cls.nomin.functions.unfreezeAccount(target).transact({'from': sender})) - - cls.name = lambda self: cls.nomin.functions.name().call() - cls.symbol = lambda self: cls.nomin.functions.symbol().call() - cls.totalSupply = lambda self: cls.nomin.functions.totalSupply().call() - cls.balanceOf = lambda self, account: cls.nomin.functions.balanceOf(account).call() - cls.transferFeeRate = lambda self: cls.nomin.functions.transferFeeRate().call() - cls.feePool = lambda self: cls.nomin.functions.feePool().call() - cls.feeAuthority = lambda self: cls.nomin.functions.feeAuthority().call() - - cls.debugWithdrawAllEther = lambda self, sender, recipient: mine_tx( - cls.nomin.functions.debugWithdrawAllEther(recipient).transact({'from': sender})) - cls.debugEmptyFeePool = lambda self, sender: mine_tx( - cls.nomin.functions.debugEmptyFeePool().transact({'from': sender})) - cls.debugFreezeAccount = lambda self, sender, target: mine_tx( - cls.nomin.functions.debugFreezeAccount(target).transact({'from': sender})) + cls.feetoken.setFeeAuthority(MASTER, cls.fee_authority) def test_constructor(self): - # Nomin-specific members - self.assertEqual(self.owner(), self.nomin_owner) - self.assertEqual(self.oracle(), self.nomin_oracle) - self.assertEqual(self.beneficiary(), self.nomin_beneficiary) - self.assertEqual(self.etherPrice(), 1000 * UNIT) - self.assertEqual(self.stalePeriod(), 60 * 60) # default one hour - self.assertEqual(self.liquidationTimestamp(), 2**256 - 1) - self.assertEqual(self.liquidationPeriod(), 14 * 24 * 60 * 60) # default fourteen days - self.assertEqual(self.poolFeeRate(), UNIT / 200) # default fifty basis points - self.assertEqual(self.nominPool(), 0) - construct_time = block_time(self.construction_txr.blockNumber) - self.assertEqual(construct_time, self.construction_price_time) - self.assertTrue(self.frozen(self.nomin_real.address)) - - # ExternStateProxyFeeToken members - self.assertEqual(self.name(), "Ether-Backed USD Nomins") - self.assertEqual(self.symbol(), "eUSD") - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.balanceOf(MASTER), 0) - self.assertEqual(self.transferFeeRate(), 15 * UNIT // 10000) - self.assertEqual(self.feeAuthority(), self.nomin_havven) - self.assertEqual(self.nomin.functions.decimals().call(), 18) - - def test_getSetOwner(self): - pre_owner = self.owner() - new_owner = DUMMY - - # Only the owner must be able to set the owner. - self.assertReverts(self.nominateOwner, new_owner, new_owner) - self.nominateOwner(pre_owner, new_owner) - self.acceptOwnership(new_owner) - self.assertEqual(self.owner(), new_owner) - - def test_getSetOracle(self): - pre_oracle = self.oracle() - new_oracle = DUMMY - - # Only the owner must be able to set the oracle. - self.assertReverts(self.setOracle, new_oracle, new_oracle) - - self.setOracle(self.owner(), new_oracle) - self.assertEqual(self.oracle(), new_oracle) - - def test_getSetCourt(self): - new_court = DUMMY - - # Only the owner must be able to set the court. - self.assertReverts(self.setOracle, new_court, new_court) - - self.setCourt(self.owner(), new_court) - self.assertEqual(self.court(), new_court) - - def test_getSetBeneficiary(self): - new_beneficiary = DUMMY - - # Only the owner must be able to set the beneficiary. - self.assertReverts(self.setBeneficiary, new_beneficiary, new_beneficiary) - - self.setBeneficiary(self.owner(), new_beneficiary) - self.assertEqual(self.beneficiary(), new_beneficiary) - - def test_getSetPoolFeeRate(self): - owner = self.owner() - new_rate = UNIT // 10 - - # Only the owner must be able to set the pool fee rate. - self.assertReverts(self.setPoolFeeRate, DUMMY, new_rate) - - # Pool fee rate must be no greater than UNIT. - self.assertReverts(self.setPoolFeeRate, owner, UNIT + 1) - self.assertReverts(self.setPoolFeeRate, owner, 2**256 - 1) - self.setPoolFeeRate(owner, UNIT) - self.assertEqual(self.poolFeeRate(), UNIT) - - self.setPoolFeeRate(owner, new_rate) - self.assertEqual(self.poolFeeRate(), new_rate) - - def test_getSetStalePeriod(self): - owner = self.owner() - new_period = 52 * 7 * 24 * 60 * 60 - - # Only the owner should be able to set the pool fee rate. - self.assertReverts(self.setStalePeriod, DUMMY, new_period) - - self.setStalePeriod(owner, new_period) - self.assertEqual(self.stalePeriod(), new_period) - - def test_updatePrice(self): - owner = self.owner() - pre_price = self.etherPrice() - new_price = 10**8 * UNIT # one hundred million dollar ethers $$$$$$ - new_price2 = UNIT // 10**6 # one ten thousandth of a cent ethers :( - pre_oracle = self.oracle() - new_oracle = DUMMY - - # Only the oracle must be able to set the current price. - self.assertReverts(self.updatePrice, new_oracle, new_price, self.now_block_time()) - - # Check if everything works with nothing in the pool. - t = self.now_block_time() - tx_receipt = self.updatePrice(pre_oracle, new_price, t) - tx_time = W3.eth.getBlock(tx_receipt.blockNumber)['timestamp'] - fast_forward(2) - self.assertEqual(self.lastPriceUpdateTime(), t) - self.assertEqual(self.etherPrice(), new_price) - - self.setOracle(owner, new_oracle) - - self.assertReverts(self.updatePrice, pre_oracle, pre_price, self.now_block_time()) - - t = self.now_block_time() - tx_receipt = self.updatePrice(new_oracle, new_price2, t) - tx_time = W3.eth.getBlock(tx_receipt.blockNumber)['timestamp'] - fast_forward(2) - - self.assertEqual(self.lastPriceUpdateTime(), t) - self.assertEqual(self.etherPrice(), new_price2) - - # Check if everything works with something in the pool. - self.updatePrice(new_oracle, UNIT, self.now_block_time()) - fast_forward(2) - backing = self.etherValue(10 * UNIT) - self.replenishPool(owner, UNIT, backing) - - t = self.now_block_time() - tx_receipt = self.updatePrice(new_oracle, pre_price, t) - fast_forward(2) - tx_time = W3.eth.getBlock(tx_receipt.blockNumber)['timestamp'] - self.assertEqual(self.lastPriceUpdateTime(), t) - self.assertEqual(self.etherPrice(), pre_price) - - # Check that an old transaction doesn't overwrite a new one. - t = self.now_block_time() - tx_receipt = self.updatePrice(new_oracle, pre_price, t) - tx_time = W3.eth.getBlock(tx_receipt.blockNumber)['timestamp'] - self.assertEqual(self.lastPriceUpdateTime(), t) - self.assertEqual(self.etherPrice(), pre_price) - # A transaction that was sent 10 seconds before the above one should fail. - self.assertReverts(self.updatePrice, new_oracle, new_price2, t - 10) - fast_forward(2) - - # Check that a transaction with the same sentTime doesn't overwrite the most recently received one. - t = self.now_block_time() - tx_receipt = self.updatePrice(new_oracle, pre_price, t) - tx_time = W3.eth.getBlock(tx_receipt.blockNumber)['timestamp'] - self.assertEqual(self.lastPriceUpdateTime(), t) - self.assertEqual(self.etherPrice(), pre_price) - # A transaction that was sent at the same time as the last one should fail. - self.assertReverts(self.updatePrice, new_oracle, new_price2, t) - # A transaction send more than 10 minutes in the future should not work. - self.assertReverts(self.updatePrice, new_oracle, new_price2, t + to_seconds(minutes=10) + 10) - # ...but 9 minutes should work. - self.updatePrice(new_oracle, new_price2, t + to_seconds(minutes=9)) - - def test_fiatValue(self): - oracle = self.oracle() - - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.fiatValue(ETHER), ETHER) - self.assertEqual(self.fiatValue(777 * ETHER), 777 * ETHER) - self.assertEqual(self.fiatValue(ETHER // 777), ETHER // 777) - self.assertEqual(self.fiatValue(10**8 * ETHER), 10**8 * ETHER) - self.assertEqual(self.fiatValue(ETHER // 10**12), ETHER // 10**12) - - self.updatePrice(oracle, 10**8 * UNIT, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.fiatValue(ETHER), 10**8 * ETHER) - self.assertEqual(self.fiatValue(317 * ETHER), 317 * 10**8 * ETHER) - self.assertEqual(self.fiatValue(ETHER // 317), 10**8 * (ETHER // 317)) - self.assertEqual(self.fiatValue(10**8 * ETHER), 10**16 * ETHER) - self.assertEqual(self.fiatValue(ETHER // 10**12), ETHER // 10**4) - - self.updatePrice(oracle, UNIT // 10**12, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.fiatValue(ETHER), ETHER // 10**12) - self.assertEqual(self.fiatValue(10**15 * ETHER), 10**3 * ETHER) - self.assertEqual(self.fiatValue((7 * ETHER) // 3), ((7 * ETHER) // 3) // 10**12) - - def test_fiatBalance(self): - owner = self.owner() - oracle = self.oracle() - pre_price = self.etherPrice() - - send_value(owner, self.nomin_real.address, ETHER // 2) - send_value(owner, self.nomin.address, ETHER // 2) - self.assertEqual(self.fiatBalance(), pre_price) - self.updatePrice(oracle, UNIT // 10**12, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.fiatBalance(), UNIT // 10**12) - self.updatePrice(oracle, 300 * UNIT, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.fiatBalance(), 300 * UNIT) - send_value(owner, self.nomin_real.address, ETHER // 2) - send_value(owner, self.nomin.address, ETHER // 2) - self.assertEqual(self.fiatBalance(), 600 * UNIT) - - def test_etherValue(self): - oracle = self.oracle() - - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.etherValue(UNIT), ETHER) - self.assertEqual(self.etherValue(777 * UNIT), 777 * ETHER) - self.assertEqual(self.etherValue(UNIT // 777), ETHER // 777) - self.assertEqual(self.etherValue(10**8 * UNIT), 10**8 * ETHER) - self.assertEqual(self.etherValue(UNIT // 10**12), ETHER // 10**12) - - self.updatePrice(oracle, 10 * UNIT, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.etherValue(UNIT), ETHER // 10) - self.assertEqual(self.etherValue(2 * UNIT), ETHER // 5) - - for v in [0.0004, 2.1, 1, 49994, 49.29384, 0.00000028, 1235759872, 2.5 * 10**25]: - vi = int(v * UNIT) - self.assertEqual(self.etherValue(vi), self.etherValueAllowStale(vi)) - - def test_collateralisationRatio(self): - owner = self.owner() - oracle = self.oracle() - - # When there are no nomins in the contract, a zero denominator causes reversion. - self.assertReverts(self.collateralisationRatio) - - # Set the ether price to $1, and issue one nomin against 2 ether. - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.replenishPool(owner, UNIT, 2 * ETHER) - self.assertEqual(self.collateralisationRatio(), 2 * UNIT) - - # Set the ether price to $2, now the collateralisation ratio should double to 4. - self.updatePrice(oracle, 2 * UNIT, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.collateralisationRatio(), 4 * UNIT) - - # Now set the ether price to 50 cents, so that the collateralisation is exactly 1 - self.updatePrice(oracle, UNIT // 2, self.now_block_time()) - fast_forward(2) - # (this should not trigger liquidation) - self.assertFalse(self.isLiquidating()) - self.assertEqual(self.collateralisationRatio(), UNIT) - - # Now double the ether in the contract to 2. - send_value(owner, self.nomin.address, ETHER) - send_value(owner, self.nomin_real.address, ETHER) - self.assertEqual(self.collateralisationRatio(), 2 * UNIT) - - def test_poolFeeIncurred(self): - poolFeeRate = self.poolFeeRate() - - self.assertEqual(self.poolFeeIncurred(0), 0) - self.assertEqual(self.poolFeeIncurred(UNIT), poolFeeRate) - self.assertEqual(self.poolFeeIncurred(10 * UNIT), 10 * poolFeeRate) - self.assertEqual(self.poolFeeIncurred(UNIT // 2), poolFeeRate // 2) - self.setPoolFeeRate(self.owner(), UNIT // 10**7) - self.assertEqual(self.poolFeeIncurred(UNIT), UNIT // 10**7) - self.assertEqual(self.poolFeeIncurred(100 * UNIT), UNIT // 10**5) - self.assertEqual(self.poolFeeIncurred(UNIT // 2), UNIT // (2 * 10**7)) - - def test_purchaseCostFiat(self): - owner = self.owner() - poolFeeRate = self.poolFeeRate() - - self.assertGreater(self.purchaseCostFiat(UNIT), UNIT) - self.assertGreater(self.purchaseCostFiat(UNIT), self.saleProceedsFiat(UNIT)) - - self.assertEqual(self.purchaseCostFiat(0), 0) - self.assertEqual(self.purchaseCostFiat(UNIT), UNIT + poolFeeRate) - self.assertEqual(self.purchaseCostFiat(10 * UNIT), 10 * (UNIT + poolFeeRate)) - self.assertEqual(self.purchaseCostFiat(UNIT // 2), (UNIT + poolFeeRate) // 2) - self.setPoolFeeRate(owner, UNIT // 10**7) - self.assertEqual(self.purchaseCostFiat(UNIT), (UNIT + UNIT // 10**7)) - self.assertEqual(self.purchaseCostFiat(100 * UNIT), 100 * (UNIT + UNIT // 10**7)) - self.assertEqual(self.purchaseCostFiat(UNIT // 2), (UNIT + UNIT // 10**7) // 2) - self.setPoolFeeRate(owner, poolFeeRate) - - def test_purchaseCostEther(self): - owner = self.owner() - oracle = self.oracle() - poolFeeRate = self.poolFeeRate() - - self.assertGreater(self.purchaseCostEther(UNIT), self.etherValue(UNIT)) - self.assertGreater(self.purchaseCostEther(UNIT), self.saleProceedsEther(UNIT)) - - self.assertEqual(self.purchaseCostEther(0), 0) - - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.purchaseCostEther(UNIT), UNIT + poolFeeRate) - self.assertEqual(self.purchaseCostEther(UNIT // 2), (UNIT + poolFeeRate) // 2) - self.setPoolFeeRate(owner, UNIT // 10**7) - self.assertEqual(self.purchaseCostEther(UNIT), (UNIT + UNIT // 10**7)) - self.assertEqual(self.purchaseCostEther(100 * UNIT), 100 * (UNIT + UNIT // 10**7)) - - self.setPoolFeeRate(owner, poolFeeRate) - self.updatePrice(oracle, UNIT // 2, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.purchaseCostEther(UNIT // 2), UNIT + poolFeeRate) - self.assertEqual(self.purchaseCostEther(3 * UNIT), 6 * (UNIT + poolFeeRate)) - - def test_purchaseCostEtherShoppingSpree(self): - owner = self.owner() - oracle = self.oracle() - self.replenishPool(owner, 800000 * UNIT, 8 * 10**9 * UNIT) - - price_multiples = [12398, 1.2384889, 7748.22, 0.238838, 0.00049944, 5.7484, 87.2211111] - qty_multiples = [2.3, 84.4828, 284.10002, 0.4992, 105.289299991, 7.651948, 0.01, 100000] - - total_qty = 0 - total_cost = 0 - pre_balance = get_eth_balance(owner) - - for price_mult in price_multiples: - for qty_mult in qty_multiples: - price = int(price_mult * UNIT) - qty = int(qty_mult * UNIT) - total_qty += qty - self.updatePrice(oracle, price, self.now_block_time()) - fast_forward(2) - cost = self.purchaseCostEther(qty) - total_cost += cost - self.buy(owner, qty, cost) - - self.assertEqual(self.balanceOf(owner), total_qty) - - # We assert only almost equal because we're ignoring gas costs. - self.assertAlmostEqual((pre_balance - get_eth_balance(owner)) / UNIT, total_cost / UNIT) - - def test_saleProceedsFiat(self): - owner = self.owner() - poolFeeRate = self.poolFeeRate() - - self.assertLess(self.saleProceedsFiat(UNIT), UNIT) - self.assertLess(self.saleProceedsFiat(UNIT), self.purchaseCostFiat(UNIT)) - - self.assertEqual(self.saleProceedsFiat(0), 0) - self.assertEqual(self.saleProceedsFiat(UNIT), UNIT - poolFeeRate) - self.assertEqual(self.saleProceedsFiat(10 * UNIT), 10 * (UNIT - poolFeeRate)) - self.assertEqual(self.saleProceedsFiat(UNIT // 2), (UNIT - poolFeeRate) // 2) - self.setPoolFeeRate(owner, UNIT // 10**7) - self.assertEqual(self.saleProceedsFiat(UNIT), (UNIT - UNIT // 10**7)) - self.assertEqual(self.saleProceedsFiat(100 * UNIT), 100 * (UNIT - UNIT // 10**7)) - self.assertEqual(self.saleProceedsFiat(UNIT // 2), (UNIT - UNIT // 10**7) // 2) - self.setPoolFeeRate(owner, poolFeeRate) - - def test_saleProceedsEther(self): - owner = self.owner() - oracle = self.oracle() - poolFeeRate = self.poolFeeRate() - - self.assertLess(self.saleProceedsEther(UNIT), self.etherValue(UNIT)) - self.assertLess(self.saleProceedsEther(UNIT), self.purchaseCostEther(UNIT)) - - self.assertEqual(self.saleProceedsEther(0), 0) - - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.saleProceedsEther(UNIT), UNIT - poolFeeRate) - self.assertEqual(self.saleProceedsEther(UNIT // 2), (UNIT - poolFeeRate) // 2) - self.setPoolFeeRate(owner, UNIT // 10**7) - self.assertEqual(self.saleProceedsEther(UNIT), (UNIT - UNIT // 10**7)) - self.assertEqual(self.saleProceedsEther(100 * UNIT), 100 * (UNIT - UNIT // 10**7)) - - self.setPoolFeeRate(owner, poolFeeRate) - self.updatePrice(oracle, UNIT // 2, self.now_block_time()) - fast_forward(2) - self.assertEqual(self.saleProceedsEther(UNIT // 2), UNIT - poolFeeRate) - self.assertEqual(self.saleProceedsEther(3 * UNIT), 6 * (UNIT - poolFeeRate)) - - for v in [0.0004, 2.1, 1, 49994, 49.29384, 0.00000028, 1235759872, 2.5 * 10**25]: - vi = int(v * UNIT) - self.assertEqual(self.saleProceedsEther(vi), self.saleProceedsEtherAllowStale(vi)) - - def test_saleProceedsEtherBearMarket(self): - owner = self.owner() - oracle = self.oracle() - - initial_qty = 800000 * UNIT - self.replenishPool(owner, initial_qty, 8 * 10**9 * UNIT) - self.buy(owner, initial_qty, self.purchaseCostEther(initial_qty)) - - price_multiples = [12398, 1.2384889, 7748.22, 0.238838, 0.00049944, 5.7484, 87.2211111] - qty_multiples = [2.3, 84.4828, 284.10002, 0.4992, 105.289299991, 7.651948, 0.01, 100000] - - total_qty = 0 - total_proceeds = 0 - pre_balance = get_eth_balance(owner) - - for price_mult in price_multiples: - for qty_mult in qty_multiples: - price = int(price_mult * UNIT) - qty = int(qty_mult * UNIT) - total_qty += qty - self.updatePrice(oracle, price, self.now_block_time()) - fast_forward(2) - proceeds = self.saleProceedsEther(qty) - total_proceeds += proceeds - self.sell(owner, qty) - - self.assertEqual(initial_qty - self.balanceOf(owner), total_qty) - - # We assert only almost equal because we're ignoring gas costs. - self.assertAlmostEqual((get_eth_balance(owner) - pre_balance) / UNIT, total_proceeds / UNIT) - - def test_priceIsStale(self): - oracle = self.oracle() - owner = self.owner() - stale_period = self.stalePeriod() - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - # Price is not stale immediately following an update. - self.assertFalse(self.priceIsStale()) - - # Price is not stale after part of the period has elapsed. - fast_forward(seconds=stale_period // 2) - self.assertFalse(self.priceIsStale()) - - # Price is not stale right up to just before the period has elapsed. - fast_forward(seconds=stale_period // 2 - 10) - self.assertFalse(self.priceIsStale()) - - # Price becomes stale immediately after the period has elapsed. - fast_forward(seconds=20) - self.assertTrue(self.priceIsStale()) - - # Price stays stale for ages. - fast_forward(seconds=100 * stale_period) - self.assertTrue(self.priceIsStale()) - - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.assertFalse(self.priceIsStale()) - - # Lengthening stale periods should not trigger staleness. - self.setStalePeriod(owner, 2 * stale_period) - self.assertFalse(self.priceIsStale()) - - # Shortening them to longer than the current elapsed period should not trigger staleness. - self.setStalePeriod(owner, stale_period) - self.assertFalse(self.priceIsStale()) - - # Shortening to shorter than the current elapsed period should trigger staleness. - fast_forward(seconds=3 * stale_period // 4) - self.setStalePeriod(owner, stale_period // 2) - self.assertTrue(self.priceIsStale()) - - # Yet if we are able to update the stale period while the price is stale, - # we should be able to turn off staleness by extending the period. - # It's an interesting question of trust as to whether we should be able to do this, say if we - # do not have access to the oracle to send a price update. But as an owner, we could just - # reset the oracle address anyway, so we allow this. - self.setStalePeriod(owner, stale_period) - self.assertFalse(self.priceIsStale()) - - def test_staleness(self): - owner = self.owner() - oracle = self.oracle() - target = W3.eth.accounts[5] - - # Set up target balance to be confiscatable for later testing. - self.assertEqual(self.court(), self.fake_court.address) - motion_id = 1 - self.fake_court.setTargetMotionID(owner, target, motion_id) - self.fake_court.setConfirming(owner, motion_id, True) - self.fake_court.setVotePasses(owner, motion_id, True) - - # Create some nomins and set a convenient price. - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - pce = self.purchaseCostEther(UNIT) - self.replenishPool(owner, 3 * UNIT, 7 * UNIT) - self.buy(owner, UNIT, pce) - - # Enter stale period. - fast_forward(seconds=10 * self.stalePeriod()) - self.assertTrue(self.priceIsStale()) - - # These calls should work if the price is stale. - oracle = self.oracle() - court = self.court() - beneficiary = self.beneficiary() - poolFeeRate = self.poolFeeRate() - stalePeriod = self.stalePeriod() - self.nominPool() - self.liquidationPeriod() - self.liquidationTimestamp() - self.etherPrice() - self.lastPriceUpdateTime() - self.frozen(self.nomin_real.address) - self.setOracle(owner, oracle) - self.setCourt(owner, court) - self.setBeneficiary(owner, beneficiary) - self.setPoolFeeRate(owner, poolFeeRate) - self.setStalePeriod(owner, stalePeriod) - self.etherValueAllowStale(UNIT) - self.saleProceedsEtherAllowStale(UNIT) - self.poolFeeIncurred(UNIT) - self.purchaseCostFiat(UNIT) - self.saleProceedsFiat(UNIT) - self.priceIsStale() - self.isLiquidating() - self.assertFalse(self.frozen(MASTER)) - self.transfer(MASTER, MASTER, 0) - self.transferFrom(MASTER, MASTER, MASTER, 0) - self.fake_court.confiscateBalance(owner, target) - self.unfreezeAccount(owner, target) - self.diminishPool(owner, UNIT) - - # These calls should not work when the price is stale. - # That they work when not stale is guaranteed by other tests, hopefully. - self.assertReverts(self.fiatValue, UNIT) - self.assertReverts(self.fiatBalance) - self.assertReverts(self.etherValue, UNIT) - self.assertReverts(self.collateralisationRatio) - self.assertReverts(self.purchaseCostEther, UNIT) - self.assertReverts(self.saleProceedsEther, UNIT) - self.assertGreater(get_eth_balance(owner), 7 * UNIT) - self.assertEqual(self.nominPool(), UNIT) - self.assertEqual(self.balanceOf(owner), UNIT) - self.assertReverts(self.replenishPool, owner, UNIT, 5 * UNIT) - self.assertReverts(self.buy, owner, UNIT, pce) - self.assertReverts(self.sell, owner, UNIT) - - # Liquidation things should work while stale... - self.forceLiquidation(owner) - self.assertTrue(self.isLiquidating()) - self.extendLiquidationPeriod(owner, 1) - - # ...except that we can't terminate liquidation unless the price is fresh. - self.assertReverts(self.terminateLiquidation, owner) - - # Confirm that sell works regardless of staleness when in liquidation - self.sell(owner, UNIT) - # We can also burn under these conditions. - self.diminishPool(owner, UNIT) - - # Finally that updating the price gets us out of the stale period - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.assertFalse(self.priceIsStale()) - self.terminateLiquidation(owner) - - # And that self-destruction works during stale period. - self.forceLiquidation(owner) - fast_forward(seconds=self.stalePeriod() + self.liquidationPeriod()) - self.assertTrue(self.isLiquidating()) - self.assertTrue(self.priceIsStale()) - self.selfDestruct(owner) - with self.assertRaises(Exception): - self.etherPrice() - - def test_transfer(self): - owner = self.owner() - oracle = self.oracle() - target = W3.eth.accounts[1] - - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.replenishPool(owner, 10 * UNIT, 20 * ETHER) - ethercost = self.purchaseCostEther(10 * UNIT) - self.buy(owner, 10 * UNIT, ethercost) - - self.assertEqual(self.balanceOf(owner), 10 * UNIT) - self.assertEqual(self.balanceOf(target), 0) - - # Should be impossible to transfer to the nomin contract itself. - self.assertReverts(self.transfer, owner, self.nomin_real.address, UNIT) - - self.transfer(owner, target, 5 * UNIT) - remainder = 10 * UNIT - self.transferPlusFee(5 * UNIT) - self.assertEqual(self.balanceOf(owner), remainder) - self.assertEqual(self.balanceOf(target), 5 * UNIT) - - self.debugFreezeAccount(owner, target) - - self.assertReverts(self.transfer, owner, target, UNIT) - # self.assertReverts(self.transfer, target, owner, UNIT) - - self.unfreezeAccount(owner, target) - - qty = (5 * UNIT * UNIT) // self.transferPlusFee(UNIT) + 1 - self.transfer(target, owner, qty) - - self.assertEqual(self.balanceOf(owner), remainder + qty) - self.assertEqual(self.balanceOf(target), 0) - - def test_transferFrom(self): - owner = self.owner() - oracle = self.oracle() - target = W3.eth.accounts[1] - proxy = W3.eth.accounts[2] - - # Unauthorized transfers should not work - self.assertReverts(self.transferFrom, proxy, owner, target, UNIT) - - # Neither should transfers that are too large for the allowance. - self.approve(owner, proxy, UNIT) - self.assertReverts(self.transferFrom, proxy, owner, target, 2 * UNIT) - - self.approve(owner, proxy, 10000 * UNIT) - - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.replenishPool(owner, 10 * UNIT, 20 * ETHER) - ethercost = self.purchaseCostEther(10 * UNIT) - self.buy(owner, 10 * UNIT, ethercost) - - self.assertEqual(self.balanceOf(owner), 10 * UNIT) - self.assertEqual(self.balanceOf(target), 0) - - # Should be impossible to transfer to the nomin contract itself. - self.assertReverts(self.transferFrom, proxy, owner, self.nomin_real.address, UNIT) - - self.transferFrom(proxy, owner, target, 5 * UNIT) - remainder = 10 * UNIT - self.transferPlusFee(5 * UNIT) - self.assertEqual(self.balanceOf(owner), remainder) - self.assertEqual(self.balanceOf(target), 5 * UNIT) - - self.debugFreezeAccount(owner, target) - - self.assertReverts(self.transferFrom, proxy, owner, target, UNIT) - self.assertReverts(self.transferFrom, proxy, target, owner, UNIT) - - self.unfreezeAccount(owner, target) - - qty = (5 * UNIT * UNIT) // self.transferPlusFee(UNIT) + 1 - self.transfer(target, owner, qty) - - self.assertEqual(self.balanceOf(owner), remainder + qty) - self.assertEqual(self.balanceOf(target), 0) - - def test_replenishPool(self): - owner = self.owner() - oracle = self.oracle() - - # Only the contract owner should be able to issue new nomins. - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.assertReverts(self.replenishPool, W3.eth.accounts[4], UNIT, 2 * ETHER) - - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.nominPool(), 0) - - # Revert if less than 2x collateral is provided - self.assertReverts(self.replenishPool, owner, UNIT, 2 * ETHER - 1) - - # Issue a nomin into the pool - self.replenishPool(owner, UNIT, 2 * ETHER) - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.nominPool(), UNIT) - self.assertEqual(get_eth_balance(self.nomin_real.address), 2 * ETHER) - - # Issuing more nomins should stack with existing supply - self.replenishPool(owner, UNIT, 2 * ETHER) - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.nominPool(), 2 * UNIT) - self.assertEqual(get_eth_balance(self.nomin_real.address), 4 * ETHER) - - # Issue more into the pool for free if price goes up - self.updatePrice(oracle, 2 * UNIT, self.now_block_time()) - fast_forward(2) - self.assertFalse(self.isLiquidating()) - self.assertReverts(self.replenishPool, owner, 2 * UNIT + 1, 0) - self.replenishPool(owner, 2 * UNIT, 0) - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.nominPool(), 4 * UNIT) - self.assertEqual(get_eth_balance(self.nomin_real.address), 4 * ETHER) - - # provide more than 2x collateral for new issuance if price drops - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.assertFalse(self.isLiquidating()) - self.assertReverts(self.replenishPool, owner, UNIT, 2 * ETHER) - self.assertReverts(self.replenishPool, owner, UNIT, 6 * ETHER - 1) - self.replenishPool(owner, UNIT, 6 * ETHER) - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.nominPool(), 5 * UNIT) - self.assertEqual(get_eth_balance(self.nomin_real.address), 10 * ETHER) - - def test_diminishPool(self): - owner = self.owner() - oracle = self.oracle() - - # issue some nomins to be burned - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.replenishPool(owner, 10 * UNIT, 20 * ETHER) - - # Only the contract owner should be able to burn nomins. - self.assertReverts(self.diminishPool, W3.eth.accounts[4], UNIT) - - # It should not be possible to burn more nomins than are in the pool. - self.assertReverts(self.diminishPool, owner, 11 * UNIT) - - # Burn part of the pool - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.nominPool(), 10 * UNIT) - self.diminishPool(owner, UNIT) - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.nominPool(), 9 * UNIT) - - # Burn the remainder of the pool - self.diminishPool(owner, self.nominPool()) - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.nominPool(), 0) - - def test_buy(self): - self.updatePrice(self.oracle(), UNIT, self.now_block_time()) - fast_forward(2) - buyer = W3.eth.accounts[4] - - # Should not be possible to buy when there's no supply - cost = self.purchaseCostEther(UNIT) - self.assertReverts(self.buy, buyer, UNIT, cost) - - # issue some nomins to be burned - self.replenishPool(self.owner(), 5 * UNIT, 10 * ETHER) - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.nominPool(), 5 * UNIT) - - # Should not be able to purchase with the wrong quantity of ether. - self.assertReverts(self.buy, buyer, UNIT, cost + 1) - self.assertReverts(self.buy, buyer, UNIT, cost - 1) - - self.assertEqual(self.balanceOf(buyer), 0) - self.buy(buyer, UNIT, cost) - self.assertEqual(self.totalSupply(), UNIT) - self.assertEqual(self.nominPool(), 4 * UNIT) - self.assertEqual(self.balanceOf(buyer), UNIT) - - # It should not be possible to buy fewer nomins than the purchase minimum - purchaseMin = UNIT // 100 - self.assertReverts(self.buy, buyer, purchaseMin - 1, self.purchaseCostEther(purchaseMin - 1)) - - # But it should be possible to buy exactly that quantity - self.buy(buyer, purchaseMin, self.purchaseCostEther(purchaseMin)) - self.assertEqual(self.totalSupply(), UNIT + UNIT // 100) - self.assertEqual(self.nominPool(), 4 * UNIT - (UNIT // 100)) - self.assertEqual(self.balanceOf(buyer), UNIT + UNIT // 100) - - # It should not be possible to buy more tokens than are in the pool - total = self.nominPool() - self.assertReverts(self.buy, buyer, total + 1, self.purchaseCostEther(total + 1)) - - self.buy(buyer, total, self.purchaseCostEther(total)) - self.assertEqual(self.totalSupply(), 5 * UNIT) - self.assertEqual(self.nominPool(), 0) - self.assertEqual(self.balanceOf(buyer), 5 * UNIT) - - # Should not be possible to buy when there's nothing in the pool - self.assertReverts(self.buy, buyer, UNIT, self.purchaseCostEther(UNIT)) - - def test_sell(self): - # Prepare a seller who owns some nomins. - self.updatePrice(self.oracle(), UNIT, self.now_block_time()) - fast_forward(2) - seller = W3.eth.accounts[4] - self.replenishPool(self.owner(), 5 * UNIT, 10 * ETHER) - self.assertEqual(self.totalSupply(), 0) - self.assertEqual(self.nominPool(), 5 * UNIT) - self.assertEqual(self.balanceOf(seller), 0) - - # It should not be possible to sell nomins if you have none. - self.assertReverts(self.sell, seller, UNIT) - - self.buy(seller, 5 * UNIT, self.purchaseCostEther(5 * UNIT)) - self.assertEqual(self.totalSupply(), 5 * UNIT) - self.assertEqual(self.nominPool(), 0) - self.assertEqual(self.balanceOf(seller), 5 * UNIT) - - # It should not be possible to sell more nomins than you possess. - self.assertReverts(self.sell, seller, 5 * UNIT + 1) - - # Selling nomins should yield back the right amount of ether. - pre_balance = get_eth_balance(seller) - self.sell(seller, 2 * UNIT) - # This assertAlmostEqual hack is only because ganache refuses to be sensible about gas prices. - # The receipt refuses to include the gas price and the values appear are inconsistent anyway. - self.assertAlmostEqual(self.saleProceedsEther(2 * UNIT) / UNIT, (get_eth_balance(seller) - pre_balance) / UNIT) - self.assertEqual(self.totalSupply(), 3 * UNIT) - self.assertEqual(self.nominPool(), 2 * UNIT) - self.assertEqual(self.balanceOf(seller), 3 * UNIT) - - def test_isLiquidating(self): - self.assertFalse(self.isLiquidating()) - self.forceLiquidation(self.owner()) - self.assertTrue(self.isLiquidating()) - - def test_forceLiquidation(self): - owner = self.owner() - # non-owners should not be able to force liquidation. - non_owner = W3.eth.accounts[6] - self.assertNotEqual(owner, non_owner) - self.assertReverts(self.forceLiquidation, non_owner) - - self.assertFalse(self.isLiquidating()) - tx_receipt = self.forceLiquidation(owner) - self.assertTrue(self.isLiquidating()) - self.assertEqual(block_time(tx_receipt.blockNumber), self.liquidationTimestamp()) - self.assertEqual(len(tx_receipt.logs), 1) - self.assertEqual(get_event_data_from_log(self.nomin_event_dict, tx_receipt.logs[0])['event'], "LiquidationBegun") - - # This call should not work if liquidation has begun. - self.assertReverts(self.forceLiquidation, owner) - - def test_autoLiquidation(self): - owner = self.owner() - oracle = self.oracle() - - # Do not liquidate if there's nothing in the pool. - self.updatePrice(oracle, UNIT // 10, self.now_block_time()) - fast_forward(2) - self.assertFalse(self.isLiquidating()) - - # Issue something so that we can liquidate. - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.replenishPool(owner, UNIT, 2 * UNIT) - - # Ordinary price updates don't cause liquidation. - self.updatePrice(oracle, UNIT // 2, self.now_block_time()) - fast_forward(2) - self.assertFalse(self.isLiquidating()) - - # Price updates inducing sub-unity collateralisation ratios cause liquidation. - tx_receipt = self.updatePrice(oracle, UNIT // 2 - 1, self.now_block_time()) - fast_forward(2) - self.assertTrue(self.isLiquidating()) - self.assertEqual(len(tx_receipt.logs), 2) - price_update_log = get_event_data_from_log(self.nomin_event_dict, tx_receipt.logs[0]) - liquidation_log = get_event_data_from_log(self.nomin_event_dict, tx_receipt.logs[1]) - self.assertEqual(price_update_log['event'], 'PriceUpdated') - self.assertEqual(liquidation_log['event'], 'LiquidationBegun') - - # The auto liquidation check should do nothing when already under liquidation. - tx_receipt = self.updatePrice(oracle, UNIT // 3 - 1, self.now_block_time()) - fast_forward(2) - self.assertEqual(len(tx_receipt.logs), 1) - price_update_log = get_event_data_from_log(self.nomin_event_dict, tx_receipt.logs[0]) - self.assertEqual(price_update_log['event'], 'PriceUpdated') - self.assertTrue(self.isLiquidating()) - - def test_extendLiquidationPeriod(self): - owner = self.owner() - - # Only owner should be able to call this. - non_owner = W3.eth.accounts[6] - self.assertNotEqual(owner, non_owner) - self.assertReverts(self.forceLiquidation, non_owner) - - fourteenDays = 14 * 24 * 60 * 60 - oneEightyDays = 180 * 24 * 60 * 60 - - self.forceLiquidation(owner) - self.assertEqual(self.liquidationPeriod(), fourteenDays) # Default 14 days. - self.assertReverts(self.extendLiquidationPeriod, owner, 12309198139871) - self.extendLiquidationPeriod(owner, 1) - self.assertEqual(self.liquidationPeriod(), fourteenDays + 1) - self.extendLiquidationPeriod(owner, 12345) - self.assertEqual(self.liquidationPeriod(), fourteenDays + 12346) - self.extendLiquidationPeriod(owner, oneEightyDays - (fourteenDays + 12346)) - self.assertEqual(self.liquidationPeriod(), oneEightyDays) - self.assertReverts(self.extendLiquidationPeriod, owner, 1) - self.assertReverts(self.extendLiquidationPeriod, owner, 12309198139871) - - def test_terminateLiquidation(self): - owner = self.owner() - oracle = self.oracle() - - # Terminating liquidation should not work when not liquidating. - self.assertReverts(self.terminateLiquidation, owner) - - self.forceLiquidation(owner) - - # Only the owner should be able to terminate liquidation. - self.assertReverts(self.terminateLiquidation, oracle) - - # Should be able to terminate liquidation if there is no supply. - tx_receipt = self.terminateLiquidation(owner) - self.assertEqual(self.liquidationTimestamp(), 2**256 - 1) - self.assertEqual(self.liquidationPeriod(), 14 * 24 * 60 * 60) - self.assertEqual(len(tx_receipt.logs), 1) - self.assertEqual(get_event_data_from_log(self.nomin_event_dict, tx_receipt.logs[0])['event'], - "LiquidationTerminated") - - # Should not be able to terminate liquidation if the supply is undercollateralised. - self.updatePrice(oracle, 2 * UNIT, self.now_block_time()) - fast_forward(2) - self.replenishPool(owner, UNIT, UNIT) - self.updatePrice(oracle, UNIT - 1, self.now_block_time()) - fast_forward(2) - self.assertTrue(self.isLiquidating()) # Price update triggers liquidation. - self.assertReverts(self.terminateLiquidation, owner) - - # But if the price recovers we should be fine to terminate. - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.terminateLiquidation(owner) - self.assertFalse(self.isLiquidating()) - - # And we should not be able to terminate liquidation if the price is stale. - self.forceLiquidation(owner) - fast_forward(seconds=2 * self.stalePeriod()) - self.assertTrue(self.priceIsStale()) - self.assertReverts(self.terminateLiquidation, owner) - - def test_canSelfDestruct(self): - owner = self.owner() - oracle = self.oracle() - - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.replenishPool(owner, 2 * UNIT, 4 * UNIT) - self.buy(owner, UNIT, self.purchaseCostEther(UNIT)) - - self.assertFalse(self.canSelfDestruct()) - self.forceLiquidation(owner) - - # Not enough time elapsed. - self.assertFalse(self.canSelfDestruct()) - fast_forward(seconds=self.liquidationPeriod() + 10) - self.assertTrue(self.canSelfDestruct()) - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - - self.terminateLiquidation(owner) - self.sell(owner, UNIT) - self.forceLiquidation(owner) - - self.assertFalse(self.canSelfDestruct()) - fast_forward(weeks=3) - self.assertTrue(self.canSelfDestruct()) - - def test_selfDestruct(self): - owner = self.owner() - oracle = self.oracle() - not_owner = W3.eth.accounts[5] - self.assertNotEqual(not_owner, owner) - - # Buy some nomins so that we can't short circuit self-destruction. - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.replenishPool(owner, UNIT, 2 * UNIT) - self.buy(owner, UNIT, self.purchaseCostEther(UNIT)) - - self.assertFalse(self.isLiquidating()) - self.assertFalse(self.canSelfDestruct()) - - # This should not work if not liquidating yet. - self.assertReverts(self.selfDestruct, owner) - - self.forceLiquidation(owner) - - # Should not work if the full liquidation period has not elapsed. - self.assertReverts(self.selfDestruct, owner) - - # Should not work if the liquidationPeriod has been extended. - buff = 1000 - fast_forward(seconds=self.liquidationPeriod() + buff // 2) - self.extendLiquidationPeriod(owner, buff) - self.assertReverts(self.selfDestruct, owner) - - # Go past the end of the liquidation period. - fast_forward(seconds=buff) - - # Only the owner should be able to call this. - self.assertReverts(self.selfDestruct, not_owner) - - # Should not be able to self-destruct if the period was terminated. - # Refresh the price so we can terminate liquidation. - self.updatePrice(self.oracle(), self.etherPrice(), self.now_block_time()) - fast_forward(2) - self.terminateLiquidation(owner) - self.assertReverts(self.selfDestruct, owner) - - # Check that the beneficiary receives the entire balance of the smart contract. - self.forceLiquidation(owner) - fast_forward(seconds=self.liquidationPeriod() + 1) - value = get_eth_balance(self.nomin_real.address) - beneficiary = self.beneficiary() - pre_balance = get_eth_balance(beneficiary) - self.selfDestruct(owner) - self.assertEqual(get_eth_balance(beneficiary) - pre_balance, value) - - def test_selfDestructShortCircuit(self): - owner = self.owner() - oracle = self.oracle() - not_owner = W3.eth.accounts[5] - self.assertNotEqual(not_owner, owner) - - # Buy some nomins so that we can't immediately short circuit self-destruction. - self.updatePrice(oracle, UNIT, self.now_block_time()) - fast_forward(2) - self.replenishPool(owner, UNIT, 2 * UNIT) - self.buy(owner, UNIT // 2, self.purchaseCostEther(UNIT // 2)) - - self.forceLiquidation(owner) - - # Should not be able to self-destruct, as one week has not yet elapsed. - self.assertReverts(self.selfDestruct, owner) - - fast_forward(weeks=2) - - # Should not be able to self-destruct as there are still some nomins in circulation. - self.assertReverts(self.selfDestruct, owner) - - # Sell all nomins back, we should be able to selfdestruct. - self.sell(owner, UNIT // 2) - tx_receipt = self.selfDestruct(owner) - self.assertEqual(len(tx_receipt.logs), 1) - log = get_event_data_from_log(self.nomin_event_dict, tx_receipt.logs[0]) - self.assertEqual(log['event'], "SelfDestructed") - - def test_confiscateBalance(self): - owner = self.owner() - target = W3.eth.accounts[2] - - self.assertEqual(self.court(), self.fake_court.address) - - # The target must have some nomins. We will issue 10 for him to buy - self.updatePrice(self.oracle(), UNIT, self.now_block_time()) - fast_forward(2) - self.replenishPool(owner, 10 * UNIT, 20 * ETHER) - ethercost = self.purchaseCostEther(10 * UNIT) - send_value(owner, target, ethercost) - self.buy(target, 10 * UNIT, ethercost) - self.assertEqual(self.balanceOf(target), 10 * UNIT) - - motion_id = 1 - self.fake_court.setTargetMotionID(owner, target, motion_id) - - # Attempt to confiscate even though the conditions are not met. - self.fake_court.setConfirming(owner, motion_id, False) - self.fake_court.setVotePasses(owner, motion_id, False) - self.assertReverts(self.fake_court.confiscateBalance, owner, target) - - self.fake_court.setConfirming(owner, motion_id, True) - self.fake_court.setVotePasses(owner, motion_id, False) - self.assertReverts(self.fake_court.confiscateBalance, owner, target) - - self.fake_court.setConfirming(owner, motion_id, False) - self.fake_court.setVotePasses(owner, motion_id, True) - self.assertReverts(self.fake_court.confiscateBalance, owner, target) - - # Set up the target balance to be confiscatable. - self.fake_court.setConfirming(owner, motion_id, True) - self.fake_court.setVotePasses(owner, motion_id, True) - - # Only the court should be able to confiscate balances. - self.assertReverts(self.confiscateBalance, owner, target) - - # Actually confiscate the balance. - pre_feePool = self.feePool() - pre_balance = self.balanceOf(target) - self.fake_court.confiscateBalance(owner, target) - self.assertEqual(self.balanceOf(target), 0) - self.assertEqual(self.feePool(), pre_feePool + pre_balance) - self.assertTrue(self.frozen(target)) - - def test_unfreezeAccount(self): - owner = self.owner() - target = W3.eth.accounts[1] + self.assertFalse(self.proxy.useDELEGATECALL()); + self.assertEqual(self.proxy.target(), self.feetoken_contract_1.address) + self.assertEqual(self.proxy.owner(), MASTER) + + def test_swap(self): + self.assertEqual(self.feetoken.name(), "Test Fee Token") + self.assertEqual(self.feetoken.symbol(), "FEE") + self.assertEqual(self.feetoken.totalSupply(), 0) + self.assertEqual(self.feetoken.transferFeeRate(), UNIT // 20) + self.assertEqual(self.feetoken.feeAuthority(), self.fee_authority) + self.assertEqual(self.feetoken.tokenState(), self.feestate.contract.address) + self.assertEqual(self.feestate.associatedContract(), self.feetoken_contract_1.address) + + self.proxy.setTarget(MASTER, self.feetoken_contract_2.address) + self.feestate.setAssociatedContract(MASTER, self.feetoken_contract_2.address) + mine_txs([self.feetoken_contract_2.functions.setTokenState(self.feestate.contract.address).transact({'from': MASTER})]) + + self.assertEqual(self.feetoken.name(), "Test Fee Token 2") + self.assertEqual(self.feetoken.tokenState(), self.feestate.contract.address) + self.assertEqual(self.feestate.associatedContract(), self.feetoken_contract_2.address) + + def test_balance_after_swap(self): + sender = self.initial_beneficiary + + receiver = fresh_account() + receiver_balance = self.feetoken.balanceOf(receiver) + self.assertEqual(receiver_balance, 0) - # The nomin contract itself should not be unfreezable. - tx_receipt = self.unfreezeAccount(owner, self.nomin_real.address) - self.assertTrue(self.frozen(self.nomin_real.address)) - self.assertEqual(len(tx_receipt.logs), 0) + value = 10 * UNIT + amountReceived = self.feetoken.amountReceived(value) + fee = value - amountReceived + tx_receipt = self.feetoken.transfer(sender, receiver, value) - # Unfreezing non-frozen accounts should not do anything. - self.assertFalse(self.frozen(target)) - tx_receipt = self.unfreezeAccount(owner, target) - self.assertFalse(self.frozen(target)) - self.assertEqual(len(tx_receipt.logs), 0) + self.assertEqual(self.feetoken.balanceOf(receiver), receiver_balance + amountReceived) - self.debugFreezeAccount(owner, target) - self.assertTrue(self.frozen(target)) + self.proxy.setTarget(MASTER, self.feetoken_contract_2.address) + self.feestate.setAssociatedContract(MASTER, self.feetoken_contract_2.address) - # Only the owner should be able to unfreeze an account. - self.assertReverts(self.unfreezeAccount, target, target) + mine_txs([self.feetoken_contract_2.functions.setTokenState(self.feestate.contract.address).transact({'from': MASTER})]) - tx_receipt = self.unfreezeAccount(owner, target) - self.assertFalse(self.frozen(target)) + self.assertEqual(self.feetoken.balanceOf(receiver), receiver_balance + amountReceived) - # Unfreezing should emit the appropriate log. - log = get_event_data_from_log(self.nomin_event_dict, tx_receipt.logs[0]) - self.assertEqual(log['event'], 'AccountUnfrozen') - def test_fallback(self): - # Fallback function should be payable. - owner = self.owner() - self.debugWithdrawAllEther(owner, owner) - self.debugEmptyFeePool(owner) - self.assertEqual(get_eth_balance(self.nomin_real.address), 0) - send_value(owner, self.nomin.address, ETHER // 2) - send_value(owner, self.nomin_real.address, ETHER // 2) - self.assertEqual(get_eth_balance(self.nomin_real.address), ETHER) + def test_DELEGATECALL(self): + # A -> A can access same data + # A -> B with same data structures can access same data + # A -> A.2 -> A.3 with extra data structures can still access same data + pass + def test_CALL_DELEGATECALL_switch(self): + # Switching to CALL and DELEGATECALL modes and back works fine. + # Test that DELEGATECALL can turn itself off. + pass diff --git a/tests/test_SafeDecimalMath.py b/tests/test_SafeDecimalMath.py index 782efcb887..46c7384b70 100644 --- a/tests/test_SafeDecimalMath.py +++ b/tests/test_SafeDecimalMath.py @@ -1,295 +1,306 @@ -import unittest - from utils.deployutils import compile_contracts, attempt_deploy, UNIT, MASTER -from utils.testutils import assertReverts - -MATH_MODULE_SOURCE = "tests/contracts/PublicMath.sol" +from utils.testutils import HavvenTestCase +from tests.contract_interfaces.safe_decimal_math_interface import SafeDecimalMathInterface def setUpModule(): print("Testing SafeDecimalMath...") + print("==========================") + print() def tearDownModule(): print() + print() -class TestSafeDecimalMath(unittest.TestCase): +class TestSafeDecimalMath(HavvenTestCase): + @classmethod + def deployContracts(cls): + sources = ["tests/contracts/PublicMath.sol"] + + compiled, cls.event_maps = cls.compileAndMapEvents(sources, remappings=['""=contracts']) + + math, tx_receipt = attempt_deploy(compiled, 'PublicMath', MASTER, []) + return math + @classmethod def setUpClass(cls): - cls.assertReverts = assertReverts - - compiled = compile_contracts([MATH_MODULE_SOURCE], - remappings=['""=contracts']) - cls.math, tx_receipt = attempt_deploy(compiled, 'PublicMath', MASTER, []) - - cls.addIsSafe = lambda self, x, y: cls.math.functions.pubAddIsSafe(x, y).call() - cls.safeAdd = lambda self, x, y: cls.math.functions.pubSafeAdd(x, y).call() - cls.subIsSafe = lambda self, x, y: cls.math.functions.pubSubIsSafe(x, y).call() - cls.safeSub = lambda self, x, y: cls.math.functions.pubSafeSub(x, y).call() - cls.mulIsSafe = lambda self, x, y: cls.math.functions.pubMulIsSafe(x, y).call() - cls.safeMul = lambda self, x, y: cls.math.functions.pubSafeMul(x, y).call() - cls.safeMul_dec = lambda self, x, y: cls.math.functions.pubSafeMul_dec(x, y).call() - cls.divIsSafe = lambda self, x, y: cls.math.functions.pubDivIsSafe(x, y).call() - cls.safeDiv = lambda self, x, y: cls.math.functions.pubSafeDiv(x, y).call() - cls.safeDiv_dec = lambda self, x, y: cls.math.functions.pubSafeDiv_dec(x, y).call() - cls.intToDec = lambda self, i: cls.math.functions.pubIntToDec(i).call() + cls.math = cls.deployContracts() + cls.safeDecMath = SafeDecimalMathInterface(cls.math, 'SafeDecimalMath') + + def test_scale(self): + self.assertEqual(self.safeDecMath.decimals(), 18) + self.assertEqual(self.safeDecMath.UNIT(), UNIT) # Test addIsSafe function def test_addIsSafe(self): - self.assertTrue(self.addIsSafe(1, 1)) - self.assertTrue(self.addIsSafe(1235151, 9249)) - self.assertTrue(self.addIsSafe(0, 0)) - self.assertTrue(self.addIsSafe(2**256 - 20, 17)) - self.assertTrue(self.addIsSafe(2**256 - 20, 19)) + self.assertTrue(self.safeDecMath.addIsSafe(1, 1)) + self.assertTrue(self.safeDecMath.addIsSafe(1235151, 9249)) + self.assertTrue(self.safeDecMath.addIsSafe(0, 0)) + self.assertTrue(self.safeDecMath.addIsSafe(2**256 - 20, 17)) + self.assertTrue(self.safeDecMath.addIsSafe(2**256 - 20, 19)) def test_addIsUnsafe(self): # These should all overflow: max representable is 2^256 - 1 - self.assertFalse(self.addIsSafe(1, 2**256 - 1)) - self.assertFalse(self.addIsSafe(2**256 - 1, 1)) - self.assertFalse(self.addIsSafe(2**255, 2**255)) - self.assertFalse(self.addIsSafe(2**256 - 1, 2**256 - 1)) + self.assertFalse(self.safeDecMath.addIsSafe(1, 2**256 - 1)) + self.assertFalse(self.safeDecMath.addIsSafe(2**256 - 1, 1)) + self.assertFalse(self.safeDecMath.addIsSafe(2**255, 2**255)) + self.assertFalse(self.safeDecMath.addIsSafe(2**256 - 1, 2**256 - 1)) # Test safeAdd function def test_addSafe(self): - self.assertEqual(self.safeAdd(1, 1), 2) - self.assertEqual(self.safeAdd(1235151, 9249), 1235151 + 9249) + self.assertEqual(self.safeDecMath.safeAdd(1, 1), 2) + self.assertEqual(self.safeDecMath.safeAdd(1235151, 9249), 1235151 + 9249) # Larger examples - self.assertEqual(self.safeAdd(2**128, 3**17), 2**128 + 3**17) - self.assertEqual(self.safeAdd(2**250, 2**250), 2**251) - self.assertEqual(self.safeAdd(2**256 - 20, 17), 2**256 - 3) + self.assertEqual(self.safeDecMath.safeAdd(2**128, 3**17), 2**128 + 3**17) + self.assertEqual(self.safeDecMath.safeAdd(2**250, 2**250), 2**251) + self.assertEqual(self.safeDecMath.safeAdd(2**256 - 20, 17), 2**256 - 3) # Additive identity - self.assertEqual(self.safeAdd(0, 0), 0) - self.assertEqual(self.safeAdd(1, 0), 1) - self.assertEqual(self.safeAdd(0, 100), 100) - self.assertEqual(self.safeAdd(10**24, 0), 10**24) + self.assertEqual(self.safeDecMath.safeAdd(0, 0), 0) + self.assertEqual(self.safeDecMath.safeAdd(1, 0), 1) + self.assertEqual(self.safeDecMath.safeAdd(0, 100), 100) + self.assertEqual(self.safeDecMath.safeAdd(10**24, 0), 10**24) # Commutativity - self.assertEqual(self.safeAdd(10114, 17998), self.safeAdd(17998, 10114)) + self.assertEqual( + self.safeDecMath.safeAdd(10114, 17998), + self.safeDecMath.safeAdd(17998, 10114) + ) def test_addUnsafe(self): # These should all overflow: max representable is 2^256 - 1 - self.assertReverts(self.safeAdd, 2**256 - 1, 2**256 - 1) - self.assertReverts(self.safeAdd, 2**255, 2**255) - self.assertReverts(self.safeAdd, 2**256 - 1, 1) - self.assertReverts(self.safeAdd, 2**256 - 100, 1000) + self.assertReverts(self.safeDecMath.safeAdd, 2**256 - 1, 2**256 - 1) + self.assertReverts(self.safeDecMath.safeAdd, 2**255, 2**255) + self.assertReverts(self.safeDecMath.safeAdd, 2**256 - 1, 1) + self.assertReverts(self.safeDecMath.safeAdd, 2**256 - 100, 1000) # Test subIsSafe function def test_subIsSafe(self): - self.assertTrue(self.subIsSafe(1, 1)) - self.assertTrue(self.subIsSafe(10, 9)) - self.assertTrue(self.subIsSafe(20, 0)) - self.assertTrue(self.subIsSafe(100000000, 123456)) - self.assertTrue(self.subIsSafe(2**256-1, 2**256-1)) - self.assertTrue(self.subIsSafe(2**256-1, 17**34)) - self.assertTrue(self.subIsSafe(2**255, 2**254)) + self.assertTrue(self.safeDecMath.subIsSafe(1, 1)) + self.assertTrue(self.safeDecMath.subIsSafe(10, 9)) + self.assertTrue(self.safeDecMath.subIsSafe(20, 0)) + self.assertTrue(self.safeDecMath.subIsSafe(100000000, 123456)) + self.assertTrue(self.safeDecMath.subIsSafe(2**256-1, 2**256-1)) + self.assertTrue(self.safeDecMath.subIsSafe(2**256-1, 17**34)) + self.assertTrue(self.safeDecMath.subIsSafe(2**255, 2**254)) def test_subIsUnsafe(self): - self.assertFalse(self.subIsSafe(0, 1)) - self.assertFalse(self.subIsSafe(10, 11)) - self.assertFalse(self.subIsSafe(1121311, 1231241414)) - self.assertFalse(self.subIsSafe(2**255, 2**256-1)) - self.assertFalse(self.subIsSafe(2**255, 2**255+1)) + self.assertFalse(self.safeDecMath.subIsSafe(0, 1)) + self.assertFalse(self.safeDecMath.subIsSafe(10, 11)) + self.assertFalse(self.safeDecMath.subIsSafe(1121311, 1231241414)) + self.assertFalse(self.safeDecMath.subIsSafe(2**255, 2**256-1)) + self.assertFalse(self.safeDecMath.subIsSafe(2**255, 2**255+1)) # Test safeSub function def test_safeSub(self): - self.assertEqual(self.safeSub(10, 9), 1) - self.assertEqual(self.safeSub(10, 1), 9) - self.assertEqual(self.safeSub(100000000, 123456), 100000000 - 123456) + self.assertEqual(self.safeDecMath.safeSub(10, 9), 1) + self.assertEqual(self.safeDecMath.safeSub(10, 1), 9) + self.assertEqual(self.safeDecMath.safeSub(100000000, 123456), 100000000 - 123456) - self.assertEqual(self.safeSub(2**256 - 1, 2**256 - 1), 0) - self.assertEqual(self.safeSub(2**256 - 1, 17**34), (2**256-1) - 17**34) - self.assertEqual(self.safeSub(2**255, 2**254), 2**254) - self.assertEqual(self.safeSub(2**255, (2**255 - 1)), 1) + self.assertEqual(self.safeDecMath.safeSub(2**256 - 1, 2**256 - 1), 0) + self.assertEqual(self.safeDecMath.safeSub(2**256 - 1, 17**34), (2**256-1) - 17**34) + self.assertEqual(self.safeDecMath.safeSub(2**255, 2**254), 2**254) + self.assertEqual(self.safeDecMath.safeSub(2**255, (2**255 - 1)), 1) # Subtractive identity element - self.assertEqual(self.safeSub(20, 0), 20) - self.assertEqual(self.safeSub(2**256 - 1, 0), 2**256 - 1) + self.assertEqual(self.safeDecMath.safeSub(20, 0), 20) + self.assertEqual(self.safeDecMath.safeSub(2**256 - 1, 0), 2**256 - 1) # Yields the identity element - self.assertEqual(self.safeSub(1, 1), 0) - self.assertEqual(self.safeSub(10**24 + 1, 10**24 + 1), 0) - self.assertEqual(self.safeSub(2**256-1, 2**256-1), 0) + self.assertEqual(self.safeDecMath.safeSub(1, 1), 0) + self.assertEqual(self.safeDecMath.safeSub(10**24 + 1, 10**24 + 1), 0) + self.assertEqual(self.safeDecMath.safeSub(2**256-1, 2**256-1), 0) def test_unsafeSub(self): - self.assertReverts(self.safeSub, 0, 1) - self.assertReverts(self.safeSub, 10, 11) - self.assertReverts(self.safeSub, 100, 100000) - self.assertReverts(self.safeSub, 2**255, 2**256 - 11) - self.assertReverts(self.safeSub, 2**256 - 11, 2**256 - 10) - self.assertReverts(self.safeSub, 0, 2**256 - 1) + self.assertReverts(self.safeDecMath.safeSub, 0, 1) + self.assertReverts(self.safeDecMath.safeSub, 10, 11) + self.assertReverts(self.safeDecMath.safeSub, 100, 100000) + self.assertReverts(self.safeDecMath.safeSub, 2**255, 2**256 - 11) + self.assertReverts(self.safeDecMath.safeSub, 2**256 - 11, 2**256 - 10) + self.assertReverts(self.safeDecMath.safeSub, 0, 2**256 - 1) # Test mulIsSafe function def test_mulIsSafe(self): - self.assertTrue(self.mulIsSafe(1, 0)) - self.assertTrue(self.mulIsSafe(0, 1)) - self.assertTrue(self.mulIsSafe(1, 1)) - self.assertTrue(self.mulIsSafe(2**254, 2)) - self.assertTrue(self.mulIsSafe(2**254, 3)) - self.assertTrue(self.mulIsSafe(2**254 - 1, 4)) - self.assertTrue(self.mulIsSafe(2**128, 2**127)) - self.assertTrue(self.mulIsSafe(2**128 - 1, 2**128 - 1)) + self.assertTrue(self.safeDecMath.mulIsSafe(1, 0)) + self.assertTrue(self.safeDecMath.mulIsSafe(0, 1)) + self.assertTrue(self.safeDecMath.mulIsSafe(1, 1)) + self.assertTrue(self.safeDecMath.mulIsSafe(2**254, 2)) + self.assertTrue(self.safeDecMath.mulIsSafe(2**254, 3)) + self.assertTrue(self.safeDecMath.mulIsSafe(2**254 - 1, 4)) + self.assertTrue(self.safeDecMath.mulIsSafe(2**128, 2**127)) + self.assertTrue(self.safeDecMath.mulIsSafe(2**128 - 1, 2**128 - 1)) def test_mulIsUnSafe(self): - self.assertFalse(self.mulIsSafe(2**255, 2)) - self.assertFalse(self.mulIsSafe(2**128, 2**128)) - self.assertFalse(self.mulIsSafe(2**128, 3**100)) - self.assertFalse(self.mulIsSafe(7**50, 2**200)) + self.assertFalse(self.safeDecMath.mulIsSafe(2**255, 2)) + self.assertFalse(self.safeDecMath.mulIsSafe(2**128, 2**128)) + self.assertFalse(self.safeDecMath.mulIsSafe(2**128, 3**100)) + self.assertFalse(self.safeDecMath.mulIsSafe(7**50, 2**200)) # Test safeMul function def test_safeMul(self): - self.assertEqual(self.safeMul(10, 10), 100) - self.assertEqual(self.safeMul(99999, 777777), 99999 * 777777) - self.assertEqual(self.safeMul(2**254, 2), 2**255) - self.assertEqual(self.safeMul(2**254 - 1, 4), (2**254 - 1) * 4) - self.assertEqual(self.safeMul(2**128, 2**127), 2**255) - self.assertEqual(self.safeMul(2**128 - 1, 2**128 - 1), (2**128 - 1)**2) + self.assertEqual(self.safeDecMath.safeMul(10, 10), 100) + self.assertEqual(self.safeDecMath.safeMul(99999, 777777), 99999 * 777777) + self.assertEqual(self.safeDecMath.safeMul(2**254, 2), 2**255) + self.assertEqual(self.safeDecMath.safeMul(2**254 - 1, 4), (2**254 - 1) * 4) + self.assertEqual(self.safeDecMath.safeMul(2**128, 2**127), 2**255) + self.assertEqual(self.safeDecMath.safeMul(2**128 - 1, 2**128 - 1), (2**128 - 1)**2) # Identity - self.assertEqual(self.safeMul(1, 1), 1) - self.assertEqual(self.safeMul(1, 2**256 - 1), 2**256 - 1) - self.assertEqual(self.safeMul(2**256 - 1, 1), 2**256 - 1) + self.assertEqual(self.safeDecMath.safeMul(1, 1), 1) + self.assertEqual(self.safeDecMath.safeMul(1, 2**256 - 1), 2**256 - 1) + self.assertEqual(self.safeDecMath.safeMul(2**256 - 1, 1), 2**256 - 1) # Zero - self.assertEqual(self.safeMul(1, 0), 0) - self.assertEqual(self.safeMul(0, 1), 0) - self.assertEqual(self.safeMul(0, 2**256 - 1), 0) - self.assertEqual(self.safeMul(2**256 - 1, 0), 0) + self.assertEqual(self.safeDecMath.safeMul(1, 0), 0) + self.assertEqual(self.safeDecMath.safeMul(0, 1), 0) + self.assertEqual(self.safeDecMath.safeMul(0, 2**256 - 1), 0) + self.assertEqual(self.safeDecMath.safeMul(2**256 - 1, 0), 0) # Commutativity - self.assertEqual(self.safeMul(10114, 17998), self.safeMul(17998, 10114)) + self.assertEqual( + self.safeDecMath.safeMul(10114, 17998), + self.safeDecMath.safeMul(17998, 10114) + ) def test_unsafeMul(self): - self.assertReverts(self.safeMul, 2**128, 2**128) - self.assertReverts(self.safeMul, 2**256 - 1, 2**256 - 1) - self.assertReverts(self.safeMul, 2**255, 2) - self.assertReverts(self.safeMul, 2**200, 3**100) - self.assertReverts(self.safeMul, 2**254, 5) + self.assertReverts(self.safeDecMath.safeMul, 2**128, 2**128) + self.assertReverts(self.safeDecMath.safeMul, 2**256 - 1, 2**256 - 1) + self.assertReverts(self.safeDecMath.safeMul, 2**255, 2) + self.assertReverts(self.safeDecMath.safeMul, 2**200, 3**100) + self.assertReverts(self.safeDecMath.safeMul, 2**254, 5) # Test safeMul_dec function def testSafeMul_dec(self): - self.assertEqual(self.safeMul_dec(99999 * UNIT, 777777 * UNIT), 99999 * 777777 * UNIT) - self.assertEqual(self.safeMul_dec(10 * UNIT, UNIT + UNIT), 20 * UNIT) - self.assertEqual(self.safeMul_dec(2**256 // UNIT, UNIT), 2**256 // UNIT) - self.assertEqual(self.safeMul_dec(2**255 - 1, 2), (2**256 - 2) // UNIT) - self.assertEqual(self.safeMul_dec(10**8 * UNIT, 10**8 * UNIT), 10**8 * 10**8 * UNIT) - self.assertEqual(self.safeMul_dec(17 * UNIT, 23 * UNIT), 17 * 23 * UNIT) - self.assertEqual(self.safeMul_dec(UNIT // 2, UNIT // 2), UNIT // 4) - self.assertEqual(self.safeMul_dec(UNIT // 25, UNIT // 5), UNIT // 125) - self.assertEqual(self.safeMul_dec(UNIT // 7, UNIT // 3), ((UNIT // 7) * (UNIT // 3)) // UNIT) + self.assertEqual(self.safeDecMath.safeMul_dec(99999 * UNIT, 777777 * UNIT), 99999 * 777777 * UNIT) + self.assertEqual(self.safeDecMath.safeMul_dec(10 * UNIT, UNIT + UNIT), 20 * UNIT) + self.assertEqual(self.safeDecMath.safeMul_dec(2**256 // UNIT, UNIT), 2**256 // UNIT) + self.assertEqual(self.safeDecMath.safeMul_dec(2**255 - 1, 2), (2**256 - 2) // UNIT) + self.assertEqual(self.safeDecMath.safeMul_dec(10**8 * UNIT, 10**8 * UNIT), 10**8 * 10**8 * UNIT) + self.assertEqual(self.safeDecMath.safeMul_dec(17 * UNIT, 23 * UNIT), 17 * 23 * UNIT) + self.assertEqual(self.safeDecMath.safeMul_dec(UNIT // 2, UNIT // 2), UNIT // 4) + self.assertEqual(self.safeDecMath.safeMul_dec(UNIT // 25, UNIT // 5), UNIT // 125) + self.assertEqual(self.safeDecMath.safeMul_dec(UNIT // 7, UNIT // 3), ((UNIT // 7) * (UNIT // 3)) // UNIT) # Test zero - self.assertEqual(self.safeMul_dec(UNIT, 0), 0) - self.assertEqual(self.safeMul_dec(0, 100), 0) + self.assertEqual(self.safeDecMath.safeMul_dec(UNIT, 0), 0) + self.assertEqual(self.safeDecMath.safeMul_dec(0, 100), 0) # Test identity - self.assertEqual(self.safeMul_dec(10 * UNIT, UNIT), 10 * UNIT) - self.assertEqual(self.safeMul_dec(UNIT, 10 * UNIT), 10 * UNIT) - self.assertEqual(self.safeMul_dec(UNIT, 1), 1) - self.assertEqual(self.safeMul_dec(1, UNIT), 1) + self.assertEqual(self.safeDecMath.safeMul_dec(10 * UNIT, UNIT), 10 * UNIT) + self.assertEqual(self.safeDecMath.safeMul_dec(UNIT, 10 * UNIT), 10 * UNIT) + self.assertEqual(self.safeDecMath.safeMul_dec(UNIT, 1), 1) + self.assertEqual(self.safeDecMath.safeMul_dec(1, UNIT), 1) # Commutativity - self.assertEqual(self.safeMul_dec(17 * UNIT, 23 * UNIT), self.safeMul_dec(23 * UNIT, 17 * UNIT)) + self.assertEqual(self.safeDecMath.safeMul_dec(17 * UNIT, 23 * UNIT), self.safeDecMath.safeMul_dec(23 * UNIT, 17 * UNIT)) # Rounding occurs towards zero - self.assertEqual(self.safeMul_dec(UNIT + 1, UNIT - 1), UNIT-1) + self.assertEqual(self.safeDecMath.safeMul_dec(UNIT + 1, UNIT - 1), UNIT-1) def testUnsafeMul_dec(self): - self.assertReverts(self.safeMul, 2**255, 2) - self.assertReverts(self.safeMul, 2**200, 2**56) - self.assertReverts(self.safeMul, 2**200, 3**40) + self.assertReverts(self.safeDecMath.safeMul, 2**255, 2) + self.assertReverts(self.safeDecMath.safeMul, 2**200, 2**56) + self.assertReverts(self.safeDecMath.safeMul, 2**200, 3**40) # Test divIsSafe function def testDivIsSafe(self): - self.assertTrue(self.divIsSafe(1, 1)) - self.assertTrue(self.divIsSafe(2**256 - 1, 2**256 - 1)) - self.assertTrue(self.divIsSafe(100, 10*20)) + self.assertTrue(self.safeDecMath.divIsSafe(1, 1)) + self.assertTrue(self.safeDecMath.divIsSafe(2**256 - 1, 2**256 - 1)) + self.assertTrue(self.safeDecMath.divIsSafe(100, 10*20)) def testDivIsUnsafe(self): - self.assertFalse(self.divIsSafe(1, 0)) - self.assertFalse(self.divIsSafe(2**256 - 1, 0)) + self.assertFalse(self.safeDecMath.divIsSafe(1, 0)) + self.assertFalse(self.safeDecMath.divIsSafe(2**256 - 1, 0)) # Test safeDiv function def testSafeDiv(self): - self.assertEqual(self.safeDiv(0, 1), 0) - self.assertEqual(self.safeDiv(1, 1), 1) - self.assertEqual(self.safeDiv(1, 2), 0) - self.assertEqual(self.safeDiv(100, 10), 10) - self.assertEqual(self.safeDiv(2**256 - 1, 1), 2**256 - 1) - self.assertEqual(self.safeDiv(3**100, 3), 3**99) - self.assertEqual(self.safeDiv(999, 2), 499) - self.assertEqual(self.safeDiv(1000, 7), 142) + self.assertEqual(self.safeDecMath.safeDiv(0, 1), 0) + self.assertEqual(self.safeDecMath.safeDiv(1, 1), 1) + self.assertEqual(self.safeDecMath.safeDiv(1, 2), 0) + self.assertEqual(self.safeDecMath.safeDiv(100, 10), 10) + self.assertEqual(self.safeDecMath.safeDiv(2**256 - 1, 1), 2**256 - 1) + self.assertEqual(self.safeDecMath.safeDiv(3**100, 3), 3**99) + self.assertEqual(self.safeDecMath.safeDiv(999, 2), 499) + self.assertEqual(self.safeDecMath.safeDiv(1000, 7), 142) def testUnsafeDiv(self): - self.assertReverts(self.safeDiv, 0, 0) - self.assertReverts(self.safeDiv, 1, 0) - self.assertReverts(self.safeDiv, 2**256 - 1, 0) + self.assertReverts(self.safeDecMath.safeDiv, 0, 0) + self.assertReverts(self.safeDecMath.safeDiv, 1, 0) + self.assertReverts(self.safeDecMath.safeDiv, 2**256 - 1, 0) # Test safeDiv_dec function def testSafeDiv_dec(self): - self.assertEqual(self.safeDiv_dec(4 * UNIT, 2 * UNIT), 2 * UNIT) - self.assertEqual(self.safeDiv_dec(UNIT, 2 * UNIT), UNIT // 2) - self.assertEqual(self.safeDiv_dec(10**8 * UNIT, 3 * UNIT), (10**8 * UNIT) // 3) - self.assertEqual(self.safeDiv_dec(20 * UNIT, UNIT // 2), 40 * UNIT) - self.assertEqual(self.safeDiv_dec(UNIT, 10 * UNIT), UNIT // 10) - - self.assertEqual(self.safeDiv_dec(10**8 * UNIT, 10**8 * UNIT), UNIT) - self.assertEqual(self.safeDiv_dec(10**8 * UNIT, UNIT), 10**8 * UNIT) - self.assertEqual(self.safeDiv_dec(10**30 * UNIT, 10**10 * UNIT), 10**20 * UNIT) - self.assertEqual(self.safeDiv_dec(2**256 // UNIT, 10 * UNIT), (2**256 // UNIT) // 10) - self.assertEqual(self.safeDiv_dec(UNIT, UNIT * UNIT), 1) - self.assertEqual(self.safeDiv_dec(10 * UNIT, UNIT * UNIT), 10) + self.assertEqual(self.safeDecMath.safeDiv_dec(4 * UNIT, 2 * UNIT), 2 * UNIT) + self.assertEqual(self.safeDecMath.safeDiv_dec(UNIT, 2 * UNIT), UNIT // 2) + self.assertEqual(self.safeDecMath.safeDiv_dec(10**8 * UNIT, 3 * UNIT), (10**8 * UNIT) // 3) + self.assertEqual(self.safeDecMath.safeDiv_dec(20 * UNIT, UNIT // 2), 40 * UNIT) + self.assertEqual(self.safeDecMath.safeDiv_dec(UNIT, 10 * UNIT), UNIT // 10) + + self.assertEqual(self.safeDecMath.safeDiv_dec(10**8 * UNIT, 10**8 * UNIT), UNIT) + self.assertEqual(self.safeDecMath.safeDiv_dec(10**8 * UNIT, UNIT), 10**8 * UNIT) + self.assertEqual(self.safeDecMath.safeDiv_dec(10**30 * UNIT, 10**10 * UNIT), 10**20 * UNIT) + self.assertEqual(self.safeDecMath.safeDiv_dec(2**256 // UNIT, 10 * UNIT), (2**256 // UNIT) // 10) + self.assertEqual(self.safeDecMath.safeDiv_dec(UNIT, UNIT * UNIT), 1) + self.assertEqual(self.safeDecMath.safeDiv_dec(10 * UNIT, UNIT * UNIT), 10) # Largest usable numerator - self.assertEqual(self.safeDiv_dec(2**256 // UNIT, UNIT), 2**256 // UNIT) + self.assertEqual(self.safeDecMath.safeDiv_dec(2**256 // UNIT, UNIT), 2**256 // UNIT) # Largest usable power of ten in the numerator - self.assertEqual(self.safeDiv_dec(10**41 * UNIT, 10**11 * UNIT), 10**30 * UNIT) + self.assertEqual(self.safeDecMath.safeDiv_dec(10**41 * UNIT, 10**11 * UNIT), 10**30 * UNIT) # Largest usable power of two in the numerator - self.assertEqual(self.safeDiv_dec(2**196, UNIT), 2**196) + self.assertEqual(self.safeDecMath.safeDiv_dec(2**196, UNIT), 2**196) # Operations yielding zero (greater than a UNIT factor difference between operands) - self.assertEqual(self.safeDiv_dec(2**256 // UNIT, 2**256 - 1), 0) - self.assertEqual(self.safeDiv_dec(UNIT - 1, UNIT * UNIT), 0) + self.assertEqual(self.safeDecMath.safeDiv_dec(2**256 // UNIT, 2**256 - 1), 0) + self.assertEqual(self.safeDecMath.safeDiv_dec(UNIT - 1, UNIT * UNIT), 0) # Identity and zero. - self.assertEqual(self.safeDiv_dec(1, UNIT), 1) - self.assertEqual(self.safeDiv_dec(100000, UNIT), 100000) - self.assertEqual(self.safeDiv_dec(UNIT, UNIT), UNIT) - self.assertEqual(self.safeDiv_dec(10 * UNIT, UNIT), 10 * UNIT) - self.assertEqual(self.safeDiv_dec(0, UNIT), 0) - self.assertEqual(self.safeDiv_dec(0, 1), 0) + self.assertEqual(self.safeDecMath.safeDiv_dec(1, UNIT), 1) + self.assertEqual(self.safeDecMath.safeDiv_dec(100000, UNIT), 100000) + self.assertEqual(self.safeDecMath.safeDiv_dec(UNIT, UNIT), UNIT) + self.assertEqual(self.safeDecMath.safeDiv_dec(10 * UNIT, UNIT), 10 * UNIT) + self.assertEqual(self.safeDecMath.safeDiv_dec(0, UNIT), 0) + self.assertEqual(self.safeDecMath.safeDiv_dec(0, 1), 0) def testUnsafeDiv_dec(self): # Numerator overflows - self.assertReverts(self.safeDiv_dec, 2**256 - 1, 1) - self.assertReverts(self.safeDiv_dec, 2**256 // UNIT + 1, 1) - self.assertReverts(self.safeDiv_dec, 10**42 * UNIT, 1) - self.assertReverts(self.safeDiv_dec, 2**197, 1) + self.assertReverts(self.safeDecMath.safeDiv_dec, 2**256 - 1, 1) + self.assertReverts(self.safeDecMath.safeDiv_dec, 2**256 // UNIT + 1, 1) + self.assertReverts(self.safeDecMath.safeDiv_dec, 10**42 * UNIT, 1) + self.assertReverts(self.safeDecMath.safeDiv_dec, 2**197, 1) # Zero denominator overflows - self.assertReverts(self.safeDiv_dec, 0, 0) - self.assertReverts(self.safeDiv_dec, 1, 0) - self.assertReverts(self.safeDiv_dec, 2**256 // UNIT, 0) + self.assertReverts(self.safeDecMath.safeDiv_dec, 0, 0) + self.assertReverts(self.safeDecMath.safeDiv_dec, 1, 0) + self.assertReverts(self.safeDecMath.safeDiv_dec, 2**256 // UNIT, 0) # Both - self.assertReverts(self.safeDiv_dec, 2**256 - 1, 0) + self.assertReverts(self.safeDecMath.safeDiv_dec, 2**256 - 1, 0) # Test intToDec function def testIntToDec(self): - self.assertEqual(self.intToDec(1), UNIT) - self.assertEqual(self.intToDec(100), 100*UNIT) - self.assertEqual(self.intToDec(UNIT), UNIT * UNIT) - self.assertEqual(self.intToDec(2**256 // UNIT), (2**256 // UNIT) * UNIT) + self.assertEqual(self.safeDecMath.intToDec(1), UNIT) + self.assertEqual(self.safeDecMath.intToDec(100), 100*UNIT) + self.assertEqual(self.safeDecMath.intToDec(UNIT), UNIT * UNIT) + self.assertEqual(self.safeDecMath.intToDec(2**256 // UNIT), (2**256 // UNIT) * UNIT) # Test out of range - self.assertReverts(self.intToDec, 2**256 // UNIT + 1) + self.assertReverts(self.safeDecMath.intToDec, 2**256 // UNIT + 1) # Test combined arithmetic def testArithmeticExpressions(self): - self.assertEqual(self.safeSub(self.safeAdd( - UNIT, self.safeDiv_dec(self.safeDiv(self.safeAdd(UNIT, UNIT), 2), UNIT) - ), self.safeMul_dec(2 * UNIT, UNIT)), 0) - self.assertEqual(self.safeDiv_dec(self.safeMul_dec(self.safeAdd( - self.intToDec(1), UNIT), self.safeMul(2, UNIT)), UNIT // 2), self.intToDec(8)) + self.assertEqual( + self.safeDecMath.safeSub(self.safeDecMath.safeAdd( + UNIT, self.safeDecMath.safeDiv_dec( + self.safeDecMath.safeDiv(self.safeDecMath.safeAdd(UNIT, UNIT), 2), UNIT + )), self.safeDecMath.safeMul_dec(2 * UNIT, UNIT)), + 0) + + self.assertEqual( + self.safeDecMath.safeDiv_dec(self.safeDecMath.safeMul_dec(self.safeDecMath.safeAdd( + self.safeDecMath.intToDec(1), UNIT), self.safeDecMath.safeMul(2, UNIT)), UNIT // 2), + self.safeDecMath.intToDec(8) + ) diff --git a/tests/test_SelfDestructible.py b/tests/test_SelfDestructible.py index 452c74d6b1..3e17ea73b9 100644 --- a/tests/test_SelfDestructible.py +++ b/tests/test_SelfDestructible.py @@ -1,21 +1,23 @@ -import unittest +from utils.deployutils import attempt_deploy, W3, MASTER, DUMMY, UNIT +from utils.deployutils import take_snapshot, restore_snapshot, fast_forward +from utils.testutils import HavvenTestCase, send_value, block_time, ZERO_ADDRESS -from utils.deployutils import compile_contracts, attempt_deploy, mine_tx, W3, MASTER, DUMMY, UNIT -from utils.deployutils import take_snapshot, restore_snapshot, fast_forward, fresh_account -from utils.testutils import assertReverts, assertClose, ZERO_ADDRESS, send_value, block_time +from tests.contract_interfaces.self_destructible_interface import SelfDestructibleInterface -SD_SOURCE = "tests/contracts/PayableSD.sol" def setUpModule(): print("Testing SelfDestructible...") + print("===========================") + print() def tearDownModule(): print() + print() -class TestSelfDestructible(unittest.TestCase): +class TestSelfDestructible(HavvenTestCase): def setUp(self): self.snapshot = take_snapshot() @@ -24,85 +26,175 @@ def tearDown(self): @classmethod def setUpClass(cls): - cls.assertReverts = assertReverts - cls.assertClose = assertClose + cls.sd_duration = 60 * 60 * 24 * 7 * 4 + cls.contract_balance = 10 * UNIT - compiled = compile_contracts([SD_SOURCE], - remappings=['""=contracts']) + sources = ["tests/contracts/PayableSD.sol"] + cls.compiled, cls.event_maps = cls.compileAndMapEvents(sources, remappings=['""=contracts']) + cls.event_map = cls.event_maps['SelfDestructible'] - cls.sd, txr = attempt_deploy(compiled, 'PayableSD', MASTER, [MASTER, DUMMY]) + cls.sd_contract, cls.deploy_tx = attempt_deploy(cls.compiled, 'PayableSD', MASTER, + [MASTER]) + cls.sd = SelfDestructibleInterface(cls.sd_contract, 'SelfDestructible') + cls.sd.setSelfDestructBeneficiary(MASTER, DUMMY) - cls.owner = lambda self: cls.sd.functions.owner().call() - cls.nominateOwner = lambda self, sender, newOwner: mine_tx( - cls.sd.functions.nominateOwner(newOwner).transact({'from': sender})) - cls.acceptOwnership = lambda self, sender: mine_tx( - cls.sd.functions.acceptOwnership().transact({'from': sender})) + # Send some value to the contract so that we can test receipt of funds by beneficiary + send_value(MASTER, cls.sd_contract.address, cls.contract_balance) - cls.initiationTime = lambda self: cls.sd.functions.initiationTime().call() - cls.beneficiary = lambda self: cls.sd.functions.beneficiary().call() + def test_constructor(self): + self.assertNotEqual(MASTER, DUMMY) + self.assertEqual(self.sd.owner(), MASTER) + self.assertEqual(self.sd.selfDestructBeneficiary(), DUMMY) + self.assertFalse(self.sd.selfDestructInitiated()) + self.assertEqual(self.sd.initiationTime(), 0) + self.assertEqual(self.sd.SELFDESTRUCT_DELAY(), self.sd_duration) + self.assertEventEquals(self.event_map, self.deploy_tx.logs[1], + "SelfDestructBeneficiaryUpdated", + {"newBeneficiary": MASTER}, + location=self.sd_contract.address) + + def test_setSelfDestructBeneficiary(self): + owner = self.sd.owner() + notowner = DUMMY + self.assertNotEqual(owner, notowner) - cls.setBeneficiary = lambda self, sender, beneficiary: mine_tx( - cls.sd.functions.setBeneficiary(beneficiary).transact({'from': sender})) - cls.initiateSelfDestruct = lambda self, sender: mine_tx( - cls.sd.functions.initiateSelfDestruct().transact({'from': sender})) - cls.terminateSelfDestruct = lambda self, sender: mine_tx( - cls.sd.functions.terminateSelfDestruct().transact({'from': sender})) - cls.selfDestruct = lambda self, sender: mine_tx( - cls.sd.functions.selfDestruct().transact({'from': sender})) + # Only the owner may set the beneficiary + self.assertReverts(self.sd.setSelfDestructBeneficiary, notowner, owner) - send_value(MASTER, cls.sd.address, 10 * UNIT) + # Beneficiary must be nonzero + self.assertReverts(self.sd.setSelfDestructBeneficiary, owner, ZERO_ADDRESS) - def test_constructor(self): - self.assertEqual(self.owner(), MASTER) - self.assertEqual(self.beneficiary(), DUMMY) - self.assertEqual(self.initiationTime(), 2**256 - 1) + # The owner can correctly set the variable... + self.assertEqual(self.sd.selfDestructBeneficiary(), DUMMY) + tx = self.sd.setSelfDestructBeneficiary(owner, owner) + self.assertEqual(self.sd.selfDestructBeneficiary(), owner) + # Event is properly emitted. + self.assertEventEquals(self.event_map, tx.logs[0], + "SelfDestructBeneficiaryUpdated", + {"newBeneficiary": owner}, + location=self.sd_contract.address) - def test_setBeneficiary(self): - owner = self.owner() - notowner = DUMMY - newBeneficiary = fresh_account() - self.assertNotEqual(owner, notowner) - self.assertReverts(self.setBeneficiary, DUMMY, MASTER) - self.assertEqual(self.beneficiary(), DUMMY) - self.setBeneficiary(MASTER, MASTER) - self.assertEqual(self.beneficiary(), MASTER) - self.setBeneficiary(MASTER, DUMMY) - self.assertEqual(self.beneficiary(), DUMMY) + # ...and set it back. + self.sd.setSelfDestructBeneficiary(owner, DUMMY) + self.assertEqual(self.sd.selfDestructBeneficiary(), DUMMY) def test_initiateSelfDestruct(self): - owner = self.owner() + owner = self.sd.owner() notowner = DUMMY self.assertNotEqual(owner, notowner) - self.assertReverts(self.initiateSelfDestruct, notowner) - self.assertEqual(self.initiationTime(), 2**256 - 1) - tx_receipt = self.initiateSelfDestruct(owner) - self.assertEqual(self.initiationTime(), block_time(tx_receipt['blockNumber'])) + + # Non-owners cannot SD the contract. + self.assertReverts(self.sd.initiateSelfDestruct, notowner) + + # Initiation time starts at 0. + self.assertEqual(self.sd.initiationTime(), 0) + self.assertFalse(self.sd.selfDestructInitiated()) + + tx = self.sd.initiateSelfDestruct(owner) + + # Initiated at the right time. + self.assertEqual(self.sd.initiationTime(), block_time(tx['blockNumber'])) + self.assertTrue(self.sd.selfDestructInitiated()) + + # Event is properly emitted. + self.assertEventEquals(self.event_map, tx.logs[0], + "SelfDestructInitiated", + {"selfDestructDelay": self.sd_duration}, + location=self.sd_contract.address) def test_terminateSelfDestruct(self): - owner = self.owner() + owner = self.sd.owner() notowner = DUMMY self.assertNotEqual(owner, notowner) - self.initiateSelfDestruct(owner) - self.assertNotEqual(self.initiationTime(), 2**256 - 1) - self.assertReverts(self.terminateSelfDestruct, notowner) - self.terminateSelfDestruct(owner) - self.assertEqual(self.initiationTime(), 2**256 - 1) + + self.assertFalse(self.sd.selfDestructInitiated()) + self.sd.initiateSelfDestruct(owner) + self.assertNotEqual(self.sd.initiationTime(), 0) + self.assertTrue(self.sd.selfDestructInitiated()) + self.assertReverts(self.sd.terminateSelfDestruct, notowner) + + tx = self.sd.terminateSelfDestruct(owner) + self.assertEqual(self.sd.initiationTime(), 0) + self.assertFalse(self.sd.selfDestructInitiated()) + + self.assertEventEquals(self.event_map, tx.logs[0], + "SelfDestructTerminated", + location=self.sd_contract.address) def test_selfDestruct(self): - owner = self.owner() + owner = self.sd.owner() notowner = DUMMY self.assertNotEqual(owner, notowner) - self.initiateSelfDestruct(owner) - self.assertReverts(self.selfDestruct, notowner) - self.assertReverts(self.selfDestruct, owner) - fast_forward(days=2) - self.assertReverts(self.selfDestruct, owner) + + # The contract cannot be self-destructed before the SD has been initiated. + self.assertReverts(self.sd.selfDestruct, owner) + + tx = self.sd.initiateSelfDestruct(owner) + self.assertEventEquals(self.event_map, tx.logs[0], + "SelfDestructInitiated", + {"selfDestructDelay": self.sd_duration}, + location=self.sd_contract.address) + + # Neither owners nor non-owners may not self-destruct before the time has elapsed. + self.assertReverts(self.sd.selfDestruct, notowner) + self.assertReverts(self.sd.selfDestruct, owner) + fast_forward(seconds=self.sd_duration, days=-1) + self.assertReverts(self.sd.selfDestruct, notowner) + self.assertReverts(self.sd.selfDestruct, owner) fast_forward(seconds=10, days=1) - beneficiary = self.beneficiary() + beneficiary = self.sd.selfDestructBeneficiary() self.assertEqual(beneficiary, DUMMY) pre_balance = W3.eth.getBalance(beneficiary) - self.selfDestruct(owner) - self.assertGreater(W3.eth.getBalance(beneficiary), pre_balance) + # Non-owner should not be able to self-destruct even if the time has elapsed. + self.assertReverts(self.sd.selfDestruct, notowner) + address = self.sd.contract.address + tx = self.sd.selfDestruct(owner) + + # The balance in the contract is correctly refunded to the beneficiary. + self.assertEqual(W3.eth.getBalance(beneficiary), pre_balance + self.contract_balance) + + self.assertEventEquals(self.event_map, tx.logs[0], + "SelfDestructed", + {"beneficiary": beneficiary}, + location=self.sd_contract.address) + + # Check contract not exist + self.assertEqual(W3.eth.getCode(address), b'\x00') + + def test_event_SelfDestructTerminated(self): + owner = self.sd.owner() + self.sd.initiateSelfDestruct(owner) + tx = self.sd.terminateSelfDestruct(owner) + self.assertEventEquals(self.event_map, tx.logs[0], + "SelfDestructTerminated", + location=self.sd_contract.address) + + def test_event_SelfDestructInitiated(self): + owner = self.sd.owner() + tx = self.sd.initiateSelfDestruct(owner) + self.assertEventEquals(self.event_map, tx.logs[0], + "SelfDestructInitiated", + {"selfDestructDelay": self.sd_duration}, + location=self.sd_contract.address) + + def test_event_SelfDestructed(self): + owner = self.sd.owner() + beneficiary = self.sd.selfDestructBeneficiary() + self.sd.initiateSelfDestruct(owner) + fast_forward(self.sd_duration + 1) + tx = self.sd.selfDestruct(owner) + self.assertEventEquals(self.event_map, tx.logs[0], + "SelfDestructed", + {"beneficiary": beneficiary}, + location=self.sd_contract.address) + + def test_event_SelfDestructBeneficiaryUpdated(self): + owner = self.sd.owner() + tx = self.sd.setSelfDestructBeneficiary(owner, owner) + self.assertEventEquals(self.event_map, tx.logs[0], + "SelfDestructBeneficiaryUpdated", + {"newBeneficiary": owner}, + location=self.sd_contract.address) diff --git a/tests/test_TokenState.py b/tests/test_TokenState.py index 1f0558637a..5a645f1e88 100644 --- a/tests/test_TokenState.py +++ b/tests/test_TokenState.py @@ -1,11 +1,10 @@ -import unittest -from utils.deployutils import compile_contracts, attempt_deploy, mine_tx, \ - UNIT, MASTER, DUMMY, fresh_accounts, take_snapshot, restore_snapshot -from utils.testutils import assertReverts -from utils.testutils import ZERO_ADDRESS - - -TokenState_SOURCE = "contracts/TokenState.sol" +from utils.deployutils import ( + UNIT, MASTER, DUMMY, + compile_contracts, attempt_deploy, + take_snapshot, restore_snapshot +) +from utils.testutils import HavvenTestCase, ZERO_ADDRESS +from tests.contract_interfaces.token_state_interface import TokenStateInterface def deploy_state(name, compiled, sender, owner, supply, beneficiary, associated_contract): @@ -17,13 +16,16 @@ def deploy_state(name, compiled, sender, owner, supply, beneficiary, associated_ def setUpModule(): print("Testing TokenState...") + print("=====================") + print() def tearDownModule(): print() + print() -class TestTokenState(unittest.TestCase): +class TestTokenState(HavvenTestCase): def setUp(self): self.snapshot = take_snapshot() @@ -31,139 +33,65 @@ def tearDown(self): restore_snapshot(self.snapshot) @classmethod - def setUpClass(cls): - cls.assertReverts = assertReverts - - cls.compiled = compile_contracts([TokenState_SOURCE], - remappings=['""=contracts']) + def deployContracts(cls): cls.owner = MASTER cls.associate = DUMMY + tokenstate, cls.deploy_tx = attempt_deploy(cls.compiled, 'TokenState', MASTER, [cls.owner, cls.associate]) + return tokenstate - cls.tokenstate, _ = attempt_deploy(cls.compiled, 'TokenState', MASTER, [cls.owner, cls.associate]) + @classmethod + def setUpClass(cls): + sources = ["contracts/TokenState.sol"] - cls.state_owner = lambda self: cls.tokenstate.functions.owner().call() - cls.associatedContract = lambda self: cls.tokenstate.functions.associatedContract().call() - cls.balanceOf = lambda self, acc: cls.tokenstate.functions.balanceOf(acc).call() - cls.allowance = lambda self, frm, to: cls.tokenstate.functions.allowance(frm, to).call() + cls.compiled, cls.event_maps = cls.compileAndMapEvents(sources) + cls.event_map = cls.event_maps['TokenState'] - cls.setAssociatedContract = lambda self, sender, addr: mine_tx( - cls.tokenstate.functions.setAssociatedContract(addr).transact({'from': sender})) - cls.setAllowance = lambda self, sender, tokenOwner, spender, value: mine_tx( - cls.tokenstate.functions.setAllowance(tokenOwner, spender, value).transact({'from': sender})) - cls.setBalanceOf = lambda self, sender, account, value: mine_tx( - cls.tokenstate.functions.setBalanceOf(account, value).transact({'from': sender})) + cls.tokenstate_contract = cls.deployContracts() + cls.tokenstate = TokenStateInterface(cls.tokenstate_contract, 'TokenState') + cls.owner = MASTER + cls.associate = DUMMY def test_constructor(self): - self.assertEquals(self.state_owner(), self.owner) - self.assertEquals(self.associatedContract(), self.associate) + self.assertNotEqual(self.owner, self.associate) + self.assertEquals(self.tokenstate.owner(), self.owner) + self.assertEquals(self.tokenstate.associatedContract(), self.associate) + self.assertEventEquals(self.event_map, + self.deploy_tx.logs[1], + "AssociatedContractUpdated", + {"associatedContract": self.tokenstate.associatedContract()}, + location=self.tokenstate_contract.address) def test_setAssociatedContract(self): new_token = ZERO_ADDRESS + self.assertNotEqual(self.tokenstate.associatedContract(), new_token) # Non-owner can't set the associated contract - self.assertReverts(self.setAssociatedContract, DUMMY, new_token) - - self.assertEqual(self.balanceOf(DUMMY), 0) - self.setBalanceOf(self.associate, DUMMY, UNIT) - self.setAssociatedContract(MASTER, new_token) - self.assertEqual(self.associatedContract(), new_token) - self.assertEqual(self.balanceOf(DUMMY), UNIT) + self.assertReverts(self.tokenstate.setAssociatedContract, DUMMY, new_token) + + self.assertEqual(self.tokenstate.balanceOf(DUMMY), 0) + self.tokenstate.setBalanceOf(self.associate, DUMMY, UNIT) + tx = self.tokenstate.setAssociatedContract(MASTER, new_token) + self.assertEqual(self.tokenstate.associatedContract(), new_token) + self.assertEqual(self.tokenstate.balanceOf(DUMMY), UNIT) + self.assertEventEquals(self.event_map, tx.logs[0], + "AssociatedContractUpdated", + {"associatedContract": new_token}, + location=self.tokenstate_contract.address) def test_setAllowance(self): - - self.assertEqual(self.allowance(MASTER, DUMMY), 0) - self.setAllowance(self.associate, MASTER, DUMMY, UNIT) - self.assertEqual(self.allowance(MASTER, DUMMY), UNIT) + self.assertEqual(self.tokenstate.allowance(MASTER, DUMMY), 0) + self.tokenstate.setAllowance(self.associate, MASTER, DUMMY, UNIT) + self.assertEqual(self.tokenstate.allowance(MASTER, DUMMY), UNIT) # Only the associated contract should be able to set allowances. self.assertNotEqual(self.associate, MASTER) - self.assertReverts(self.setAllowance, MASTER, MASTER, DUMMY, UNIT) - + self.assertReverts(self.tokenstate.setAllowance, MASTER, MASTER, DUMMY, UNIT) def test_setBalanceOf(self): - self.assertEqual(self.balanceOf(MASTER), 0) - self.setBalanceOf(self.associate, MASTER, UNIT) - self.assertEqual(self.balanceOf(MASTER), UNIT) + self.assertEqual(self.tokenstate.balanceOf(MASTER), 0) + self.tokenstate.setBalanceOf(self.associate, MASTER, UNIT) + self.assertEqual(self.tokenstate.balanceOf(MASTER), UNIT) # Only the associated contract should be able to set allowances. self.assertNotEqual(self.associate, MASTER) - self.assertReverts(self.setBalanceOf, MASTER, MASTER, 2*UNIT) - - """ - def test_balances_after_swap(self): - valid_token, txr = attempt_deploy( # initial supply and beneficiary don't have to be set, as state exists - self.compiled, 'PublicExternStateProxyToken', MASTER, ["Test2", "TEST2", 0, ZERO_ADDRESS, self.tokenstate.address, MASTER] - ) - # new token only reads balances, but state doesn't accept any changes from it, until the token is - # set in the state as the associated contract - - self.assertEqual(valid_token.functions.balanceOf(MASTER).call(), 1000 * UNIT) - self.assertEqual(self.tok_balanceOf(MASTER), 1000 * UNIT) - self.assertEqual(valid_token.functions.balanceOf(DUMMY).call(), 0) - self.assertEqual(self.tok_balanceOf(DUMMY), 0) - - self.tok_transfer_byProxy(MASTER, DUMMY, 10 * UNIT) - - self.assertEqual(valid_token.functions.balanceOf(MASTER).call(), 990 * UNIT) - self.assertEqual(self.tok_balanceOf(MASTER), 990 * UNIT) - self.assertEqual(valid_token.functions.balanceOf(DUMMY).call(), 10 * UNIT) - self.assertEqual(self.tok_balanceOf(DUMMY), 10 * UNIT) - - # assert transaction reverts before the state sets the associated contract - self.assertReverts(valid_token.functions.transfer_byProxy(DUMMY, 10 * UNIT).transact, {'from': MASTER}) - - self.state_setAssociatedContract(MASTER, valid_token.address) - - # do the transaction with the new token - mine_tx(valid_token.functions.transfer_byProxy(DUMMY, 10 * UNIT).transact({'from': MASTER})) - - self.assertEqual(valid_token.functions.balanceOf(MASTER).call(), 980 * UNIT) - self.assertEqual(self.tok_balanceOf(MASTER), 980 * UNIT) - self.assertEqual(valid_token.functions.balanceOf(DUMMY).call(), 20 * UNIT) - self.assertEqual(self.tok_balanceOf(DUMMY), 20 * UNIT) - - self.assertReverts(self.tok_transfer_byProxy, MASTER, DUMMY, 10 * UNIT) - - def test_allowances(self): - valid_token, txr = attempt_deploy( # initial supply and beneficiary don't have to be set, as state exists - self.compiled, 'PublicExternStateProxyToken', MASTER, ["Test2", "TEST2", 0, ZERO_ADDRESS, self.tokenstate.address, MASTER] - ) - fake_proxy, _ = attempt_deploy(self.compiled, 'FakeProxy', MASTER, []) - mine_tx(valid_token.functions.setProxy(fake_proxy.address).transact({'from': MASTER})) - - self.assertEqual(self.tok_allowance(MASTER, DUMMY), 0) - self.tok_approve(MASTER, DUMMY, 100 * UNIT) - self.assertEqual(self.tok_allowance(MASTER, DUMMY), 100 * UNIT) - self.assertEqual(valid_token.functions.allowance(MASTER, DUMMY).call(), 100 * UNIT) - - self.tok_transferFrom_byProxy(DUMMY, MASTER, DUMMY, 20 * UNIT) - - self.assertEqual(self.tok_balanceOf(MASTER), 980 * UNIT) - self.assertEqual(self.tok_balanceOf(DUMMY), 20 * UNIT) - self.assertEqual(self.tok_allowance(MASTER, DUMMY), 80 * UNIT) - self.assertEqual(valid_token.functions.allowance(MASTER, DUMMY).call(), 80 * UNIT) - - self.state_setAssociatedContract(MASTER, valid_token.address) - - self.assertReverts(self.tok_transferFrom_byProxy, DUMMY, MASTER, DUMMY, 20 * UNIT) - - mine_tx(valid_token.functions.transferFrom_byProxy(MASTER, DUMMY, 20 * UNIT).transact({'from': DUMMY})) - - self.assertEqual(self.state_balanceOf(MASTER), 960 * UNIT) - self.assertEqual(self.state_balanceOf(DUMMY), 40 * UNIT) - self.assertEqual(self.tok_allowance(MASTER, DUMMY), 60 * UNIT) - self.assertEqual(self.state_allowance(MASTER, DUMMY), 60 * UNIT) - self.assertEqual(valid_token.functions.allowance(MASTER, DUMMY).call(), 60 * UNIT) - - mine_tx(valid_token.functions.approve(DUMMY, 0).transact({'from': MASTER})) - - self.assertEqual(self.state_balanceOf(MASTER), 960 * UNIT) - self.assertEqual(self.state_balanceOf(DUMMY), 40 * UNIT) - self.assertEqual(self.tok_allowance(MASTER, DUMMY), 0) - self.assertEqual(self.state_allowance(MASTER, DUMMY), 0) - self.assertEqual(valid_token.functions.allowance(MASTER, DUMMY).call(), 0) - - self.assertReverts( - valid_token.functions.transferFrom_byProxy(MASTER, DUMMY, 20 * UNIT).transact, {'from': DUMMY} - ) - """ + self.assertReverts(self.tokenstate.setBalanceOf, MASTER, MASTER, 2*UNIT) diff --git a/tests/test_Upgrade.py b/tests/test_Upgrade.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/utils/deployutils.py b/utils/deployutils.py index 8d49ae01ae..ea09c6504c 100644 --- a/utils/deployutils.py +++ b/utils/deployutils.py @@ -3,6 +3,7 @@ from web3 import Web3, HTTPProvider from solc import compile_files from utils.generalutils import to_seconds, TERMCOLORS +from eth_utils import function_abi_to_4byte_selector BLOCKCHAIN_ADDRESS = "http://localhost:8545" W3 = Web3(HTTPProvider(BLOCKCHAIN_ADDRESS)) @@ -24,6 +25,8 @@ # what account was last accessed, assumes ganache-cli was started with enough actors last_accessed_account = 1 +PERFORMANCE_DATA = {} + def fresh_account(): """Return first account after DUMMY""" @@ -44,14 +47,17 @@ def fresh_accounts(num_accs): return accs[:num_accs] -def attempt(function, func_args, init_string, print_status=True, print_exception=True): +def attempt(function, func_args, init_string, func_kwargs=None, print_status=True, print_exception=True): + if func_kwargs is None: + func_kwargs = {} + if print_status: print(init_string, end="", flush=True) pad = (STATUS_ALIGN_SPACING - len(init_string)) % STATUS_ALIGN_SPACING reset = TERMCOLORS.RESET try: - result = function(*func_args) + result = function(*func_args, **func_kwargs) if print_status: print(f"{TERMCOLORS.GREEN}{' '*pad}Done!{reset}") return result @@ -100,11 +106,29 @@ def restore_snapshot(snapshot): force_mine_block() -def mine_tx(tx_hash): +def mine_tx(tx_hash, function_name, contract_name): + global PERFORMANCE_DATA tx_receipt = W3.eth.getTransactionReceipt(tx_hash) while tx_receipt is None: time.sleep(POLLING_INTERVAL) tx_receipt = W3.eth.getTransactionReceipt(tx_hash) + + gas = tx_receipt['gasUsed'] + + if type(function_name) != str: + raise Exception(function_name) + if type(contract_name) != str: + raise Exception(contract_name) + + if contract_name in PERFORMANCE_DATA: + if function_name in PERFORMANCE_DATA[contract_name]: + values = PERFORMANCE_DATA[contract_name][function_name] + PERFORMANCE_DATA[contract_name][function_name] = (values[0] + gas, values[1] + 1, min([values[2], gas]), max([values[3], gas])) + else: + PERFORMANCE_DATA[contract_name][function_name] = (gas, 1, gas, gas) + else: + PERFORMANCE_DATA[contract_name] = {function_name: (gas, 1, gas, gas)} + return tx_receipt @@ -132,7 +156,7 @@ def deploy_contract(compiled_sol, contract_name, deploy_account, constructor_arg tx_hash = contract.deploy( transaction={'from': deploy_account, 'gas': gas}, args=constructor_args ) - tx_receipt = mine_tx(tx_hash) + tx_receipt = mine_txs([tx_hash])[tx_hash] contract_instance = W3.eth.contract(address=tx_receipt['contractAddress'], abi=contract_interface['abi']) return contract_instance, tx_receipt diff --git a/utils/testutils.py b/utils/testutils.py index 1005825cc9..9e9f3ff0c5 100644 --- a/utils/testutils.py +++ b/utils/testutils.py @@ -1,31 +1,61 @@ +import unittest + from web3.utils.events import get_event_data from eth_utils import event_abi_to_log_topic -from utils.deployutils import mine_tx, W3 +from utils.deployutils import mine_txs, W3, compile_contracts, attempt ZERO_ADDRESS = "0x" + "0" * 40 -def assertClose(testcase, actual, expected, precision=5, msg=''): - if expected == 0: - if actual == 0: - # this should always pass - testcase.assertEqual(actual, expected) - return - expected, actual = actual, expected - - testcase.assertAlmostEqual( - actual / expected, - 1, - places=precision, - msg=msg + f'\n{actual} ≉ {expected}' - ) - - -def assertReverts(testcase, function, *args): - with testcase.assertRaises(ValueError) as error: - function(*args) - testcase.assertTrue("revert" in error.exception.args[0]['message']) +class HavvenTestCase(unittest.TestCase): + event_maps = {} + event_map = {} + + def assertReverts(self, func, *args): + with self.assertRaises(ValueError) as error: + func(*args) + self.assertTrue("revert" in error.exception.args[0]['message']) + + def assertEventEquals(self, event_map, log, event_name, fields=None, location=None): + if fields is None: + fields = {} + event_data = get_event_data_from_log(event_map, log) + self.assertIsNotNone(event_data) + self.assertEqual(event_data['event'], event_name) + + # Iterate through the event data rather than the fields parameter + # to ensure that all fields of the event are checked. + self.assertEqual(len(fields), len(event_data['args'])) + for k, v in event_data['args'].items(): + self.assertEqual(fields[k], v, msg=f"\nField: <{k}> For event: <{event_name}>") + if location: + self.assertEqual(event_data['address'], location) + + def assertClose(self, actual, expected, precision=5, msg=''): + if expected == 0: + if actual == 0: + # this should always pass + self.assertEqual(actual, expected) + return + expected, actual = actual, expected + + self.assertAlmostEqual( + actual / expected, + 1, + places=precision, + msg=msg + f'\n{actual} ≉ {expected}' + ) + + @classmethod + def compileAndMapEvents(cls, source_paths, remappings=None): + if remappings is None: + remappings = [] + compiled = attempt(compile_contracts, [source_paths], "Compiling contracts...", + func_kwargs={'remappings': remappings}) + event_maps = {name: generate_topic_event_map(compiled[name]['abi']) + for name in compiled} + return compiled, event_maps def block_time(block_num=None): @@ -35,7 +65,7 @@ def block_time(block_num=None): def send_value(sender, recipient, value): - return mine_tx(W3.eth.sendTransaction({'from': sender, 'to': recipient, 'value': value})) + return mine_txs([W3.eth.sendTransaction({'from': sender, 'to': recipient, 'value': value})]) def get_eth_balance(account):