diff --git a/eth-contracts/.gitignore b/eth-contracts/.gitignore index 54cf6273cf8..c1b943b1259 100644 --- a/eth-contracts/.gitignore +++ b/eth-contracts/.gitignore @@ -5,4 +5,7 @@ .npmrc ganache_log.txt node_modules/ -build/ \ No newline at end of file +build/ +coverage/ +coverage.json +coverage.zip diff --git a/eth-contracts/.solcover.js b/eth-contracts/.solcover.js new file mode 100644 index 00000000000..a0621fcd962 --- /dev/null +++ b/eth-contracts/.solcover.js @@ -0,0 +1,5 @@ +module.exports = { + providerOptions: { + 'total_accounts': 20 + } +} diff --git a/eth-contracts/contracts/Governance.sol b/eth-contracts/contracts/Governance.sol index a1469e9e768..24020253c3e 100644 --- a/eth-contracts/contracts/Governance.sol +++ b/eth-contracts/contracts/Governance.sol @@ -1,7 +1,7 @@ pragma solidity ^0.5.0; import "./service/registry/RegistryContract.sol"; -import "./staking/Staking.sol"; +import "./staking/StakingInterface.sol"; import "./service/interface/registry/RegistryInterface.sol"; @@ -102,7 +102,7 @@ contract Governance is RegistryContract { address proposer = msg.sender; // Require proposer is active Staker - Staking stakingContract = Staking(registry.getContract(stakingProxyOwnerKey)); + StakingInterface stakingContract = StakingInterface(registry.getContract(stakingProxyOwnerKey)); require( stakingContract.totalStakedFor(proposer) > 0, "Proposer must be active staker with non-zero stake." @@ -158,7 +158,7 @@ contract Governance is RegistryContract { ); // Require voter is active Staker + get voterStake. - Staking stakingContract = Staking(registry.getContract(stakingProxyOwnerKey)); + StakingInterface stakingContract = StakingInterface(registry.getContract(stakingProxyOwnerKey)); uint256 voterStake = stakingContract.totalStakedForAt( voter, proposals[_proposalId].startBlockNumber @@ -223,7 +223,7 @@ contract Governance is RegistryContract { ); // Require msg.sender is active Staker. - Staking stakingContract = Staking(registry.getContract(stakingProxyOwnerKey)); + StakingInterface stakingContract = StakingInterface(registry.getContract(stakingProxyOwnerKey)); require( stakingContract.totalStakedForAt( msg.sender, proposals[_proposalId].startBlockNumber @@ -372,4 +372,4 @@ contract Governance is RegistryContract { return returnData; } -} \ No newline at end of file +} diff --git a/eth-contracts/contracts/res/TimeHelpers.sol b/eth-contracts/contracts/res/TimeHelpers.sol deleted file mode 100644 index f4a5d5725d7..00000000000 --- a/eth-contracts/contracts/res/TimeHelpers.sol +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SPDX-License-Identitifer: MIT - */ - -pragma solidity ^0.5.0; - -import "./Uint256Helpers.sol"; - - -contract TimeHelpers { - using Uint256Helpers for uint256; - - /** - * @dev Returns the current block number. - * Using a function rather than `block.number` allows us to easily mock the block number in - * tests. - */ - function getBlockNumber() internal view returns (uint256) { - return block.number; - } - - /** - * @dev Returns the current block number, converted to uint64. - * Using a function rather than `block.number` allows us to easily mock the block number in - * tests. - */ - function getBlockNumber64() internal view returns (uint64) { - return getBlockNumber().toUint64(); - } - - /** - * @dev Returns the current timestamp. - * Using a function rather than `block.timestamp` allows us to easily mock it in - * tests. - */ - function getTimestamp() internal view returns (uint256) { - return block.timestamp; // solium-disable-line security/no-block-members - } - - /** - * @dev Returns the current timestamp, converted to uint64. - * Using a function rather than `block.timestamp` allows us to easily mock it in - * tests. - */ - function getTimestamp64() internal view returns (uint64) { - return getTimestamp().toUint64(); - } -} diff --git a/eth-contracts/contracts/res/Uint256Helpers.sol b/eth-contracts/contracts/res/Uint256Helpers.sol deleted file mode 100644 index 72cb13d464b..00000000000 --- a/eth-contracts/contracts/res/Uint256Helpers.sol +++ /dev/null @@ -1,13 +0,0 @@ -pragma solidity ^0.5.0; - - -library Uint256Helpers { - uint256 private constant MAX_UINT64 = uint64(-1); - - string private constant ERROR_NUMBER_TOO_BIG = "UINT64_NUMBER_TOO_BIG"; - - function toUint64(uint256 a) internal pure returns (uint64) { - require(a <= MAX_UINT64, ERROR_NUMBER_TOO_BIG); - return uint64(a); - } -} diff --git a/eth-contracts/contracts/service/ClaimsManager.sol b/eth-contracts/contracts/service/ClaimsManager.sol index d612c540956..8b7103e8ef0 100644 --- a/eth-contracts/contracts/service/ClaimsManager.sol +++ b/eth-contracts/contracts/service/ClaimsManager.sol @@ -1,5 +1,5 @@ pragma solidity ^0.5.0; -import "../staking/Staking.sol"; +import "../staking/StakingInterface.sol"; import "./registry/RegistryContract.sol"; import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Mintable.sol"; import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; @@ -70,7 +70,6 @@ contract ClaimsManager is RegistryContract { audiusToken = ERC20Mintable(tokenAddress); registry = RegistryInterface(_registryAddress); - fundBlock = 0; fundRoundBlockDiff = 10; fundBlock = 0; fundingAmount = 20 * 10**uint256(DECIMALS); // 20 AUDS = 20 * 10**uint256(DECIMALS) @@ -108,7 +107,7 @@ contract ClaimsManager is RegistryContract { // Permissioned to stakers or contract deployer function initiateRound() external { requireIsInitialized(); - bool senderStaked = Staking( + bool senderStaked = StakingInterface( registry.getContract(stakingProxyOwnerKey) ).totalStakedFor(msg.sender) > 0; @@ -146,7 +145,7 @@ contract ClaimsManager is RegistryContract { ); address stakingAddress = registry.getContract(stakingProxyOwnerKey); - Staking stakingContract = Staking(stakingAddress); + StakingInterface stakingContract = StakingInterface(stakingAddress); // Prevent duplicate claim uint lastUserClaimBlock = stakingContract.lastClaimedFor(_claimer); require(lastUserClaimBlock <= fundBlock, "Claim already processed for user"); @@ -200,7 +199,7 @@ contract ClaimsManager is RegistryContract { } function claimPending(address _sp) external view returns (bool pending) { - uint lastClaimedForSP = Staking( + uint lastClaimedForSP = StakingInterface( registry.getContract(stakingProxyOwnerKey) ).lastClaimedFor(_sp); return (lastClaimedForSP < fundBlock); diff --git a/eth-contracts/contracts/service/DelegateManager.sol b/eth-contracts/contracts/service/DelegateManager.sol index f5e7e3723cd..b121c65f842 100644 --- a/eth-contracts/contracts/service/DelegateManager.sol +++ b/eth-contracts/contracts/service/DelegateManager.sol @@ -5,7 +5,7 @@ import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol import "./registry/RegistryContract.sol"; import "./interface/registry/RegistryInterface.sol"; -import "../staking/Staking.sol"; +import "../staking/StakingInterface.sol"; import "./ServiceProviderFactory.sol"; import "./ClaimsManager.sol"; @@ -119,7 +119,7 @@ contract DelegateManager is RegistryContract { "Delegation not permitted for SP pending claim" ); address delegator = msg.sender; - Staking stakingContract = Staking( + StakingInterface stakingContract = StakingInterface( registry.getContract(stakingProxyOwnerKey) ); @@ -251,12 +251,10 @@ contract DelegateManager is RegistryContract { "Delegator must be staked for SP" ); - Staking stakingContract = Staking( - registry.getContract(stakingProxyOwnerKey) - ); - // Stake on behalf of target service provider - stakingContract.undelegateStakeFor( + StakingInterface( + registry.getContract(stakingProxyOwnerKey) + ).undelegateStakeFor( serviceProvider, delegator, unstakeAmount, @@ -330,7 +328,7 @@ contract DelegateManager is RegistryContract { ); // Amount stored in staking contract for owner - uint totalBalanceInStaking = Staking( + uint totalBalanceInStaking = StakingInterface( registry.getContract(stakingProxyOwnerKey) ).totalStakedFor(msg.sender); require(totalBalanceInStaking > 0, "Stake required for claim"); @@ -412,7 +410,7 @@ contract DelegateManager is RegistryContract { msg.sender == registry.getContract(governanceKey), "Slash only callable from governance contract" ); - Staking stakingContract = Staking( + StakingInterface stakingContract = StakingInterface( registry.getContract(stakingProxyOwnerKey) ); diff --git a/eth-contracts/contracts/service/ServiceProviderFactory.sol b/eth-contracts/contracts/service/ServiceProviderFactory.sol index b14cd575def..9c7c756aea8 100644 --- a/eth-contracts/contracts/service/ServiceProviderFactory.sol +++ b/eth-contracts/contracts/service/ServiceProviderFactory.sol @@ -2,7 +2,7 @@ pragma solidity ^0.5.0; import "./registry/RegistryContract.sol"; import "./ServiceTypeManager.sol"; -import "../staking/ERCStaking.sol"; +import "../staking/StakingInterface.sol"; import "./interface/registry/RegistryInterface.sol"; @@ -146,7 +146,7 @@ contract ServiceProviderFactory is RegistryContract { // Stake token amount from msg.sender if (_stakeAmount > 0) { - ERCStaking( + StakingInterface( registry.getContract(stakingProxyOwnerKey) ).stakeFor(msg.sender, _stakeAmount, empty); } @@ -225,7 +225,7 @@ contract ServiceProviderFactory is RegistryContract { bool unstaked = false; // owned by the user if (spDetails[msg.sender].numberOfEndpoints == 1) { - ERCStaking stakingContract = ERCStaking( + StakingInterface stakingContract = StakingInterface( registry.getContract(stakingProxyOwnerKey) ); unstakeAmount = stakingContract.totalStakedFor(msg.sender); @@ -310,7 +310,7 @@ contract ServiceProviderFactory is RegistryContract { "Registered endpoint required to decrease stake" ); - ERCStaking stakingContract = ERCStaking( + StakingInterface stakingContract = StakingInterface( registry.getContract(stakingProxyOwnerKey) ); @@ -350,7 +350,7 @@ contract ServiceProviderFactory is RegistryContract { "Registered endpoint required to decrease stake" ); - ERCStaking stakingContract = ERCStaking( + StakingInterface stakingContract = StakingInterface( registry.getContract(stakingProxyOwnerKey) ); @@ -528,10 +528,9 @@ contract ServiceProviderFactory is RegistryContract { function validateAccountStakeBalance(address sp) external view returns (uint stakedForOwner) { - ERCStaking stakingContract = ERCStaking( + uint currentlyStakedForOwner = StakingInterface( registry.getContract(stakingProxyOwnerKey) - ); - uint currentlyStakedForOwner = stakingContract.totalStakedFor(sp); + ).totalStakedFor(sp); require( currentlyStakedForOwner >= spDetails[sp].minAccountStake, @@ -552,11 +551,10 @@ contract ServiceProviderFactory is RegistryContract { * @notice Update service provider bound status */ function updateServiceProviderBoundStatus(address _serviceProvider) internal { - ERCStaking stakingContract = ERCStaking( - registry.getContract(stakingProxyOwnerKey) - ); // Validate bounds for total stake - uint totalSPStake = stakingContract.totalStakedFor(_serviceProvider); + uint totalSPStake = StakingInterface( + registry.getContract(stakingProxyOwnerKey) + ).totalStakedFor(_serviceProvider); if (totalSPStake < spDetails[_serviceProvider].minAccountStake || totalSPStake > spDetails[_serviceProvider].maxAccountStake) { // Indicate this service provider is out of bounds diff --git a/eth-contracts/contracts/staking/Staking.sol b/eth-contracts/contracts/staking/Staking.sol index fd5e0cda2c8..1103f1e17f4 100644 --- a/eth-contracts/contracts/staking/Staking.sol +++ b/eth-contracts/contracts/staking/Staking.sol @@ -1,6 +1,6 @@ pragma solidity ^0.5.0; -import "./ERCStaking.sol"; +import "./StakingInterface.sol"; import "./Checkpointing.sol"; import "../service/interface/registry/RegistryInterface.sol"; import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; @@ -8,12 +8,11 @@ import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/SafeERC20 import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Burnable.sol"; import "../res/IsContract.sol"; -import "../res/TimeHelpers.sol"; import "../service/registry/RegistryContract.sol"; /** NOTE - will call RegistryContract.constructor, which calls Ownable constructor */ -contract Staking is RegistryContract, ERCStaking, ERCStakingHistory, IsContract, TimeHelpers { +contract Staking is RegistryContract, StakingInterface, IsContract { using SafeMath for uint256; using Checkpointing for Checkpointing.History; using SafeERC20 for ERC20; @@ -96,7 +95,7 @@ contract Staking is RegistryContract, ERCStaking, ERCStakingHistory, IsContract, bytes("")); // TODO: RM bytes requirement if unused // Update claim history even if no value claimed - accounts[_stakerAccount].claimHistory.add64(getBlockNumber64(), _amount); + accounts[_stakerAccount].claimHistory.add(block.number, _amount); } /** @@ -385,7 +384,7 @@ contract Staking is RegistryContract, ERCStaking, ERCStakingHistory, IsContract, } // add new value to account history - accounts[_accountAddress].stakedHistory.add64(getBlockNumber64(), newStake); + accounts[_accountAddress].stakedHistory.add(block.number, newStake); } function _modifyTotalStaked(uint256 _by, bool _increase) internal { @@ -399,17 +398,6 @@ contract Staking is RegistryContract, ERCStaking, ERCStakingHistory, IsContract, } // add new value to total history - totalStakedHistory.add64(getBlockNumber64(), newStake); - } - - function _transfer(address _from, address _to, uint256 _amount) internal { - // transferring 0 staked tokens is invalid - require(_amount > 0, ERROR_AMOUNT_ZERO); - - // update stakes - _modifyStakeBalance(_from, _amount, false); - _modifyStakeBalance(_to, _amount, true); - - emit StakeTransferred(_from,_amount, _to); + totalStakedHistory.add(block.number, newStake); } } diff --git a/eth-contracts/contracts/staking/ERCStaking.sol b/eth-contracts/contracts/staking/StakingInterface.sol similarity index 68% rename from eth-contracts/contracts/staking/ERCStaking.sol rename to eth-contracts/contracts/staking/StakingInterface.sol index 72153867244..dcf83e7549c 100644 --- a/eth-contracts/contracts/staking/ERCStaking.sol +++ b/eth-contracts/contracts/staking/StakingInterface.sol @@ -4,22 +4,27 @@ pragma solidity ^0.5.0; // Modified interface for ERC900: https://eips.ethereum.org/EIPS/eip-900 // Eliminates direct stake operations -interface ERCStaking { +interface StakingInterface { event Staked(address indexed user, uint256 amount, uint256 total, bytes data); event Unstaked(address indexed user, uint256 amount, uint256 total, bytes data); function stakeFor(address user, uint256 amount, bytes calldata data) external; function unstakeFor(address user, uint256 amount, bytes calldata data) external; + function stakeRewards(uint256 amount, address stakerAccount) external; + function delegateStakeFor(address accountAddress, address delegatorAddress, uint256 amount, bytes calldata data) external; + function undelegateStakeFor(address accountAddress, address delegatorAddress, uint256 amount, bytes calldata data) external; + function slash(uint256 amount, address slashAddress) external; + + function totalStakedFor(address addr) external view returns (uint256); function totalStaked() external view returns (uint256); function token() external view returns (address); function supportsHistory() external pure returns (bool); -} -interface ERCStakingHistory { function lastStakedFor(address addr) external view returns (uint256); function totalStakedForAt(address addr, uint256 blockNumber) external view returns (uint256); function totalStakedAt(uint256 blockNumber) external view returns (uint256); + function lastClaimedFor(address addr) external view returns (uint256); }