From 415af90ae7c54cc660db972c89ea797a69d9c1ab Mon Sep 17 00:00:00 2001 From: Alex Towle Date: Wed, 21 Aug 2019 14:46:35 -0700 Subject: [PATCH] `@0x:contracts-exchange` Added the MixinStakingManager contract --- .../contracts/src/MixinExchangeCore.sol | 4 +- .../contracts/src/MixinStakingManager.sol | 67 ++++++++ .../src/interfaces/IStakingManager.sol | 47 ++++++ contracts/exchange/package.json | 2 +- contracts/exchange/src/artifacts.ts | 6 +- contracts/exchange/src/wrappers.ts | 2 + contracts/exchange/test/staking_manager.ts | 143 ++++++++++++++++++ contracts/exchange/test/utils/constants.ts | 3 + contracts/exchange/test/utils/types.ts | 3 + contracts/exchange/tsconfig.json | 2 + 10 files changed, 276 insertions(+), 3 deletions(-) create mode 100644 contracts/exchange/contracts/src/MixinStakingManager.sol create mode 100644 contracts/exchange/contracts/src/interfaces/IStakingManager.sol create mode 100644 contracts/exchange/test/staking_manager.ts diff --git a/contracts/exchange/contracts/src/MixinExchangeCore.sol b/contracts/exchange/contracts/src/MixinExchangeCore.sol index 995fcc3e49..7dea2cd04a 100644 --- a/contracts/exchange/contracts/src/MixinExchangeCore.sol +++ b/contracts/exchange/contracts/src/MixinExchangeCore.sol @@ -28,13 +28,15 @@ import "@0x/contracts-exchange-libs/contracts/src/LibExchangeRichErrors.sol"; import "./interfaces/IExchangeCore.sol"; import "./MixinAssetProxyDispatcher.sol"; import "./MixinSignatureValidator.sol"; +import "./MixinStakingManager.sol"; contract MixinExchangeCore is LibEIP712ExchangeDomain, IExchangeCore, MixinAssetProxyDispatcher, - MixinSignatureValidator + MixinSignatureValidator, + MixinStakingManager { using LibOrder for LibOrder.Order; using LibSafeMath for uint256; diff --git a/contracts/exchange/contracts/src/MixinStakingManager.sol b/contracts/exchange/contracts/src/MixinStakingManager.sol new file mode 100644 index 0000000000..dd6f1d4136 --- /dev/null +++ b/contracts/exchange/contracts/src/MixinStakingManager.sol @@ -0,0 +1,67 @@ +/* + + 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/Ownable.sol"; +import "./interfaces/IStakingManager.sol"; + + +contract MixinStakingManager is + IStakingManager, + Ownable +{ + // The protocol fee multiplier -- the owner can update this field. + uint256 public protocolFeeMultiplier; + + // The address of the registered staking contract -- the owner can update this field. + address public staking; + + // The address of the wrapped ether contract -- the owner can update this field. + address public weth; + + /// @dev Allows the owner to update the protocol fee multiplier. + /// @param updatedProtocolFeeMultiplier The updated protocol fee multiplier. + function updateProtocolFeeMultiplier(uint256 updatedProtocolFeeMultiplier) + external + onlyOwner() + { + emit UpdatedProtocolFeeMultiplier(protocolFeeMultiplier, updatedProtocolFeeMultiplier); + protocolFeeMultiplier = updatedProtocolFeeMultiplier; + } + + /// @dev Allows the owner to update the staking address. + /// @param updatedStaking The updated staking contract address. + function updateStakingAddress(address updatedStaking) + external + onlyOwner() + { + emit UpdatedStakingAddress(staking, updatedStaking); + staking = updatedStaking; + } + + /// @dev Allows the owner to update the WETH address. + /// @param updatedWeth The updated WETH contract address. + function updateWethAddress(address updatedWeth) + external + onlyOwner() + { + emit UpdatedWethAddress(weth, updatedWeth); + weth = updatedWeth; + } +} diff --git a/contracts/exchange/contracts/src/interfaces/IStakingManager.sol b/contracts/exchange/contracts/src/interfaces/IStakingManager.sol new file mode 100644 index 0000000000..8442ad3b32 --- /dev/null +++ b/contracts/exchange/contracts/src/interfaces/IStakingManager.sol @@ -0,0 +1,47 @@ +/* + + 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; + + +contract IStakingManager { + + // Logs updates to the protocol fee multiplier. + event UpdatedProtocolFeeMultiplier(uint256 oldProtocolFeeMultiplier, uint256 updatedProtocolFeeMultiplier); + + // Logs updates to the staking address. + event UpdatedStakingAddress(address oldStaking, address updatedStaking); + + // Logs updates to the weth address. + event UpdatedWethAddress(address oldWeth, address updatedWeth); + + /// @dev Allows the owner to update the protocol fee multiplier. + /// @param updatedProtocolFeeMultiplier The updated protocol fee multiplier. + function updateProtocolFeeMultiplier(uint256 updatedProtocolFeeMultiplier) + external; + + /// @dev Allows the owner to update the staking address. + /// @param updatedStaking The updated staking contract address. + function updateStakingAddress(address updatedStaking) + external; + + /// @dev Allows the owner to update the WETH address. + /// @param updatedWeth The updated WETH contract address. + function updateWethAddress(address updatedWeth) + external; +} diff --git a/contracts/exchange/package.json b/contracts/exchange/package.json index de09b6a28b..ec74973882 100644 --- a/contracts/exchange/package.json +++ b/contracts/exchange/package.json @@ -35,7 +35,7 @@ "compile:truffle": "truffle compile" }, "config": { - "abis": "./generated-artifacts/@(Exchange|ExchangeWrapper|IAssetProxy|IAssetProxyDispatcher|IEIP1271Wallet|IExchange|IExchangeCore|IMatchOrders|ISignatureValidator|ITransactions|ITransferSimulator|IWallet|IWrapperFunctions|IsolatedExchange|LibExchangeRichErrorDecoder|MixinAssetProxyDispatcher|MixinExchangeCore|MixinMatchOrders|MixinSignatureValidator|MixinTransactions|MixinTransferSimulator|MixinWrapperFunctions|ReentrancyTester|TestAssetProxyDispatcher|TestExchangeInternals|TestLibExchangeRichErrorDecoder|TestSignatureValidator|TestTransactions|TestValidatorWallet|TestWrapperFunctions|Whitelist).json", + "abis": "./generated-artifacts/@(Exchange|ExchangeWrapper|IAssetProxy|IAssetProxyDispatcher|IEIP1271Wallet|IExchange|IExchangeCore|IMatchOrders|ISignatureValidator|IStakingManager|ITransactions|ITransferSimulator|IWallet|IWrapperFunctions|IsolatedExchange|LibExchangeRichErrorDecoder|MixinAssetProxyDispatcher|MixinExchangeCore|MixinMatchOrders|MixinSignatureValidator|MixinStakingManager|MixinTransactions|MixinTransferSimulator|MixinWrapperFunctions|ReentrancyTester|TestAssetProxyDispatcher|TestExchangeInternals|TestLibExchangeRichErrorDecoder|TestSignatureValidator|TestTransactions|TestValidatorWallet|TestWrapperFunctions|Whitelist).json", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." }, "repository": { diff --git a/contracts/exchange/src/artifacts.ts b/contracts/exchange/src/artifacts.ts index 72d4f1c186..99841d2454 100644 --- a/contracts/exchange/src/artifacts.ts +++ b/contracts/exchange/src/artifacts.ts @@ -14,16 +14,18 @@ import * as IExchange from '../generated-artifacts/IExchange.json'; import * as IExchangeCore from '../generated-artifacts/IExchangeCore.json'; import * as IMatchOrders from '../generated-artifacts/IMatchOrders.json'; import * as ISignatureValidator from '../generated-artifacts/ISignatureValidator.json'; -import * as IsolatedExchange from '../generated-artifacts/IsolatedExchange.json'; +import * as IStakingManager from '../generated-artifacts/IStakingManager.json'; import * as ITransactions from '../generated-artifacts/ITransactions.json'; import * as ITransferSimulator from '../generated-artifacts/ITransferSimulator.json'; import * as IWallet from '../generated-artifacts/IWallet.json'; import * as IWrapperFunctions from '../generated-artifacts/IWrapperFunctions.json'; +import * as IsolatedExchange from '../generated-artifacts/IsolatedExchange.json'; import * as LibExchangeRichErrorDecoder from '../generated-artifacts/LibExchangeRichErrorDecoder.json'; import * as MixinAssetProxyDispatcher from '../generated-artifacts/MixinAssetProxyDispatcher.json'; import * as MixinExchangeCore from '../generated-artifacts/MixinExchangeCore.json'; import * as MixinMatchOrders from '../generated-artifacts/MixinMatchOrders.json'; import * as MixinSignatureValidator from '../generated-artifacts/MixinSignatureValidator.json'; +import * as MixinStakingManager from '../generated-artifacts/MixinStakingManager.json'; import * as MixinTransactions from '../generated-artifacts/MixinTransactions.json'; import * as MixinTransferSimulator from '../generated-artifacts/MixinTransferSimulator.json'; import * as MixinWrapperFunctions from '../generated-artifacts/MixinWrapperFunctions.json'; @@ -44,6 +46,7 @@ export const artifacts = { MixinExchangeCore: MixinExchangeCore as ContractArtifact, MixinMatchOrders: MixinMatchOrders as ContractArtifact, MixinSignatureValidator: MixinSignatureValidator as ContractArtifact, + MixinStakingManager: MixinStakingManager as ContractArtifact, MixinTransactions: MixinTransactions as ContractArtifact, MixinTransferSimulator: MixinTransferSimulator as ContractArtifact, MixinWrapperFunctions: MixinWrapperFunctions as ContractArtifact, @@ -54,6 +57,7 @@ export const artifacts = { IExchangeCore: IExchangeCore as ContractArtifact, IMatchOrders: IMatchOrders as ContractArtifact, ISignatureValidator: ISignatureValidator as ContractArtifact, + IStakingManager: IStakingManager as ContractArtifact, ITransactions: ITransactions as ContractArtifact, ITransferSimulator: ITransferSimulator as ContractArtifact, IWallet: IWallet as ContractArtifact, diff --git a/contracts/exchange/src/wrappers.ts b/contracts/exchange/src/wrappers.ts index f64c78b5cf..2c598a39f8 100644 --- a/contracts/exchange/src/wrappers.ts +++ b/contracts/exchange/src/wrappers.ts @@ -12,6 +12,7 @@ export * from '../generated-wrappers/i_exchange'; export * from '../generated-wrappers/i_exchange_core'; export * from '../generated-wrappers/i_match_orders'; export * from '../generated-wrappers/i_signature_validator'; +export * from '../generated-wrappers/i_staking_manager'; export * from '../generated-wrappers/i_transactions'; export * from '../generated-wrappers/i_transfer_simulator'; export * from '../generated-wrappers/i_wallet'; @@ -22,6 +23,7 @@ export * from '../generated-wrappers/mixin_asset_proxy_dispatcher'; export * from '../generated-wrappers/mixin_exchange_core'; export * from '../generated-wrappers/mixin_match_orders'; export * from '../generated-wrappers/mixin_signature_validator'; +export * from '../generated-wrappers/mixin_staking_manager'; export * from '../generated-wrappers/mixin_transactions'; export * from '../generated-wrappers/mixin_transfer_simulator'; export * from '../generated-wrappers/mixin_wrapper_functions'; diff --git a/contracts/exchange/test/staking_manager.ts b/contracts/exchange/test/staking_manager.ts new file mode 100644 index 0000000000..63eeae7809 --- /dev/null +++ b/contracts/exchange/test/staking_manager.ts @@ -0,0 +1,143 @@ +import { blockchainTests, constants, expect, LogDecoder } from '@0x/contracts-test-utils'; +import { BigNumber, OwnableRevertErrors } from '@0x/utils'; +import { LogWithDecodedArgs } from 'ethereum-types'; + +import { + artifacts, + ExchangeContract, + ExchangeUpdatedProtocolFeeMultiplierEventArgs, + ExchangeUpdatedStakingAddressEventArgs, + ExchangeUpdatedWethAddressEventArgs, +} from '../src'; + +blockchainTests.resets('MixinStakingManager', env => { + let accounts: string[]; + let exchange: ExchangeContract; + let logDecoder: LogDecoder; + let nonOwner: string; + let owner: string; + let staking: string; + let weth: string; + + // The protocolFeeMultiplier that will be used to test the update functions. + const protocolFeeMultiplier = new BigNumber(15000); + + before(async () => { + accounts = await env.web3Wrapper.getAvailableAddressesAsync(); + owner = accounts[0]; + nonOwner = accounts[1]; + staking = accounts[2]; + weth = accounts[3]; + + // Update the from address of the txDefaults. This is the address that will become the owner. + env.txDefaults.from = owner; + + // Deploy the exchange contract. + exchange = await ExchangeContract.deployFrom0xArtifactAsync( + artifacts.Exchange, + env.provider, + env.txDefaults, + {}, + new BigNumber(1337), + ); + + // Configure the log decoder + logDecoder = new LogDecoder(env.web3Wrapper, artifacts); + }); + + blockchainTests.resets('updateProtocolFeeMultiplier', () => { + it('should revert if msg.sender != owner', async () => { + const expectedError = new OwnableRevertErrors.OnlyOwnerError(nonOwner, owner); + + // Ensure that the transaction reverts with the expected rich error. + const tx = exchange.updateStakingAddress.sendTransactionAsync(staking, { + from: nonOwner, + }); + return expect(tx).to.revertWith(expectedError); + }); + + it('should succeed and emit an UpdatedProtocolFeeMultiplier event if msg.sender == owner', async () => { + // Call the `updateProtocolFeeMultiplier()` function and get the receipt. + const receipt = await logDecoder.getTxWithDecodedLogsAsync( + await exchange.updateProtocolFeeMultiplier.sendTransactionAsync(protocolFeeMultiplier, { + from: owner, + }), + ); + + // Verify that the staking address was actually updated to the correct address. + const updated = await exchange.protocolFeeMultiplier.callAsync(); + expect(updated).bignumber.to.be.eq(protocolFeeMultiplier); + + // Ensure that the correct `UpdatedStakingAddress` event was logged. + // tslint:disable:no-unnecessary-type-assertion + const updatedEvent = receipt.logs[0] as LogWithDecodedArgs; + expect(updatedEvent.event).to.be.eq('UpdatedProtocolFeeMultiplier'); + expect(updatedEvent.args.oldProtocolFeeMultiplier).bignumber.to.be.eq(constants.ZERO_AMOUNT); + expect(updatedEvent.args.updatedProtocolFeeMultiplier).bignumber.to.be.eq(protocolFeeMultiplier); + }); + }); + + blockchainTests.resets('updateStakingAddress', () => { + it('should revert if msg.sender != owner', async () => { + const expectedError = new OwnableRevertErrors.OnlyOwnerError(nonOwner, owner); + + // Ensure that the transaction reverts with the expected rich error. + const tx = exchange.updateStakingAddress.sendTransactionAsync(staking, { + from: nonOwner, + }); + return expect(tx).to.revertWith(expectedError); + }); + + it('should succeed and emit an UpdatedStakingAddress event if msg.sender == owner', async () => { + // Call the `updateStakingAddress()` function and get the receipt. + const receipt = await logDecoder.getTxWithDecodedLogsAsync( + await exchange.updateStakingAddress.sendTransactionAsync(staking, { + from: owner, + }), + ); + + // Verify that the staking address was actually updated to the correct address. + const updated = await exchange.staking.callAsync(); + expect(updated).to.be.eq(staking); + + // Ensure that the correct `UpdatedStakingAddress` event was logged. + // tslint:disable:no-unnecessary-type-assertion + const updatedEvent = receipt.logs[0] as LogWithDecodedArgs; + expect(updatedEvent.event).to.be.eq('UpdatedStakingAddress'); + expect(updatedEvent.args.oldStaking).to.be.eq(constants.NULL_ADDRESS); + expect(updatedEvent.args.updatedStaking).to.be.eq(staking); + }); + }); + + blockchainTests.resets('updateWethAddress', () => { + it('should revert if msg.sender != owner', async () => { + const expectedError = new OwnableRevertErrors.OnlyOwnerError(nonOwner, owner); + + // Ensure that the transaction reverts with the expected rich error. + const tx = exchange.updateWethAddress.sendTransactionAsync(weth, { + from: nonOwner, + }); + return expect(tx).to.revertWith(expectedError); + }); + + it('should succeed and emit an UpdatedStakingAddress event if msg.sender == owner', async () => { + // Call the `updateWethAddress()` function and get the receipt. + const receipt = await logDecoder.getTxWithDecodedLogsAsync( + await exchange.updateWethAddress.sendTransactionAsync(weth, { + from: owner, + }), + ); + + // Verify that the staking address was actually updated to the correct address. + const updated = await exchange.weth.callAsync(); + expect(updated).to.be.eq(weth); + + // Ensure that the correct `UpdatedStakingAddress` event was logged. + // tslint:disable:no-unnecessary-type-assertion + const updatedEvent = receipt.logs[0] as LogWithDecodedArgs; + expect(updatedEvent.event).to.be.eq('UpdatedWethAddress'); + expect(updatedEvent.args.oldWeth).to.be.eq(constants.NULL_ADDRESS); + expect(updatedEvent.args.updatedWeth).to.be.eq(weth); + }); + }); +}); diff --git a/contracts/exchange/test/utils/constants.ts b/contracts/exchange/test/utils/constants.ts index 5462aff56a..8f9f3123ca 100644 --- a/contracts/exchange/test/utils/constants.ts +++ b/contracts/exchange/test/utils/constants.ts @@ -9,6 +9,9 @@ export const constants = { ExchangeFunctionName.RegisterAssetProxy, ExchangeFunctionName.SimulateDispatchTransferFromCalls, ExchangeFunctionName.TransferOwnership, + ExchangeFunctionName.UpdateProtocolFeeMultiplier, + ExchangeFunctionName.UpdateStakingAddress, + ExchangeFunctionName.UpdateWethAddress, ], SINGLE_FILL_FN_NAMES: [ ExchangeFunctionName.FillOrder, diff --git a/contracts/exchange/test/utils/types.ts b/contracts/exchange/test/utils/types.ts index 7d4d87bc1f..55c3614d3c 100644 --- a/contracts/exchange/test/utils/types.ts +++ b/contracts/exchange/test/utils/types.ts @@ -32,4 +32,7 @@ export enum ExchangeFunctionName { SetSignatureValidatorApproval = 'setSignatureValidatorApproval', SimulateDispatchTransferFromCalls = 'simulateDispatchTransferFromCalls', TransferOwnership = 'transferOwnership', + UpdateProtocolFeeMultiplier = 'updateProtocolFeeMultiplier', + UpdateStakingAddress = 'updateStakingAddress', + UpdateWethAddress = 'updateWethAddress', } diff --git a/contracts/exchange/tsconfig.json b/contracts/exchange/tsconfig.json index 63279898fc..8988c9ce5d 100644 --- a/contracts/exchange/tsconfig.json +++ b/contracts/exchange/tsconfig.json @@ -12,6 +12,7 @@ "generated-artifacts/IExchangeCore.json", "generated-artifacts/IMatchOrders.json", "generated-artifacts/ISignatureValidator.json", + "generated-artifacts/IStakingManager.json", "generated-artifacts/ITransactions.json", "generated-artifacts/ITransferSimulator.json", "generated-artifacts/IWallet.json", @@ -22,6 +23,7 @@ "generated-artifacts/MixinExchangeCore.json", "generated-artifacts/MixinMatchOrders.json", "generated-artifacts/MixinSignatureValidator.json", + "generated-artifacts/MixinStakingManager.json", "generated-artifacts/MixinTransactions.json", "generated-artifacts/MixinTransferSimulator.json", "generated-artifacts/MixinWrapperFunctions.json",