New logic for makers joining pools
This commit is contained in:
@@ -97,7 +97,7 @@ contract MixinExchangeFees is
|
||||
{
|
||||
uint256 amount = msg.value;
|
||||
bytes32 poolId = getStakingPoolIdOfMaker(makerAddress);
|
||||
if (poolId != NIL_MAKER_ID) {
|
||||
if (poolId != NIL_POOL_ID) {
|
||||
// There is a pool associated with `makerAddress`.
|
||||
// TODO(dorothy-zbornak): When we have epoch locks on delegating, we could
|
||||
// preclude pools that have no delegated stake, since they will never have
|
||||
|
@@ -32,7 +32,7 @@ contract MixinConstants is
|
||||
// The upper 16 bytes represent the pool id, so this would be an increment of 1. See MixinStakinPool for more information.
|
||||
uint256 constant internal POOL_ID_INCREMENT_AMOUNT = 0x0000000000000000000000000000000100000000000000000000000000000000;
|
||||
|
||||
bytes32 constant internal NIL_MAKER_ID = 0x0000000000000000000000000000000000000000000000000000000000000000;
|
||||
bytes32 constant internal NIL_POOL_ID = 0x0000000000000000000000000000000000000000000000000000000000000000;
|
||||
|
||||
address constant internal NIL_ADDRESS = 0x0000000000000000000000000000000000000000;
|
||||
|
||||
|
@@ -31,4 +31,6 @@ contract MixinDeploymentConstants {
|
||||
uint32 constant internal REWARD_DELEGATED_STAKE_WEIGHT = 900000; // 90%
|
||||
|
||||
uint256 constant internal CHAIN_ID = 1;
|
||||
|
||||
uint256 constant internal MAX_MAKERS_IN_POOL = 10;
|
||||
}
|
||||
|
@@ -32,7 +32,6 @@ contract MixinStorage is
|
||||
Ownable,
|
||||
MixinConstants
|
||||
{
|
||||
|
||||
constructor()
|
||||
public
|
||||
Ownable()
|
||||
@@ -76,12 +75,12 @@ contract MixinStorage is
|
||||
// mapping from Pool Id to Pool
|
||||
mapping (bytes32 => IStructs.Pool) internal poolById;
|
||||
|
||||
// mapping from Maker Address to Pool Id
|
||||
// A Maker can only hold a single token
|
||||
mapping (address => bytes32) internal poolIdByMakerAddress;
|
||||
// mapping from Maker Address to a struct representing the pool the maker has joined and
|
||||
// whether the operator of that pool has subsequently added the maker.
|
||||
mapping (address => IStructs.MakerPoolJoinStatus) internal poolJoinedByMakerAddress;
|
||||
|
||||
// mapping from Pool Id to Addresses
|
||||
mapping (bytes32 => address[]) internal makerAddressesByPoolId;
|
||||
// mapping from Pool Id to number of makers assigned to that pool
|
||||
mapping (bytes32 => uint256) internal numMakersByPoolId;
|
||||
|
||||
// current epoch
|
||||
uint256 internal currentEpoch = INITIAL_EPOCH;
|
||||
@@ -119,4 +118,3 @@ contract MixinStorage is
|
||||
// Denominator for cobb douglas alpha factor.
|
||||
uint256 internal cobbDouglasAlphaDenomintor = 6;
|
||||
}
|
||||
|
||||
|
@@ -97,6 +97,14 @@ interface IStakingEvents {
|
||||
uint32 operatorShare
|
||||
);
|
||||
|
||||
/// @dev Emitted by MixinStakingPool when a new maker requests to join a pool.
|
||||
/// @param poolId Unique id of pool.
|
||||
/// @param makerAddress Adress of maker joining the pool.
|
||||
event PendingAddMakerToPool(
|
||||
bytes32 poolId,
|
||||
address makerAddress
|
||||
);
|
||||
|
||||
/// @dev Emitted by MixinStakingPool when a new maker is added to a pool.
|
||||
/// @param poolId Unique id of pool.
|
||||
/// @param makerAddress Adress of maker added to pool.
|
||||
|
@@ -31,14 +31,6 @@ interface IStructs {
|
||||
NSignatureTypes // 0x05, number of signature types. Always leave at end.
|
||||
}
|
||||
|
||||
/// @dev Required fields for a maker to approve a staking pool.
|
||||
/// @param poolId Unique Id of staking pool.
|
||||
/// @param makerAddress Address of maker who has approved the pool.
|
||||
struct StakingPoolApproval {
|
||||
bytes32 poolId;
|
||||
address makerAddress;
|
||||
}
|
||||
|
||||
/// @dev Status for Staking Pools (see MixinStakingPool).
|
||||
/// @param operatorAddress Address of pool operator.
|
||||
/// @param operatorShare Portion of pool rewards owned by operator, in ppm.
|
||||
@@ -102,4 +94,13 @@ interface IStructs {
|
||||
uint256 numerator;
|
||||
uint256 denominator;
|
||||
}
|
||||
|
||||
/// @dev State for keeping track of which pool a maker has joined, and if the operator has
|
||||
/// added them (see MixinStakingPool).
|
||||
/// @param poolId Unique Id of staking pool.
|
||||
/// @param confirmed Whether the operator has added the maker to the pool.
|
||||
struct MakerPoolJoinStatus {
|
||||
bytes32 poolId;
|
||||
bool confirmed;
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -38,30 +38,6 @@ library LibStakingRichErrors {
|
||||
bytes4 internal constant EXCHANGE_ADDRESS_NOT_REGISTERED_ERROR_SELECTOR =
|
||||
0x7dc025b0;
|
||||
|
||||
// bytes4(keccak256("SignatureLengthGreaterThan0RequiredError()"))
|
||||
bytes internal constant SIGNATURE_LENGTH_GREATER_THAN_0_REQUIRED_ERROR =
|
||||
hex"2dcb01d9";
|
||||
|
||||
// bytes4(keccak256("SignatureUnsupportedError(bytes)"))
|
||||
bytes4 internal constant SIGNATURE_UNSUPPORTED_ERROR_SELECTOR =
|
||||
0xffca2a70;
|
||||
|
||||
// bytes4(keccak256("SignatureIllegalError(bytes)"))
|
||||
bytes4 internal constant SIGNATURE_ILLEGAL_ERROR_SELECTOR =
|
||||
0x4a95093c;
|
||||
|
||||
// bytes4(keccak256("SignatureLength0RequiredError(bytes)"))
|
||||
bytes4 internal constant SIGNATURE_LENGTH_0_REQUIRED_ERROR_SELECTOR =
|
||||
0xcbcd59a2;
|
||||
|
||||
// bytes4(keccak256("SignatureLength65RequiredError(bytes)"))
|
||||
bytes4 internal constant SIGNATURE_LENGTH_65_REQUIRED_ERROR_SELECTOR =
|
||||
0x091d7ab9;
|
||||
|
||||
// bytes4(keccak256("WalletError(address,bytes)"))
|
||||
bytes4 internal constant WALLET_ERROR_SELECTOR =
|
||||
0x0cfc935d;
|
||||
|
||||
// bytes4(keccak256("InsufficientBalanceError(uint256,uint256)"))
|
||||
bytes4 internal constant INSUFFICIENT_BALANCE_ERROR_SELECTOR =
|
||||
0x84c8b7c9;
|
||||
@@ -74,17 +50,9 @@ library LibStakingRichErrors {
|
||||
bytes4 internal constant ONLY_CALLABLE_BY_POOL_OPERATOR_OR_MAKER_ERROR_SELECTOR =
|
||||
0x7d9e1c10;
|
||||
|
||||
// bytes4(keccak256("InvalidMakerSignatureError(bytes32,address,bytes)"))
|
||||
bytes4 internal constant INVALID_MAKER_SIGNATURE_ERROR_SELECTOR =
|
||||
0x726b89c8;
|
||||
|
||||
// bytes4(keccak256("MakerAddressAlreadyRegisteredError(address)"))
|
||||
bytes4 internal constant MAKER_ADDRESS_ALREADY_REGISTERED_ERROR_SELECTOR =
|
||||
0x5a3971da;
|
||||
|
||||
// bytes4(keccak256("MakerAddressNotRegisteredError(address,bytes32,bytes32)"))
|
||||
bytes4 internal constant MAKER_ADDRESS_NOT_REGISTERED_ERROR_SELECTOR =
|
||||
0x12ab07e8;
|
||||
// bytes4(keccak256("MakerPoolAssignmentError(uint8,address,bytes32)"))
|
||||
bytes4 internal constant MAKER_POOL_ASSIGNMENT_ERROR_SELECTOR =
|
||||
0x69945e3f;
|
||||
|
||||
// bytes4(keccak256("WithdrawAmountExceedsMemberBalanceError(uint256,uint256)"))
|
||||
bytes4 internal constant WITHDRAW_AMOUNT_EXCEEDS_MEMBER_BALANCE_ERROR_SELECTOR =
|
||||
@@ -138,6 +106,13 @@ library LibStakingRichErrors {
|
||||
bytes internal constant PROXY_DESTINATION_CANNOT_BE_NIL =
|
||||
hex"01ecebea";
|
||||
|
||||
enum MakerPoolAssignmentErrorCodes {
|
||||
MAKER_ADDRESS_ALREADY_REGISTERED,
|
||||
MAKER_ADDRESS_NOT_REGISTERED,
|
||||
MAKER_ADDRESS_NOT_PENDING_ADD,
|
||||
POOL_IS_FULL
|
||||
}
|
||||
|
||||
// solhint-disable func-name-mixedcase
|
||||
function MiscalculatedRewardsError(
|
||||
uint256 totalRewardsPaid,
|
||||
@@ -193,81 +168,6 @@ library LibStakingRichErrors {
|
||||
);
|
||||
}
|
||||
|
||||
function SignatureLengthGreaterThan0RequiredError()
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return SIGNATURE_LENGTH_GREATER_THAN_0_REQUIRED_ERROR;
|
||||
}
|
||||
|
||||
function SignatureUnsupportedError(
|
||||
bytes memory signature
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
SIGNATURE_UNSUPPORTED_ERROR_SELECTOR,
|
||||
signature
|
||||
);
|
||||
}
|
||||
|
||||
function SignatureIllegalError(
|
||||
bytes memory signature
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
SIGNATURE_ILLEGAL_ERROR_SELECTOR,
|
||||
signature
|
||||
);
|
||||
}
|
||||
|
||||
function SignatureLength0RequiredError(
|
||||
bytes memory signature
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
SIGNATURE_LENGTH_0_REQUIRED_ERROR_SELECTOR,
|
||||
signature
|
||||
);
|
||||
}
|
||||
|
||||
function SignatureLength65RequiredError(
|
||||
bytes memory signature
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
SIGNATURE_LENGTH_65_REQUIRED_ERROR_SELECTOR,
|
||||
signature
|
||||
);
|
||||
}
|
||||
|
||||
function WalletError(
|
||||
address walletAddress,
|
||||
bytes memory errorData
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
WALLET_ERROR_SELECTOR,
|
||||
walletAddress,
|
||||
errorData
|
||||
);
|
||||
}
|
||||
|
||||
function InsufficientBalanceError(
|
||||
uint256 amount,
|
||||
uint256 balance
|
||||
@@ -315,39 +215,9 @@ library LibStakingRichErrors {
|
||||
);
|
||||
}
|
||||
|
||||
function InvalidMakerSignatureError(
|
||||
bytes32 poolId,
|
||||
function MakerPoolAssignmentError(
|
||||
MakerPoolAssignmentErrorCodes errorCode,
|
||||
address makerAddress,
|
||||
bytes memory makerSignature
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
INVALID_MAKER_SIGNATURE_ERROR_SELECTOR,
|
||||
poolId,
|
||||
makerAddress,
|
||||
makerSignature
|
||||
);
|
||||
}
|
||||
|
||||
function MakerAddressAlreadyRegisteredError(
|
||||
address makerAddress
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
MAKER_ADDRESS_ALREADY_REGISTERED_ERROR_SELECTOR,
|
||||
makerAddress
|
||||
);
|
||||
}
|
||||
|
||||
function MakerAddressNotRegisteredError(
|
||||
address makerAddress,
|
||||
bytes32 makerPoolId,
|
||||
bytes32 poolId
|
||||
)
|
||||
internal
|
||||
@@ -355,9 +225,9 @@ library LibStakingRichErrors {
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
MAKER_ADDRESS_NOT_REGISTERED_ERROR_SELECTOR,
|
||||
MAKER_POOL_ASSIGNMENT_ERROR_SELECTOR,
|
||||
errorCode,
|
||||
makerAddress,
|
||||
makerPoolId,
|
||||
poolId
|
||||
);
|
||||
}
|
||||
|
@@ -22,8 +22,6 @@ pragma experimental ABIEncoderV2;
|
||||
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
||||
import "../libs/LibStakingRichErrors.sol";
|
||||
import "../libs/LibSignatureValidator.sol";
|
||||
import "../libs/LibEIP712Hash.sol";
|
||||
import "../interfaces/IStructs.sol";
|
||||
import "../interfaces/IStakingEvents.sol";
|
||||
import "../immutable/MixinConstants.sol";
|
||||
@@ -99,8 +97,9 @@ contract MixinStakingPool is
|
||||
/// @dev Create a new staking pool. The sender will be the operator of this pool.
|
||||
/// Note that an operator must be payable.
|
||||
/// @param operatorShare Portion of rewards owned by the operator, in ppm.
|
||||
/// @param addOperatorAsMaker Adds operator to the created pool as a maker for convenience iff true.
|
||||
/// @return poolId The unique pool id generated for this pool.
|
||||
function createStakingPool(uint32 operatorShare)
|
||||
function createStakingPool(uint32 operatorShare, bool addOperatorAsMaker)
|
||||
external
|
||||
returns (bytes32 poolId)
|
||||
{
|
||||
@@ -125,48 +124,119 @@ contract MixinStakingPool is
|
||||
// register pool in reward vault
|
||||
rewardVault.registerStakingPool(poolId, operatorShare);
|
||||
|
||||
// notify
|
||||
// Staking pool has been created
|
||||
emit StakingPoolCreated(poolId, operatorAddress, operatorShare);
|
||||
|
||||
if (addOperatorAsMaker) {
|
||||
// Is the maker already in a pool?
|
||||
if (isMakerAssignedToStakingPool(operatorAddress)) {
|
||||
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
|
||||
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.MAKER_ADDRESS_ALREADY_REGISTERED,
|
||||
operatorAddress,
|
||||
getStakingPoolIdOfMaker(operatorAddress)
|
||||
));
|
||||
}
|
||||
|
||||
IStructs.MakerPoolJoinStatus memory poolJoinStatus = IStructs.MakerPoolJoinStatus({
|
||||
poolId: poolId,
|
||||
confirmed: true
|
||||
});
|
||||
poolJoinedByMakerAddress[operatorAddress] = poolJoinStatus;
|
||||
numMakersByPoolId[poolId] += 1;
|
||||
|
||||
// Operator has been added as a maker to tbe pool
|
||||
emit MakerAddedToStakingPool(
|
||||
poolId,
|
||||
operatorAddress
|
||||
);
|
||||
}
|
||||
|
||||
return poolId;
|
||||
}
|
||||
|
||||
function joinStakingPoolAsMaker(
|
||||
bytes32 poolId
|
||||
)
|
||||
external
|
||||
{
|
||||
// Is the maker already in a pool?
|
||||
address makerAddress = msg.sender;
|
||||
if (isMakerAssignedToStakingPool(makerAddress)) {
|
||||
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
|
||||
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.MAKER_ADDRESS_ALREADY_REGISTERED,
|
||||
makerAddress,
|
||||
getStakingPoolIdOfMaker(makerAddress)
|
||||
));
|
||||
}
|
||||
|
||||
IStructs.MakerPoolJoinStatus memory poolJoinStatus = IStructs.MakerPoolJoinStatus({
|
||||
poolId: poolId,
|
||||
confirmed: false
|
||||
});
|
||||
poolJoinedByMakerAddress[makerAddress] = poolJoinStatus;
|
||||
|
||||
// Maker has joined to the pool, awaiting operator confirmation
|
||||
emit PendingAddMakerToPool(
|
||||
poolId,
|
||||
makerAddress
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Adds a maker to a staking pool. Note that this is only callable by the pool operator.
|
||||
/// Note also that the maker must have previously called joinStakingPoolAsMaker.
|
||||
/// @param poolId Unique id of pool.
|
||||
/// @param makerAddress Address of maker.
|
||||
/// @param makerSignature Signature proving that maker has agreed to join the pool.
|
||||
function addMakerToStakingPool(
|
||||
bytes32 poolId,
|
||||
address makerAddress,
|
||||
bytes calldata makerSignature
|
||||
address makerAddress
|
||||
)
|
||||
external
|
||||
onlyStakingPoolOperator(poolId)
|
||||
{
|
||||
// sanity check - did maker agree to join this pool?
|
||||
if (!isValidMakerSignature(poolId, makerAddress, makerSignature)) {
|
||||
LibRichErrors.rrevert(LibStakingRichErrors.InvalidMakerSignatureError(
|
||||
poolId,
|
||||
makerAddress,
|
||||
makerSignature
|
||||
));
|
||||
}
|
||||
// Is the maker already in a pool?
|
||||
if (isMakerAssignedToStakingPool(makerAddress)) {
|
||||
LibRichErrors.rrevert(LibStakingRichErrors.MakerAddressAlreadyRegisteredError(
|
||||
makerAddress
|
||||
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
|
||||
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.MAKER_ADDRESS_ALREADY_REGISTERED,
|
||||
makerAddress,
|
||||
getStakingPoolIdOfMaker(makerAddress)
|
||||
));
|
||||
}
|
||||
|
||||
poolIdByMakerAddress[makerAddress] = poolId;
|
||||
makerAddressesByPoolId[poolId].push(makerAddress);
|
||||
// Is the maker trying to join this pool?
|
||||
bytes32 makerPendingPoolId = poolJoinedByMakerAddress[makerAddress].poolId;
|
||||
if (makerPendingPoolId != poolId) {
|
||||
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
|
||||
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.MAKER_ADDRESS_NOT_PENDING_ADD,
|
||||
makerAddress,
|
||||
makerPendingPoolId
|
||||
));
|
||||
}
|
||||
|
||||
// notify
|
||||
// Is the pool already full?
|
||||
if (getNumberOfMakersInStakingPool(poolId) == MAX_MAKERS_IN_POOL) {
|
||||
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
|
||||
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.POOL_IS_FULL,
|
||||
makerAddress,
|
||||
poolId
|
||||
));
|
||||
}
|
||||
|
||||
// Add maker to pool
|
||||
IStructs.MakerPoolJoinStatus memory poolJoinStatus = IStructs.MakerPoolJoinStatus({
|
||||
poolId: poolId,
|
||||
confirmed: true
|
||||
});
|
||||
poolJoinedByMakerAddress[makerAddress] = poolJoinStatus;
|
||||
numMakersByPoolId[poolId] += 1;
|
||||
|
||||
// Maker has been added to the pool
|
||||
emit MakerAddedToStakingPool(
|
||||
poolId,
|
||||
makerAddress
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Adds a maker to a staking pool. Note that this is only callable by the pool operator or maker.
|
||||
/// @dev Removes a maker from a staking pool. Note that this is only callable by the pool operator or maker.
|
||||
/// Note also that the maker does not have to *agree* to leave the pool; this action is
|
||||
/// at the sole discretion of the pool operator.
|
||||
/// @param poolId Unique id of pool.
|
||||
@@ -180,90 +250,41 @@ contract MixinStakingPool is
|
||||
{
|
||||
bytes32 makerPoolId = getStakingPoolIdOfMaker(makerAddress);
|
||||
if (makerPoolId != poolId) {
|
||||
LibRichErrors.rrevert(LibStakingRichErrors.MakerAddressNotRegisteredError(
|
||||
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
|
||||
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.MAKER_ADDRESS_NOT_REGISTERED,
|
||||
makerAddress,
|
||||
makerPoolId,
|
||||
poolId
|
||||
makerPoolId
|
||||
));
|
||||
}
|
||||
|
||||
// load list of makers for the input pool.
|
||||
address[] storage makerAddressesByPoolIdPtr = makerAddressesByPoolId[poolId];
|
||||
uint256 makerAddressesByPoolIdLength = makerAddressesByPoolIdPtr.length;
|
||||
// remove the pool and confirmation from the maker status
|
||||
IStructs.MakerPoolJoinStatus memory poolJoinStatus = IStructs.MakerPoolJoinStatus({
|
||||
poolId: NIL_POOL_ID,
|
||||
confirmed: false
|
||||
});
|
||||
poolJoinedByMakerAddress[makerAddress] = poolJoinStatus;
|
||||
numMakersByPoolId[poolId] -= 1;
|
||||
|
||||
// find index of maker to remove.
|
||||
uint indexOfMakerAddress = 0;
|
||||
for (; indexOfMakerAddress < makerAddressesByPoolIdLength; ++indexOfMakerAddress) {
|
||||
if (makerAddressesByPoolIdPtr[indexOfMakerAddress] == makerAddress) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// remove the maker from the list of makers for this pool.
|
||||
// (i) move maker at end of list to the slot occupied by the maker to remove, then
|
||||
// (ii) zero out the slot at the end of the list and decrement the length.
|
||||
uint256 indexOfLastMakerAddress = makerAddressesByPoolIdLength - 1;
|
||||
if (indexOfMakerAddress != indexOfLastMakerAddress) {
|
||||
makerAddressesByPoolIdPtr[indexOfMakerAddress] = makerAddressesByPoolIdPtr[indexOfLastMakerAddress];
|
||||
}
|
||||
makerAddressesByPoolIdPtr[indexOfLastMakerAddress] = NIL_ADDRESS;
|
||||
makerAddressesByPoolIdPtr.length -= 1;
|
||||
|
||||
// reset the pool id assigned to the maker.
|
||||
poolIdByMakerAddress[makerAddress] = NIL_MAKER_ID;
|
||||
|
||||
// notify
|
||||
// Maker has been removed from the pool`
|
||||
emit MakerRemovedFromStakingPool(
|
||||
poolId,
|
||||
makerAddress
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Returns true iff the input signature is valid; meaning that the maker agrees to
|
||||
/// be added to the pool.
|
||||
/// @param poolId Unique id of pool the maker wishes to join.
|
||||
/// @param makerAddress Address of maker.
|
||||
/// @param makerSignature Signature of the maker.
|
||||
/// @return isValid True iff the maker agrees to be added to the pool.
|
||||
function isValidMakerSignature(bytes32 poolId, address makerAddress, bytes memory makerSignature)
|
||||
public
|
||||
view
|
||||
returns (bool isValid)
|
||||
{
|
||||
bytes32 approvalHash = getStakingPoolApprovalMessageHash(poolId, makerAddress);
|
||||
isValid = LibSignatureValidator._isValidSignature(approvalHash, makerAddress, makerSignature);
|
||||
return isValid;
|
||||
}
|
||||
|
||||
/// @dev Returns the approval message hash - this is what a maker must sign in order to
|
||||
/// be added to a pool.
|
||||
/// @param poolId Unique id of pool the maker wishes to join.
|
||||
/// @param makerAddress Address of maker.
|
||||
/// @return approvalHash Hash of message the maker must sign.
|
||||
function getStakingPoolApprovalMessageHash(bytes32 poolId, address makerAddress)
|
||||
public
|
||||
view
|
||||
returns (bytes32 approvalHash)
|
||||
{
|
||||
IStructs.StakingPoolApproval memory approval = IStructs.StakingPoolApproval({
|
||||
poolId: poolId,
|
||||
makerAddress: makerAddress
|
||||
});
|
||||
|
||||
// hash approval message and check signer address
|
||||
address verifierAddress = address(this);
|
||||
approvalHash = LibEIP712Hash._hashStakingPoolApprovalMessage(approval, CHAIN_ID, verifierAddress);
|
||||
|
||||
return approvalHash;
|
||||
}
|
||||
|
||||
/// @dev Returns the pool id of an input maker.
|
||||
/// @dev Returns the pool id of the input maker.
|
||||
/// @param makerAddress Address of maker
|
||||
/// @return Pool id, nil if maker is not yet assigned to a pool.
|
||||
function getStakingPoolIdOfMaker(address makerAddress)
|
||||
public
|
||||
view
|
||||
returns (bytes32)
|
||||
{
|
||||
return poolIdByMakerAddress[makerAddress];
|
||||
if (isMakerAssignedToStakingPool(makerAddress)) {
|
||||
return poolJoinedByMakerAddress[makerAddress].poolId;
|
||||
} else {
|
||||
return NIL_POOL_ID;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Returns true iff the maker is assigned to a staking pool.
|
||||
@@ -274,18 +295,18 @@ contract MixinStakingPool is
|
||||
view
|
||||
returns (bool)
|
||||
{
|
||||
return getStakingPoolIdOfMaker(makerAddress) != NIL_MAKER_ID;
|
||||
return poolJoinedByMakerAddress[makerAddress].confirmed;
|
||||
}
|
||||
|
||||
/// @dev Returns the makers for a given pool.
|
||||
/// @dev Returns the current number of makers in a given pool.
|
||||
/// @param poolId Unique id of pool.
|
||||
/// @return _makerAddressesByPoolId Makers for pool.
|
||||
function getMakersForStakingPool(bytes32 poolId)
|
||||
/// @return Size of pool.
|
||||
function getNumberOfMakersInStakingPool(bytes32 poolId)
|
||||
public
|
||||
view
|
||||
returns (address[] memory _makerAddressesByPoolId)
|
||||
returns (uint256)
|
||||
{
|
||||
return makerAddressesByPoolId[poolId];
|
||||
return numMakersByPoolId[poolId];
|
||||
}
|
||||
|
||||
/// @dev Returns the unique id that will be assigned to the next pool that is created.
|
||||
|
@@ -30,7 +30,7 @@ import "../immutable/MixinConstants.sol";
|
||||
|
||||
|
||||
/// @dev This vault manages staking pool rewards.
|
||||
/// Rewards can be deposited and withdraw by the staking contract.
|
||||
/// Rewards can be deposited and withdrawn by the staking contract.
|
||||
/// There is a "Catastrophic Failure Mode" that, when invoked, only
|
||||
/// allows withdrawals to be made. Once this vault is in catastrophic
|
||||
/// failure mode, it cannot be returned to normal mode; this prevents
|
||||
|
@@ -76,10 +76,10 @@ contract TestStorageLayout is
|
||||
if sub(poolById_slot, slot) { revertIncorrectStorageSlot() }
|
||||
slot := add(slot, 1)
|
||||
|
||||
if sub(poolIdByMakerAddress_slot, slot) { revertIncorrectStorageSlot() }
|
||||
if sub(poolJoinedByMakerAddress_slot, slot) { revertIncorrectStorageSlot() }
|
||||
slot := add(slot, 1)
|
||||
|
||||
if sub(makerAddressesByPoolId_slot, slot) { revertIncorrectStorageSlot() }
|
||||
if sub(numMakersByPoolId_slot, slot) { revertIncorrectStorageSlot() }
|
||||
slot := add(slot, 1)
|
||||
|
||||
if sub(currentEpoch_slot, slot) { revertIncorrectStorageSlot() }
|
||||
|
@@ -37,7 +37,7 @@
|
||||
},
|
||||
"config": {
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./generated-artifacts/@(EthVault|IEthVault|IStaking|IStakingEvents|IStakingPoolRewardVault|IStakingProxy|IStructs|IVaultCore|IWallet|IZrxVault|LibEIP712Hash|LibFixedMath|LibFixedMathRichErrors|LibProxy|LibSafeDowncast|LibSignatureValidator|LibStakingRichErrors|MixinConstants|MixinDeploymentConstants|MixinEthVault|MixinExchangeFees|MixinExchangeManager|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolRewardVault|MixinStakingPoolRewards|MixinStorage|MixinVaultCore|MixinZrxVault|ReadOnlyProxy|Staking|StakingPoolRewardVault|StakingProxy|TestCobbDouglas|TestLibFixedMath|TestStorageLayout|ZrxVault).json"
|
||||
"abis": "./generated-artifacts/@(EthVault|IEthVault|IStaking|IStakingEvents|IStakingPoolRewardVault|IStakingProxy|IStructs|IVaultCore|IZrxVault|LibFixedMath|LibFixedMathRichErrors|LibProxy|LibSafeDowncast|LibStakingRichErrors|MixinConstants|MixinDeploymentConstants|MixinEthVault|MixinExchangeFees|MixinExchangeManager|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolRewardVault|MixinStakingPoolRewards|MixinStorage|MixinVaultCore|MixinZrxVault|ReadOnlyProxy|Staking|StakingPoolRewardVault|StakingProxy|TestCobbDouglas|TestLibFixedMath|TestStorageLayout|ZrxVault).json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@@ -13,14 +13,11 @@ import * as IStakingPoolRewardVault from '../generated-artifacts/IStakingPoolRew
|
||||
import * as IStakingProxy from '../generated-artifacts/IStakingProxy.json';
|
||||
import * as IStructs from '../generated-artifacts/IStructs.json';
|
||||
import * as IVaultCore from '../generated-artifacts/IVaultCore.json';
|
||||
import * as IWallet from '../generated-artifacts/IWallet.json';
|
||||
import * as IZrxVault from '../generated-artifacts/IZrxVault.json';
|
||||
import * as LibEIP712Hash from '../generated-artifacts/LibEIP712Hash.json';
|
||||
import * as LibFixedMath from '../generated-artifacts/LibFixedMath.json';
|
||||
import * as LibFixedMathRichErrors from '../generated-artifacts/LibFixedMathRichErrors.json';
|
||||
import * as LibProxy from '../generated-artifacts/LibProxy.json';
|
||||
import * as LibSafeDowncast from '../generated-artifacts/LibSafeDowncast.json';
|
||||
import * as LibSignatureValidator from '../generated-artifacts/LibSignatureValidator.json';
|
||||
import * as LibStakingRichErrors from '../generated-artifacts/LibStakingRichErrors.json';
|
||||
import * as MixinConstants from '../generated-artifacts/MixinConstants.json';
|
||||
import * as MixinDeploymentConstants from '../generated-artifacts/MixinDeploymentConstants.json';
|
||||
@@ -61,14 +58,11 @@ export const artifacts = {
|
||||
IStakingProxy: IStakingProxy as ContractArtifact,
|
||||
IStructs: IStructs as ContractArtifact,
|
||||
IVaultCore: IVaultCore as ContractArtifact,
|
||||
IWallet: IWallet as ContractArtifact,
|
||||
IZrxVault: IZrxVault as ContractArtifact,
|
||||
LibEIP712Hash: LibEIP712Hash as ContractArtifact,
|
||||
LibFixedMath: LibFixedMath as ContractArtifact,
|
||||
LibFixedMathRichErrors: LibFixedMathRichErrors as ContractArtifact,
|
||||
LibProxy: LibProxy as ContractArtifact,
|
||||
LibSafeDowncast: LibSafeDowncast as ContractArtifact,
|
||||
LibSignatureValidator: LibSignatureValidator as ContractArtifact,
|
||||
LibStakingRichErrors: LibStakingRichErrors as ContractArtifact,
|
||||
MixinStake: MixinStake as ContractArtifact,
|
||||
MixinStakeBalances: MixinStakeBalances as ContractArtifact,
|
||||
|
@@ -11,14 +11,11 @@ export * from '../generated-wrappers/i_staking_pool_reward_vault';
|
||||
export * from '../generated-wrappers/i_staking_proxy';
|
||||
export * from '../generated-wrappers/i_structs';
|
||||
export * from '../generated-wrappers/i_vault_core';
|
||||
export * from '../generated-wrappers/i_wallet';
|
||||
export * from '../generated-wrappers/i_zrx_vault';
|
||||
export * from '../generated-wrappers/lib_e_i_p712_hash';
|
||||
export * from '../generated-wrappers/lib_fixed_math';
|
||||
export * from '../generated-wrappers/lib_fixed_math_rich_errors';
|
||||
export * from '../generated-wrappers/lib_proxy';
|
||||
export * from '../generated-wrappers/lib_safe_downcast';
|
||||
export * from '../generated-wrappers/lib_signature_validator';
|
||||
export * from '../generated-wrappers/lib_staking_rich_errors';
|
||||
export * from '../generated-wrappers/mixin_constants';
|
||||
export * from '../generated-wrappers/mixin_deployment_constants';
|
||||
|
@@ -1,41 +1,47 @@
|
||||
import { SignatureType } from '@0x/types';
|
||||
import { expect } from '@0x/contracts-test-utils';
|
||||
import { RevertError } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { StakingWrapper } from '../utils/staking_wrapper';
|
||||
import { SignedStakingPoolApproval } from '../utils/types';
|
||||
import { constants as stakingConstants } from '../utils/constants';
|
||||
|
||||
import { BaseActor } from './base_actor';
|
||||
|
||||
export class MakerActor extends BaseActor {
|
||||
private readonly _ownerPrivateKeyIfExists?: Buffer;
|
||||
private readonly _signatureVerifierIfExists?: string;
|
||||
private readonly _chainIdIfExists?: number;
|
||||
public async joinStakingPoolAsMakerAsync(poolId: string, revertError?: RevertError): Promise<void> {
|
||||
// Join pool
|
||||
const txReceiptPromise = this._stakingWrapper.joinStakingPoolAsMakerAsync(poolId, this._owner);
|
||||
|
||||
constructor(
|
||||
owner: string,
|
||||
stakingWrapper: StakingWrapper,
|
||||
ownerPrivateKey?: Buffer,
|
||||
signatureVerifier?: string,
|
||||
chainId?: number,
|
||||
) {
|
||||
super(owner, stakingWrapper);
|
||||
this._ownerPrivateKeyIfExists = ownerPrivateKey;
|
||||
this._signatureVerifierIfExists = signatureVerifier;
|
||||
this._chainIdIfExists = chainId;
|
||||
if (revertError !== undefined) {
|
||||
await expect(txReceiptPromise).to.revertWith(revertError);
|
||||
return;
|
||||
}
|
||||
await txReceiptPromise;
|
||||
|
||||
// Pool id of the maker should be nil (join would've thrown otherwise)
|
||||
const poolIdOfMaker = await this._stakingWrapper.getStakingPoolIdOfMakerAsync(this._owner);
|
||||
expect(poolIdOfMaker, 'pool id of maker').to.be.equal(stakingConstants.NIL_POOL_ID);
|
||||
}
|
||||
|
||||
public signApprovalForStakingPool(
|
||||
public async removeMakerFromStakingPoolAsync(
|
||||
poolId: string,
|
||||
signatureType: SignatureType = SignatureType.EthSign,
|
||||
): SignedStakingPoolApproval {
|
||||
const approval = this._stakingWrapper.signApprovalForStakingPool(
|
||||
makerAddress: string,
|
||||
revertError?: RevertError,
|
||||
): Promise<void> {
|
||||
// remove maker (should fail if makerAddress !== this._owner)
|
||||
const txReceiptPromise = this._stakingWrapper.removeMakerFromStakingPoolAsync(
|
||||
poolId,
|
||||
makerAddress,
|
||||
this._owner,
|
||||
this._ownerPrivateKeyIfExists,
|
||||
this._signatureVerifierIfExists,
|
||||
this._chainIdIfExists,
|
||||
signatureType,
|
||||
);
|
||||
return approval;
|
||||
|
||||
if (revertError !== undefined) {
|
||||
await expect(txReceiptPromise).to.revertWith(revertError);
|
||||
return;
|
||||
}
|
||||
await txReceiptPromise;
|
||||
|
||||
// check the pool id of the maker
|
||||
const poolIdOfMakerAfterRemoving = await this._stakingWrapper.getStakingPoolIdOfMakerAsync(this._owner);
|
||||
expect(poolIdOfMakerAfterRemoving, 'pool id of maker').to.be.equal(stakingConstants.NIL_POOL_ID);
|
||||
}
|
||||
}
|
||||
|
@@ -3,20 +3,23 @@ import { RevertError } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { constants as stakingConstants } from '../utils/constants';
|
||||
import { StakingWrapper } from '../utils/staking_wrapper';
|
||||
|
||||
import { BaseActor } from './base_actor';
|
||||
|
||||
export class PoolOperatorActor extends BaseActor {
|
||||
constructor(owner: string, stakingWrapper: StakingWrapper) {
|
||||
super(owner, stakingWrapper);
|
||||
}
|
||||
|
||||
public async createStakingPoolAsync(operatorShare: number, revertError?: RevertError): Promise<string> {
|
||||
public async createStakingPoolAsync(
|
||||
operatorShare: number,
|
||||
addOperatorAsMaker: boolean,
|
||||
revertError?: RevertError,
|
||||
): Promise<string> {
|
||||
// query next pool id
|
||||
const nextPoolId = await this._stakingWrapper.getNextStakingPoolIdAsync();
|
||||
// create pool
|
||||
const poolIdPromise = this._stakingWrapper.createStakingPoolAsync(this._owner, operatorShare);
|
||||
const poolIdPromise = this._stakingWrapper.createStakingPoolAsync(
|
||||
this._owner,
|
||||
operatorShare,
|
||||
addOperatorAsMaker,
|
||||
);
|
||||
if (revertError !== undefined) {
|
||||
await expect(poolIdPromise).to.revertWith(revertError);
|
||||
return '';
|
||||
@@ -24,21 +27,24 @@ export class PoolOperatorActor extends BaseActor {
|
||||
const poolId = await poolIdPromise;
|
||||
// validate pool id
|
||||
expect(poolId, 'pool id').to.be.bignumber.equal(nextPoolId);
|
||||
|
||||
if (addOperatorAsMaker) {
|
||||
// check the pool id of the operator
|
||||
const poolIdOfMaker = await this._stakingWrapper.getStakingPoolIdOfMakerAsync(this._owner);
|
||||
expect(poolIdOfMaker, 'pool id of maker').to.be.equal(poolId);
|
||||
// check the number of makers in the pool
|
||||
const numMakersAfterRemoving = await this._stakingWrapper.getNumberOfMakersInStakingPoolAsync(poolId);
|
||||
expect(numMakersAfterRemoving, 'number of makers in pool').to.be.bignumber.equal(1);
|
||||
}
|
||||
return poolId;
|
||||
}
|
||||
public async addMakerToStakingPoolAsync(
|
||||
poolId: string,
|
||||
makerAddress: string,
|
||||
makerSignature: string,
|
||||
revertError?: RevertError,
|
||||
): Promise<void> {
|
||||
// add maker
|
||||
const txReceiptPromise = this._stakingWrapper.addMakerToStakingPoolAsync(
|
||||
poolId,
|
||||
makerAddress,
|
||||
makerSignature,
|
||||
this._owner,
|
||||
);
|
||||
const txReceiptPromise = this._stakingWrapper.addMakerToStakingPoolAsync(poolId, makerAddress, this._owner);
|
||||
if (revertError !== undefined) {
|
||||
await expect(txReceiptPromise).to.revertWith(revertError);
|
||||
return;
|
||||
@@ -47,9 +53,6 @@ export class PoolOperatorActor extends BaseActor {
|
||||
// check the pool id of the maker
|
||||
const poolIdOfMaker = await this._stakingWrapper.getStakingPoolIdOfMakerAsync(makerAddress);
|
||||
expect(poolIdOfMaker, 'pool id of maker').to.be.equal(poolId);
|
||||
// check the list of makers for the pool
|
||||
const makerAddressesForPool = await this._stakingWrapper.getMakersForStakingPoolAsync(poolId);
|
||||
expect(makerAddressesForPool, 'maker addresses for pool').to.include(makerAddress);
|
||||
}
|
||||
public async removeMakerFromStakingPoolAsync(
|
||||
poolId: string,
|
||||
@@ -70,8 +73,5 @@ export class PoolOperatorActor extends BaseActor {
|
||||
// check the pool id of the maker
|
||||
const poolIdOfMakerAfterRemoving = await this._stakingWrapper.getStakingPoolIdOfMakerAsync(makerAddress);
|
||||
expect(poolIdOfMakerAfterRemoving, 'pool id of maker').to.be.equal(stakingConstants.NIL_POOL_ID);
|
||||
// check the list of makers for the pool
|
||||
const makerAddressesForPoolAfterRemoving = await this._stakingWrapper.getMakersForStakingPoolAsync(poolId);
|
||||
expect(makerAddressesForPoolAfterRemoving, 'maker addresses for pool').to.not.include(makerAddress);
|
||||
}
|
||||
}
|
||||
|
@@ -36,7 +36,7 @@ blockchainTests.resets('Catastrophe Tests', env => {
|
||||
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
// deploy staking contracts
|
||||
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract, accounts);
|
||||
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract);
|
||||
await stakingWrapper.deployAndConfigureContractsAsync();
|
||||
});
|
||||
|
||||
|
@@ -42,7 +42,7 @@ describe('Epochs', () => {
|
||||
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
// deploy staking contracts
|
||||
stakingWrapper = new StakingWrapper(provider, owner, erc20ProxyContract, zrxTokenContract, accounts);
|
||||
stakingWrapper = new StakingWrapper(provider, owner, erc20ProxyContract, zrxTokenContract);
|
||||
await stakingWrapper.deployAndConfigureContractsAsync();
|
||||
});
|
||||
beforeEach(async () => {
|
||||
|
@@ -33,7 +33,7 @@ blockchainTests('Exchange Integrations', env => {
|
||||
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
// deploy staking contracts
|
||||
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract, accounts);
|
||||
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract);
|
||||
await stakingWrapper.deployAndConfigureContractsAsync();
|
||||
});
|
||||
blockchainTests.resets('Exchange Tracking in Staking Contract', () => {
|
||||
|
@@ -2,7 +2,6 @@ import { ERC20ProxyContract, ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
||||
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { blockchainTests, constants, expect } from '@0x/contracts-test-utils';
|
||||
import { StakingRevertErrors } from '@0x/order-utils';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { MakerActor } from './actors/maker_actor';
|
||||
@@ -36,7 +35,7 @@ blockchainTests('Staking Pool Management', env => {
|
||||
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, DUMMY_TOKEN_DECIMALS);
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
// deploy staking contracts
|
||||
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract, accounts);
|
||||
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract);
|
||||
await stakingWrapper.deployAndConfigureContractsAsync();
|
||||
});
|
||||
blockchainTests.resets('Staking Pool Management', () => {
|
||||
@@ -46,12 +45,23 @@ blockchainTests('Staking Pool Management', env => {
|
||||
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
|
||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
||||
// create pool
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare);
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
|
||||
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||
// check that the next pool id was incremented
|
||||
const expectedNextPoolId = '0x0000000000000000000000000000000200000000000000000000000000000000';
|
||||
const nextPoolId = await stakingWrapper.getNextStakingPoolIdAsync();
|
||||
expect(nextPoolId).to.be.equal(expectedNextPoolId);
|
||||
expect(nextPoolId).to.be.equal(stakingConstants.SECOND_POOL_ID);
|
||||
});
|
||||
it('Should successfully create a pool and add owner as a maker', async () => {
|
||||
// test parameters
|
||||
const operatorAddress = users[0];
|
||||
const operatorShare = 39;
|
||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
||||
// create pool
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
||||
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||
// check that the next pool id was incremented
|
||||
const nextPoolId = await stakingWrapper.getNextStakingPoolIdAsync();
|
||||
expect(nextPoolId).to.be.equal(stakingConstants.SECOND_POOL_ID);
|
||||
});
|
||||
it('Should throw if poolOperatorShare is > PPM_DENOMINATOR', async () => {
|
||||
// test parameters
|
||||
@@ -60,7 +70,7 @@ blockchainTests('Staking Pool Management', env => {
|
||||
const operatorShare = PPM_100_PERCENT + 1;
|
||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
||||
// create pool
|
||||
const tx = poolOperator.createStakingPoolAsync(operatorShare);
|
||||
const tx = poolOperator.createStakingPoolAsync(operatorShare, true);
|
||||
const expectedPoolId = stakingConstants.INITIAL_POOL_ID;
|
||||
const expectedError = new StakingRevertErrors.InvalidPoolOperatorShareError(expectedPoolId, operatorShare);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
@@ -73,41 +83,138 @@ blockchainTests('Staking Pool Management', env => {
|
||||
const makerAddress = users[1];
|
||||
const maker = new MakerActor(makerAddress, stakingWrapper);
|
||||
// create pool
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare);
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
||||
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||
// add maker to pool
|
||||
const makerApproval = maker.signApprovalForStakingPool(poolId);
|
||||
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, makerApproval.signature);
|
||||
// remove maker from pool
|
||||
// maker joins pool
|
||||
await maker.joinStakingPoolAsMakerAsync(poolId);
|
||||
// operator adds maker to pool
|
||||
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
|
||||
// operator removes maker from pool
|
||||
await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddress);
|
||||
});
|
||||
it('Should successfully add/remove multipler makers to the same pool', async () => {
|
||||
it('Maker should successfully remove themselves from a pool', async () => {
|
||||
// test parameters
|
||||
const operatorAddress = users[0];
|
||||
const operatorShare = 39;
|
||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
||||
const makerAddress = users[1];
|
||||
const maker = new MakerActor(makerAddress, stakingWrapper);
|
||||
// create pool
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
||||
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||
// maker joins pool
|
||||
await maker.joinStakingPoolAsMakerAsync(poolId);
|
||||
// operator adds maker to pool
|
||||
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
|
||||
// maker removes themselves from pool
|
||||
await maker.removeMakerFromStakingPoolAsync(poolId, makerAddress);
|
||||
});
|
||||
it('Should successfully add/remove multiple makers to the same pool', async () => {
|
||||
// test parameters
|
||||
const operatorAddress = users[0];
|
||||
const operatorShare = 39;
|
||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
||||
const makerAddresses = users.slice(1, 4);
|
||||
const makers = [
|
||||
new MakerActor(makerAddresses[0], stakingWrapper),
|
||||
new MakerActor(makerAddresses[1], stakingWrapper),
|
||||
new MakerActor(makerAddresses[2], stakingWrapper),
|
||||
];
|
||||
const makers = makerAddresses.map(makerAddress => new MakerActor(makerAddress, stakingWrapper));
|
||||
|
||||
// create pool
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare);
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
|
||||
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||
|
||||
// add makers to pool
|
||||
const makerApprovals = [
|
||||
makers[0].signApprovalForStakingPool(poolId),
|
||||
makers[1].signApprovalForStakingPool(poolId),
|
||||
makers[2].signApprovalForStakingPool(poolId),
|
||||
];
|
||||
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddresses[0], makerApprovals[0].signature);
|
||||
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddresses[1], makerApprovals[1].signature);
|
||||
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddresses[2], makerApprovals[2].signature);
|
||||
await Promise.all(makers.map(async maker => maker.joinStakingPoolAsMakerAsync(poolId)));
|
||||
await Promise.all(
|
||||
makerAddresses.map(async makerAddress => poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress)),
|
||||
);
|
||||
|
||||
// check the number of makers in the pool
|
||||
let numMakers = await stakingWrapper.getNumberOfMakersInStakingPoolAsync(poolId);
|
||||
expect(numMakers, 'number of makers in pool after adding').to.be.bignumber.equal(3);
|
||||
|
||||
// remove maker from pool
|
||||
await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddresses[0]);
|
||||
await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddresses[1]);
|
||||
await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddresses[2]);
|
||||
await Promise.all(
|
||||
makerAddresses.map(async makerAddress =>
|
||||
poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddress),
|
||||
),
|
||||
);
|
||||
|
||||
// check the number of makers in the pool
|
||||
numMakers = await stakingWrapper.getNumberOfMakersInStakingPoolAsync(poolId);
|
||||
expect(numMakers, 'number of makers in pool after removing').to.be.bignumber.equal(0);
|
||||
});
|
||||
it('Should fail if maker already assigned to another pool tries to join', async () => {
|
||||
// test parameters
|
||||
const operatorShare = 39;
|
||||
const assignedPoolOperator = new PoolOperatorActor(users[0], stakingWrapper);
|
||||
const otherPoolOperator = new PoolOperatorActor(users[1], stakingWrapper);
|
||||
|
||||
const makerAddress = users[2];
|
||||
const maker = new MakerActor(makerAddress, stakingWrapper);
|
||||
|
||||
// create pools
|
||||
const assignedPoolId = await assignedPoolOperator.createStakingPoolAsync(operatorShare, true);
|
||||
const otherPoolId = await otherPoolOperator.createStakingPoolAsync(operatorShare, true);
|
||||
expect(assignedPoolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||
expect(otherPoolId).to.be.equal(stakingConstants.SECOND_POOL_ID);
|
||||
|
||||
// maker joins first pool
|
||||
await maker.joinStakingPoolAsMakerAsync(assignedPoolId);
|
||||
// first pool operator adds maker
|
||||
await assignedPoolOperator.addMakerToStakingPoolAsync(assignedPoolId, makerAddress);
|
||||
|
||||
const revertError = new StakingRevertErrors.MakerPoolAssignmentError(
|
||||
StakingRevertErrors.MakerPoolAssignmentErrorCodes.MakerAddressAlreadyRegistered,
|
||||
makerAddress,
|
||||
assignedPoolId,
|
||||
);
|
||||
// second pool operator now tries to add maker
|
||||
await otherPoolOperator.addMakerToStakingPoolAsync(otherPoolId, makerAddress, revertError);
|
||||
});
|
||||
it('Should fail to add maker to pool if the maker has not joined any pools', async () => {
|
||||
// test parameters
|
||||
const operatorAddress = users[0];
|
||||
const operatorShare = 39;
|
||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
||||
|
||||
const makerAddress = users[1];
|
||||
|
||||
// create pool
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
||||
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||
|
||||
const revertError = new StakingRevertErrors.MakerPoolAssignmentError(
|
||||
StakingRevertErrors.MakerPoolAssignmentErrorCodes.MakerAddressNotPendingAdd,
|
||||
makerAddress,
|
||||
stakingConstants.NIL_POOL_ID,
|
||||
);
|
||||
// operator adds maker to pool
|
||||
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, revertError);
|
||||
});
|
||||
it('Should fail to add maker to pool if the maker joined a different pool', async () => {
|
||||
// test parameters
|
||||
const operatorShare = 39;
|
||||
const assignedPoolOperator = new PoolOperatorActor(users[0], stakingWrapper);
|
||||
const otherPoolOperator = new PoolOperatorActor(users[1], stakingWrapper);
|
||||
|
||||
const makerAddress = users[2];
|
||||
const maker = new MakerActor(makerAddress, stakingWrapper);
|
||||
|
||||
// create pools
|
||||
const joinedPoolId = await assignedPoolOperator.createStakingPoolAsync(operatorShare, true);
|
||||
const otherPoolId = await otherPoolOperator.createStakingPoolAsync(operatorShare, true);
|
||||
expect(joinedPoolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||
expect(otherPoolId).to.be.equal(stakingConstants.SECOND_POOL_ID);
|
||||
|
||||
// maker joins first pool
|
||||
await maker.joinStakingPoolAsMakerAsync(joinedPoolId);
|
||||
|
||||
const revertError = new StakingRevertErrors.MakerPoolAssignmentError(
|
||||
StakingRevertErrors.MakerPoolAssignmentErrorCodes.MakerAddressNotPendingAdd,
|
||||
makerAddress,
|
||||
joinedPoolId,
|
||||
);
|
||||
// second pool operator now tries to add maker
|
||||
await otherPoolOperator.addMakerToStakingPoolAsync(otherPoolId, makerAddress, revertError);
|
||||
});
|
||||
it('Should fail to add the same maker twice', async () => {
|
||||
// test parameters
|
||||
@@ -117,14 +224,18 @@ blockchainTests('Staking Pool Management', env => {
|
||||
const makerAddress = users[1];
|
||||
const maker = new MakerActor(makerAddress, stakingWrapper);
|
||||
// create pool
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare);
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
||||
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||
// add maker to pool
|
||||
const makerApproval = maker.signApprovalForStakingPool(poolId);
|
||||
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, makerApproval.signature);
|
||||
const revertError = new StakingRevertErrors.MakerAddressAlreadyRegisteredError(makerAddress);
|
||||
await maker.joinStakingPoolAsMakerAsync(poolId);
|
||||
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
|
||||
const revertError = new StakingRevertErrors.MakerPoolAssignmentError(
|
||||
StakingRevertErrors.MakerPoolAssignmentErrorCodes.MakerAddressAlreadyRegistered,
|
||||
makerAddress,
|
||||
poolId,
|
||||
);
|
||||
// add same maker to pool again
|
||||
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, makerApproval.signature, revertError);
|
||||
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, revertError);
|
||||
});
|
||||
it('Should fail to remove a maker that does not exist', async () => {
|
||||
// test parameters
|
||||
@@ -133,87 +244,16 @@ blockchainTests('Staking Pool Management', env => {
|
||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
||||
const makerAddress = users[1];
|
||||
// create pool
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare);
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
||||
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||
const revertError = new StakingRevertErrors.MakerAddressNotRegisteredError(
|
||||
const revertError = new StakingRevertErrors.MakerPoolAssignmentError(
|
||||
StakingRevertErrors.MakerPoolAssignmentErrorCodes.MakerAddressNotRegistered,
|
||||
makerAddress,
|
||||
stakingConstants.NIL_POOL_ID,
|
||||
poolId,
|
||||
);
|
||||
// remove non-existent maker from pool
|
||||
await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddress, revertError);
|
||||
});
|
||||
it('Should fail to add a maker who signed with the wrong private key', async () => {
|
||||
// test parameters
|
||||
const operatorAddress = users[0];
|
||||
const operatorShare = 39;
|
||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
||||
const makerAddress = users[1];
|
||||
const badMakerPrivateKey = ethUtil.toBuffer(
|
||||
'0x0000000000000000000000000000000000000000000000000000000000000001',
|
||||
);
|
||||
const maker = new MakerActor(makerAddress, stakingWrapper, badMakerPrivateKey);
|
||||
// create pool
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare);
|
||||
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||
// add maker to poolxx
|
||||
const makerApproval = maker.signApprovalForStakingPool(poolId);
|
||||
const revertError = new StakingRevertErrors.InvalidMakerSignatureError(
|
||||
poolId,
|
||||
makerAddress,
|
||||
makerApproval.signature,
|
||||
);
|
||||
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, makerApproval.signature, revertError);
|
||||
});
|
||||
it('Should fail to add a maker who signed with the wrong staking contract address', async () => {
|
||||
// test parameters
|
||||
const operatorAddress = users[0];
|
||||
const operatorShare = 39;
|
||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
||||
const makerAddress = users[1];
|
||||
const forceMakerKeyLookup = undefined;
|
||||
const notStakingContractAddress = users[2];
|
||||
const maker = new MakerActor(makerAddress, stakingWrapper, forceMakerKeyLookup, notStakingContractAddress);
|
||||
// create pool
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare);
|
||||
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||
// add maker to pool
|
||||
const makerApproval = maker.signApprovalForStakingPool(poolId);
|
||||
const revertError = new StakingRevertErrors.InvalidMakerSignatureError(
|
||||
poolId,
|
||||
makerAddress,
|
||||
makerApproval.signature,
|
||||
);
|
||||
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, makerApproval.signature, revertError);
|
||||
});
|
||||
it('Should fail to add a maker who signed with the wrong chain id', async () => {
|
||||
// test parameters
|
||||
const operatorAddress = users[0];
|
||||
const operatorShare = 39;
|
||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
||||
const makerAddress = users[1];
|
||||
const forceMakerKeyLookup = undefined;
|
||||
const forceStakingContractLookup = undefined;
|
||||
const badChainId = 209348;
|
||||
const maker = new MakerActor(
|
||||
makerAddress,
|
||||
stakingWrapper,
|
||||
forceMakerKeyLookup,
|
||||
forceStakingContractLookup,
|
||||
badChainId,
|
||||
);
|
||||
// create pool
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare);
|
||||
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||
// add maker to pool
|
||||
const makerApproval = maker.signApprovalForStakingPool(poolId);
|
||||
const revertError = new StakingRevertErrors.InvalidMakerSignatureError(
|
||||
poolId,
|
||||
makerAddress,
|
||||
makerApproval.signature,
|
||||
);
|
||||
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, makerApproval.signature, revertError);
|
||||
});
|
||||
it('Should fail to add a maker when called by someone other than the pool operator', async () => {
|
||||
// test parameters
|
||||
const operatorAddress = users[0];
|
||||
@@ -223,45 +263,78 @@ blockchainTests('Staking Pool Management', env => {
|
||||
const maker = new MakerActor(makerAddress, stakingWrapper);
|
||||
const notOperatorAddress = users[2];
|
||||
// create pool
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare);
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
||||
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||
// add maker to pool
|
||||
const makerApproval = maker.signApprovalForStakingPool(poolId);
|
||||
await maker.joinStakingPoolAsMakerAsync(poolId);
|
||||
const revertError = new StakingRevertErrors.OnlyCallableByPoolOperatorError(
|
||||
notOperatorAddress,
|
||||
operatorAddress,
|
||||
);
|
||||
const tx = stakingWrapper.addMakerToStakingPoolAsync(
|
||||
poolId,
|
||||
makerAddress,
|
||||
makerApproval.signature,
|
||||
notOperatorAddress,
|
||||
);
|
||||
const tx = stakingWrapper.addMakerToStakingPoolAsync(poolId, makerAddress, notOperatorAddress);
|
||||
await expect(tx).to.revertWith(revertError);
|
||||
});
|
||||
it('Should fail to remove a maker when called by someone other than the pool operator', async () => {
|
||||
it('Should fail to remove a maker when called by someone other than the pool operator or maker', async () => {
|
||||
// test parameters
|
||||
const operatorAddress = users[0];
|
||||
const operatorShare = 39;
|
||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
||||
const makerAddress = users[1];
|
||||
const maker = new MakerActor(makerAddress, stakingWrapper);
|
||||
const notOperatorAddress = users[2];
|
||||
const neitherOperatorNorMakerAddress = users[2];
|
||||
// create pool
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare);
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
||||
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||
// add maker to pool
|
||||
const makerApproval = maker.signApprovalForStakingPool(poolId);
|
||||
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, makerApproval.signature);
|
||||
await maker.joinStakingPoolAsMakerAsync(poolId);
|
||||
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
|
||||
// try to remove the maker address from an address other than the operator
|
||||
const revertError = new StakingRevertErrors.OnlyCallableByPoolOperatorOrMakerError(
|
||||
notOperatorAddress,
|
||||
neitherOperatorNorMakerAddress,
|
||||
operatorAddress,
|
||||
makerAddress,
|
||||
);
|
||||
const tx = stakingWrapper.removeMakerFromStakingPoolAsync(poolId, makerAddress, notOperatorAddress);
|
||||
const tx = stakingWrapper.removeMakerFromStakingPoolAsync(
|
||||
poolId,
|
||||
makerAddress,
|
||||
neitherOperatorNorMakerAddress,
|
||||
);
|
||||
await expect(tx).to.revertWith(revertError);
|
||||
});
|
||||
it('Should fail to add a maker if the pool is full', async () => {
|
||||
// test parameters
|
||||
const operatorAddress = users[0];
|
||||
const operatorShare = 39;
|
||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
||||
|
||||
const makerAddresses = users.slice(1, stakingConstants.MAX_MAKERS_IN_POOL + 2);
|
||||
const makers = makerAddresses.map(makerAddress => new MakerActor(makerAddress, stakingWrapper));
|
||||
|
||||
// create pool
|
||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
|
||||
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||
|
||||
// add makers to pool
|
||||
await Promise.all(makers.map(async maker => maker.joinStakingPoolAsMakerAsync(poolId)));
|
||||
await Promise.all(
|
||||
_.initial(makerAddresses).map(async makerAddress =>
|
||||
poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress),
|
||||
),
|
||||
);
|
||||
|
||||
// check the number of makers in the pool
|
||||
const numMakers = await stakingWrapper.getNumberOfMakersInStakingPoolAsync(poolId);
|
||||
expect(numMakers, 'number of makers in pool').to.be.bignumber.equal(stakingConstants.MAX_MAKERS_IN_POOL);
|
||||
|
||||
const lastMakerAddress = _.last(makerAddresses) as string;
|
||||
// Try to add last maker to the pool
|
||||
const revertError = new StakingRevertErrors.MakerPoolAssignmentError(
|
||||
StakingRevertErrors.MakerPoolAssignmentErrorCodes.PoolIsFull,
|
||||
lastMakerAddress,
|
||||
poolId,
|
||||
);
|
||||
await poolOperator.addMakerToStakingPoolAsync(poolId, lastMakerAddress, revertError);
|
||||
});
|
||||
});
|
||||
});
|
||||
// tslint:enable:no-unnecessary-type-assertion
|
||||
|
@@ -46,16 +46,13 @@ blockchainTests.resets('Testing Rewards', () => {
|
||||
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
// deploy staking contracts
|
||||
stakingWrapper = new StakingWrapper(provider, owner, erc20ProxyContract, zrxTokenContract, accounts);
|
||||
stakingWrapper = new StakingWrapper(provider, owner, erc20ProxyContract, zrxTokenContract);
|
||||
await stakingWrapper.deployAndConfigureContractsAsync();
|
||||
// setup stakers
|
||||
stakers = [new StakerActor(actors[0], stakingWrapper), new StakerActor(actors[1], stakingWrapper)];
|
||||
// setup pools
|
||||
poolOperator = actors[2];
|
||||
poolId = await stakingWrapper.createStakingPoolAsync(poolOperator, 0);
|
||||
// add operator as maker
|
||||
const approvalMessage = stakingWrapper.signApprovalForStakingPool(poolId, poolOperator);
|
||||
await stakingWrapper.addMakerToStakingPoolAsync(poolId, poolOperator, approvalMessage.signature, poolOperator);
|
||||
poolId = await stakingWrapper.createStakingPoolAsync(poolOperator, 0, true);
|
||||
// set exchange address
|
||||
await stakingWrapper.addExchangeAddressAsync(exchangeAddress);
|
||||
// associate operators for tracking in Finalizer
|
||||
|
@@ -41,7 +41,7 @@ blockchainTests('End-To-End Simulations', env => {
|
||||
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
// deploy staking contracts
|
||||
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract, accounts);
|
||||
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract);
|
||||
await stakingWrapper.deployAndConfigureContractsAsync();
|
||||
});
|
||||
blockchainTests.resets('Simulations', () => {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { ERC20ProxyContract, ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
||||
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { blockchainTests, describe, provider, web3Wrapper } from '@0x/contracts-test-utils';
|
||||
import { blockchainTests, describe, web3Wrapper } from '@0x/contracts-test-utils';
|
||||
import { StakingRevertErrors } from '@0x/order-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
@@ -10,7 +10,7 @@ import { StakingWrapper } from './utils/staking_wrapper';
|
||||
import { StakeInfo, StakeStatus } from './utils/types';
|
||||
|
||||
// tslint:disable:no-unnecessary-type-assertion
|
||||
blockchainTests.resets('Stake Statuses', () => {
|
||||
blockchainTests.resets('Stake Statuses', env => {
|
||||
// constants
|
||||
const ZRX_TOKEN_DECIMALS = new BigNumber(18);
|
||||
const ZERO = new BigNumber(0);
|
||||
@@ -34,21 +34,21 @@ blockchainTests.resets('Stake Statuses', () => {
|
||||
owner = accounts[0];
|
||||
actors = accounts.slice(2, 5);
|
||||
// deploy erc20 proxy
|
||||
erc20Wrapper = new ERC20Wrapper(provider, accounts, owner);
|
||||
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
|
||||
erc20ProxyContract = await erc20Wrapper.deployProxyAsync();
|
||||
// deploy zrx token
|
||||
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
// deploy staking contracts
|
||||
stakingWrapper = new StakingWrapper(provider, owner, erc20ProxyContract, zrxTokenContract, accounts);
|
||||
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract);
|
||||
await stakingWrapper.deployAndConfigureContractsAsync();
|
||||
// setup new staker
|
||||
staker = new StakerActor(actors[0], stakingWrapper);
|
||||
// setup pools
|
||||
poolOperator = actors[1];
|
||||
poolIds = await Promise.all([
|
||||
await stakingWrapper.createStakingPoolAsync(poolOperator, 4),
|
||||
await stakingWrapper.createStakingPoolAsync(poolOperator, 5),
|
||||
await stakingWrapper.createStakingPoolAsync(poolOperator, 4, false),
|
||||
await stakingWrapper.createStakingPoolAsync(poolOperator, 5, false),
|
||||
]);
|
||||
});
|
||||
describe('Stake', () => {
|
||||
|
@@ -128,7 +128,7 @@ export class Simulation {
|
||||
const poolOperator = new PoolOperatorActor(poolOperatorAddress, this._stakingWrapper);
|
||||
this._poolOperators.push(poolOperator);
|
||||
// create a pool id for this operator
|
||||
const poolId = await poolOperator.createStakingPoolAsync(p.poolOperatorShares[i]);
|
||||
const poolId = await poolOperator.createStakingPoolAsync(p.poolOperatorShares[i], false);
|
||||
this._poolIds.push(poolId);
|
||||
// each pool operator can also be a staker/delegator
|
||||
const poolOperatorAsDelegator = new DelegatorActor(poolOperatorAddress, this._stakingWrapper);
|
||||
@@ -156,9 +156,9 @@ export class Simulation {
|
||||
// tslint:disable-next-line no-unused-variable
|
||||
for (const j of _.range(numberOfMakersInPool)) {
|
||||
const maker = this._makers[makerIdx];
|
||||
const makerApproval = maker.signApprovalForStakingPool(poolId);
|
||||
await maker.joinStakingPoolAsMakerAsync(poolId);
|
||||
const makerAddress = maker.getOwner();
|
||||
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, makerApproval.signature);
|
||||
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
|
||||
makerIdx += 1;
|
||||
}
|
||||
poolIdx += 1;
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -4,6 +4,7 @@ export const constants = {
|
||||
MAX_UINT_64: new BigNumber(2).pow(256).minus(1),
|
||||
TOKEN_MULTIPLIER: new BigNumber(10).pow(18),
|
||||
INITIAL_POOL_ID: '0x0000000000000000000000000000000100000000000000000000000000000000',
|
||||
SECOND_POOL_ID: '0x0000000000000000000000000000000200000000000000000000000000000000',
|
||||
NIL_POOL_ID: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
||||
NIL_ADDRESS: '0x0000000000000000000000000000000000000000',
|
||||
INITIAL_EPOCH: new BigNumber(0),
|
||||
@@ -11,4 +12,5 @@ export const constants = {
|
||||
EPOCH_DURATION_IN_SECONDS: new BigNumber(1000), // @TODO SET FOR DEPLOYMENT*/
|
||||
TIMELOCK_DURATION_IN_EPOCHS: new BigNumber(3), // @TODO SET FOR DEPLOYMENT
|
||||
CHAIN_ID: 1,
|
||||
MAX_MAKERS_IN_POOL: 10, // @TODO SET FOR DEPLOYMENT,
|
||||
};
|
||||
|
@@ -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;
|
||||
},
|
||||
};
|
@@ -1,7 +1,6 @@
|
||||
import { ERC20ProxyContract } from '@0x/contracts-asset-proxy';
|
||||
import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { constants as testUtilsConstants, LogDecoder, txDefaults } from '@0x/contracts-test-utils';
|
||||
import { SignatureType } from '@0x/types';
|
||||
import { LogDecoder, txDefaults } from '@0x/contracts-test-utils';
|
||||
import { BigNumber, logUtils } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||
@@ -17,9 +16,8 @@ import {
|
||||
ZrxVaultContract,
|
||||
} from '../../src';
|
||||
|
||||
import { ApprovalFactory } from './approval_factory';
|
||||
import { constants } from './constants';
|
||||
import { SignedStakingPoolApproval, StakeBalance } from './types';
|
||||
import { StakeBalance } from './types';
|
||||
|
||||
export class StakingWrapper {
|
||||
private readonly _web3Wrapper: Web3Wrapper;
|
||||
@@ -28,7 +26,6 @@ export class StakingWrapper {
|
||||
private readonly _ownerAddress: string;
|
||||
private readonly _erc20ProxyContract: ERC20ProxyContract;
|
||||
private readonly _zrxTokenContract: DummyERC20TokenContract;
|
||||
private readonly _accounts: string[];
|
||||
private _stakingContractIfExists?: StakingContract;
|
||||
private _stakingProxyContractIfExists?: StakingProxyContract;
|
||||
private _zrxVaultContractIfExists?: ZrxVaultContract;
|
||||
@@ -67,7 +64,6 @@ export class StakingWrapper {
|
||||
ownerAddres: string,
|
||||
erc20ProxyContract: ERC20ProxyContract,
|
||||
zrxTokenContract: DummyERC20TokenContract,
|
||||
accounts: string[],
|
||||
) {
|
||||
this._web3Wrapper = new Web3Wrapper(provider);
|
||||
this._provider = provider;
|
||||
@@ -76,7 +72,6 @@ export class StakingWrapper {
|
||||
this._ownerAddress = ownerAddres;
|
||||
this._erc20ProxyContract = erc20ProxyContract;
|
||||
this._zrxTokenContract = zrxTokenContract;
|
||||
this._accounts = accounts;
|
||||
}
|
||||
public getStakingContract(): StakingContract {
|
||||
this._validateDeployedOrThrow();
|
||||
@@ -283,23 +278,36 @@ export class StakingWrapper {
|
||||
const nextPoolId = await this._callAsync(calldata);
|
||||
return nextPoolId;
|
||||
}
|
||||
public async createStakingPoolAsync(operatorAddress: string, operatorShare: number): Promise<string> {
|
||||
const calldata = this.getStakingContract().createStakingPool.getABIEncodedTransactionData(operatorShare);
|
||||
public async createStakingPoolAsync(
|
||||
operatorAddress: string,
|
||||
operatorShare: number,
|
||||
addOperatorAsMaker: boolean,
|
||||
): Promise<string> {
|
||||
const calldata = this.getStakingContract().createStakingPool.getABIEncodedTransactionData(
|
||||
operatorShare,
|
||||
addOperatorAsMaker,
|
||||
);
|
||||
const txReceipt = await this._executeTransactionAsync(calldata, operatorAddress);
|
||||
const createStakingPoolLog = this._logDecoder.decodeLogOrThrow(txReceipt.logs[0]);
|
||||
const poolId = (createStakingPoolLog as any).args.poolId;
|
||||
return poolId;
|
||||
}
|
||||
public async joinStakingPoolAsMakerAsync(
|
||||
poolId: string,
|
||||
makerAddress: string,
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const calldata = this.getStakingContract().joinStakingPoolAsMaker.getABIEncodedTransactionData(poolId);
|
||||
const txReceipt = await this._executeTransactionAsync(calldata, makerAddress);
|
||||
return txReceipt;
|
||||
}
|
||||
public async addMakerToStakingPoolAsync(
|
||||
poolId: string,
|
||||
makerAddress: string,
|
||||
makerSignature: string,
|
||||
operatorAddress: string,
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const calldata = this.getStakingContract().addMakerToStakingPool.getABIEncodedTransactionData(
|
||||
poolId,
|
||||
makerAddress,
|
||||
makerSignature,
|
||||
);
|
||||
const txReceipt = await this._executeTransactionAsync(calldata, operatorAddress);
|
||||
return txReceipt;
|
||||
@@ -321,55 +329,11 @@ export class StakingWrapper {
|
||||
const poolId = await this._callAsync(calldata);
|
||||
return poolId;
|
||||
}
|
||||
public async getMakersForStakingPoolAsync(poolId: string): Promise<string[]> {
|
||||
const calldata = this.getStakingContract().getMakersForStakingPool.getABIEncodedTransactionData(poolId);
|
||||
const returndata = await this._callAsync(calldata);
|
||||
const makerAddresses = this.getStakingContract().getMakersForStakingPool.getABIDecodedReturnData(returndata);
|
||||
return makerAddresses;
|
||||
}
|
||||
public async isValidMakerSignatureAsync(
|
||||
poolId: string,
|
||||
makerAddress: string,
|
||||
makerSignature: string,
|
||||
): Promise<boolean> {
|
||||
const calldata = this.getStakingContract().isValidMakerSignature.getABIEncodedTransactionData(
|
||||
poolId,
|
||||
makerAddress,
|
||||
makerSignature,
|
||||
);
|
||||
const returndata = await this._callAsync(calldata);
|
||||
const isValid = this.getStakingContract().isValidMakerSignature.getABIDecodedReturnData(returndata);
|
||||
return isValid;
|
||||
}
|
||||
public async getStakingPoolApprovalMessageHashAsync(poolId: string, makerAddress: string): Promise<string> {
|
||||
const calldata = this.getStakingContract().getStakingPoolApprovalMessageHash.getABIEncodedTransactionData(
|
||||
poolId,
|
||||
makerAddress,
|
||||
);
|
||||
const returndata = await this._callAsync(calldata);
|
||||
const messageHash = this.getStakingContract().getStakingPoolApprovalMessageHash.getABIDecodedReturnData(
|
||||
returndata,
|
||||
);
|
||||
return messageHash;
|
||||
}
|
||||
public signApprovalForStakingPool(
|
||||
poolId: string,
|
||||
makerAddress: string,
|
||||
makerPrivateKeyIfExists?: Buffer,
|
||||
verifierAddressIfExists?: string,
|
||||
chainIdIfExists?: number,
|
||||
signatureType: SignatureType = SignatureType.EthSign,
|
||||
): SignedStakingPoolApproval {
|
||||
const makerPrivateKey =
|
||||
makerPrivateKeyIfExists !== undefined
|
||||
? makerPrivateKeyIfExists
|
||||
: testUtilsConstants.TESTRPC_PRIVATE_KEYS[this._accounts.indexOf(makerAddress)];
|
||||
const verifierAddress =
|
||||
verifierAddressIfExists !== undefined ? verifierAddressIfExists : this.getStakingProxyContract().address;
|
||||
const chainId = chainIdIfExists !== undefined ? chainIdIfExists : constants.CHAIN_ID;
|
||||
const approvalFactory = new ApprovalFactory(makerPrivateKey, verifierAddress, chainId);
|
||||
const signedStakingPoolApproval = approvalFactory.newSignedApproval(poolId, makerAddress, signatureType);
|
||||
return signedStakingPoolApproval;
|
||||
public async getNumberOfMakersInStakingPoolAsync(poolId: string): Promise<BigNumber> {
|
||||
const calldata = this.getStakingContract().getNumberOfMakersInStakingPool.getABIEncodedTransactionData(poolId);
|
||||
const returnData = await this._callAsync(calldata);
|
||||
const value = this.getStakingContract().getNumberOfMakersInStakingPool.getABIDecodedReturnData(returnData);
|
||||
return value;
|
||||
}
|
||||
///// EPOCHS /////
|
||||
public async goToNextEpochAsync(): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
|
@@ -1,16 +1,5 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
export interface StakingPoolApproval {
|
||||
makerAddress: string;
|
||||
poolId: string;
|
||||
verifyingContractAddress: string;
|
||||
chainId: number;
|
||||
}
|
||||
|
||||
export interface SignedStakingPoolApproval extends StakingPoolApproval {
|
||||
signature: string;
|
||||
}
|
||||
|
||||
export interface StakerBalances {
|
||||
zrxBalance: BigNumber;
|
||||
stakeBalance: BigNumber;
|
||||
|
@@ -33,7 +33,7 @@ blockchainTests('Staking Vaults', env => {
|
||||
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
// deploy staking contracts
|
||||
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract, accounts);
|
||||
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract);
|
||||
await stakingWrapper.deployAndConfigureContractsAsync();
|
||||
});
|
||||
blockchainTests.resets('Reward Vault', () => {
|
||||
@@ -41,7 +41,7 @@ blockchainTests('Staking Vaults', env => {
|
||||
// 1 setup test parameters
|
||||
const poolOperator = users[0];
|
||||
const operatorShare = 39;
|
||||
const poolId = await stakingWrapper.createStakingPoolAsync(poolOperator, operatorShare);
|
||||
const poolId = await stakingWrapper.createStakingPoolAsync(poolOperator, operatorShare, true);
|
||||
const stakingContractAddress = stakingWrapper.getStakingContract().address;
|
||||
const notStakingContractAddress = poolOperator;
|
||||
// create pool in vault
|
||||
|
@@ -11,14 +11,11 @@
|
||||
"generated-artifacts/IStakingProxy.json",
|
||||
"generated-artifacts/IStructs.json",
|
||||
"generated-artifacts/IVaultCore.json",
|
||||
"generated-artifacts/IWallet.json",
|
||||
"generated-artifacts/IZrxVault.json",
|
||||
"generated-artifacts/LibEIP712Hash.json",
|
||||
"generated-artifacts/LibFixedMath.json",
|
||||
"generated-artifacts/LibFixedMathRichErrors.json",
|
||||
"generated-artifacts/LibProxy.json",
|
||||
"generated-artifacts/LibSafeDowncast.json",
|
||||
"generated-artifacts/LibSignatureValidator.json",
|
||||
"generated-artifacts/LibStakingRichErrors.json",
|
||||
"generated-artifacts/MixinConstants.json",
|
||||
"generated-artifacts/MixinDeploymentConstants.json",
|
||||
|
Reference in New Issue
Block a user