From c57d17dc58c4757ed6fb9312b8a81a6e8f1dbcf9 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 6 Jun 2019 23:01:17 -0700 Subject: [PATCH] Exchange tracking --- contracts/staking/contracts/src/Staking.sol | 33 +++++++++ .../contracts/src/core/MixinExchange.sol | 68 +++++++++++++++++++ .../contracts/src/immutable/MixinStorage.sol | 3 + .../src/interfaces/IStakingEvents.sol | 8 +++ contracts/staking/test/core_test.ts | 27 +++++++- .../staking/test/utils/staking_wrapper.ts | 21 +++++- packages/types/src/index.ts | 3 + 7 files changed, 159 insertions(+), 4 deletions(-) create mode 100644 contracts/staking/contracts/src/core/MixinExchange.sol diff --git a/contracts/staking/contracts/src/Staking.sol b/contracts/staking/contracts/src/Staking.sol index d19d39c42a..c33b3a709f 100644 --- a/contracts/staking/contracts/src/Staking.sol +++ b/contracts/staking/contracts/src/Staking.sol @@ -25,6 +25,7 @@ import "./core/MixinPools.sol"; import "./core/MixinEpoch.sol"; import "./core/MixinRewards.sol"; import "./core/MixinFees.sol"; +import "./core/MixinExchange.sol"; contract Staking is @@ -32,6 +33,7 @@ contract Staking is //IStakingEvents, MixinConstants, MixinStorage, + MixinExchange, MixinEpoch, MixinRewards, MixinStake, @@ -350,9 +352,17 @@ contract Staking is } ///// FEES ///// + modifier onlyExchange() { + require( + _isValidExchangeAddress(msg.sender), + "ONLY_CALLABLE_BY_EXCHANGE" + ); + _; + } function payProtocolFee(address makerAddress) external payable + onlyExchange { _payProtocolFee(makerAddress, msg.value); } @@ -373,6 +383,29 @@ contract Staking is return _getTotalProtocolFeesThisEpoch(); } + ///// EXCHANGES ///// + // @TODO - Only by 0x multi-sig + + function isValidExchangeAddress(address addr) + external + view + returns (bool) + { + return _isValidExchangeAddress(addr); + } + + function addExchangeAddress(address addr) + external + { + _addExchangeAddress(addr); + } + + function removeExchangeAddress(address addr) + external + { + _removeExchangeAddress(addr); + } + ///// SETTERS ///// function setZrxVault(address _zrxVault) diff --git a/contracts/staking/contracts/src/core/MixinExchange.sol b/contracts/staking/contracts/src/core/MixinExchange.sol new file mode 100644 index 0000000000..699cd837cf --- /dev/null +++ b/contracts/staking/contracts/src/core/MixinExchange.sol @@ -0,0 +1,68 @@ +/* + + Copyright 2018 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.5; + +import "../interfaces/IVault.sol"; +import "../libs/LibZrxToken.sol"; +import "@0x/contracts-utils/contracts/src/SafeMath.sol"; +import "../immutable/MixinStorage.sol"; +import "../immutable/MixinConstants.sol"; +import "../interfaces/IStakingEvents.sol"; +import "./MixinStakeBalances.sol"; +import "./MixinEpoch.sol"; +import "./MixinPools.sol"; + + +contract MixinExchange is + SafeMath, + IStakingEvents, + MixinConstants, + MixinStorage +{ + + function _isValidExchangeAddress(address addr) + internal + view + returns (bool) + { + return validExchanges[addr]; + } + + function _addExchangeAddress(address addr) + internal + { + require( + !validExchanges[addr], + "EXCHANGE_ADDRESS_ALREADY_REGISTERED" + ); + validExchanges[addr] = true; + emit ExchangeAdded(addr); + } + + function _removeExchangeAddress(address addr) + internal + { + require( + validExchanges[addr], + "EXCHANGE_ADDRESS_NOT_REGISTERED" + ); + validExchanges[addr] = false; + emit ExchangeRemoved(addr); + } +} \ No newline at end of file diff --git a/contracts/staking/contracts/src/immutable/MixinStorage.sol b/contracts/staking/contracts/src/immutable/MixinStorage.sol index 1244d83390..4df088479a 100644 --- a/contracts/staking/contracts/src/immutable/MixinStorage.sol +++ b/contracts/staking/contracts/src/immutable/MixinStorage.sol @@ -94,6 +94,9 @@ contract MixinStorage is // shadow balances by mapping (address => mapping (bytes32 => uint256)) shadowRewardsInPoolByOwner; + // registrered 0x exchanges + mapping (address => bool) validExchanges; + /* // mapping from Owner to Pool Id to Amount Delegated diff --git a/contracts/staking/contracts/src/interfaces/IStakingEvents.sol b/contracts/staking/contracts/src/interfaces/IStakingEvents.sol index df19f145fd..db4e767205 100644 --- a/contracts/staking/contracts/src/interfaces/IStakingEvents.sol +++ b/contracts/staking/contracts/src/interfaces/IStakingEvents.sol @@ -17,4 +17,12 @@ interface IStakingEvents { address operatorAddress, uint8 operatorShare ); + + event ExchangeAdded( + address exchangeAddress + ); + + event ExchangeRemoved( + address exchangeAddress + ); } \ No newline at end of file diff --git a/contracts/staking/test/core_test.ts b/contracts/staking/test/core_test.ts index 9bdaeff2ec..15de3b9170 100644 --- a/contracts/staking/test/core_test.ts +++ b/contracts/staking/test/core_test.ts @@ -525,7 +525,32 @@ describe('Staking Core', () => { } }); - it.only('Protocol Fees', async () => { + it.only('Exchange Tracking', async () => { + // 1 try querying an invalid addresses + const invalidAddress = "0x0000000000000000000000000000000000000001"; + const isInvalidAddressValid = await stakingWrapper.isValidExchangeAddressAsync(invalidAddress); + expect(isInvalidAddressValid).to.be.false(); + // 2 add valid address + await stakingWrapper.addExchangeAddressAsync(exchange); + const isValidAddressValid = await stakingWrapper.isValidExchangeAddressAsync(exchange); + expect(isValidAddressValid).to.be.true(); + // 3 try adding valid address again + await expectTransactionFailedAsync( + stakingWrapper.addExchangeAddressAsync(exchange), + RevertReason.ExchangeAddressAlreadyRegistered + ); + // 4 remove valid address + await stakingWrapper.removeExchangeAddressAsync(exchange); + const isValidAddressStillValid = await stakingWrapper.isValidExchangeAddressAsync(exchange); + expect(isValidAddressStillValid).to.be.false(); + // 5 try removing valid address again + await expectTransactionFailedAsync( + stakingWrapper.removeExchangeAddressAsync(exchange), + RevertReason.ExchangeAddressNotRegistered + ); + }); + + it.skip('Protocol Fees', async () => { ///// 1 SETUP POOLS ///// const poolOperators = stakers.slice(0, 3); const operatorShares = [39, 59, 43]; diff --git a/contracts/staking/test/utils/staking_wrapper.ts b/contracts/staking/test/utils/staking_wrapper.ts index deb6c7b31e..600c0cf37c 100644 --- a/contracts/staking/test/utils/staking_wrapper.ts +++ b/contracts/staking/test/utils/staking_wrapper.ts @@ -317,26 +317,41 @@ export class StakingWrapper { const value = this.getStakingContract().getCurrentTimelockPeriod.getABIDecodedReturnData(returnData); return value; } - ///// FEES ///// + ///// PROTOCOL FEES ///// public async payProtocolFeeAsync(makerAddress: string, amount: BigNumber): Promise { const calldata = this.getStakingContract().payProtocolFee.getABIEncodedTransactionData(makerAddress); const txReceipt = await this._executeTransactionAsync(calldata, this._ownerAddres, amount); return txReceipt; } - public async getProtocolFeesThisEpochByPoolAsync(poolId: string): Promise { const calldata = this.getStakingContract().getProtocolFeesThisEpochByPool.getABIEncodedTransactionData(poolId); const returnData = await this._callAsync(calldata); const value = this.getStakingContract().getProtocolFeesThisEpochByPool.getABIDecodedReturnData(returnData); return value; } - public async getTotalProtocolFeesThisEpochAsync(): Promise { const calldata = this.getStakingContract().getTotalProtocolFeesThisEpoch.getABIEncodedTransactionData(); const returnData = await this._callAsync(calldata); const value = this.getStakingContract().getTotalProtocolFeesThisEpoch.getABIDecodedReturnData(returnData); return value; } + ///// EXCHANGES ///// + public async isValidExchangeAddressAsync(exchangeAddress: string): Promise { + const calldata = this.getStakingContract().isValidExchangeAddress.getABIEncodedTransactionData(exchangeAddress); + const returnData = await this._callAsync(calldata); + const value = this.getStakingContract().isValidExchangeAddress.getABIDecodedReturnData(returnData); + return value; + } + public async addExchangeAddressAsync(exchangeAddress: string): Promise { + const calldata = this.getStakingContract().addExchangeAddress.getABIEncodedTransactionData(exchangeAddress); + const txReceipt = await this._executeTransactionAsync(calldata, this._ownerAddres); + return txReceipt; + } + public async removeExchangeAddressAsync(exchangeAddress: string): Promise { + const calldata = this.getStakingContract().removeExchangeAddress.getABIEncodedTransactionData(exchangeAddress); + const txReceipt = await this._executeTransactionAsync(calldata, this._ownerAddres); + return txReceipt; + } /* ///// REWARDS ///// diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 4dd7ec295b..b62153db25 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -343,6 +343,9 @@ export enum RevertReason { // Staking OnlyCallableByPoolOperator = 'ONLY_CALLABLE_BY_POOL_OPERATOR', MakerAddressAlreadyRegistered = 'MAKER_ADDRESS_ALREADY_REGISTERED', + OnlyCallableByExchange = 'ONLY_CALLABLE_BY_EXCHANGE', + ExchangeAddressAlreadyRegistered = 'EXCHANGE_ADDRESS_ALREADY_REGISTERED', + ExchangeAddressNotRegistered = 'EXCHANGE_ADDRESS_NOT_REGISTERED', } export enum StatusCodes {