@0x/contracts-zero-ex: Address review feedback.

This commit is contained in:
Lawrence Forman
2020-05-18 14:55:57 -04:00
parent d2f581853d
commit 6359f1950e
5 changed files with 0 additions and 563 deletions

View File

@@ -1,65 +0,0 @@
/*
Copyright 2020 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.6.5;
library LibPuppetRichErrors {
// solhint-disable func-name-mixedcase
function PuppetExecuteFailedError(
address puppet,
address callTarget,
bytes memory callData,
uint256 callValue,
bytes memory errorData
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("PuppetExecuteFailedError(address,address,bytes,uint256,bytes)")),
puppet,
callTarget,
callData,
callValue,
errorData
);
}
function PuppetExecuteWithFailedError(
address puppet,
address callTarget,
bytes memory callData,
bytes memory errorData
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("PuppetExecuteWithFailedError(address,address,bytes,bytes)")),
puppet,
callTarget,
callData,
errorData
);
}
}

View File

@@ -1,61 +0,0 @@
/*
Copyright 2020 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.6.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/v06/interfaces/IOwnableV06.sol";
/// @dev A contract that can execute arbitrary calls from its owner.
interface IPuppet {
/// @dev Execute an arbitrary call. Only an authority can call this.
/// @param target The call target.
/// @param callData The call data.
/// @param value Ether to attach to the call.
/// @return resultData The data returned by the call.
function execute(
address payable target,
bytes calldata callData,
uint256 value
)
external
payable
returns (bytes memory resultData);
/// @dev Execute an arbitrary delegatecall, in the context of this puppet.
/// Only an authority can call this.
/// @param target The call target.
/// @param callData The call data.
/// @return resultData The data returned by the call.
function executeWith(
address payable target,
bytes calldata callData
)
external
payable
returns (bytes memory resultData);
/// @dev Allows the puppet to receive ETH.
receive() external payable;
/// @dev Fetch the immutable owner/deployer of this contract.
/// @return owner_ The immutable owner/deployer/
function owner() external view returns (address owner_);
}

View File

@@ -1,175 +0,0 @@
/*
Copyright 2020 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.6.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
import "@0x/contracts-utils/contracts/src/v06/errors/LibOwnableRichErrorsV06.sol";
import "../errors/LibPuppetRichErrors.sol";
import "./IPuppet.sol";
/// @dev A contract that can execute arbitrary calls from its owner.
contract Puppet is
IPuppet
{
// solhint-disable no-unused-vars,indent,no-empty-blocks
using LibRichErrorsV06 for bytes;
// solhint-disable
/// @dev Store the owner/deployer as an immutable to make this contract stateless.
address public override immutable owner;
// solhint-enable
constructor() public {
// The deployer is the owner.
owner = msg.sender;
}
/// @dev Allows only the (immutable) owner to call a function.
modifier onlyOwner() virtual {
if (msg.sender != owner) {
LibOwnableRichErrorsV06.OnlyOwnerError(
msg.sender,
owner
).rrevert();
}
_;
}
/// @dev Execute an arbitrary call. Only an authority can call this.
/// @param target The call target.
/// @param callData The call data.
/// @param value Ether to attach to the call.
/// @return resultData The data returned by the call.
function execute(
address payable target,
bytes calldata callData,
uint256 value
)
external
payable
override
onlyOwner
returns (bytes memory resultData)
{
bool success;
(success, resultData) = target.call{value: value}(callData);
if (!success) {
LibPuppetRichErrors
.PuppetExecuteFailedError(
address(this),
target,
callData,
value,
resultData
)
.rrevert();
}
}
/// @dev Execute an arbitrary delegatecall, in the context of this puppet.
/// Only an authority can call this.
/// @param target The call target.
/// @param callData The call data.
/// @return resultData The data returned by the call.
function executeWith(
address payable target,
bytes calldata callData
)
external
payable
override
onlyOwner
returns (bytes memory resultData)
{
bool success;
(success, resultData) = target.delegatecall(callData);
if (!success) {
LibPuppetRichErrors
.PuppetExecuteWithFailedError(
address(this),
target,
callData,
resultData
)
.rrevert();
}
}
// solhint-disable
/// @dev Allows this contract to receive ether.
receive() external override payable {}
// solhint-enable
/// @dev Signal support for receiving ERC1155 tokens.
/// @param interfaceID The interface ID, as per ERC-165 rules.
/// @return hasSupport `true` if this contract supports an ERC-165 interface.
function supportsInterface(bytes4 interfaceID)
external
pure
returns (bool hasSupport)
{
return interfaceID == this.supportsInterface.selector ||
interfaceID == this.onERC1155Received.selector ^ this.onERC1155BatchReceived.selector ||
interfaceID == this.tokenFallback.selector;
}
/// @dev Allow this contract to receive ERC1155 tokens.
/// @return success `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
function onERC1155Received(
address, // operator,
address, // from,
uint256, // id,
uint256, // value,
bytes calldata //data
)
external
pure
returns (bytes4 success)
{
return this.onERC1155Received.selector;
}
/// @dev Allow this contract to receive ERC1155 tokens.
/// @return success `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
function onERC1155BatchReceived(
address, // operator,
address, // from,
uint256[] calldata, // ids,
uint256[] calldata, // values,
bytes calldata // data
)
external
pure
returns (bytes4 success)
{
return this.onERC1155BatchReceived.selector;
}
/// @dev Allows this contract to receive ERC223 tokens.
function tokenFallback(
address, // from,
uint256, // value,
bytes calldata // value
)
external
pure
{}
}

View File

@@ -1,51 +0,0 @@
/*
Copyright 2020 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.6.5;
pragma experimental ABIEncoderV2;
contract TestPuppetTarget {
event PuppetTargetCalled(
address context,
address sender,
bytes data,
uint256 value
);
bytes4 private constant MAGIC_BYTES = 0x12345678;
bytes private constant REVERTING_DATA = hex"1337";
fallback() external payable {
if (keccak256(msg.data) == keccak256(REVERTING_DATA)) {
revert("TestPuppetTarget/REVERT");
}
emit PuppetTargetCalled(
address(this),
msg.sender,
msg.data,
msg.value
);
bytes4 rval = MAGIC_BYTES;
assembly {
mstore(0, rval)
return(0, 32)
}
}
}

View File

@@ -1,211 +0,0 @@
import {
blockchainTests,
constants,
expect,
getRandomInteger,
randomAddress,
verifyEventsFromLogs,
} from '@0x/contracts-test-utils';
import { hexUtils, OwnableRevertErrors, StringRevertError, ZeroExRevertErrors } from '@0x/utils';
import { artifacts } from './artifacts';
import { PuppetContract, TestPuppetTargetContract, TestPuppetTargetEvents } from './wrappers';
blockchainTests.resets('Puppets', env => {
let owner: string;
let puppet: PuppetContract;
let puppetTarget: TestPuppetTargetContract;
before(async () => {
[owner] = await env.getAccountAddressesAsync();
puppet = await PuppetContract.deployFrom0xArtifactAsync(
artifacts.Puppet,
env.provider,
{
...env.txDefaults,
from: owner,
},
artifacts,
);
puppetTarget = await TestPuppetTargetContract.deployFrom0xArtifactAsync(
artifacts.TestPuppetTarget,
env.provider,
env.txDefaults,
artifacts,
);
});
const TARGET_RETURN_VALUE = hexUtils.rightPad('0x12345678');
const REVERTING_DATA = '0x1337';
it('owned by deployer', () => {
return expect(puppet.owner().callAsync()).to.eventually.eq(owner);
});
describe('execute()', () => {
it('non-owner cannot call execute()', async () => {
const notOwner = randomAddress();
const tx = puppet
.execute(randomAddress(), hexUtils.random(), getRandomInteger(0, '100e18'))
.callAsync({ from: notOwner });
return expect(tx).to.revertWith(new OwnableRevertErrors.OnlyOwnerError(notOwner));
});
it('owner can call execute()', async () => {
const targetData = hexUtils.random(128);
const receipt = await puppet
.execute(puppetTarget.address, targetData, constants.ZERO_AMOUNT)
.awaitTransactionSuccessAsync({ from: owner });
verifyEventsFromLogs(
receipt.logs,
[
{
context: puppetTarget.address,
sender: puppet.address,
data: targetData,
value: constants.ZERO_AMOUNT,
},
],
TestPuppetTargetEvents.PuppetTargetCalled,
);
});
it('owner can call execute() with attached ETH', async () => {
const targetData = hexUtils.random(128);
const callValue = getRandomInteger(1, '1e18');
const receipt = await puppet
.execute(puppetTarget.address, targetData, callValue)
.awaitTransactionSuccessAsync({ from: owner, value: callValue });
verifyEventsFromLogs(
receipt.logs,
[
{
context: puppetTarget.address,
sender: puppet.address,
data: targetData,
value: callValue,
},
],
TestPuppetTargetEvents.PuppetTargetCalled,
);
});
it('owner can call execute() can transfer less ETH than attached', async () => {
const targetData = hexUtils.random(128);
const callValue = getRandomInteger(1, '1e18');
const receipt = await puppet
.execute(puppetTarget.address, targetData, callValue.minus(1))
.awaitTransactionSuccessAsync({ from: owner, value: callValue });
verifyEventsFromLogs(
receipt.logs,
[
{
context: puppetTarget.address,
sender: puppet.address,
data: targetData,
value: callValue.minus(1),
},
],
TestPuppetTargetEvents.PuppetTargetCalled,
);
});
it('puppet returns call result', async () => {
const result = await puppet
.execute(puppetTarget.address, hexUtils.random(128), constants.ZERO_AMOUNT)
.callAsync({ from: owner });
expect(result).to.eq(TARGET_RETURN_VALUE);
});
it('puppet wraps call revert', async () => {
const tx = puppet
.execute(puppetTarget.address, REVERTING_DATA, constants.ZERO_AMOUNT)
.callAsync({ from: owner });
return expect(tx).to.revertWith(
new ZeroExRevertErrors.Puppet.PuppetExecuteFailedError(
puppet.address,
puppetTarget.address,
REVERTING_DATA,
constants.ZERO_AMOUNT,
new StringRevertError('TestPuppetTarget/REVERT').encode(),
),
);
});
it('puppet can receive ETH', async () => {
await env.web3Wrapper.sendTransactionAsync({
to: puppet.address,
from: owner,
value: 1,
});
const bal = await env.web3Wrapper.getBalanceInWeiAsync(puppet.address);
expect(bal).to.bignumber.eq(1);
});
});
describe('executeWith()', () => {
it('non-owner cannot call executeWith()', async () => {
const notOwner = randomAddress();
const tx = puppet.executeWith(randomAddress(), hexUtils.random()).callAsync({ from: notOwner });
return expect(tx).to.revertWith(new OwnableRevertErrors.OnlyOwnerError(notOwner));
});
it('owner can call executeWith()', async () => {
const targetData = hexUtils.random(128);
const receipt = await puppet
.executeWith(puppetTarget.address, targetData)
.awaitTransactionSuccessAsync({ from: owner });
verifyEventsFromLogs(
receipt.logs,
[
{
context: puppet.address,
sender: owner,
data: targetData,
value: constants.ZERO_AMOUNT,
},
],
TestPuppetTargetEvents.PuppetTargetCalled,
);
});
it('executeWith() is payable', async () => {
const targetData = hexUtils.random(128);
const callValue = getRandomInteger(1, '1e18');
const receipt = await puppet
.executeWith(puppetTarget.address, targetData)
.awaitTransactionSuccessAsync({ from: owner, value: callValue });
verifyEventsFromLogs(
receipt.logs,
[
{
context: puppet.address,
sender: owner,
data: targetData,
value: callValue,
},
],
TestPuppetTargetEvents.PuppetTargetCalled,
);
});
it('puppet returns call result', async () => {
const result = await puppet
.executeWith(puppetTarget.address, hexUtils.random(128))
.callAsync({ from: owner });
expect(result).to.eq(TARGET_RETURN_VALUE);
});
it('puppet wraps call revert', async () => {
const tx = puppet.executeWith(puppetTarget.address, REVERTING_DATA).callAsync({ from: owner });
return expect(tx).to.revertWith(
new ZeroExRevertErrors.Puppet.PuppetExecuteWithFailedError(
puppet.address,
puppetTarget.address,
REVERTING_DATA,
new StringRevertError('TestPuppetTarget/REVERT').encode(),
),
);
});
});
});