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; uint256 amount = msg.value;
bytes32 poolId = getStakingPoolIdOfMaker(makerAddress); bytes32 poolId = getStakingPoolIdOfMaker(makerAddress);
if (poolId != NIL_MAKER_ID) { if (poolId != NIL_POOL_ID) {
// There is a pool associated with `makerAddress`. // There is a pool associated with `makerAddress`.
// TODO(dorothy-zbornak): When we have epoch locks on delegating, we could // TODO(dorothy-zbornak): When we have epoch locks on delegating, we could
// preclude pools that have no delegated stake, since they will never have // 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. // 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; 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; address constant internal NIL_ADDRESS = 0x0000000000000000000000000000000000000000;

View File

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

View File

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

View File

@@ -97,6 +97,14 @@ interface IStakingEvents {
uint32 operatorShare 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. /// @dev Emitted by MixinStakingPool when a new maker is added to a pool.
/// @param poolId Unique id of pool. /// @param poolId Unique id of pool.
/// @param makerAddress Adress of maker added to 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. 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). /// @dev Status for Staking Pools (see MixinStakingPool).
/// @param operatorAddress Address of pool operator. /// @param operatorAddress Address of pool operator.
/// @param operatorShare Portion of pool rewards owned by operator, in ppm. /// @param operatorShare Portion of pool rewards owned by operator, in ppm.
@@ -102,4 +94,13 @@ interface IStructs {
uint256 numerator; uint256 numerator;
uint256 denominator; 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 = bytes4 internal constant EXCHANGE_ADDRESS_NOT_REGISTERED_ERROR_SELECTOR =
0x7dc025b0; 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(keccak256("InsufficientBalanceError(uint256,uint256)"))
bytes4 internal constant INSUFFICIENT_BALANCE_ERROR_SELECTOR = bytes4 internal constant INSUFFICIENT_BALANCE_ERROR_SELECTOR =
0x84c8b7c9; 0x84c8b7c9;
@@ -74,17 +50,9 @@ library LibStakingRichErrors {
bytes4 internal constant ONLY_CALLABLE_BY_POOL_OPERATOR_OR_MAKER_ERROR_SELECTOR = bytes4 internal constant ONLY_CALLABLE_BY_POOL_OPERATOR_OR_MAKER_ERROR_SELECTOR =
0x7d9e1c10; 0x7d9e1c10;
// bytes4(keccak256("InvalidMakerSignatureError(bytes32,address,bytes)")) // bytes4(keccak256("MakerPoolAssignmentError(uint8,address,bytes32)"))
bytes4 internal constant INVALID_MAKER_SIGNATURE_ERROR_SELECTOR = bytes4 internal constant MAKER_POOL_ASSIGNMENT_ERROR_SELECTOR =
0x726b89c8; 0x69945e3f;
// 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("WithdrawAmountExceedsMemberBalanceError(uint256,uint256)")) // bytes4(keccak256("WithdrawAmountExceedsMemberBalanceError(uint256,uint256)"))
bytes4 internal constant WITHDRAW_AMOUNT_EXCEEDS_MEMBER_BALANCE_ERROR_SELECTOR = bytes4 internal constant WITHDRAW_AMOUNT_EXCEEDS_MEMBER_BALANCE_ERROR_SELECTOR =
@@ -138,6 +106,13 @@ library LibStakingRichErrors {
bytes internal constant PROXY_DESTINATION_CANNOT_BE_NIL = bytes internal constant PROXY_DESTINATION_CANNOT_BE_NIL =
hex"01ecebea"; 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 // solhint-disable func-name-mixedcase
function MiscalculatedRewardsError( function MiscalculatedRewardsError(
uint256 totalRewardsPaid, 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( function InsufficientBalanceError(
uint256 amount, uint256 amount,
uint256 balance uint256 balance
@@ -315,39 +215,9 @@ library LibStakingRichErrors {
); );
} }
function InvalidMakerSignatureError( function MakerPoolAssignmentError(
bytes32 poolId, MakerPoolAssignmentErrorCodes errorCode,
address makerAddress, 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 bytes32 poolId
) )
internal internal
@@ -355,9 +225,9 @@ library LibStakingRichErrors {
returns (bytes memory) returns (bytes memory)
{ {
return abi.encodeWithSelector( return abi.encodeWithSelector(
MAKER_ADDRESS_NOT_REGISTERED_ERROR_SELECTOR, MAKER_POOL_ASSIGNMENT_ERROR_SELECTOR,
errorCode,
makerAddress, makerAddress,
makerPoolId,
poolId poolId
); );
} }

View File

@@ -22,8 +22,6 @@ pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol"; import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol"; import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "../libs/LibStakingRichErrors.sol"; import "../libs/LibStakingRichErrors.sol";
import "../libs/LibSignatureValidator.sol";
import "../libs/LibEIP712Hash.sol";
import "../interfaces/IStructs.sol"; import "../interfaces/IStructs.sol";
import "../interfaces/IStakingEvents.sol"; import "../interfaces/IStakingEvents.sol";
import "../immutable/MixinConstants.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. /// @dev Create a new staking pool. The sender will be the operator of this pool.
/// Note that an operator must be payable. /// Note that an operator must be payable.
/// @param operatorShare Portion of rewards owned by the operator, in ppm. /// @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. /// @return poolId The unique pool id generated for this pool.
function createStakingPool(uint32 operatorShare) function createStakingPool(uint32 operatorShare, bool addOperatorAsMaker)
external external
returns (bytes32 poolId) returns (bytes32 poolId)
{ {
@@ -125,48 +124,119 @@ contract MixinStakingPool is
// register pool in reward vault // register pool in reward vault
rewardVault.registerStakingPool(poolId, operatorShare); rewardVault.registerStakingPool(poolId, operatorShare);
// notify // Staking pool has been created
emit StakingPoolCreated(poolId, operatorAddress, operatorShare); 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; 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. /// @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 poolId Unique id of pool.
/// @param makerAddress Address of maker. /// @param makerAddress Address of maker.
/// @param makerSignature Signature proving that maker has agreed to join the pool.
function addMakerToStakingPool( function addMakerToStakingPool(
bytes32 poolId, bytes32 poolId,
address makerAddress, address makerAddress
bytes calldata makerSignature
) )
external external
onlyStakingPoolOperator(poolId) onlyStakingPoolOperator(poolId)
{ {
// sanity check - did maker agree to join this pool? // Is the maker already in a pool?
if (!isValidMakerSignature(poolId, makerAddress, makerSignature)) {
LibRichErrors.rrevert(LibStakingRichErrors.InvalidMakerSignatureError(
poolId,
makerAddress,
makerSignature
));
}
if (isMakerAssignedToStakingPool(makerAddress)) { if (isMakerAssignedToStakingPool(makerAddress)) {
LibRichErrors.rrevert(LibStakingRichErrors.MakerAddressAlreadyRegisteredError( LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
makerAddress LibStakingRichErrors.MakerPoolAssignmentErrorCodes.MAKER_ADDRESS_ALREADY_REGISTERED,
makerAddress,
getStakingPoolIdOfMaker(makerAddress)
)); ));
} }
poolIdByMakerAddress[makerAddress] = poolId; // Is the maker trying to join this pool?
makerAddressesByPoolId[poolId].push(makerAddress); 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( emit MakerAddedToStakingPool(
poolId, poolId,
makerAddress 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 /// 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. /// at the sole discretion of the pool operator.
/// @param poolId Unique id of pool. /// @param poolId Unique id of pool.
@@ -180,90 +250,41 @@ contract MixinStakingPool is
{ {
bytes32 makerPoolId = getStakingPoolIdOfMaker(makerAddress); bytes32 makerPoolId = getStakingPoolIdOfMaker(makerAddress);
if (makerPoolId != poolId) { if (makerPoolId != poolId) {
LibRichErrors.rrevert(LibStakingRichErrors.MakerAddressNotRegisteredError( LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.MAKER_ADDRESS_NOT_REGISTERED,
makerAddress, makerAddress,
makerPoolId, makerPoolId
poolId
)); ));
} }
// load list of makers for the input pool. // remove the pool and confirmation from the maker status
address[] storage makerAddressesByPoolIdPtr = makerAddressesByPoolId[poolId]; IStructs.MakerPoolJoinStatus memory poolJoinStatus = IStructs.MakerPoolJoinStatus({
uint256 makerAddressesByPoolIdLength = makerAddressesByPoolIdPtr.length; poolId: NIL_POOL_ID,
confirmed: false
});
poolJoinedByMakerAddress[makerAddress] = poolJoinStatus;
numMakersByPoolId[poolId] -= 1;
// find index of maker to remove. // Maker has been removed from the pool`
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
emit MakerRemovedFromStakingPool( emit MakerRemovedFromStakingPool(
poolId, poolId,
makerAddress makerAddress
); );
} }
/// @dev Returns true iff the input signature is valid; meaning that the maker agrees to /// @dev Returns the pool id of the input maker.
/// be added to the pool. /// @param makerAddress Address of maker
/// @param poolId Unique id of pool the maker wishes to join. /// @return Pool id, nil if maker is not yet assigned to a pool.
/// @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.
function getStakingPoolIdOfMaker(address makerAddress) function getStakingPoolIdOfMaker(address makerAddress)
public public
view view
returns (bytes32) 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. /// @dev Returns true iff the maker is assigned to a staking pool.
@@ -274,18 +295,18 @@ contract MixinStakingPool is
view view
returns (bool) 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. /// @param poolId Unique id of pool.
/// @return _makerAddressesByPoolId Makers for pool. /// @return Size of pool.
function getMakersForStakingPool(bytes32 poolId) function getNumberOfMakersInStakingPool(bytes32 poolId)
public public
view 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. /// @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. /// @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 /// There is a "Catastrophic Failure Mode" that, when invoked, only
/// allows withdrawals to be made. Once this vault is in catastrophic /// allows withdrawals to be made. Once this vault is in catastrophic
/// failure mode, it cannot be returned to normal mode; this prevents /// 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() } if sub(poolById_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1) slot := add(slot, 1)
if sub(poolIdByMakerAddress_slot, slot) { revertIncorrectStorageSlot() } if sub(poolJoinedByMakerAddress_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1) slot := add(slot, 1)
if sub(makerAddressesByPoolId_slot, slot) { revertIncorrectStorageSlot() } if sub(numMakersByPoolId_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1) slot := add(slot, 1)
if sub(currentEpoch_slot, slot) { revertIncorrectStorageSlot() } if sub(currentEpoch_slot, slot) { revertIncorrectStorageSlot() }

View File

@@ -37,7 +37,7 @@
}, },
"config": { "config": {
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", "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": { "repository": {
"type": "git", "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 IStakingProxy from '../generated-artifacts/IStakingProxy.json';
import * as IStructs from '../generated-artifacts/IStructs.json'; import * as IStructs from '../generated-artifacts/IStructs.json';
import * as IVaultCore from '../generated-artifacts/IVaultCore.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 IZrxVault from '../generated-artifacts/IZrxVault.json';
import * as LibEIP712Hash from '../generated-artifacts/LibEIP712Hash.json';
import * as LibFixedMath from '../generated-artifacts/LibFixedMath.json'; import * as LibFixedMath from '../generated-artifacts/LibFixedMath.json';
import * as LibFixedMathRichErrors from '../generated-artifacts/LibFixedMathRichErrors.json'; import * as LibFixedMathRichErrors from '../generated-artifacts/LibFixedMathRichErrors.json';
import * as LibProxy from '../generated-artifacts/LibProxy.json'; import * as LibProxy from '../generated-artifacts/LibProxy.json';
import * as LibSafeDowncast from '../generated-artifacts/LibSafeDowncast.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 LibStakingRichErrors from '../generated-artifacts/LibStakingRichErrors.json';
import * as MixinConstants from '../generated-artifacts/MixinConstants.json'; import * as MixinConstants from '../generated-artifacts/MixinConstants.json';
import * as MixinDeploymentConstants from '../generated-artifacts/MixinDeploymentConstants.json'; import * as MixinDeploymentConstants from '../generated-artifacts/MixinDeploymentConstants.json';
@@ -61,14 +58,11 @@ export const artifacts = {
IStakingProxy: IStakingProxy as ContractArtifact, IStakingProxy: IStakingProxy as ContractArtifact,
IStructs: IStructs as ContractArtifact, IStructs: IStructs as ContractArtifact,
IVaultCore: IVaultCore as ContractArtifact, IVaultCore: IVaultCore as ContractArtifact,
IWallet: IWallet as ContractArtifact,
IZrxVault: IZrxVault as ContractArtifact, IZrxVault: IZrxVault as ContractArtifact,
LibEIP712Hash: LibEIP712Hash as ContractArtifact,
LibFixedMath: LibFixedMath as ContractArtifact, LibFixedMath: LibFixedMath as ContractArtifact,
LibFixedMathRichErrors: LibFixedMathRichErrors as ContractArtifact, LibFixedMathRichErrors: LibFixedMathRichErrors as ContractArtifact,
LibProxy: LibProxy as ContractArtifact, LibProxy: LibProxy as ContractArtifact,
LibSafeDowncast: LibSafeDowncast as ContractArtifact, LibSafeDowncast: LibSafeDowncast as ContractArtifact,
LibSignatureValidator: LibSignatureValidator as ContractArtifact,
LibStakingRichErrors: LibStakingRichErrors as ContractArtifact, LibStakingRichErrors: LibStakingRichErrors as ContractArtifact,
MixinStake: MixinStake as ContractArtifact, MixinStake: MixinStake as ContractArtifact,
MixinStakeBalances: MixinStakeBalances 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_staking_proxy';
export * from '../generated-wrappers/i_structs'; export * from '../generated-wrappers/i_structs';
export * from '../generated-wrappers/i_vault_core'; export * from '../generated-wrappers/i_vault_core';
export * from '../generated-wrappers/i_wallet';
export * from '../generated-wrappers/i_zrx_vault'; 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';
export * from '../generated-wrappers/lib_fixed_math_rich_errors'; export * from '../generated-wrappers/lib_fixed_math_rich_errors';
export * from '../generated-wrappers/lib_proxy'; export * from '../generated-wrappers/lib_proxy';
export * from '../generated-wrappers/lib_safe_downcast'; 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/lib_staking_rich_errors';
export * from '../generated-wrappers/mixin_constants'; export * from '../generated-wrappers/mixin_constants';
export * from '../generated-wrappers/mixin_deployment_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 * as _ from 'lodash';
import { StakingWrapper } from '../utils/staking_wrapper'; import { constants as stakingConstants } from '../utils/constants';
import { SignedStakingPoolApproval } from '../utils/types';
import { BaseActor } from './base_actor'; import { BaseActor } from './base_actor';
export class MakerActor extends BaseActor { export class MakerActor extends BaseActor {
private readonly _ownerPrivateKeyIfExists?: Buffer; public async joinStakingPoolAsMakerAsync(poolId: string, revertError?: RevertError): Promise<void> {
private readonly _signatureVerifierIfExists?: string; // Join pool
private readonly _chainIdIfExists?: number; const txReceiptPromise = this._stakingWrapper.joinStakingPoolAsMakerAsync(poolId, this._owner);
constructor( if (revertError !== undefined) {
owner: string, await expect(txReceiptPromise).to.revertWith(revertError);
stakingWrapper: StakingWrapper, return;
ownerPrivateKey?: Buffer, }
signatureVerifier?: string, await txReceiptPromise;
chainId?: number,
) { // Pool id of the maker should be nil (join would've thrown otherwise)
super(owner, stakingWrapper); const poolIdOfMaker = await this._stakingWrapper.getStakingPoolIdOfMakerAsync(this._owner);
this._ownerPrivateKeyIfExists = ownerPrivateKey; expect(poolIdOfMaker, 'pool id of maker').to.be.equal(stakingConstants.NIL_POOL_ID);
this._signatureVerifierIfExists = signatureVerifier;
this._chainIdIfExists = chainId;
} }
public signApprovalForStakingPool( public async removeMakerFromStakingPoolAsync(
poolId: string, poolId: string,
signatureType: SignatureType = SignatureType.EthSign, makerAddress: string,
): SignedStakingPoolApproval { revertError?: RevertError,
const approval = this._stakingWrapper.signApprovalForStakingPool( ): Promise<void> {
// remove maker (should fail if makerAddress !== this._owner)
const txReceiptPromise = this._stakingWrapper.removeMakerFromStakingPoolAsync(
poolId, poolId,
makerAddress,
this._owner, 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 * as _ from 'lodash';
import { constants as stakingConstants } from '../utils/constants'; import { constants as stakingConstants } from '../utils/constants';
import { StakingWrapper } from '../utils/staking_wrapper';
import { BaseActor } from './base_actor'; import { BaseActor } from './base_actor';
export class PoolOperatorActor extends BaseActor { export class PoolOperatorActor extends BaseActor {
constructor(owner: string, stakingWrapper: StakingWrapper) { public async createStakingPoolAsync(
super(owner, stakingWrapper); operatorShare: number,
} addOperatorAsMaker: boolean,
revertError?: RevertError,
public async createStakingPoolAsync(operatorShare: number, revertError?: RevertError): Promise<string> { ): Promise<string> {
// query next pool id // query next pool id
const nextPoolId = await this._stakingWrapper.getNextStakingPoolIdAsync(); const nextPoolId = await this._stakingWrapper.getNextStakingPoolIdAsync();
// create pool // create pool
const poolIdPromise = this._stakingWrapper.createStakingPoolAsync(this._owner, operatorShare); const poolIdPromise = this._stakingWrapper.createStakingPoolAsync(
this._owner,
operatorShare,
addOperatorAsMaker,
);
if (revertError !== undefined) { if (revertError !== undefined) {
await expect(poolIdPromise).to.revertWith(revertError); await expect(poolIdPromise).to.revertWith(revertError);
return ''; return '';
@@ -24,21 +27,24 @@ export class PoolOperatorActor extends BaseActor {
const poolId = await poolIdPromise; const poolId = await poolIdPromise;
// validate pool id // validate pool id
expect(poolId, 'pool id').to.be.bignumber.equal(nextPoolId); 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; return poolId;
} }
public async addMakerToStakingPoolAsync( public async addMakerToStakingPoolAsync(
poolId: string, poolId: string,
makerAddress: string, makerAddress: string,
makerSignature: string,
revertError?: RevertError, revertError?: RevertError,
): Promise<void> { ): Promise<void> {
// add maker // add maker
const txReceiptPromise = this._stakingWrapper.addMakerToStakingPoolAsync( const txReceiptPromise = this._stakingWrapper.addMakerToStakingPoolAsync(poolId, makerAddress, this._owner);
poolId,
makerAddress,
makerSignature,
this._owner,
);
if (revertError !== undefined) { if (revertError !== undefined) {
await expect(txReceiptPromise).to.revertWith(revertError); await expect(txReceiptPromise).to.revertWith(revertError);
return; return;
@@ -47,9 +53,6 @@ export class PoolOperatorActor extends BaseActor {
// check the pool id of the maker // check the pool id of the maker
const poolIdOfMaker = await this._stakingWrapper.getStakingPoolIdOfMakerAsync(makerAddress); const poolIdOfMaker = await this._stakingWrapper.getStakingPoolIdOfMakerAsync(makerAddress);
expect(poolIdOfMaker, 'pool id of maker').to.be.equal(poolId); 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( public async removeMakerFromStakingPoolAsync(
poolId: string, poolId: string,
@@ -70,8 +73,5 @@ export class PoolOperatorActor extends BaseActor {
// check the pool id of the maker // check the pool id of the maker
const poolIdOfMakerAfterRemoving = await this._stakingWrapper.getStakingPoolIdOfMakerAsync(makerAddress); const poolIdOfMakerAfterRemoving = await this._stakingWrapper.getStakingPoolIdOfMakerAsync(makerAddress);
expect(poolIdOfMakerAfterRemoving, 'pool id of maker').to.be.equal(stakingConstants.NIL_POOL_ID); 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); [zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
await erc20Wrapper.setBalancesAndAllowancesAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync();
// deploy staking contracts // deploy staking contracts
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract, accounts); stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract);
await stakingWrapper.deployAndConfigureContractsAsync(); await stakingWrapper.deployAndConfigureContractsAsync();
}); });

View File

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

View File

@@ -33,7 +33,7 @@ blockchainTests('Exchange Integrations', env => {
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS); [zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
await erc20Wrapper.setBalancesAndAllowancesAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync();
// deploy staking contracts // deploy staking contracts
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract, accounts); stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract);
await stakingWrapper.deployAndConfigureContractsAsync(); await stakingWrapper.deployAndConfigureContractsAsync();
}); });
blockchainTests.resets('Exchange Tracking in Staking Contract', () => { 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 { DummyERC20TokenContract } from '@0x/contracts-erc20';
import { blockchainTests, constants, expect } from '@0x/contracts-test-utils'; import { blockchainTests, constants, expect } from '@0x/contracts-test-utils';
import { StakingRevertErrors } from '@0x/order-utils'; import { StakingRevertErrors } from '@0x/order-utils';
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { MakerActor } from './actors/maker_actor'; import { MakerActor } from './actors/maker_actor';
@@ -36,7 +35,7 @@ blockchainTests('Staking Pool Management', env => {
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, DUMMY_TOKEN_DECIMALS); [zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, DUMMY_TOKEN_DECIMALS);
await erc20Wrapper.setBalancesAndAllowancesAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync();
// deploy staking contracts // deploy staking contracts
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract, accounts); stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract);
await stakingWrapper.deployAndConfigureContractsAsync(); await stakingWrapper.deployAndConfigureContractsAsync();
}); });
blockchainTests.resets('Staking Pool Management', () => { blockchainTests.resets('Staking Pool Management', () => {
@@ -46,12 +45,23 @@ blockchainTests('Staking Pool Management', env => {
const operatorShare = (39 / 100) * PPM_DENOMINATOR; const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper); const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
// create pool // create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare); const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID); expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// check that the next pool id was incremented // check that the next pool id was incremented
const expectedNextPoolId = '0x0000000000000000000000000000000200000000000000000000000000000000';
const nextPoolId = await stakingWrapper.getNextStakingPoolIdAsync(); 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 () => { it('Should throw if poolOperatorShare is > PPM_DENOMINATOR', async () => {
// test parameters // test parameters
@@ -60,7 +70,7 @@ blockchainTests('Staking Pool Management', env => {
const operatorShare = PPM_100_PERCENT + 1; const operatorShare = PPM_100_PERCENT + 1;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper); const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
// create pool // create pool
const tx = poolOperator.createStakingPoolAsync(operatorShare); const tx = poolOperator.createStakingPoolAsync(operatorShare, true);
const expectedPoolId = stakingConstants.INITIAL_POOL_ID; const expectedPoolId = stakingConstants.INITIAL_POOL_ID;
const expectedError = new StakingRevertErrors.InvalidPoolOperatorShareError(expectedPoolId, operatorShare); const expectedError = new StakingRevertErrors.InvalidPoolOperatorShareError(expectedPoolId, operatorShare);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
@@ -73,41 +83,138 @@ blockchainTests('Staking Pool Management', env => {
const makerAddress = users[1]; const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingWrapper); const maker = new MakerActor(makerAddress, stakingWrapper);
// create pool // create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare); const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID); expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add maker to pool // maker joins pool
const makerApproval = maker.signApprovalForStakingPool(poolId); await maker.joinStakingPoolAsMakerAsync(poolId);
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, makerApproval.signature); // operator adds maker to pool
// remove maker from pool await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
// operator removes maker from pool
await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddress); 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 // test parameters
const operatorAddress = users[0]; const operatorAddress = users[0];
const operatorShare = 39; const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper); const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddresses = users.slice(1, 4); const makerAddresses = users.slice(1, 4);
const makers = [ const makers = makerAddresses.map(makerAddress => new MakerActor(makerAddress, stakingWrapper));
new MakerActor(makerAddresses[0], stakingWrapper),
new MakerActor(makerAddresses[1], stakingWrapper),
new MakerActor(makerAddresses[2], stakingWrapper),
];
// create pool // create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare); const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID); expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add makers to pool // add makers to pool
const makerApprovals = [ await Promise.all(makers.map(async maker => maker.joinStakingPoolAsMakerAsync(poolId)));
makers[0].signApprovalForStakingPool(poolId), await Promise.all(
makers[1].signApprovalForStakingPool(poolId), makerAddresses.map(async makerAddress => poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress)),
makers[2].signApprovalForStakingPool(poolId), );
];
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddresses[0], makerApprovals[0].signature); // check the number of makers in the pool
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddresses[1], makerApprovals[1].signature); let numMakers = await stakingWrapper.getNumberOfMakersInStakingPoolAsync(poolId);
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddresses[2], makerApprovals[2].signature); expect(numMakers, 'number of makers in pool after adding').to.be.bignumber.equal(3);
// remove maker from pool // remove maker from pool
await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddresses[0]); await Promise.all(
await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddresses[1]); makerAddresses.map(async makerAddress =>
await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddresses[2]); 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 () => { it('Should fail to add the same maker twice', async () => {
// test parameters // test parameters
@@ -117,14 +224,18 @@ blockchainTests('Staking Pool Management', env => {
const makerAddress = users[1]; const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingWrapper); const maker = new MakerActor(makerAddress, stakingWrapper);
// create pool // create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare); const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID); expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add maker to pool // add maker to pool
const makerApproval = maker.signApprovalForStakingPool(poolId); await maker.joinStakingPoolAsMakerAsync(poolId);
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, makerApproval.signature); await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
const revertError = new StakingRevertErrors.MakerAddressAlreadyRegisteredError(makerAddress); const revertError = new StakingRevertErrors.MakerPoolAssignmentError(
StakingRevertErrors.MakerPoolAssignmentErrorCodes.MakerAddressAlreadyRegistered,
makerAddress,
poolId,
);
// add same maker to pool again // 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 () => { it('Should fail to remove a maker that does not exist', async () => {
// test parameters // test parameters
@@ -133,87 +244,16 @@ blockchainTests('Staking Pool Management', env => {
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper); const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddress = users[1]; const makerAddress = users[1];
// create pool // create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare); const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID); expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
const revertError = new StakingRevertErrors.MakerAddressNotRegisteredError( const revertError = new StakingRevertErrors.MakerPoolAssignmentError(
StakingRevertErrors.MakerPoolAssignmentErrorCodes.MakerAddressNotRegistered,
makerAddress, makerAddress,
stakingConstants.NIL_POOL_ID, stakingConstants.NIL_POOL_ID,
poolId,
); );
// remove non-existent maker from pool // remove non-existent maker from pool
await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddress, revertError); 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 () => { it('Should fail to add a maker when called by someone other than the pool operator', async () => {
// test parameters // test parameters
const operatorAddress = users[0]; const operatorAddress = users[0];
@@ -223,45 +263,78 @@ blockchainTests('Staking Pool Management', env => {
const maker = new MakerActor(makerAddress, stakingWrapper); const maker = new MakerActor(makerAddress, stakingWrapper);
const notOperatorAddress = users[2]; const notOperatorAddress = users[2];
// create pool // create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare); const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID); expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add maker to pool // add maker to pool
const makerApproval = maker.signApprovalForStakingPool(poolId); await maker.joinStakingPoolAsMakerAsync(poolId);
const revertError = new StakingRevertErrors.OnlyCallableByPoolOperatorError( const revertError = new StakingRevertErrors.OnlyCallableByPoolOperatorError(
notOperatorAddress, notOperatorAddress,
operatorAddress, operatorAddress,
); );
const tx = stakingWrapper.addMakerToStakingPoolAsync( const tx = stakingWrapper.addMakerToStakingPoolAsync(poolId, makerAddress, notOperatorAddress);
poolId,
makerAddress,
makerApproval.signature,
notOperatorAddress,
);
await expect(tx).to.revertWith(revertError); 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 // test parameters
const operatorAddress = users[0]; const operatorAddress = users[0];
const operatorShare = 39; const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper); const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddress = users[1]; const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingWrapper); const maker = new MakerActor(makerAddress, stakingWrapper);
const notOperatorAddress = users[2]; const neitherOperatorNorMakerAddress = users[2];
// create pool // create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare); const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID); expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add maker to pool // add maker to pool
const makerApproval = maker.signApprovalForStakingPool(poolId); await maker.joinStakingPoolAsMakerAsync(poolId);
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, makerApproval.signature); await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
// try to remove the maker address from an address other than the operator // try to remove the maker address from an address other than the operator
const revertError = new StakingRevertErrors.OnlyCallableByPoolOperatorOrMakerError( const revertError = new StakingRevertErrors.OnlyCallableByPoolOperatorOrMakerError(
notOperatorAddress, neitherOperatorNorMakerAddress,
operatorAddress, operatorAddress,
makerAddress, makerAddress,
); );
const tx = stakingWrapper.removeMakerFromStakingPoolAsync(poolId, makerAddress, notOperatorAddress); const tx = stakingWrapper.removeMakerFromStakingPoolAsync(
poolId,
makerAddress,
neitherOperatorNorMakerAddress,
);
await expect(tx).to.revertWith(revertError); 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 // tslint:enable:no-unnecessary-type-assertion

View File

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

View File

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

View File

@@ -128,7 +128,7 @@ export class Simulation {
const poolOperator = new PoolOperatorActor(poolOperatorAddress, this._stakingWrapper); const poolOperator = new PoolOperatorActor(poolOperatorAddress, this._stakingWrapper);
this._poolOperators.push(poolOperator); this._poolOperators.push(poolOperator);
// create a pool id for this operator // 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); this._poolIds.push(poolId);
// each pool operator can also be a staker/delegator // each pool operator can also be a staker/delegator
const poolOperatorAsDelegator = new DelegatorActor(poolOperatorAddress, this._stakingWrapper); const poolOperatorAsDelegator = new DelegatorActor(poolOperatorAddress, this._stakingWrapper);
@@ -156,9 +156,9 @@ export class Simulation {
// tslint:disable-next-line no-unused-variable // tslint:disable-next-line no-unused-variable
for (const j of _.range(numberOfMakersInPool)) { for (const j of _.range(numberOfMakersInPool)) {
const maker = this._makers[makerIdx]; const maker = this._makers[makerIdx];
const makerApproval = maker.signApprovalForStakingPool(poolId); await maker.joinStakingPoolAsMakerAsync(poolId);
const makerAddress = maker.getOwner(); const makerAddress = maker.getOwner();
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, makerApproval.signature); await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
makerIdx += 1; makerIdx += 1;
} }
poolIdx += 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), MAX_UINT_64: new BigNumber(2).pow(256).minus(1),
TOKEN_MULTIPLIER: new BigNumber(10).pow(18), TOKEN_MULTIPLIER: new BigNumber(10).pow(18),
INITIAL_POOL_ID: '0x0000000000000000000000000000000100000000000000000000000000000000', INITIAL_POOL_ID: '0x0000000000000000000000000000000100000000000000000000000000000000',
SECOND_POOL_ID: '0x0000000000000000000000000000000200000000000000000000000000000000',
NIL_POOL_ID: '0x0000000000000000000000000000000000000000000000000000000000000000', NIL_POOL_ID: '0x0000000000000000000000000000000000000000000000000000000000000000',
NIL_ADDRESS: '0x0000000000000000000000000000000000000000', NIL_ADDRESS: '0x0000000000000000000000000000000000000000',
INITIAL_EPOCH: new BigNumber(0), INITIAL_EPOCH: new BigNumber(0),
@@ -11,4 +12,5 @@ export const constants = {
EPOCH_DURATION_IN_SECONDS: new BigNumber(1000), // @TODO SET FOR DEPLOYMENT*/ EPOCH_DURATION_IN_SECONDS: new BigNumber(1000), // @TODO SET FOR DEPLOYMENT*/
TIMELOCK_DURATION_IN_EPOCHS: new BigNumber(3), // @TODO SET FOR DEPLOYMENT TIMELOCK_DURATION_IN_EPOCHS: new BigNumber(3), // @TODO SET FOR DEPLOYMENT
CHAIN_ID: 1, 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 { ERC20ProxyContract } from '@0x/contracts-asset-proxy';
import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20'; import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
import { constants as testUtilsConstants, LogDecoder, txDefaults } from '@0x/contracts-test-utils'; import { LogDecoder, txDefaults } from '@0x/contracts-test-utils';
import { SignatureType } from '@0x/types';
import { BigNumber, logUtils } from '@0x/utils'; import { BigNumber, logUtils } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper'; import { Web3Wrapper } from '@0x/web3-wrapper';
import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
@@ -17,9 +16,8 @@ import {
ZrxVaultContract, ZrxVaultContract,
} from '../../src'; } from '../../src';
import { ApprovalFactory } from './approval_factory';
import { constants } from './constants'; import { constants } from './constants';
import { SignedStakingPoolApproval, StakeBalance } from './types'; import { StakeBalance } from './types';
export class StakingWrapper { export class StakingWrapper {
private readonly _web3Wrapper: Web3Wrapper; private readonly _web3Wrapper: Web3Wrapper;
@@ -28,7 +26,6 @@ export class StakingWrapper {
private readonly _ownerAddress: string; private readonly _ownerAddress: string;
private readonly _erc20ProxyContract: ERC20ProxyContract; private readonly _erc20ProxyContract: ERC20ProxyContract;
private readonly _zrxTokenContract: DummyERC20TokenContract; private readonly _zrxTokenContract: DummyERC20TokenContract;
private readonly _accounts: string[];
private _stakingContractIfExists?: StakingContract; private _stakingContractIfExists?: StakingContract;
private _stakingProxyContractIfExists?: StakingProxyContract; private _stakingProxyContractIfExists?: StakingProxyContract;
private _zrxVaultContractIfExists?: ZrxVaultContract; private _zrxVaultContractIfExists?: ZrxVaultContract;
@@ -67,7 +64,6 @@ export class StakingWrapper {
ownerAddres: string, ownerAddres: string,
erc20ProxyContract: ERC20ProxyContract, erc20ProxyContract: ERC20ProxyContract,
zrxTokenContract: DummyERC20TokenContract, zrxTokenContract: DummyERC20TokenContract,
accounts: string[],
) { ) {
this._web3Wrapper = new Web3Wrapper(provider); this._web3Wrapper = new Web3Wrapper(provider);
this._provider = provider; this._provider = provider;
@@ -76,7 +72,6 @@ export class StakingWrapper {
this._ownerAddress = ownerAddres; this._ownerAddress = ownerAddres;
this._erc20ProxyContract = erc20ProxyContract; this._erc20ProxyContract = erc20ProxyContract;
this._zrxTokenContract = zrxTokenContract; this._zrxTokenContract = zrxTokenContract;
this._accounts = accounts;
} }
public getStakingContract(): StakingContract { public getStakingContract(): StakingContract {
this._validateDeployedOrThrow(); this._validateDeployedOrThrow();
@@ -283,23 +278,36 @@ export class StakingWrapper {
const nextPoolId = await this._callAsync(calldata); const nextPoolId = await this._callAsync(calldata);
return nextPoolId; return nextPoolId;
} }
public async createStakingPoolAsync(operatorAddress: string, operatorShare: number): Promise<string> { public async createStakingPoolAsync(
const calldata = this.getStakingContract().createStakingPool.getABIEncodedTransactionData(operatorShare); operatorAddress: string,
operatorShare: number,
addOperatorAsMaker: boolean,
): Promise<string> {
const calldata = this.getStakingContract().createStakingPool.getABIEncodedTransactionData(
operatorShare,
addOperatorAsMaker,
);
const txReceipt = await this._executeTransactionAsync(calldata, operatorAddress); const txReceipt = await this._executeTransactionAsync(calldata, operatorAddress);
const createStakingPoolLog = this._logDecoder.decodeLogOrThrow(txReceipt.logs[0]); const createStakingPoolLog = this._logDecoder.decodeLogOrThrow(txReceipt.logs[0]);
const poolId = (createStakingPoolLog as any).args.poolId; const poolId = (createStakingPoolLog as any).args.poolId;
return 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( public async addMakerToStakingPoolAsync(
poolId: string, poolId: string,
makerAddress: string, makerAddress: string,
makerSignature: string,
operatorAddress: string, operatorAddress: string,
): Promise<TransactionReceiptWithDecodedLogs> { ): Promise<TransactionReceiptWithDecodedLogs> {
const calldata = this.getStakingContract().addMakerToStakingPool.getABIEncodedTransactionData( const calldata = this.getStakingContract().addMakerToStakingPool.getABIEncodedTransactionData(
poolId, poolId,
makerAddress, makerAddress,
makerSignature,
); );
const txReceipt = await this._executeTransactionAsync(calldata, operatorAddress); const txReceipt = await this._executeTransactionAsync(calldata, operatorAddress);
return txReceipt; return txReceipt;
@@ -321,55 +329,11 @@ export class StakingWrapper {
const poolId = await this._callAsync(calldata); const poolId = await this._callAsync(calldata);
return poolId; return poolId;
} }
public async getMakersForStakingPoolAsync(poolId: string): Promise<string[]> { public async getNumberOfMakersInStakingPoolAsync(poolId: string): Promise<BigNumber> {
const calldata = this.getStakingContract().getMakersForStakingPool.getABIEncodedTransactionData(poolId); const calldata = this.getStakingContract().getNumberOfMakersInStakingPool.getABIEncodedTransactionData(poolId);
const returndata = await this._callAsync(calldata); const returnData = await this._callAsync(calldata);
const makerAddresses = this.getStakingContract().getMakersForStakingPool.getABIDecodedReturnData(returndata); const value = this.getStakingContract().getNumberOfMakersInStakingPool.getABIDecodedReturnData(returnData);
return makerAddresses; return value;
}
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;
} }
///// EPOCHS ///// ///// EPOCHS /////
public async goToNextEpochAsync(): Promise<TransactionReceiptWithDecodedLogs> { public async goToNextEpochAsync(): Promise<TransactionReceiptWithDecodedLogs> {

View File

@@ -1,16 +1,5 @@
import { BigNumber } from '@0x/utils'; 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 { export interface StakerBalances {
zrxBalance: BigNumber; zrxBalance: BigNumber;
stakeBalance: BigNumber; stakeBalance: BigNumber;

View File

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

View File

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

View File

@@ -150,12 +150,6 @@ export const constants = {
{ name: 'approvalExpirationTimeSeconds', type: 'uint256' }, { name: 'approvalExpirationTimeSeconds', type: 'uint256' },
], ],
}, },
STAKING_DOMAIN_NAME: '0x Protocol Staking',
STAKING_DOMAIN_VERSION: '1.0.0',
STAKING_POOL_APPROVAL_SCHEMA: {
name: 'StakingPoolApproval',
parameters: [{ name: 'poolId', type: 'bytes32' }, { name: 'makerAddress', type: 'address' }],
},
ERC20_METHOD_ABI, ERC20_METHOD_ABI,
ERC721_METHOD_ABI, ERC721_METHOD_ABI,
MULTI_ASSET_METHOD_ABI, MULTI_ASSET_METHOD_ABI,

View File

@@ -125,34 +125,4 @@ export const eip712Utils = {
); );
return typedData; return typedData;
}, },
/**
* Creates an Coordiantor typedData EIP712TypedData object for use with the Coordinator extension contract
* @return A typed data object
*/
createStakingPoolApprovalTypedData: (
poolId: string,
makerAddress: string,
verifyingContractAddress: string,
chainId: number,
): EIP712TypedData => {
const domain = {
name: constants.STAKING_DOMAIN_NAME,
version: constants.STAKING_DOMAIN_VERSION,
verifyingContractAddress,
chainId,
};
const approval = {
poolId,
makerAddress,
};
const typedData = eip712Utils.createTypedData(
constants.STAKING_POOL_APPROVAL_SCHEMA.name,
{
StakingPoolApproval: constants.STAKING_POOL_APPROVAL_SCHEMA.parameters,
},
approval,
domain,
);
return typedData;
},
}; };

View File

@@ -2,6 +2,13 @@ import { BigNumber, RevertError } from '@0x/utils';
// tslint:disable:max-classes-per-file // tslint:disable:max-classes-per-file
export enum MakerPoolAssignmentErrorCodes {
MakerAddressAlreadyRegistered,
MakerAddressNotRegistered,
MakerAddressNotPendingAdd,
PoolIsFull,
}
export class MiscalculatedRewardsError extends RevertError { export class MiscalculatedRewardsError extends RevertError {
constructor(totalRewardsPaid?: BigNumber | number | string, initialContractBalance?: BigNumber | number | string) { constructor(totalRewardsPaid?: BigNumber | number | string, initialContractBalance?: BigNumber | number | string) {
super( super(
@@ -36,42 +43,6 @@ export class ExchangeAddressNotRegisteredError extends RevertError {
} }
} }
export class SignatureLengthGreaterThan0RequiredError extends RevertError {
constructor() {
super('SignatureLengthGreaterThan0RequiredError', 'SignatureLengthGreaterThan0RequiredError()', {});
}
}
export class SignatureUnsupportedError extends RevertError {
constructor(signature?: string) {
super('SignatureUnsupportedError', 'SignatureUnsupportedError(bytes signature)', { signature });
}
}
export class SignatureIllegalError extends RevertError {
constructor(signature?: string) {
super('SignatureIllegalError', 'SignatureIllegalError(bytes signature)', { signature });
}
}
export class SignatureLength0RequiredError extends RevertError {
constructor(signature?: string) {
super('SignatureLength0RequiredError', 'SignatureLength0RequiredError(bytes signature)', { signature });
}
}
export class SignatureLength65RequiredError extends RevertError {
constructor(signature?: string) {
super('SignatureLength65RequiredError', 'SignatureLength65RequiredError(bytes signature)', { signature });
}
}
export class WalletError extends RevertError {
constructor(walletAddress?: string, errorData?: string) {
super('WalletError', 'WalletError(address walletAddress, bytes errorData)', { walletAddress, errorData });
}
}
export class InsufficientBalanceError extends RevertError { export class InsufficientBalanceError extends RevertError {
constructor(amount?: BigNumber | number | string, balance?: BigNumber | number | string) { constructor(amount?: BigNumber | number | string, balance?: BigNumber | number | string) {
super('InsufficientBalanceError', 'InsufficientBalanceError(uint256 amount, uint256 balance)', { super('InsufficientBalanceError', 'InsufficientBalanceError(uint256 amount, uint256 balance)', {
@@ -101,30 +72,16 @@ export class OnlyCallableByPoolOperatorOrMakerError extends RevertError {
} }
} }
export class InvalidMakerSignatureError extends RevertError { export class MakerPoolAssignmentError extends RevertError {
constructor(poolId?: string, makerAddress?: string, makerSignature?: string) { constructor(error?: MakerPoolAssignmentErrorCodes, makerAddress?: string, poolId?: string) {
super( super(
'InvalidMakerSignatureError', 'MakerPoolAssignmentError',
'InvalidMakerSignatureError(bytes32 poolId, address makerAddress, bytes makerSignature)', 'MakerPoolAssignmentError(uint8 error, address makerAddress, bytes32 poolId)',
{ poolId, makerAddress, makerSignature }, {
); error,
} makerAddress,
} poolId,
},
export class MakerAddressAlreadyRegisteredError extends RevertError {
constructor(makerAddress?: string) {
super('MakerAddressAlreadyRegisteredError', 'MakerAddressAlreadyRegisteredError(address makerAddress)', {
makerAddress,
});
}
}
export class MakerAddressNotRegisteredError extends RevertError {
constructor(makerAddress?: string, makerPoolId?: string, poolId?: string) {
super(
'MakerAddressNotRegisteredError',
'MakerAddressNotRegisteredError(address makerAddress, bytes32 makerPoolId, bytes32 poolId)',
{ makerAddress, makerPoolId, poolId },
); );
} }
} }
@@ -143,7 +100,7 @@ export class BlockTimestampTooLowError extends RevertError {
constructor(epochEndTime?: BigNumber | number | string, currentBlockTimestamp?: BigNumber | number | string) { constructor(epochEndTime?: BigNumber | number | string, currentBlockTimestamp?: BigNumber | number | string) {
super( super(
'BlockTimestampTooLowError', 'BlockTimestampTooLowError',
'BlockTimestampTooLowError(uint64 epochEndTime, uint64 currentBlockTimestamp)', 'BlockTimestampTooLowError(uint256 epochEndTime, uint256 currentBlockTimestamp)',
{ epochEndTime, currentBlockTimestamp }, { epochEndTime, currentBlockTimestamp },
); );
} }
@@ -227,18 +184,10 @@ const types = [
OnlyCallableByExchangeError, OnlyCallableByExchangeError,
ExchangeAddressAlreadyRegisteredError, ExchangeAddressAlreadyRegisteredError,
ExchangeAddressNotRegisteredError, ExchangeAddressNotRegisteredError,
SignatureLengthGreaterThan0RequiredError,
SignatureUnsupportedError,
SignatureIllegalError,
SignatureLength0RequiredError,
SignatureLength65RequiredError,
WalletError,
InsufficientBalanceError, InsufficientBalanceError,
OnlyCallableByPoolOperatorError, OnlyCallableByPoolOperatorError,
OnlyCallableByPoolOperatorOrMakerError, OnlyCallableByPoolOperatorOrMakerError,
InvalidMakerSignatureError, MakerPoolAssignmentError,
MakerAddressAlreadyRegisteredError,
MakerAddressNotRegisteredError,
WithdrawAmountExceedsMemberBalanceError, WithdrawAmountExceedsMemberBalanceError,
BlockTimestampTooLowError, BlockTimestampTooLowError,
OnlyCallableByStakingContractError, OnlyCallableByStakingContractError,

View File

@@ -12,6 +12,7 @@ declare module 'ganache-core' {
gasLimit?: number; gasLimit?: number;
vmErrorsOnRPCResponse?: boolean; vmErrorsOnRPCResponse?: boolean;
db_path?: string; db_path?: string;
total_accounts?: number;
} }
export function provider(opts: GanacheOpts): EthereumTypes.Provider; export function provider(opts: GanacheOpts): EthereumTypes.Provider;
} }