New logic for makers joining pools

This commit is contained in:
Michael Zhu
2019-09-06 16:05:32 -07:00
committed by Greg Hysen
parent 24af39d4a8
commit 86a28f0d19
37 changed files with 470 additions and 1033 deletions

View File

@@ -97,7 +97,7 @@ contract MixinExchangeFees is
{
uint256 amount = msg.value;
bytes32 poolId = getStakingPoolIdOfMaker(makerAddress);
if (poolId != NIL_MAKER_ID) {
if (poolId != NIL_POOL_ID) {
// There is a pool associated with `makerAddress`.
// TODO(dorothy-zbornak): When we have epoch locks on delegating, we could
// preclude pools that have no delegated stake, since they will never have

View File

@@ -32,7 +32,7 @@ contract MixinConstants is
// The upper 16 bytes represent the pool id, so this would be an increment of 1. See MixinStakinPool for more information.
uint256 constant internal POOL_ID_INCREMENT_AMOUNT = 0x0000000000000000000000000000000100000000000000000000000000000000;
bytes32 constant internal NIL_MAKER_ID = 0x0000000000000000000000000000000000000000000000000000000000000000;
bytes32 constant internal NIL_POOL_ID = 0x0000000000000000000000000000000000000000000000000000000000000000;
address constant internal NIL_ADDRESS = 0x0000000000000000000000000000000000000000;

View File

@@ -31,4 +31,6 @@ contract MixinDeploymentConstants {
uint32 constant internal REWARD_DELEGATED_STAKE_WEIGHT = 900000; // 90%
uint256 constant internal CHAIN_ID = 1;
uint256 constant internal MAX_MAKERS_IN_POOL = 10;
}

View File

@@ -32,7 +32,6 @@ contract MixinStorage is
Ownable,
MixinConstants
{
constructor()
public
Ownable()
@@ -76,12 +75,12 @@ contract MixinStorage is
// mapping from Pool Id to Pool
mapping (bytes32 => IStructs.Pool) internal poolById;
// mapping from Maker Address to Pool Id
// A Maker can only hold a single token
mapping (address => bytes32) internal poolIdByMakerAddress;
// mapping from Maker Address to a struct representing the pool the maker has joined and
// whether the operator of that pool has subsequently added the maker.
mapping (address => IStructs.MakerPoolJoinStatus) internal poolJoinedByMakerAddress;
// mapping from Pool Id to Addresses
mapping (bytes32 => address[]) internal makerAddressesByPoolId;
// mapping from Pool Id to number of makers assigned to that pool
mapping (bytes32 => uint256) internal numMakersByPoolId;
// current epoch
uint256 internal currentEpoch = INITIAL_EPOCH;
@@ -119,4 +118,3 @@ contract MixinStorage is
// Denominator for cobb douglas alpha factor.
uint256 internal cobbDouglasAlphaDenomintor = 6;
}

View File

@@ -97,6 +97,14 @@ interface IStakingEvents {
uint32 operatorShare
);
/// @dev Emitted by MixinStakingPool when a new maker requests to join a pool.
/// @param poolId Unique id of pool.
/// @param makerAddress Adress of maker joining the pool.
event PendingAddMakerToPool(
bytes32 poolId,
address makerAddress
);
/// @dev Emitted by MixinStakingPool when a new maker is added to a pool.
/// @param poolId Unique id of pool.
/// @param makerAddress Adress of maker added to pool.

View File

@@ -31,14 +31,6 @@ interface IStructs {
NSignatureTypes // 0x05, number of signature types. Always leave at end.
}
/// @dev Required fields for a maker to approve a staking pool.
/// @param poolId Unique Id of staking pool.
/// @param makerAddress Address of maker who has approved the pool.
struct StakingPoolApproval {
bytes32 poolId;
address makerAddress;
}
/// @dev Status for Staking Pools (see MixinStakingPool).
/// @param operatorAddress Address of pool operator.
/// @param operatorShare Portion of pool rewards owned by operator, in ppm.
@@ -102,4 +94,13 @@ interface IStructs {
uint256 numerator;
uint256 denominator;
}
/// @dev State for keeping track of which pool a maker has joined, and if the operator has
/// added them (see MixinStakingPool).
/// @param poolId Unique Id of staking pool.
/// @param confirmed Whether the operator has added the maker to the pool.
struct MakerPoolJoinStatus {
bytes32 poolId;
bool confirmed;
}
}

View File

@@ -1,32 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
interface IWallet /* is EIP-1271 */ {
/// @dev Should return whether the signature provided is valid for the provided data
/// @param data Arbitrary length data signed on the behalf of address(this)
/// @param signature Signature byte array associated with _data
///
/// MUST return the bytes4 magic value 0x20c13b0b when function passes.
/// MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5)
/// MUST allow external calls
function isValidSignature(
bytes calldata data,
bytes calldata signature)
external
view
returns (bytes4 magicValue);
}

View File

@@ -1,100 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/LibEIP712.sol";
import "../interfaces/IStructs.sol";
library LibEIP712Hash {
// EIP712 Domain Name value for the Staking contract
string constant internal EIP712_STAKING_DOMAIN_NAME = "0x Protocol Staking";
// EIP712 Domain Version value for the Staking contract
string constant internal EIP712_STAKING_DOMAIN_VERSION = "1.0.0";
// Hash for the EIP712 StakingPool approval message
// keccak256(abi.encodePacked(
// "StakingPoolApproval(",
// "bytes32 poolId,",
// "address makerAddress",
// ")"
// ));
bytes32 constant internal EIP712_STAKING_POOL_APPROVAL_SCHEMA_HASH = 0x9b699f12ef1c0f7b43076182dcccc0c548c9a784cfcf27114f98d684e06826b6;
/// @dev Calculated the EIP712 hash of the StakingPool approval mesasage using the domain separator of this contract.
/// @param approval StakingPool approval message containing the transaction hash, transaction signature, and expiration of the approval.
/// @return EIP712 hash of the StakingPool approval message with the domain separator of this contract.
function _hashStakingPoolApprovalMessage(
IStructs.StakingPoolApproval memory approval,
uint256 chainId,
address verifierAddress
)
internal
pure
returns (bytes32 approvalHash)
{
approvalHash = _hashEIP712StakingMessage(
_hashStakingPoolApproval(approval),
chainId,
verifierAddress
);
return approvalHash;
}
/// @dev Calculates EIP712 encoding for a hash struct in the EIP712 domain
/// of this contract.
/// @param hashStruct The EIP712 hash struct.
/// @return EIP712 hash applied to this EIP712 Domain.
function _hashEIP712StakingMessage(
bytes32 hashStruct,
uint256 chainId,
address verifierAddress
)
internal
pure
returns (bytes32 result)
{
bytes32 eip712StakingDomainHash = LibEIP712.hashEIP712Domain(
EIP712_STAKING_DOMAIN_NAME,
EIP712_STAKING_DOMAIN_VERSION,
chainId,
verifierAddress
);
return LibEIP712.hashEIP712Message(eip712StakingDomainHash, hashStruct);
}
/// @dev Calculated the EIP712 hash of the StakingPool approval mesasage with no domain separator.
/// @param approval StakingPool approval message containing the transaction hash, transaction signature, and expiration of the approval.
/// @return EIP712 hash of the StakingPool approval message with no domain separator.
function _hashStakingPoolApproval(IStructs.StakingPoolApproval memory approval)
internal
pure
returns (bytes32 result)
{
result = keccak256(abi.encode(
EIP712_STAKING_POOL_APPROVAL_SCHEMA_HASH,
approval.poolId,
approval.makerAddress
));
return result;
}
}

View File

@@ -1,192 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
import "../libs/LibStakingRichErrors.sol";
import "../interfaces/IStructs.sol";
import "../interfaces/IWallet.sol";
library LibSignatureValidator {
using LibBytes for bytes;
// bytes4(keccak256("isValidSignature(bytes,bytes)")
bytes4 constant internal EIP1271_MAGIC_VALUE = 0x20c13b0b;
/// @dev Verifies that a hash has been signed by the given signer.
/// @param hash Any 32 byte hash.
/// @param signerAddress Address that should have signed the given hash.
/// @param signature Proof that the hash has been signed by signer.
/// @return True if the address recovered from the provided signature matches the input signer address.
function _isValidSignature(
bytes32 hash,
address signerAddress,
bytes memory signature
)
internal
view
returns (bool isValid)
{
if (signature.length == 0) {
LibRichErrors.rrevert(LibStakingRichErrors.SignatureLengthGreaterThan0RequiredError());
}
// Pop last byte off of signature byte array.
uint8 signatureTypeRaw = uint8(signature.popLastByte());
// Ensure signature is supported
if (signatureTypeRaw >= uint8(IStructs.SignatureType.NSignatureTypes)) {
LibRichErrors.rrevert(LibStakingRichErrors.SignatureUnsupportedError(
signature
));
}
IStructs.SignatureType signatureType = IStructs.SignatureType(signatureTypeRaw);
// Variables are not scoped in Solidity.
uint8 v;
bytes32 r;
bytes32 s;
address recovered;
// Always illegal signature.
// This is always an implicit option since a signer can create a
// signature array with invalid type or length. We may as well make
// it an explicit option. This aids testing and analysis. It is
// also the initialization value for the enum type.
if (signatureType == IStructs.SignatureType.Illegal) {
LibRichErrors.rrevert(LibStakingRichErrors.SignatureIllegalError(
signature
));
// Always invalid signature.
// Like Illegal, this is always implicitly available and therefore
// offered explicitly. It can be implicitly created by providing
// a correctly formatted but incorrect signature.
} else if (signatureType == IStructs.SignatureType.Invalid) {
if (signature.length > 0) {
LibRichErrors.rrevert(LibStakingRichErrors.SignatureLength0RequiredError(
signature
));
}
isValid = false;
return isValid;
// Signature using EIP712
} else if (signatureType == IStructs.SignatureType.EIP712) {
if (signature.length != 65) {
LibRichErrors.rrevert(LibStakingRichErrors.SignatureLength65RequiredError(
signature
));
}
v = uint8(signature[0]);
r = signature.readBytes32(1);
s = signature.readBytes32(33);
recovered = ecrecover(
hash,
v,
r,
s
);
isValid = signerAddress == recovered;
return isValid;
// Signed using web3.eth_sign
} else if (signatureType == IStructs.SignatureType.EthSign) {
if (signature.length != 65) {
LibRichErrors.rrevert(LibStakingRichErrors.SignatureLength65RequiredError(
signature
));
}
v = uint8(signature[0]);
r = signature.readBytes32(1);
s = signature.readBytes32(33);
recovered = ecrecover(
keccak256(abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
hash
)),
v,
r,
s
);
isValid = signerAddress == recovered;
return isValid;
// Signature verified by wallet contract.
// If used with an order, the maker of the order is the wallet contract.
} else if (signatureType == IStructs.SignatureType.Wallet) {
isValid = _isValidWalletSignature(
hash,
signerAddress,
signature
);
return isValid;
}
// Anything else is illegal (We do not return false because
// the signature may actually be valid, just not in a format
// that we currently support. In this case returning false
// may lead the caller to incorrectly believe that the
// signature was invalid.)
LibRichErrors.rrevert(LibStakingRichErrors.SignatureUnsupportedError(
signature
));
}
/// @dev Verifies signature using logic defined by Wallet contract.
/// @param hash Any 32 byte hash.
/// @param walletAddress Address that should have signed the given hash
/// and defines its own signature verification method.
/// @param signature Proof that the hash has been signed by signer.
/// @return True if signature is valid for given wallet..
function _isValidWalletSignature(
bytes32 hash,
address walletAddress,
bytes memory signature
)
internal
view
returns (bool isValid)
{
// contruct hash as bytes, so that it is a valid EIP-1271 payload
bytes memory hashAsBytes = new bytes(32);
assembly {
mstore(add(hashAsBytes, 32), hash)
}
// Static call `isValidSignature` in the destination wallet
bytes memory callData = abi.encodeWithSelector(
IWallet(walletAddress).isValidSignature.selector,
hash,
signature
);
(bool success, bytes memory result) = walletAddress.staticcall(callData);
// Sanity check call and extract the magic value
if (!success) {
LibRichErrors.rrevert(LibStakingRichErrors.WalletError(
walletAddress,
result
));
}
bytes4 magicValue = result.readBytes4(0);
isValid = (magicValue == EIP1271_MAGIC_VALUE);
return isValid;
}
}

View File

@@ -38,30 +38,6 @@ library LibStakingRichErrors {
bytes4 internal constant EXCHANGE_ADDRESS_NOT_REGISTERED_ERROR_SELECTOR =
0x7dc025b0;
// bytes4(keccak256("SignatureLengthGreaterThan0RequiredError()"))
bytes internal constant SIGNATURE_LENGTH_GREATER_THAN_0_REQUIRED_ERROR =
hex"2dcb01d9";
// bytes4(keccak256("SignatureUnsupportedError(bytes)"))
bytes4 internal constant SIGNATURE_UNSUPPORTED_ERROR_SELECTOR =
0xffca2a70;
// bytes4(keccak256("SignatureIllegalError(bytes)"))
bytes4 internal constant SIGNATURE_ILLEGAL_ERROR_SELECTOR =
0x4a95093c;
// bytes4(keccak256("SignatureLength0RequiredError(bytes)"))
bytes4 internal constant SIGNATURE_LENGTH_0_REQUIRED_ERROR_SELECTOR =
0xcbcd59a2;
// bytes4(keccak256("SignatureLength65RequiredError(bytes)"))
bytes4 internal constant SIGNATURE_LENGTH_65_REQUIRED_ERROR_SELECTOR =
0x091d7ab9;
// bytes4(keccak256("WalletError(address,bytes)"))
bytes4 internal constant WALLET_ERROR_SELECTOR =
0x0cfc935d;
// bytes4(keccak256("InsufficientBalanceError(uint256,uint256)"))
bytes4 internal constant INSUFFICIENT_BALANCE_ERROR_SELECTOR =
0x84c8b7c9;
@@ -74,17 +50,9 @@ library LibStakingRichErrors {
bytes4 internal constant ONLY_CALLABLE_BY_POOL_OPERATOR_OR_MAKER_ERROR_SELECTOR =
0x7d9e1c10;
// bytes4(keccak256("InvalidMakerSignatureError(bytes32,address,bytes)"))
bytes4 internal constant INVALID_MAKER_SIGNATURE_ERROR_SELECTOR =
0x726b89c8;
// bytes4(keccak256("MakerAddressAlreadyRegisteredError(address)"))
bytes4 internal constant MAKER_ADDRESS_ALREADY_REGISTERED_ERROR_SELECTOR =
0x5a3971da;
// bytes4(keccak256("MakerAddressNotRegisteredError(address,bytes32,bytes32)"))
bytes4 internal constant MAKER_ADDRESS_NOT_REGISTERED_ERROR_SELECTOR =
0x12ab07e8;
// bytes4(keccak256("MakerPoolAssignmentError(uint8,address,bytes32)"))
bytes4 internal constant MAKER_POOL_ASSIGNMENT_ERROR_SELECTOR =
0x69945e3f;
// bytes4(keccak256("WithdrawAmountExceedsMemberBalanceError(uint256,uint256)"))
bytes4 internal constant WITHDRAW_AMOUNT_EXCEEDS_MEMBER_BALANCE_ERROR_SELECTOR =
@@ -138,6 +106,13 @@ library LibStakingRichErrors {
bytes internal constant PROXY_DESTINATION_CANNOT_BE_NIL =
hex"01ecebea";
enum MakerPoolAssignmentErrorCodes {
MAKER_ADDRESS_ALREADY_REGISTERED,
MAKER_ADDRESS_NOT_REGISTERED,
MAKER_ADDRESS_NOT_PENDING_ADD,
POOL_IS_FULL
}
// solhint-disable func-name-mixedcase
function MiscalculatedRewardsError(
uint256 totalRewardsPaid,
@@ -193,81 +168,6 @@ library LibStakingRichErrors {
);
}
function SignatureLengthGreaterThan0RequiredError()
internal
pure
returns (bytes memory)
{
return SIGNATURE_LENGTH_GREATER_THAN_0_REQUIRED_ERROR;
}
function SignatureUnsupportedError(
bytes memory signature
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
SIGNATURE_UNSUPPORTED_ERROR_SELECTOR,
signature
);
}
function SignatureIllegalError(
bytes memory signature
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
SIGNATURE_ILLEGAL_ERROR_SELECTOR,
signature
);
}
function SignatureLength0RequiredError(
bytes memory signature
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
SIGNATURE_LENGTH_0_REQUIRED_ERROR_SELECTOR,
signature
);
}
function SignatureLength65RequiredError(
bytes memory signature
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
SIGNATURE_LENGTH_65_REQUIRED_ERROR_SELECTOR,
signature
);
}
function WalletError(
address walletAddress,
bytes memory errorData
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
WALLET_ERROR_SELECTOR,
walletAddress,
errorData
);
}
function InsufficientBalanceError(
uint256 amount,
uint256 balance
@@ -315,39 +215,9 @@ library LibStakingRichErrors {
);
}
function InvalidMakerSignatureError(
bytes32 poolId,
function MakerPoolAssignmentError(
MakerPoolAssignmentErrorCodes errorCode,
address makerAddress,
bytes memory makerSignature
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
INVALID_MAKER_SIGNATURE_ERROR_SELECTOR,
poolId,
makerAddress,
makerSignature
);
}
function MakerAddressAlreadyRegisteredError(
address makerAddress
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
MAKER_ADDRESS_ALREADY_REGISTERED_ERROR_SELECTOR,
makerAddress
);
}
function MakerAddressNotRegisteredError(
address makerAddress,
bytes32 makerPoolId,
bytes32 poolId
)
internal
@@ -355,9 +225,9 @@ library LibStakingRichErrors {
returns (bytes memory)
{
return abi.encodeWithSelector(
MAKER_ADDRESS_NOT_REGISTERED_ERROR_SELECTOR,
MAKER_POOL_ASSIGNMENT_ERROR_SELECTOR,
errorCode,
makerAddress,
makerPoolId,
poolId
);
}

View File

@@ -22,8 +22,6 @@ pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "../libs/LibStakingRichErrors.sol";
import "../libs/LibSignatureValidator.sol";
import "../libs/LibEIP712Hash.sol";
import "../interfaces/IStructs.sol";
import "../interfaces/IStakingEvents.sol";
import "../immutable/MixinConstants.sol";
@@ -99,8 +97,9 @@ contract MixinStakingPool is
/// @dev Create a new staking pool. The sender will be the operator of this pool.
/// Note that an operator must be payable.
/// @param operatorShare Portion of rewards owned by the operator, in ppm.
/// @param addOperatorAsMaker Adds operator to the created pool as a maker for convenience iff true.
/// @return poolId The unique pool id generated for this pool.
function createStakingPool(uint32 operatorShare)
function createStakingPool(uint32 operatorShare, bool addOperatorAsMaker)
external
returns (bytes32 poolId)
{
@@ -125,48 +124,119 @@ contract MixinStakingPool is
// register pool in reward vault
rewardVault.registerStakingPool(poolId, operatorShare);
// notify
// Staking pool has been created
emit StakingPoolCreated(poolId, operatorAddress, operatorShare);
if (addOperatorAsMaker) {
// Is the maker already in a pool?
if (isMakerAssignedToStakingPool(operatorAddress)) {
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.MAKER_ADDRESS_ALREADY_REGISTERED,
operatorAddress,
getStakingPoolIdOfMaker(operatorAddress)
));
}
IStructs.MakerPoolJoinStatus memory poolJoinStatus = IStructs.MakerPoolJoinStatus({
poolId: poolId,
confirmed: true
});
poolJoinedByMakerAddress[operatorAddress] = poolJoinStatus;
numMakersByPoolId[poolId] += 1;
// Operator has been added as a maker to tbe pool
emit MakerAddedToStakingPool(
poolId,
operatorAddress
);
}
return poolId;
}
function joinStakingPoolAsMaker(
bytes32 poolId
)
external
{
// Is the maker already in a pool?
address makerAddress = msg.sender;
if (isMakerAssignedToStakingPool(makerAddress)) {
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.MAKER_ADDRESS_ALREADY_REGISTERED,
makerAddress,
getStakingPoolIdOfMaker(makerAddress)
));
}
IStructs.MakerPoolJoinStatus memory poolJoinStatus = IStructs.MakerPoolJoinStatus({
poolId: poolId,
confirmed: false
});
poolJoinedByMakerAddress[makerAddress] = poolJoinStatus;
// Maker has joined to the pool, awaiting operator confirmation
emit PendingAddMakerToPool(
poolId,
makerAddress
);
}
/// @dev Adds a maker to a staking pool. Note that this is only callable by the pool operator.
/// Note also that the maker must have previously called joinStakingPoolAsMaker.
/// @param poolId Unique id of pool.
/// @param makerAddress Address of maker.
/// @param makerSignature Signature proving that maker has agreed to join the pool.
function addMakerToStakingPool(
bytes32 poolId,
address makerAddress,
bytes calldata makerSignature
address makerAddress
)
external
onlyStakingPoolOperator(poolId)
{
// sanity check - did maker agree to join this pool?
if (!isValidMakerSignature(poolId, makerAddress, makerSignature)) {
LibRichErrors.rrevert(LibStakingRichErrors.InvalidMakerSignatureError(
poolId,
makerAddress,
makerSignature
));
}
// Is the maker already in a pool?
if (isMakerAssignedToStakingPool(makerAddress)) {
LibRichErrors.rrevert(LibStakingRichErrors.MakerAddressAlreadyRegisteredError(
makerAddress
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.MAKER_ADDRESS_ALREADY_REGISTERED,
makerAddress,
getStakingPoolIdOfMaker(makerAddress)
));
}
poolIdByMakerAddress[makerAddress] = poolId;
makerAddressesByPoolId[poolId].push(makerAddress);
// Is the maker trying to join this pool?
bytes32 makerPendingPoolId = poolJoinedByMakerAddress[makerAddress].poolId;
if (makerPendingPoolId != poolId) {
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.MAKER_ADDRESS_NOT_PENDING_ADD,
makerAddress,
makerPendingPoolId
));
}
// notify
// Is the pool already full?
if (getNumberOfMakersInStakingPool(poolId) == MAX_MAKERS_IN_POOL) {
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.POOL_IS_FULL,
makerAddress,
poolId
));
}
// Add maker to pool
IStructs.MakerPoolJoinStatus memory poolJoinStatus = IStructs.MakerPoolJoinStatus({
poolId: poolId,
confirmed: true
});
poolJoinedByMakerAddress[makerAddress] = poolJoinStatus;
numMakersByPoolId[poolId] += 1;
// Maker has been added to the pool
emit MakerAddedToStakingPool(
poolId,
makerAddress
);
}
/// @dev Adds a maker to a staking pool. Note that this is only callable by the pool operator or maker.
/// @dev Removes a maker from a staking pool. Note that this is only callable by the pool operator or maker.
/// Note also that the maker does not have to *agree* to leave the pool; this action is
/// at the sole discretion of the pool operator.
/// @param poolId Unique id of pool.
@@ -180,90 +250,41 @@ contract MixinStakingPool is
{
bytes32 makerPoolId = getStakingPoolIdOfMaker(makerAddress);
if (makerPoolId != poolId) {
LibRichErrors.rrevert(LibStakingRichErrors.MakerAddressNotRegisteredError(
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.MAKER_ADDRESS_NOT_REGISTERED,
makerAddress,
makerPoolId,
poolId
makerPoolId
));
}
// load list of makers for the input pool.
address[] storage makerAddressesByPoolIdPtr = makerAddressesByPoolId[poolId];
uint256 makerAddressesByPoolIdLength = makerAddressesByPoolIdPtr.length;
// remove the pool and confirmation from the maker status
IStructs.MakerPoolJoinStatus memory poolJoinStatus = IStructs.MakerPoolJoinStatus({
poolId: NIL_POOL_ID,
confirmed: false
});
poolJoinedByMakerAddress[makerAddress] = poolJoinStatus;
numMakersByPoolId[poolId] -= 1;
// find index of maker to remove.
uint indexOfMakerAddress = 0;
for (; indexOfMakerAddress < makerAddressesByPoolIdLength; ++indexOfMakerAddress) {
if (makerAddressesByPoolIdPtr[indexOfMakerAddress] == makerAddress) {
break;
}
}
// remove the maker from the list of makers for this pool.
// (i) move maker at end of list to the slot occupied by the maker to remove, then
// (ii) zero out the slot at the end of the list and decrement the length.
uint256 indexOfLastMakerAddress = makerAddressesByPoolIdLength - 1;
if (indexOfMakerAddress != indexOfLastMakerAddress) {
makerAddressesByPoolIdPtr[indexOfMakerAddress] = makerAddressesByPoolIdPtr[indexOfLastMakerAddress];
}
makerAddressesByPoolIdPtr[indexOfLastMakerAddress] = NIL_ADDRESS;
makerAddressesByPoolIdPtr.length -= 1;
// reset the pool id assigned to the maker.
poolIdByMakerAddress[makerAddress] = NIL_MAKER_ID;
// notify
// Maker has been removed from the pool`
emit MakerRemovedFromStakingPool(
poolId,
makerAddress
);
}
/// @dev Returns true iff the input signature is valid; meaning that the maker agrees to
/// be added to the pool.
/// @param poolId Unique id of pool the maker wishes to join.
/// @param makerAddress Address of maker.
/// @param makerSignature Signature of the maker.
/// @return isValid True iff the maker agrees to be added to the pool.
function isValidMakerSignature(bytes32 poolId, address makerAddress, bytes memory makerSignature)
public
view
returns (bool isValid)
{
bytes32 approvalHash = getStakingPoolApprovalMessageHash(poolId, makerAddress);
isValid = LibSignatureValidator._isValidSignature(approvalHash, makerAddress, makerSignature);
return isValid;
}
/// @dev Returns the approval message hash - this is what a maker must sign in order to
/// be added to a pool.
/// @param poolId Unique id of pool the maker wishes to join.
/// @param makerAddress Address of maker.
/// @return approvalHash Hash of message the maker must sign.
function getStakingPoolApprovalMessageHash(bytes32 poolId, address makerAddress)
public
view
returns (bytes32 approvalHash)
{
IStructs.StakingPoolApproval memory approval = IStructs.StakingPoolApproval({
poolId: poolId,
makerAddress: makerAddress
});
// hash approval message and check signer address
address verifierAddress = address(this);
approvalHash = LibEIP712Hash._hashStakingPoolApprovalMessage(approval, CHAIN_ID, verifierAddress);
return approvalHash;
}
/// @dev Returns the pool id of an input maker.
/// @dev Returns the pool id of the input maker.
/// @param makerAddress Address of maker
/// @return Pool id, nil if maker is not yet assigned to a pool.
function getStakingPoolIdOfMaker(address makerAddress)
public
view
returns (bytes32)
{
return poolIdByMakerAddress[makerAddress];
if (isMakerAssignedToStakingPool(makerAddress)) {
return poolJoinedByMakerAddress[makerAddress].poolId;
} else {
return NIL_POOL_ID;
}
}
/// @dev Returns true iff the maker is assigned to a staking pool.
@@ -274,18 +295,18 @@ contract MixinStakingPool is
view
returns (bool)
{
return getStakingPoolIdOfMaker(makerAddress) != NIL_MAKER_ID;
return poolJoinedByMakerAddress[makerAddress].confirmed;
}
/// @dev Returns the makers for a given pool.
/// @dev Returns the current number of makers in a given pool.
/// @param poolId Unique id of pool.
/// @return _makerAddressesByPoolId Makers for pool.
function getMakersForStakingPool(bytes32 poolId)
/// @return Size of pool.
function getNumberOfMakersInStakingPool(bytes32 poolId)
public
view
returns (address[] memory _makerAddressesByPoolId)
returns (uint256)
{
return makerAddressesByPoolId[poolId];
return numMakersByPoolId[poolId];
}
/// @dev Returns the unique id that will be assigned to the next pool that is created.

View File

@@ -30,7 +30,7 @@ import "../immutable/MixinConstants.sol";
/// @dev This vault manages staking pool rewards.
/// Rewards can be deposited and withdraw by the staking contract.
/// Rewards can be deposited and withdrawn by the staking contract.
/// There is a "Catastrophic Failure Mode" that, when invoked, only
/// allows withdrawals to be made. Once this vault is in catastrophic
/// failure mode, it cannot be returned to normal mode; this prevents

View File

@@ -76,10 +76,10 @@ contract TestStorageLayout is
if sub(poolById_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(poolIdByMakerAddress_slot, slot) { revertIncorrectStorageSlot() }
if sub(poolJoinedByMakerAddress_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(makerAddressesByPoolId_slot, slot) { revertIncorrectStorageSlot() }
if sub(numMakersByPoolId_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(currentEpoch_slot, slot) { revertIncorrectStorageSlot() }

View File

@@ -37,7 +37,7 @@
},
"config": {
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./generated-artifacts/@(EthVault|IEthVault|IStaking|IStakingEvents|IStakingPoolRewardVault|IStakingProxy|IStructs|IVaultCore|IWallet|IZrxVault|LibEIP712Hash|LibFixedMath|LibFixedMathRichErrors|LibProxy|LibSafeDowncast|LibSignatureValidator|LibStakingRichErrors|MixinConstants|MixinDeploymentConstants|MixinEthVault|MixinExchangeFees|MixinExchangeManager|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolRewardVault|MixinStakingPoolRewards|MixinStorage|MixinVaultCore|MixinZrxVault|ReadOnlyProxy|Staking|StakingPoolRewardVault|StakingProxy|TestCobbDouglas|TestLibFixedMath|TestStorageLayout|ZrxVault).json"
"abis": "./generated-artifacts/@(EthVault|IEthVault|IStaking|IStakingEvents|IStakingPoolRewardVault|IStakingProxy|IStructs|IVaultCore|IZrxVault|LibFixedMath|LibFixedMathRichErrors|LibProxy|LibSafeDowncast|LibStakingRichErrors|MixinConstants|MixinDeploymentConstants|MixinEthVault|MixinExchangeFees|MixinExchangeManager|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolRewardVault|MixinStakingPoolRewards|MixinStorage|MixinVaultCore|MixinZrxVault|ReadOnlyProxy|Staking|StakingPoolRewardVault|StakingProxy|TestCobbDouglas|TestLibFixedMath|TestStorageLayout|ZrxVault).json"
},
"repository": {
"type": "git",

View File

@@ -13,14 +13,11 @@ import * as IStakingPoolRewardVault from '../generated-artifacts/IStakingPoolRew
import * as IStakingProxy from '../generated-artifacts/IStakingProxy.json';
import * as IStructs from '../generated-artifacts/IStructs.json';
import * as IVaultCore from '../generated-artifacts/IVaultCore.json';
import * as IWallet from '../generated-artifacts/IWallet.json';
import * as IZrxVault from '../generated-artifacts/IZrxVault.json';
import * as LibEIP712Hash from '../generated-artifacts/LibEIP712Hash.json';
import * as LibFixedMath from '../generated-artifacts/LibFixedMath.json';
import * as LibFixedMathRichErrors from '../generated-artifacts/LibFixedMathRichErrors.json';
import * as LibProxy from '../generated-artifacts/LibProxy.json';
import * as LibSafeDowncast from '../generated-artifacts/LibSafeDowncast.json';
import * as LibSignatureValidator from '../generated-artifacts/LibSignatureValidator.json';
import * as LibStakingRichErrors from '../generated-artifacts/LibStakingRichErrors.json';
import * as MixinConstants from '../generated-artifacts/MixinConstants.json';
import * as MixinDeploymentConstants from '../generated-artifacts/MixinDeploymentConstants.json';
@@ -61,14 +58,11 @@ export const artifacts = {
IStakingProxy: IStakingProxy as ContractArtifact,
IStructs: IStructs as ContractArtifact,
IVaultCore: IVaultCore as ContractArtifact,
IWallet: IWallet as ContractArtifact,
IZrxVault: IZrxVault as ContractArtifact,
LibEIP712Hash: LibEIP712Hash as ContractArtifact,
LibFixedMath: LibFixedMath as ContractArtifact,
LibFixedMathRichErrors: LibFixedMathRichErrors as ContractArtifact,
LibProxy: LibProxy as ContractArtifact,
LibSafeDowncast: LibSafeDowncast as ContractArtifact,
LibSignatureValidator: LibSignatureValidator as ContractArtifact,
LibStakingRichErrors: LibStakingRichErrors as ContractArtifact,
MixinStake: MixinStake as ContractArtifact,
MixinStakeBalances: MixinStakeBalances as ContractArtifact,

View File

@@ -11,14 +11,11 @@ export * from '../generated-wrappers/i_staking_pool_reward_vault';
export * from '../generated-wrappers/i_staking_proxy';
export * from '../generated-wrappers/i_structs';
export * from '../generated-wrappers/i_vault_core';
export * from '../generated-wrappers/i_wallet';
export * from '../generated-wrappers/i_zrx_vault';
export * from '../generated-wrappers/lib_e_i_p712_hash';
export * from '../generated-wrappers/lib_fixed_math';
export * from '../generated-wrappers/lib_fixed_math_rich_errors';
export * from '../generated-wrappers/lib_proxy';
export * from '../generated-wrappers/lib_safe_downcast';
export * from '../generated-wrappers/lib_signature_validator';
export * from '../generated-wrappers/lib_staking_rich_errors';
export * from '../generated-wrappers/mixin_constants';
export * from '../generated-wrappers/mixin_deployment_constants';

View File

@@ -1,41 +1,47 @@
import { SignatureType } from '@0x/types';
import { expect } from '@0x/contracts-test-utils';
import { RevertError } from '@0x/utils';
import * as _ from 'lodash';
import { StakingWrapper } from '../utils/staking_wrapper';
import { SignedStakingPoolApproval } from '../utils/types';
import { constants as stakingConstants } from '../utils/constants';
import { BaseActor } from './base_actor';
export class MakerActor extends BaseActor {
private readonly _ownerPrivateKeyIfExists?: Buffer;
private readonly _signatureVerifierIfExists?: string;
private readonly _chainIdIfExists?: number;
public async joinStakingPoolAsMakerAsync(poolId: string, revertError?: RevertError): Promise<void> {
// Join pool
const txReceiptPromise = this._stakingWrapper.joinStakingPoolAsMakerAsync(poolId, this._owner);
constructor(
owner: string,
stakingWrapper: StakingWrapper,
ownerPrivateKey?: Buffer,
signatureVerifier?: string,
chainId?: number,
) {
super(owner, stakingWrapper);
this._ownerPrivateKeyIfExists = ownerPrivateKey;
this._signatureVerifierIfExists = signatureVerifier;
this._chainIdIfExists = chainId;
if (revertError !== undefined) {
await expect(txReceiptPromise).to.revertWith(revertError);
return;
}
await txReceiptPromise;
// Pool id of the maker should be nil (join would've thrown otherwise)
const poolIdOfMaker = await this._stakingWrapper.getStakingPoolIdOfMakerAsync(this._owner);
expect(poolIdOfMaker, 'pool id of maker').to.be.equal(stakingConstants.NIL_POOL_ID);
}
public signApprovalForStakingPool(
public async removeMakerFromStakingPoolAsync(
poolId: string,
signatureType: SignatureType = SignatureType.EthSign,
): SignedStakingPoolApproval {
const approval = this._stakingWrapper.signApprovalForStakingPool(
makerAddress: string,
revertError?: RevertError,
): Promise<void> {
// remove maker (should fail if makerAddress !== this._owner)
const txReceiptPromise = this._stakingWrapper.removeMakerFromStakingPoolAsync(
poolId,
makerAddress,
this._owner,
this._ownerPrivateKeyIfExists,
this._signatureVerifierIfExists,
this._chainIdIfExists,
signatureType,
);
return approval;
if (revertError !== undefined) {
await expect(txReceiptPromise).to.revertWith(revertError);
return;
}
await txReceiptPromise;
// check the pool id of the maker
const poolIdOfMakerAfterRemoving = await this._stakingWrapper.getStakingPoolIdOfMakerAsync(this._owner);
expect(poolIdOfMakerAfterRemoving, 'pool id of maker').to.be.equal(stakingConstants.NIL_POOL_ID);
}
}

View File

@@ -3,20 +3,23 @@ import { RevertError } from '@0x/utils';
import * as _ from 'lodash';
import { constants as stakingConstants } from '../utils/constants';
import { StakingWrapper } from '../utils/staking_wrapper';
import { BaseActor } from './base_actor';
export class PoolOperatorActor extends BaseActor {
constructor(owner: string, stakingWrapper: StakingWrapper) {
super(owner, stakingWrapper);
}
public async createStakingPoolAsync(operatorShare: number, revertError?: RevertError): Promise<string> {
public async createStakingPoolAsync(
operatorShare: number,
addOperatorAsMaker: boolean,
revertError?: RevertError,
): Promise<string> {
// query next pool id
const nextPoolId = await this._stakingWrapper.getNextStakingPoolIdAsync();
// create pool
const poolIdPromise = this._stakingWrapper.createStakingPoolAsync(this._owner, operatorShare);
const poolIdPromise = this._stakingWrapper.createStakingPoolAsync(
this._owner,
operatorShare,
addOperatorAsMaker,
);
if (revertError !== undefined) {
await expect(poolIdPromise).to.revertWith(revertError);
return '';
@@ -24,21 +27,24 @@ export class PoolOperatorActor extends BaseActor {
const poolId = await poolIdPromise;
// validate pool id
expect(poolId, 'pool id').to.be.bignumber.equal(nextPoolId);
if (addOperatorAsMaker) {
// check the pool id of the operator
const poolIdOfMaker = await this._stakingWrapper.getStakingPoolIdOfMakerAsync(this._owner);
expect(poolIdOfMaker, 'pool id of maker').to.be.equal(poolId);
// check the number of makers in the pool
const numMakersAfterRemoving = await this._stakingWrapper.getNumberOfMakersInStakingPoolAsync(poolId);
expect(numMakersAfterRemoving, 'number of makers in pool').to.be.bignumber.equal(1);
}
return poolId;
}
public async addMakerToStakingPoolAsync(
poolId: string,
makerAddress: string,
makerSignature: string,
revertError?: RevertError,
): Promise<void> {
// add maker
const txReceiptPromise = this._stakingWrapper.addMakerToStakingPoolAsync(
poolId,
makerAddress,
makerSignature,
this._owner,
);
const txReceiptPromise = this._stakingWrapper.addMakerToStakingPoolAsync(poolId, makerAddress, this._owner);
if (revertError !== undefined) {
await expect(txReceiptPromise).to.revertWith(revertError);
return;
@@ -47,9 +53,6 @@ export class PoolOperatorActor extends BaseActor {
// check the pool id of the maker
const poolIdOfMaker = await this._stakingWrapper.getStakingPoolIdOfMakerAsync(makerAddress);
expect(poolIdOfMaker, 'pool id of maker').to.be.equal(poolId);
// check the list of makers for the pool
const makerAddressesForPool = await this._stakingWrapper.getMakersForStakingPoolAsync(poolId);
expect(makerAddressesForPool, 'maker addresses for pool').to.include(makerAddress);
}
public async removeMakerFromStakingPoolAsync(
poolId: string,
@@ -70,8 +73,5 @@ export class PoolOperatorActor extends BaseActor {
// check the pool id of the maker
const poolIdOfMakerAfterRemoving = await this._stakingWrapper.getStakingPoolIdOfMakerAsync(makerAddress);
expect(poolIdOfMakerAfterRemoving, 'pool id of maker').to.be.equal(stakingConstants.NIL_POOL_ID);
// check the list of makers for the pool
const makerAddressesForPoolAfterRemoving = await this._stakingWrapper.getMakersForStakingPoolAsync(poolId);
expect(makerAddressesForPoolAfterRemoving, 'maker addresses for pool').to.not.include(makerAddress);
}
}

View File

@@ -36,7 +36,7 @@ blockchainTests.resets('Catastrophe Tests', env => {
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
await erc20Wrapper.setBalancesAndAllowancesAsync();
// deploy staking contracts
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract, accounts);
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract);
await stakingWrapper.deployAndConfigureContractsAsync();
});

View File

@@ -42,7 +42,7 @@ describe('Epochs', () => {
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
await erc20Wrapper.setBalancesAndAllowancesAsync();
// deploy staking contracts
stakingWrapper = new StakingWrapper(provider, owner, erc20ProxyContract, zrxTokenContract, accounts);
stakingWrapper = new StakingWrapper(provider, owner, erc20ProxyContract, zrxTokenContract);
await stakingWrapper.deployAndConfigureContractsAsync();
});
beforeEach(async () => {

View File

@@ -33,7 +33,7 @@ blockchainTests('Exchange Integrations', env => {
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
await erc20Wrapper.setBalancesAndAllowancesAsync();
// deploy staking contracts
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract, accounts);
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract);
await stakingWrapper.deployAndConfigureContractsAsync();
});
blockchainTests.resets('Exchange Tracking in Staking Contract', () => {

View File

@@ -2,7 +2,6 @@ import { ERC20ProxyContract, ERC20Wrapper } from '@0x/contracts-asset-proxy';
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
import { blockchainTests, constants, expect } from '@0x/contracts-test-utils';
import { StakingRevertErrors } from '@0x/order-utils';
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';
import { MakerActor } from './actors/maker_actor';
@@ -36,7 +35,7 @@ blockchainTests('Staking Pool Management', env => {
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, DUMMY_TOKEN_DECIMALS);
await erc20Wrapper.setBalancesAndAllowancesAsync();
// deploy staking contracts
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract, accounts);
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract);
await stakingWrapper.deployAndConfigureContractsAsync();
});
blockchainTests.resets('Staking Pool Management', () => {
@@ -46,12 +45,23 @@ blockchainTests('Staking Pool Management', env => {
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare);
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// check that the next pool id was incremented
const expectedNextPoolId = '0x0000000000000000000000000000000200000000000000000000000000000000';
const nextPoolId = await stakingWrapper.getNextStakingPoolIdAsync();
expect(nextPoolId).to.be.equal(expectedNextPoolId);
expect(nextPoolId).to.be.equal(stakingConstants.SECOND_POOL_ID);
});
it('Should successfully create a pool and add owner as a maker', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// check that the next pool id was incremented
const nextPoolId = await stakingWrapper.getNextStakingPoolIdAsync();
expect(nextPoolId).to.be.equal(stakingConstants.SECOND_POOL_ID);
});
it('Should throw if poolOperatorShare is > PPM_DENOMINATOR', async () => {
// test parameters
@@ -60,7 +70,7 @@ blockchainTests('Staking Pool Management', env => {
const operatorShare = PPM_100_PERCENT + 1;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
// create pool
const tx = poolOperator.createStakingPoolAsync(operatorShare);
const tx = poolOperator.createStakingPoolAsync(operatorShare, true);
const expectedPoolId = stakingConstants.INITIAL_POOL_ID;
const expectedError = new StakingRevertErrors.InvalidPoolOperatorShareError(expectedPoolId, operatorShare);
return expect(tx).to.revertWith(expectedError);
@@ -73,41 +83,138 @@ blockchainTests('Staking Pool Management', env => {
const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingWrapper);
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare);
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add maker to pool
const makerApproval = maker.signApprovalForStakingPool(poolId);
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, makerApproval.signature);
// remove maker from pool
// maker joins pool
await maker.joinStakingPoolAsMakerAsync(poolId);
// operator adds maker to pool
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
// operator removes maker from pool
await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddress);
});
it('Should successfully add/remove multipler makers to the same pool', async () => {
it('Maker should successfully remove themselves from a pool', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingWrapper);
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// maker joins pool
await maker.joinStakingPoolAsMakerAsync(poolId);
// operator adds maker to pool
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
// maker removes themselves from pool
await maker.removeMakerFromStakingPoolAsync(poolId, makerAddress);
});
it('Should successfully add/remove multiple makers to the same pool', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddresses = users.slice(1, 4);
const makers = [
new MakerActor(makerAddresses[0], stakingWrapper),
new MakerActor(makerAddresses[1], stakingWrapper),
new MakerActor(makerAddresses[2], stakingWrapper),
];
const makers = makerAddresses.map(makerAddress => new MakerActor(makerAddress, stakingWrapper));
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare);
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add makers to pool
const makerApprovals = [
makers[0].signApprovalForStakingPool(poolId),
makers[1].signApprovalForStakingPool(poolId),
makers[2].signApprovalForStakingPool(poolId),
];
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddresses[0], makerApprovals[0].signature);
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddresses[1], makerApprovals[1].signature);
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddresses[2], makerApprovals[2].signature);
await Promise.all(makers.map(async maker => maker.joinStakingPoolAsMakerAsync(poolId)));
await Promise.all(
makerAddresses.map(async makerAddress => poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress)),
);
// check the number of makers in the pool
let numMakers = await stakingWrapper.getNumberOfMakersInStakingPoolAsync(poolId);
expect(numMakers, 'number of makers in pool after adding').to.be.bignumber.equal(3);
// remove maker from pool
await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddresses[0]);
await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddresses[1]);
await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddresses[2]);
await Promise.all(
makerAddresses.map(async makerAddress =>
poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddress),
),
);
// check the number of makers in the pool
numMakers = await stakingWrapper.getNumberOfMakersInStakingPoolAsync(poolId);
expect(numMakers, 'number of makers in pool after removing').to.be.bignumber.equal(0);
});
it('Should fail if maker already assigned to another pool tries to join', async () => {
// test parameters
const operatorShare = 39;
const assignedPoolOperator = new PoolOperatorActor(users[0], stakingWrapper);
const otherPoolOperator = new PoolOperatorActor(users[1], stakingWrapper);
const makerAddress = users[2];
const maker = new MakerActor(makerAddress, stakingWrapper);
// create pools
const assignedPoolId = await assignedPoolOperator.createStakingPoolAsync(operatorShare, true);
const otherPoolId = await otherPoolOperator.createStakingPoolAsync(operatorShare, true);
expect(assignedPoolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
expect(otherPoolId).to.be.equal(stakingConstants.SECOND_POOL_ID);
// maker joins first pool
await maker.joinStakingPoolAsMakerAsync(assignedPoolId);
// first pool operator adds maker
await assignedPoolOperator.addMakerToStakingPoolAsync(assignedPoolId, makerAddress);
const revertError = new StakingRevertErrors.MakerPoolAssignmentError(
StakingRevertErrors.MakerPoolAssignmentErrorCodes.MakerAddressAlreadyRegistered,
makerAddress,
assignedPoolId,
);
// second pool operator now tries to add maker
await otherPoolOperator.addMakerToStakingPoolAsync(otherPoolId, makerAddress, revertError);
});
it('Should fail to add maker to pool if the maker has not joined any pools', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddress = users[1];
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
const revertError = new StakingRevertErrors.MakerPoolAssignmentError(
StakingRevertErrors.MakerPoolAssignmentErrorCodes.MakerAddressNotPendingAdd,
makerAddress,
stakingConstants.NIL_POOL_ID,
);
// operator adds maker to pool
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, revertError);
});
it('Should fail to add maker to pool if the maker joined a different pool', async () => {
// test parameters
const operatorShare = 39;
const assignedPoolOperator = new PoolOperatorActor(users[0], stakingWrapper);
const otherPoolOperator = new PoolOperatorActor(users[1], stakingWrapper);
const makerAddress = users[2];
const maker = new MakerActor(makerAddress, stakingWrapper);
// create pools
const joinedPoolId = await assignedPoolOperator.createStakingPoolAsync(operatorShare, true);
const otherPoolId = await otherPoolOperator.createStakingPoolAsync(operatorShare, true);
expect(joinedPoolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
expect(otherPoolId).to.be.equal(stakingConstants.SECOND_POOL_ID);
// maker joins first pool
await maker.joinStakingPoolAsMakerAsync(joinedPoolId);
const revertError = new StakingRevertErrors.MakerPoolAssignmentError(
StakingRevertErrors.MakerPoolAssignmentErrorCodes.MakerAddressNotPendingAdd,
makerAddress,
joinedPoolId,
);
// second pool operator now tries to add maker
await otherPoolOperator.addMakerToStakingPoolAsync(otherPoolId, makerAddress, revertError);
});
it('Should fail to add the same maker twice', async () => {
// test parameters
@@ -117,14 +224,18 @@ blockchainTests('Staking Pool Management', env => {
const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingWrapper);
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare);
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add maker to pool
const makerApproval = maker.signApprovalForStakingPool(poolId);
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, makerApproval.signature);
const revertError = new StakingRevertErrors.MakerAddressAlreadyRegisteredError(makerAddress);
await maker.joinStakingPoolAsMakerAsync(poolId);
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
const revertError = new StakingRevertErrors.MakerPoolAssignmentError(
StakingRevertErrors.MakerPoolAssignmentErrorCodes.MakerAddressAlreadyRegistered,
makerAddress,
poolId,
);
// add same maker to pool again
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, makerApproval.signature, revertError);
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, revertError);
});
it('Should fail to remove a maker that does not exist', async () => {
// test parameters
@@ -133,87 +244,16 @@ blockchainTests('Staking Pool Management', env => {
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddress = users[1];
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare);
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
const revertError = new StakingRevertErrors.MakerAddressNotRegisteredError(
const revertError = new StakingRevertErrors.MakerPoolAssignmentError(
StakingRevertErrors.MakerPoolAssignmentErrorCodes.MakerAddressNotRegistered,
makerAddress,
stakingConstants.NIL_POOL_ID,
poolId,
);
// remove non-existent maker from pool
await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddress, revertError);
});
it('Should fail to add a maker who signed with the wrong private key', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddress = users[1];
const badMakerPrivateKey = ethUtil.toBuffer(
'0x0000000000000000000000000000000000000000000000000000000000000001',
);
const maker = new MakerActor(makerAddress, stakingWrapper, badMakerPrivateKey);
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add maker to poolxx
const makerApproval = maker.signApprovalForStakingPool(poolId);
const revertError = new StakingRevertErrors.InvalidMakerSignatureError(
poolId,
makerAddress,
makerApproval.signature,
);
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, makerApproval.signature, revertError);
});
it('Should fail to add a maker who signed with the wrong staking contract address', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddress = users[1];
const forceMakerKeyLookup = undefined;
const notStakingContractAddress = users[2];
const maker = new MakerActor(makerAddress, stakingWrapper, forceMakerKeyLookup, notStakingContractAddress);
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add maker to pool
const makerApproval = maker.signApprovalForStakingPool(poolId);
const revertError = new StakingRevertErrors.InvalidMakerSignatureError(
poolId,
makerAddress,
makerApproval.signature,
);
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, makerApproval.signature, revertError);
});
it('Should fail to add a maker who signed with the wrong chain id', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddress = users[1];
const forceMakerKeyLookup = undefined;
const forceStakingContractLookup = undefined;
const badChainId = 209348;
const maker = new MakerActor(
makerAddress,
stakingWrapper,
forceMakerKeyLookup,
forceStakingContractLookup,
badChainId,
);
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add maker to pool
const makerApproval = maker.signApprovalForStakingPool(poolId);
const revertError = new StakingRevertErrors.InvalidMakerSignatureError(
poolId,
makerAddress,
makerApproval.signature,
);
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, makerApproval.signature, revertError);
});
it('Should fail to add a maker when called by someone other than the pool operator', async () => {
// test parameters
const operatorAddress = users[0];
@@ -223,45 +263,78 @@ blockchainTests('Staking Pool Management', env => {
const maker = new MakerActor(makerAddress, stakingWrapper);
const notOperatorAddress = users[2];
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare);
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add maker to pool
const makerApproval = maker.signApprovalForStakingPool(poolId);
await maker.joinStakingPoolAsMakerAsync(poolId);
const revertError = new StakingRevertErrors.OnlyCallableByPoolOperatorError(
notOperatorAddress,
operatorAddress,
);
const tx = stakingWrapper.addMakerToStakingPoolAsync(
poolId,
makerAddress,
makerApproval.signature,
notOperatorAddress,
);
const tx = stakingWrapper.addMakerToStakingPoolAsync(poolId, makerAddress, notOperatorAddress);
await expect(tx).to.revertWith(revertError);
});
it('Should fail to remove a maker when called by someone other than the pool operator', async () => {
it('Should fail to remove a maker when called by someone other than the pool operator or maker', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingWrapper);
const notOperatorAddress = users[2];
const neitherOperatorNorMakerAddress = users[2];
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare);
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add maker to pool
const makerApproval = maker.signApprovalForStakingPool(poolId);
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, makerApproval.signature);
await maker.joinStakingPoolAsMakerAsync(poolId);
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
// try to remove the maker address from an address other than the operator
const revertError = new StakingRevertErrors.OnlyCallableByPoolOperatorOrMakerError(
notOperatorAddress,
neitherOperatorNorMakerAddress,
operatorAddress,
makerAddress,
);
const tx = stakingWrapper.removeMakerFromStakingPoolAsync(poolId, makerAddress, notOperatorAddress);
const tx = stakingWrapper.removeMakerFromStakingPoolAsync(
poolId,
makerAddress,
neitherOperatorNorMakerAddress,
);
await expect(tx).to.revertWith(revertError);
});
it('Should fail to add a maker if the pool is full', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddresses = users.slice(1, stakingConstants.MAX_MAKERS_IN_POOL + 2);
const makers = makerAddresses.map(makerAddress => new MakerActor(makerAddress, stakingWrapper));
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add makers to pool
await Promise.all(makers.map(async maker => maker.joinStakingPoolAsMakerAsync(poolId)));
await Promise.all(
_.initial(makerAddresses).map(async makerAddress =>
poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress),
),
);
// check the number of makers in the pool
const numMakers = await stakingWrapper.getNumberOfMakersInStakingPoolAsync(poolId);
expect(numMakers, 'number of makers in pool').to.be.bignumber.equal(stakingConstants.MAX_MAKERS_IN_POOL);
const lastMakerAddress = _.last(makerAddresses) as string;
// Try to add last maker to the pool
const revertError = new StakingRevertErrors.MakerPoolAssignmentError(
StakingRevertErrors.MakerPoolAssignmentErrorCodes.PoolIsFull,
lastMakerAddress,
poolId,
);
await poolOperator.addMakerToStakingPoolAsync(poolId, lastMakerAddress, revertError);
});
});
});
// tslint:enable:no-unnecessary-type-assertion

View File

@@ -46,16 +46,13 @@ blockchainTests.resets('Testing Rewards', () => {
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
await erc20Wrapper.setBalancesAndAllowancesAsync();
// deploy staking contracts
stakingWrapper = new StakingWrapper(provider, owner, erc20ProxyContract, zrxTokenContract, accounts);
stakingWrapper = new StakingWrapper(provider, owner, erc20ProxyContract, zrxTokenContract);
await stakingWrapper.deployAndConfigureContractsAsync();
// setup stakers
stakers = [new StakerActor(actors[0], stakingWrapper), new StakerActor(actors[1], stakingWrapper)];
// setup pools
poolOperator = actors[2];
poolId = await stakingWrapper.createStakingPoolAsync(poolOperator, 0);
// add operator as maker
const approvalMessage = stakingWrapper.signApprovalForStakingPool(poolId, poolOperator);
await stakingWrapper.addMakerToStakingPoolAsync(poolId, poolOperator, approvalMessage.signature, poolOperator);
poolId = await stakingWrapper.createStakingPoolAsync(poolOperator, 0, true);
// set exchange address
await stakingWrapper.addExchangeAddressAsync(exchangeAddress);
// associate operators for tracking in Finalizer

View File

@@ -41,7 +41,7 @@ blockchainTests('End-To-End Simulations', env => {
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
await erc20Wrapper.setBalancesAndAllowancesAsync();
// deploy staking contracts
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract, accounts);
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract);
await stakingWrapper.deployAndConfigureContractsAsync();
});
blockchainTests.resets('Simulations', () => {

View File

@@ -1,6 +1,6 @@
import { ERC20ProxyContract, ERC20Wrapper } from '@0x/contracts-asset-proxy';
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
import { blockchainTests, describe, provider, web3Wrapper } from '@0x/contracts-test-utils';
import { blockchainTests, describe, web3Wrapper } from '@0x/contracts-test-utils';
import { StakingRevertErrors } from '@0x/order-utils';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
@@ -10,7 +10,7 @@ import { StakingWrapper } from './utils/staking_wrapper';
import { StakeInfo, StakeStatus } from './utils/types';
// tslint:disable:no-unnecessary-type-assertion
blockchainTests.resets('Stake Statuses', () => {
blockchainTests.resets('Stake Statuses', env => {
// constants
const ZRX_TOKEN_DECIMALS = new BigNumber(18);
const ZERO = new BigNumber(0);
@@ -34,21 +34,21 @@ blockchainTests.resets('Stake Statuses', () => {
owner = accounts[0];
actors = accounts.slice(2, 5);
// deploy erc20 proxy
erc20Wrapper = new ERC20Wrapper(provider, accounts, owner);
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
erc20ProxyContract = await erc20Wrapper.deployProxyAsync();
// deploy zrx token
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
await erc20Wrapper.setBalancesAndAllowancesAsync();
// deploy staking contracts
stakingWrapper = new StakingWrapper(provider, owner, erc20ProxyContract, zrxTokenContract, accounts);
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract);
await stakingWrapper.deployAndConfigureContractsAsync();
// setup new staker
staker = new StakerActor(actors[0], stakingWrapper);
// setup pools
poolOperator = actors[1];
poolIds = await Promise.all([
await stakingWrapper.createStakingPoolAsync(poolOperator, 4),
await stakingWrapper.createStakingPoolAsync(poolOperator, 5),
await stakingWrapper.createStakingPoolAsync(poolOperator, 4, false),
await stakingWrapper.createStakingPoolAsync(poolOperator, 5, false),
]);
});
describe('Stake', () => {

View File

@@ -128,7 +128,7 @@ export class Simulation {
const poolOperator = new PoolOperatorActor(poolOperatorAddress, this._stakingWrapper);
this._poolOperators.push(poolOperator);
// create a pool id for this operator
const poolId = await poolOperator.createStakingPoolAsync(p.poolOperatorShares[i]);
const poolId = await poolOperator.createStakingPoolAsync(p.poolOperatorShares[i], false);
this._poolIds.push(poolId);
// each pool operator can also be a staker/delegator
const poolOperatorAsDelegator = new DelegatorActor(poolOperatorAddress, this._stakingWrapper);
@@ -156,9 +156,9 @@ export class Simulation {
// tslint:disable-next-line no-unused-variable
for (const j of _.range(numberOfMakersInPool)) {
const maker = this._makers[makerIdx];
const makerApproval = maker.signApprovalForStakingPool(poolId);
await maker.joinStakingPoolAsMakerAsync(poolId);
const makerAddress = maker.getOwner();
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, makerApproval.signature);
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
makerIdx += 1;
}
poolIdx += 1;

View File

@@ -1,40 +0,0 @@
import { signingUtils } from '@0x/contracts-test-utils';
import { SignatureType } from '@0x/types';
import * as ethUtil from 'ethereumjs-util';
import { hashUtils } from './hash_utils';
import { SignedStakingPoolApproval } from './types';
export class ApprovalFactory {
private readonly _privateKey: Buffer;
private readonly _verifyingContractAddress: string;
private readonly _chainId: number;
constructor(privateKey: Buffer, verifyingContractAddress: string, chainId: number) {
this._privateKey = privateKey;
this._verifyingContractAddress = verifyingContractAddress;
this._chainId = chainId;
}
public newSignedApproval(
poolId: string,
makerAddress: string,
signatureType: SignatureType = SignatureType.EthSign,
): SignedStakingPoolApproval {
const approvalHashBuff = hashUtils.getStakingPoolApprovalHashBuffer(
poolId,
makerAddress,
this._verifyingContractAddress,
this._chainId,
);
const signatureBuff = signingUtils.signMessage(approvalHashBuff, this._privateKey, signatureType);
const signedApproval = {
makerAddress,
poolId,
verifyingContractAddress: this._verifyingContractAddress,
chainId: this._chainId,
signature: ethUtil.addHexPrefix(signatureBuff.toString('hex')),
};
return signedApproval;
}
}

View File

@@ -4,6 +4,7 @@ export const constants = {
MAX_UINT_64: new BigNumber(2).pow(256).minus(1),
TOKEN_MULTIPLIER: new BigNumber(10).pow(18),
INITIAL_POOL_ID: '0x0000000000000000000000000000000100000000000000000000000000000000',
SECOND_POOL_ID: '0x0000000000000000000000000000000200000000000000000000000000000000',
NIL_POOL_ID: '0x0000000000000000000000000000000000000000000000000000000000000000',
NIL_ADDRESS: '0x0000000000000000000000000000000000000000',
INITIAL_EPOCH: new BigNumber(0),
@@ -11,4 +12,5 @@ export const constants = {
EPOCH_DURATION_IN_SECONDS: new BigNumber(1000), // @TODO SET FOR DEPLOYMENT*/
TIMELOCK_DURATION_IN_EPOCHS: new BigNumber(3), // @TODO SET FOR DEPLOYMENT
CHAIN_ID: 1,
MAX_MAKERS_IN_POOL: 10, // @TODO SET FOR DEPLOYMENT,
};

View File

@@ -1,32 +0,0 @@
import { eip712Utils } from '@0x/order-utils';
import { signTypedDataUtils } from '@0x/utils';
import * as _ from 'lodash';
export const hashUtils = {
getStakingPoolApprovalHashBuffer(
poolId: string,
makerAddress: string,
verifyingContractAddress: string,
chainId: number,
): Buffer {
const typedData = eip712Utils.createStakingPoolApprovalTypedData(
poolId,
makerAddress,
verifyingContractAddress,
chainId,
);
const hashBuffer = signTypedDataUtils.generateTypedDataHash(typedData);
return hashBuffer;
},
getStakingPoolApprovalHashHex(
poolId: string,
makerAddress: string,
verifyingContractAddress: string,
chainId: number,
): string {
const hashHex = `0x${hashUtils
.getStakingPoolApprovalHashBuffer(poolId, makerAddress, verifyingContractAddress, chainId)
.toString('hex')}`;
return hashHex;
},
};

View File

@@ -1,7 +1,6 @@
import { ERC20ProxyContract } from '@0x/contracts-asset-proxy';
import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
import { constants as testUtilsConstants, LogDecoder, txDefaults } from '@0x/contracts-test-utils';
import { SignatureType } from '@0x/types';
import { LogDecoder, txDefaults } from '@0x/contracts-test-utils';
import { BigNumber, logUtils } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
@@ -17,9 +16,8 @@ import {
ZrxVaultContract,
} from '../../src';
import { ApprovalFactory } from './approval_factory';
import { constants } from './constants';
import { SignedStakingPoolApproval, StakeBalance } from './types';
import { StakeBalance } from './types';
export class StakingWrapper {
private readonly _web3Wrapper: Web3Wrapper;
@@ -28,7 +26,6 @@ export class StakingWrapper {
private readonly _ownerAddress: string;
private readonly _erc20ProxyContract: ERC20ProxyContract;
private readonly _zrxTokenContract: DummyERC20TokenContract;
private readonly _accounts: string[];
private _stakingContractIfExists?: StakingContract;
private _stakingProxyContractIfExists?: StakingProxyContract;
private _zrxVaultContractIfExists?: ZrxVaultContract;
@@ -67,7 +64,6 @@ export class StakingWrapper {
ownerAddres: string,
erc20ProxyContract: ERC20ProxyContract,
zrxTokenContract: DummyERC20TokenContract,
accounts: string[],
) {
this._web3Wrapper = new Web3Wrapper(provider);
this._provider = provider;
@@ -76,7 +72,6 @@ export class StakingWrapper {
this._ownerAddress = ownerAddres;
this._erc20ProxyContract = erc20ProxyContract;
this._zrxTokenContract = zrxTokenContract;
this._accounts = accounts;
}
public getStakingContract(): StakingContract {
this._validateDeployedOrThrow();
@@ -283,23 +278,36 @@ export class StakingWrapper {
const nextPoolId = await this._callAsync(calldata);
return nextPoolId;
}
public async createStakingPoolAsync(operatorAddress: string, operatorShare: number): Promise<string> {
const calldata = this.getStakingContract().createStakingPool.getABIEncodedTransactionData(operatorShare);
public async createStakingPoolAsync(
operatorAddress: string,
operatorShare: number,
addOperatorAsMaker: boolean,
): Promise<string> {
const calldata = this.getStakingContract().createStakingPool.getABIEncodedTransactionData(
operatorShare,
addOperatorAsMaker,
);
const txReceipt = await this._executeTransactionAsync(calldata, operatorAddress);
const createStakingPoolLog = this._logDecoder.decodeLogOrThrow(txReceipt.logs[0]);
const poolId = (createStakingPoolLog as any).args.poolId;
return poolId;
}
public async joinStakingPoolAsMakerAsync(
poolId: string,
makerAddress: string,
): Promise<TransactionReceiptWithDecodedLogs> {
const calldata = this.getStakingContract().joinStakingPoolAsMaker.getABIEncodedTransactionData(poolId);
const txReceipt = await this._executeTransactionAsync(calldata, makerAddress);
return txReceipt;
}
public async addMakerToStakingPoolAsync(
poolId: string,
makerAddress: string,
makerSignature: string,
operatorAddress: string,
): Promise<TransactionReceiptWithDecodedLogs> {
const calldata = this.getStakingContract().addMakerToStakingPool.getABIEncodedTransactionData(
poolId,
makerAddress,
makerSignature,
);
const txReceipt = await this._executeTransactionAsync(calldata, operatorAddress);
return txReceipt;
@@ -321,55 +329,11 @@ export class StakingWrapper {
const poolId = await this._callAsync(calldata);
return poolId;
}
public async getMakersForStakingPoolAsync(poolId: string): Promise<string[]> {
const calldata = this.getStakingContract().getMakersForStakingPool.getABIEncodedTransactionData(poolId);
const returndata = await this._callAsync(calldata);
const makerAddresses = this.getStakingContract().getMakersForStakingPool.getABIDecodedReturnData(returndata);
return makerAddresses;
}
public async isValidMakerSignatureAsync(
poolId: string,
makerAddress: string,
makerSignature: string,
): Promise<boolean> {
const calldata = this.getStakingContract().isValidMakerSignature.getABIEncodedTransactionData(
poolId,
makerAddress,
makerSignature,
);
const returndata = await this._callAsync(calldata);
const isValid = this.getStakingContract().isValidMakerSignature.getABIDecodedReturnData(returndata);
return isValid;
}
public async getStakingPoolApprovalMessageHashAsync(poolId: string, makerAddress: string): Promise<string> {
const calldata = this.getStakingContract().getStakingPoolApprovalMessageHash.getABIEncodedTransactionData(
poolId,
makerAddress,
);
const returndata = await this._callAsync(calldata);
const messageHash = this.getStakingContract().getStakingPoolApprovalMessageHash.getABIDecodedReturnData(
returndata,
);
return messageHash;
}
public signApprovalForStakingPool(
poolId: string,
makerAddress: string,
makerPrivateKeyIfExists?: Buffer,
verifierAddressIfExists?: string,
chainIdIfExists?: number,
signatureType: SignatureType = SignatureType.EthSign,
): SignedStakingPoolApproval {
const makerPrivateKey =
makerPrivateKeyIfExists !== undefined
? makerPrivateKeyIfExists
: testUtilsConstants.TESTRPC_PRIVATE_KEYS[this._accounts.indexOf(makerAddress)];
const verifierAddress =
verifierAddressIfExists !== undefined ? verifierAddressIfExists : this.getStakingProxyContract().address;
const chainId = chainIdIfExists !== undefined ? chainIdIfExists : constants.CHAIN_ID;
const approvalFactory = new ApprovalFactory(makerPrivateKey, verifierAddress, chainId);
const signedStakingPoolApproval = approvalFactory.newSignedApproval(poolId, makerAddress, signatureType);
return signedStakingPoolApproval;
public async getNumberOfMakersInStakingPoolAsync(poolId: string): Promise<BigNumber> {
const calldata = this.getStakingContract().getNumberOfMakersInStakingPool.getABIEncodedTransactionData(poolId);
const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().getNumberOfMakersInStakingPool.getABIDecodedReturnData(returnData);
return value;
}
///// EPOCHS /////
public async goToNextEpochAsync(): Promise<TransactionReceiptWithDecodedLogs> {

View File

@@ -1,16 +1,5 @@
import { BigNumber } from '@0x/utils';
export interface StakingPoolApproval {
makerAddress: string;
poolId: string;
verifyingContractAddress: string;
chainId: number;
}
export interface SignedStakingPoolApproval extends StakingPoolApproval {
signature: string;
}
export interface StakerBalances {
zrxBalance: BigNumber;
stakeBalance: BigNumber;

View File

@@ -33,7 +33,7 @@ blockchainTests('Staking Vaults', env => {
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
await erc20Wrapper.setBalancesAndAllowancesAsync();
// deploy staking contracts
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract, accounts);
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract);
await stakingWrapper.deployAndConfigureContractsAsync();
});
blockchainTests.resets('Reward Vault', () => {
@@ -41,7 +41,7 @@ blockchainTests('Staking Vaults', env => {
// 1 setup test parameters
const poolOperator = users[0];
const operatorShare = 39;
const poolId = await stakingWrapper.createStakingPoolAsync(poolOperator, operatorShare);
const poolId = await stakingWrapper.createStakingPoolAsync(poolOperator, operatorShare, true);
const stakingContractAddress = stakingWrapper.getStakingContract().address;
const notStakingContractAddress = poolOperator;
// create pool in vault

View File

@@ -11,14 +11,11 @@
"generated-artifacts/IStakingProxy.json",
"generated-artifacts/IStructs.json",
"generated-artifacts/IVaultCore.json",
"generated-artifacts/IWallet.json",
"generated-artifacts/IZrxVault.json",
"generated-artifacts/LibEIP712Hash.json",
"generated-artifacts/LibFixedMath.json",
"generated-artifacts/LibFixedMathRichErrors.json",
"generated-artifacts/LibProxy.json",
"generated-artifacts/LibSafeDowncast.json",
"generated-artifacts/LibSignatureValidator.json",
"generated-artifacts/LibStakingRichErrors.json",
"generated-artifacts/MixinConstants.json",
"generated-artifacts/MixinDeploymentConstants.json",