diff --git a/contracts/staking/contracts/src/StakingProxy.sol b/contracts/staking/contracts/src/StakingProxy.sol index b47bc5f77b..4f793d352e 100644 --- a/contracts/staking/contracts/src/StakingProxy.sol +++ b/contracts/staking/contracts/src/StakingProxy.sol @@ -17,6 +17,7 @@ */ pragma solidity ^0.5.9; +pragma experimental ABIEncoderV2; import "@0x/contracts-utils/contracts/src/Ownable.sol"; import "./libs/LibProxy.sol"; @@ -72,6 +73,30 @@ contract StakingProxy is _attachStakingContract(_stakingContract); } + /// @dev Detach the current staking contract. + /// Note that this is callable only by this contract's owner. + function detachStakingContract() + external + onlyOwner + { + stakingContract = NIL_ADDRESS; + emit StakingContractDetachedFromProxy(); + } + + /// @dev Set read-only mode (state cannot be changed). + function setReadOnlyMode(bool readOnlyMode) + external + onlyOwner + { + if (readOnlyMode) { + stakingContract = readOnlyProxy; + } else { + stakingContract = readOnlyProxyCallee; + } + + emit ReadOnlyModeSet(readOnlyMode); + } + /// @dev Batch executes a series of calls to the exchange contract. /// @param data An array of data that encodes a sequence of functions to /// call in the staking contracts. @@ -99,31 +124,7 @@ contract StakingProxy is batchReturnData[i] = returnData; } - return batchReturnData - } - - /// @dev Detach the current staking contract. - /// Note that this is callable only by this contract's owner. - function detachStakingContract() - external - onlyOwner - { - stakingContract = NIL_ADDRESS; - emit StakingContractDetachedFromProxy(); - } - - /// @dev Set read-only mode (state cannot be changed). - function setReadOnlyMode(bool readOnlyMode) - external - onlyOwner - { - if (readOnlyMode) { - stakingContract = readOnlyProxy; - } else { - stakingContract = readOnlyProxyCallee; - } - - emit ReadOnlyModeSet(readOnlyMode); + return batchReturnData; } /// @dev Attach a staking contract; future calls will be delegated to the staking contract. diff --git a/contracts/staking/contracts/src/libs/LibProxy.sol b/contracts/staking/contracts/src/libs/LibProxy.sol index 5367956690..36dc2c3ca9 100644 --- a/contracts/staking/contracts/src/libs/LibProxy.sol +++ b/contracts/staking/contracts/src/libs/LibProxy.sol @@ -122,7 +122,7 @@ library LibProxy { { if (destination == address(0)) { LibRichErrors.rrevert( - LibStakingRichErrors.ProxyDestinationCannotBeNil() + LibStakingRichErrors.ProxyDestinationCannotBeNilError() ); } diff --git a/contracts/staking/contracts/test/TestLibProxy.sol b/contracts/staking/contracts/test/TestLibProxy.sol index 7943f660c8..a4f5397aa5 100644 --- a/contracts/staking/contracts/test/TestLibProxy.sol +++ b/contracts/staking/contracts/test/TestLibProxy.sol @@ -71,4 +71,14 @@ contract TestLibProxy { proxyCallArgs = args; (success, returnData) = address(this).call(data); } + + /// @dev Calls the destination with the provided calldata. + /// @param destination The contract that should be called by the proxy call. + /// @param data The bytes that should be used to call into the destination contract. + function publicSimpleProxyCallWithData(address destination, bytes memory data) + public + returns (bool success, bytes memory returnData) + { + return destination.simpleProxyCallWithData(data); + } } diff --git a/contracts/staking/test/unit_tests/lib_proxy_unit_test.ts b/contracts/staking/test/unit_tests/lib_proxy_unit_test.ts index d3595fd94e..4105c056d7 100644 --- a/contracts/staking/test/unit_tests/lib_proxy_unit_test.ts +++ b/contracts/staking/test/unit_tests/lib_proxy_unit_test.ts @@ -11,7 +11,7 @@ import { StakingRevertErrors } from '@0x/order-utils'; import { artifacts, TestLibProxyContract, TestLibProxyReceiverContract } from '../../src'; -blockchainTests.resets.only('LibProxy', env => { +blockchainTests.resets('LibProxy', env => { let proxy: TestLibProxyContract; let receiver: TestLibProxyReceiverContract; @@ -37,6 +37,14 @@ blockchainTests.resets.only('LibProxy', env => { NeverRevert, } + interface PublicProxyCallArgs { + destination: string; + revertRule: RevertRule; + customEgressSelector: string; + ignoreIngressSelector: boolean; + calldata: string; + } + // Choose a random 4 byte string of calldata to send and prepend with `0x00` to ensure // that it does not call `externalProxyCall` by accident. This calldata will make the fallback // in `TestLibProxyReceiver` fail because it is 4 bytes long. @@ -51,14 +59,6 @@ blockchainTests.resets.only('LibProxy', env => { return hexConcat('0x00', hexRandom(35)); } - interface PublicProxyCallArgs { - destination: string; - revertRule: RevertRule; - customEgressSelector: string; - ignoreIngressSelector: boolean; - calldata: string; - } - // Exposes `publicProxyCall()` with useful default arguments. async function publicProxyCallAsync(args: Partial): Promise<[boolean, string]> { return proxy.publicProxyCall.callAsync( @@ -72,27 +72,27 @@ blockchainTests.resets.only('LibProxy', env => { ); } + // Verifies that the result of a given call to `proxyCall()` results in specified outcome + function verifyPostConditions(result: [boolean, string], success: boolean, calldata: string): void { + expect(result[0]).to.be.eq(success); + expect(result[1]).to.be.eq(calldata); + } + + // Verifies that the result of a given call to `proxyCall()` results in `ProxyDestinationCannotBeNilError` + function verifyDestinationZeroError(result: [boolean, string]): void { + const expectedError = new StakingRevertErrors.ProxyDestinationCannotBeNilError(); + expect(result[0]).to.be.false(); + expect(result[1]).to.be.eq(expectedError.encode()); + } + describe('proxyCall', () => { - // Verifies that the result of a given call to `proxyCall()` results in specified outcome - function verifyPostConditions(result: [boolean, string], success: boolean, calldata: string): void { - expect(result[0]).to.be.eq(success); - expect(result[1]).to.be.eq(calldata); - } - describe('Failure Conditions', () => { - // Verifies that the result of a given call to `proxyCall()` results in `ProxyDestinationCannotBeNilError` - function checkDestinationZeroError(result: [boolean, string]): void { - const expectedError = new StakingRevertErrors.ProxyDestinationCannotBeNilError(); - expect(result[0]).to.be.false(); - expect(result[1]).to.be.eq(expectedError.encode()); - } - it('should revert when the destination is address zero', async () => { - checkDestinationZeroError(await publicProxyCallAsync({ destination: constants.NULL_ADDRESS })); + verifyDestinationZeroError(await publicProxyCallAsync({ destination: constants.NULL_ADDRESS })); }); it('should revert when the destination is address zero and revertRule == AlwaysRevert', async () => { - checkDestinationZeroError( + verifyDestinationZeroError( await publicProxyCallAsync({ destination: constants.NULL_ADDRESS, revertRule: RevertRule.AlwaysRevert, @@ -101,7 +101,7 @@ blockchainTests.resets.only('LibProxy', env => { }); it('should revert when the destination is address zero and revertRule == NeverRevert', async () => { - checkDestinationZeroError( + verifyDestinationZeroError( await publicProxyCallAsync({ destination: constants.NULL_ADDRESS, revertRule: RevertRule.NeverRevert, @@ -271,4 +271,33 @@ blockchainTests.resets.only('LibProxy', env => { ]); }); }); + + describe('simpleProxyCallWithData', () => { + it('should revert if address zero is the destination', async () => { + const expectedError = new StakingRevertErrors.ProxyDestinationCannotBeNilError(); + const tx = proxy.publicSimpleProxyCallWithData.awaitTransactionSuccessAsync( + constants.NULL_ADDRESS, + constants.NULL_BYTES, + ); + return expect(tx).to.revertWith(expectedError); + }); + + it('should call the receiver with the correct calldata and report a success when the receiver succeeds', async () => { + const calldata = constructRandomSuccessCalldata(); + verifyPostConditions( + await proxy.publicSimpleProxyCallWithData.callAsync(receiver.address, calldata), + true, + calldata, + ); + }); + + it('should call the receiver with the correct calldata and report a success when the receiver succeeds', async () => { + const calldata = constructRandomFailureCalldata(); + verifyPostConditions( + await proxy.publicSimpleProxyCallWithData.callAsync(receiver.address, calldata), + false, + calldata, + ); + }); + }); });