Removed LibProxy and inlined proxy call in StakingProxy.

This commit is contained in:
Greg Hysen 2019-10-28 15:41:08 -07:00
parent 011ecb8f4b
commit 9c181f09ba
9 changed files with 14 additions and 516 deletions

View File

@ -19,7 +19,6 @@
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "./libs/LibProxy.sol";
import "./libs/LibSafeDowncast.sol";
import "./immutable/MixinStorage.sol";
import "./immutable/MixinConstants.sol";
@ -32,7 +31,6 @@ contract StakingProxy is
MixinStorage,
MixinConstants
{
using LibProxy for address;
using LibSafeDowncast for uint256;
/// @dev Constructor.
@ -56,11 +54,19 @@ contract StakingProxy is
external
payable
{
stakingContract.proxyCall(
LibProxy.RevertRule.REVERT_ON_ERROR,
bytes4(0), // no custom egress selector
false // do not ignore ingress selector
);
// Call the staking contract with the provided calldata.
(bool success, bytes memory returnData) = stakingContract.delegatecall(msg.data);
// Revert on failure or return on success.
assembly {
switch success
case 0 {
revert(add(0x20, returnData), mload(returnData))
}
default {
return(add(0x20, returnData), mload(returnData))
}
}
}
/// @dev Attach a staking contract; future calls will be delegated to the staking contract.

View File

@ -1,112 +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/LibRichErrors.sol";
import "./LibStakingRichErrors.sol";
library LibProxy {
enum RevertRule {
REVERT_ON_ERROR,
ALWAYS_REVERT,
NEVER_REVERT
}
/// @dev Proxies incoming call to destination contract.
/// @param destination Address to call.
/// @param revertRule Describes scenarios in which this function reverts.
/// @param customEgressSelector Custom selector used to call destination contract.
/// @param ignoreIngressSelector Ignore the selector used to call into this contract.
function proxyCall(
address destination,
RevertRule revertRule,
bytes4 customEgressSelector,
bool ignoreIngressSelector
)
internal
{
if (destination == address(0)) {
LibRichErrors.rrevert(
LibStakingRichErrors.ProxyDestinationCannotBeNilError()
);
}
assembly {
// store selector of destination function
let freeMemPtr := 0
if gt(customEgressSelector, 0) {
mstore(0x0, customEgressSelector)
freeMemPtr := add(freeMemPtr, 4)
}
// adjust the calldata offset, if we should ignore the selector
let calldataOffset := 0
if gt(ignoreIngressSelector, 0) {
calldataOffset := 4
}
// copy calldata to memory
calldatacopy(
freeMemPtr,
calldataOffset,
calldatasize()
)
freeMemPtr := add(
freeMemPtr,
sub(calldatasize(), calldataOffset)
)
// delegate call into staking contract
let success := delegatecall(
gas, // forward all gas
destination, // calling staking contract
0x0, // start of input (calldata)
freeMemPtr, // length of input (calldata)
0x0, // write output over input
0 // length of output is unknown
)
// copy return data to memory and *return*
returndatacopy(
0x0,
0x0,
returndatasize()
)
switch revertRule
case 1 { // ALWAYS_REVERT
revert(0, returndatasize())
}
case 2 { // NEVER_REVERT
return(0, returndatasize())
}
// solhint-disable no-empty-blocks
default {} // REVERT_ON_ERROR (handled below)
// rethrow any exceptions
if iszero(success) {
revert(0, returndatasize())
}
// return call results
return(0, returndatasize())
}
}
}

View File

@ -1,74 +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.
*/
/**********************************************
THIS IS AN EXTREMELY DANGEROUS CONTRACT!
IT IS ONLY INTENDED FOR TESTING AND SHOULD
NEVER BE USED IN PRODUCTION!
**********************************************/
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "../src/libs/LibProxy.sol";
// solhint-disable payable-fallback
contract TestLibProxy {
using LibProxy for address;
// The arguments of `proxyCall()`.
struct ProxyCallArguments {
address destination;
LibProxy.RevertRule revertRule;
bytes4 customEgressSelector;
bool ignoreIngressSelector;
}
// The current arguments that should be passed in the call to `proxyCall()`. This
// state allows us to send in the exact calldata that should be sent to `proxyCall()`
// while still being able to test any combination of inputs to `proxyCall()`.
ProxyCallArguments internal proxyCallArgs;
/// @dev Exposes the `proxyCall()` library function from LibProxy.
function ()
external
{
proxyCallArgs.destination.proxyCall(
proxyCallArgs.revertRule,
proxyCallArgs.customEgressSelector,
proxyCallArgs.ignoreIngressSelector
);
}
/// @dev Calls back into this contract with the calldata that should be sent in the call
/// to `proxyCall()` after setting the `proxyCallArgs` appropriately.
/// @param args The struct that should be set to `proxyCallArgs`.
/// @param data The bytes that should be used to call back into this contract.
function publicProxyCall(ProxyCallArguments memory args, bytes memory data)
public
returns (bool success, bytes memory returnData)
{
proxyCallArgs = args;
(success, returnData) = address(this).call(data);
}
}

View File

@ -1,45 +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;
// solhint-disable payable-fallback
contract TestLibProxyReceiver {
function ()
external
{
// Done in assembly to allow the return.
assembly {
let calldataSize := calldatasize()
// Copy all calldata into memory.
calldatacopy(0, 0, calldataSize)
// If the calldatasize is equal to 4, revert.
// This allows us to test `proxyCall` with reverts.
if eq(calldataSize, 4) {
revert(0, 4)
}
// Return. This allows us to test `proxyCall` with returns.
return(0, calldataSize)
}
}
}

View File

@ -37,7 +37,7 @@
},
"config": {
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./generated-artifacts/@(IStaking|IStakingEvents|IStakingProxy|IStorage|IStorageInit|IStructs|IZrxVault|LibCobbDouglas|LibFixedMath|LibFixedMathRichErrors|LibProxy|LibSafeDowncast|LibStakingRichErrors|MixinAbstract|MixinConstants|MixinCumulativeRewards|MixinDeploymentConstants|MixinExchangeFees|MixinExchangeManager|MixinFinalizer|MixinParams|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolRewards|MixinStorage|Staking|StakingProxy|TestAssertStorageParams|TestCobbDouglas|TestCumulativeRewardTracking|TestDelegatorRewards|TestExchangeManager|TestFinalizer|TestInitTarget|TestLibFixedMath|TestLibProxy|TestLibProxyReceiver|TestLibSafeDowncast|TestMixinParams|TestMixinStake|TestMixinStakeStorage|TestMixinStakingPool|TestProtocolFees|TestStaking|TestStakingNoWETH|TestStakingProxy|TestStorageLayoutAndConstants|ZrxVault).json"
"abis": "./generated-artifacts/@(IStaking|IStakingEvents|IStakingProxy|IStorage|IStorageInit|IStructs|IZrxVault|LibCobbDouglas|LibFixedMath|LibFixedMathRichErrors|LibSafeDowncast|LibStakingRichErrors|MixinAbstract|MixinConstants|MixinCumulativeRewards|MixinDeploymentConstants|MixinExchangeFees|MixinExchangeManager|MixinFinalizer|MixinParams|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolRewards|MixinStorage|Staking|StakingProxy|TestAssertStorageParams|TestCobbDouglas|TestCumulativeRewardTracking|TestDelegatorRewards|TestExchangeManager|TestFinalizer|TestInitTarget|TestLibFixedMath|TestLibSafeDowncast|TestMixinParams|TestMixinStake|TestMixinStakeStorage|TestMixinStakingPool|TestProtocolFees|TestStaking|TestStakingNoWETH|TestStakingProxy|TestStorageLayoutAndConstants|ZrxVault).json"
},
"repository": {
"type": "git",

View File

@ -15,7 +15,6 @@ import * as IZrxVault from '../generated-artifacts/IZrxVault.json';
import * as LibCobbDouglas from '../generated-artifacts/LibCobbDouglas.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 LibStakingRichErrors from '../generated-artifacts/LibStakingRichErrors.json';
import * as MixinAbstract from '../generated-artifacts/MixinAbstract.json';
@ -43,8 +42,6 @@ import * as TestExchangeManager from '../generated-artifacts/TestExchangeManager
import * as TestFinalizer from '../generated-artifacts/TestFinalizer.json';
import * as TestInitTarget from '../generated-artifacts/TestInitTarget.json';
import * as TestLibFixedMath from '../generated-artifacts/TestLibFixedMath.json';
import * as TestLibProxy from '../generated-artifacts/TestLibProxy.json';
import * as TestLibProxyReceiver from '../generated-artifacts/TestLibProxyReceiver.json';
import * as TestLibSafeDowncast from '../generated-artifacts/TestLibSafeDowncast.json';
import * as TestMixinParams from '../generated-artifacts/TestMixinParams.json';
import * as TestMixinStake from '../generated-artifacts/TestMixinStake.json';
@ -75,7 +72,6 @@ export const artifacts = {
LibCobbDouglas: LibCobbDouglas as ContractArtifact,
LibFixedMath: LibFixedMath as ContractArtifact,
LibFixedMathRichErrors: LibFixedMathRichErrors as ContractArtifact,
LibProxy: LibProxy as ContractArtifact,
LibSafeDowncast: LibSafeDowncast as ContractArtifact,
LibStakingRichErrors: LibStakingRichErrors as ContractArtifact,
MixinStake: MixinStake as ContractArtifact,
@ -96,8 +92,6 @@ export const artifacts = {
TestFinalizer: TestFinalizer as ContractArtifact,
TestInitTarget: TestInitTarget as ContractArtifact,
TestLibFixedMath: TestLibFixedMath as ContractArtifact,
TestLibProxy: TestLibProxy as ContractArtifact,
TestLibProxyReceiver: TestLibProxyReceiver as ContractArtifact,
TestLibSafeDowncast: TestLibSafeDowncast as ContractArtifact,
TestMixinParams: TestMixinParams as ContractArtifact,
TestMixinStake: TestMixinStake as ContractArtifact,

View File

@ -13,7 +13,6 @@ export * from '../generated-wrappers/i_zrx_vault';
export * from '../generated-wrappers/lib_cobb_douglas';
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_staking_rich_errors';
export * from '../generated-wrappers/mixin_abstract';
@ -41,8 +40,6 @@ export * from '../generated-wrappers/test_exchange_manager';
export * from '../generated-wrappers/test_finalizer';
export * from '../generated-wrappers/test_init_target';
export * from '../generated-wrappers/test_lib_fixed_math';
export * from '../generated-wrappers/test_lib_proxy';
export * from '../generated-wrappers/test_lib_proxy_receiver';
export * from '../generated-wrappers/test_lib_safe_downcast';
export * from '../generated-wrappers/test_mixin_params';
export * from '../generated-wrappers/test_mixin_stake';

View File

@ -1,265 +0,0 @@
import { blockchainTests, constants, expect, hexConcat, hexRandom, hexSlice } from '@0x/contracts-test-utils';
import { StakingRevertErrors } from '@0x/order-utils';
import { cartesianProduct } from 'js-combinatorics';
import { artifacts, TestLibProxyContract, TestLibProxyReceiverContract } from '../../src';
blockchainTests.resets('LibProxy unit tests', env => {
let proxy: TestLibProxyContract;
let receiver: TestLibProxyReceiverContract;
before(async () => {
proxy = await TestLibProxyContract.deployFrom0xArtifactAsync(
artifacts.TestLibProxy,
env.provider,
env.txDefaults,
artifacts,
);
receiver = await TestLibProxyReceiverContract.deployFrom0xArtifactAsync(
artifacts.TestLibProxyReceiver,
env.provider,
env.txDefaults,
artifacts,
);
});
enum RevertRule {
RevertOnError,
AlwaysRevert,
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.
function constructRandomFailureCalldata(): string {
return hexConcat('0x00', hexRandom(3));
}
// Choose a random 24 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` succeed because it isn't 4 bytes long.
function constructRandomSuccessCalldata(): string {
return hexConcat('0x00', hexRandom(35));
}
// Exposes `publicProxyCall()` with useful default arguments.
async function publicProxyCallAsync(args: Partial<PublicProxyCallArgs>): Promise<[boolean, string]> {
return proxy.publicProxyCall.callAsync(
{
destination: args.destination || receiver.address,
revertRule: args.revertRule || RevertRule.RevertOnError,
customEgressSelector: args.customEgressSelector || constants.NULL_BYTES4,
ignoreIngressSelector: args.ignoreIngressSelector || false,
},
args.calldata || constructRandomSuccessCalldata(),
);
}
// 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', () => {
describe('Failure Conditions', () => {
it('should revert when the destination is address zero', async () => {
verifyDestinationZeroError(await publicProxyCallAsync({ destination: constants.NULL_ADDRESS }));
});
it('should revert when the destination is address zero and revertRule == AlwaysRevert', async () => {
verifyDestinationZeroError(
await publicProxyCallAsync({
destination: constants.NULL_ADDRESS,
revertRule: RevertRule.AlwaysRevert,
}),
);
});
it('should revert when the destination is address zero and revertRule == NeverRevert', async () => {
verifyDestinationZeroError(
await publicProxyCallAsync({
destination: constants.NULL_ADDRESS,
revertRule: RevertRule.NeverRevert,
}),
);
});
});
describe('RevertRule Checks', () => {
it('should revert with the correct data when the call succeeds and revertRule = AlwaysRevert', async () => {
const calldata = constructRandomSuccessCalldata();
// Ensure that the returndata (the provided calldata) is correct.
verifyPostConditions(
await publicProxyCallAsync({
calldata,
revertRule: RevertRule.AlwaysRevert,
}),
false,
calldata,
);
});
it('should revert with the correct data when the call falls and revertRule = AlwaysRevert', async () => {
const calldata = constructRandomFailureCalldata();
// Ensure that the returndata (the provided calldata) is correct.
verifyPostConditions(
await publicProxyCallAsync({
calldata,
revertRule: RevertRule.AlwaysRevert,
}),
false,
calldata,
);
});
it('should succeed with the correct data when the call succeeds and revertRule = NeverRevert', async () => {
const calldata = constructRandomSuccessCalldata();
// Ensure that the returndata (the provided calldata) is correct.
verifyPostConditions(
await publicProxyCallAsync({
calldata,
revertRule: RevertRule.NeverRevert,
}),
true,
calldata,
);
});
it('should succeed with the correct data when the call falls and revertRule = NeverRevert', async () => {
const calldata = constructRandomFailureCalldata();
// Ensure that the returndata (the provided calldata) is correct.
verifyPostConditions(
await publicProxyCallAsync({
calldata,
revertRule: RevertRule.NeverRevert,
}),
true,
calldata,
);
});
it('should succeed with the correct data when the call succeeds and revertRule = RevertOnError', async () => {
const calldata = constructRandomSuccessCalldata();
// Ensure that the returndata (the provided calldata) is correct.
verifyPostConditions(
await publicProxyCallAsync({
calldata,
}),
true,
calldata,
);
});
it('should revert with the correct data when the call falls and revertRule = RevertOnError', async () => {
const calldata = constructRandomFailureCalldata();
// Ensure that the returndata (the provided calldata) is correct.
verifyPostConditions(
await publicProxyCallAsync({
calldata,
}),
false,
calldata,
);
});
});
describe('Combinatorial Tests', () => {
// Combinatorial Scenarios for `proxyCall()`.
function getCombinatorialTestDescription(params: [RevertRule, boolean, string, string]): string {
const REVERT_RULE_NAMES = ['RevertOnError', 'AlwaysRevert', 'NeverRevert'];
return [
`revertRule: ${REVERT_RULE_NAMES[params[0]]}`,
`ignoreIngressSelector: ${params[1]}`,
`customEgressSelector: ${params[2]}`,
`calldata: ${
params[3].length / 2 - 2 > 4
? // tslint:disable-next-line
hexSlice(params[3], 0, 4) + '...'
: params[3]
}`,
].join(', ');
}
const scenarios = [
// revertRule
[RevertRule.RevertOnError, RevertRule.AlwaysRevert, RevertRule.NeverRevert],
// ignoreIngressSelector
[false, true],
// customEgressSelector
[
constants.NULL_BYTES4,
// Random failure calldata is used because it is nonzero and
// won't collide.
constructRandomFailureCalldata(),
],
// calldata
[constructRandomFailureCalldata(), constructRandomSuccessCalldata()],
] as [RevertRule[], boolean[], string[], string[]];
for (const params of cartesianProduct(...scenarios).toArray()) {
const [revertRule, shouldIgnoreIngressSelector, customEgressSelector, calldata] = params;
it(getCombinatorialTestDescription(params), async () => {
// Determine whether or not the call should succeed.
let shouldSucceed = true;
if (
((shouldIgnoreIngressSelector && customEgressSelector !== constants.NULL_BYTES4) ||
(!shouldIgnoreIngressSelector && customEgressSelector === constants.NULL_BYTES4)) &&
calldata.length === 10 // This corresponds to a hex length of 4
) {
shouldSucceed = false;
}
// Override the above success value if the RevertRule defines the success.
if (revertRule === RevertRule.AlwaysRevert) {
shouldSucceed = false;
}
if (revertRule === RevertRule.NeverRevert) {
shouldSucceed = true;
}
// Construct the data that should be returned.
let returnData = calldata;
if (shouldIgnoreIngressSelector) {
returnData = hexSlice(returnData, 4);
}
if (customEgressSelector !== constants.NULL_BYTES4) {
returnData = hexConcat(customEgressSelector, returnData);
}
const [didSucceed, actualReturnData] = await publicProxyCallAsync({
calldata,
customEgressSelector,
ignoreIngressSelector: shouldIgnoreIngressSelector,
revertRule,
});
expect(didSucceed).to.be.eq(shouldSucceed);
expect(actualReturnData).to.be.eq(returnData);
});
}
});
});
});

View File

@ -13,7 +13,6 @@
"generated-artifacts/LibCobbDouglas.json",
"generated-artifacts/LibFixedMath.json",
"generated-artifacts/LibFixedMathRichErrors.json",
"generated-artifacts/LibProxy.json",
"generated-artifacts/LibSafeDowncast.json",
"generated-artifacts/LibStakingRichErrors.json",
"generated-artifacts/MixinAbstract.json",
@ -41,8 +40,6 @@
"generated-artifacts/TestFinalizer.json",
"generated-artifacts/TestInitTarget.json",
"generated-artifacts/TestLibFixedMath.json",
"generated-artifacts/TestLibProxy.json",
"generated-artifacts/TestLibProxyReceiver.json",
"generated-artifacts/TestLibSafeDowncast.json",
"generated-artifacts/TestMixinParams.json",
"generated-artifacts/TestMixinStake.json",