@0x/contracts-zero-ex: Revamp TransformERC20.

This commit is contained in:
Lawrence Forman
2020-05-13 16:05:08 -04:00
parent d9a9bc35e3
commit af45409959
12 changed files with 239 additions and 632 deletions

View File

@@ -44,25 +44,22 @@ library LibPuppetRichErrors {
);
}
function InvalidPuppetInstanceError(address puppet)
function PuppetExecuteWithFailedError(
address puppet,
address callTarget,
bytes memory callData,
bytes memory errorData
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("InvalidPuppetInstanceError(address)")),
puppet
);
}
function PuppetNotAcquiredError(address puppet)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("PuppetNotAcquiredError(address)")),
puppet
bytes4(keccak256("PuppetExecuteWithFailedError(address,address,bytes,bytes)")),
puppet,
callTarget,
callData,
errorData
);
}
}

View File

@@ -19,14 +19,14 @@
pragma solidity ^0.6.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/v06/interfaces/IAuthorizableV06.sol";
import "@0x/contracts-utils/contracts/src/v06/interfaces/IOwnableV06.sol";
/// @dev A contract that can execute arbitrary calls from an authority.
/// @dev A contract that can execute arbitrary calls from its owner.
interface IPuppet is
IAuthorizableV06
IOwnableV06
{
/// @dev Execute an arbitrary call.
/// @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.
@@ -40,6 +40,19 @@ interface IPuppet is
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;
}

View File

@@ -20,21 +20,20 @@ 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/AuthorizableV06.sol";
import "@0x/contracts-utils/contracts/src/v06/OwnableV06.sol";
import "../errors/LibPuppetRichErrors.sol";
import "./IPuppet.sol";
/// @dev A contract that can execute arbitrary calls from an authority.
/// @dev A contract that can execute arbitrary calls from its owner.
contract Puppet is
IPuppet,
AuthorizableV06
OwnableV06
{
// solhint-disable no-unused-vars,indent,no-empty-blocks
using LibRichErrorsV06 for bytes;
/// @dev Execute an arbitrary call, forwarding any ether attached and
/// refunding any remaining ether. Only an authority can call this.
/// @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.
@@ -47,7 +46,7 @@ contract Puppet is
external
payable
override
onlyAuthorized
onlyOwner
returns (bytes memory resultData)
{
bool success;
@@ -65,16 +64,39 @@ contract Puppet is
}
}
// solhint-disable state-visibility
/// @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 state-visibility
/// @dev Destroy this contract. Only callable by the owner.
/// @param ethReceiver A payable recipient of any ETH in this contract.
function die(address payable ethReceiver) external onlyOwner {
selfdestruct(ethReceiver);
}
// solhint-enable
/// @dev Signal support for receiving ERC1155 tokens.
/// @param interfaceID The interface ID, as per ERC-165 rules.
@@ -88,6 +110,7 @@ contract Puppet is
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(

View File

@@ -1,55 +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 "../puppets/IPuppet.sol";
/// @dev Feature to manage a pool of puppet workers.
interface IPuppetPool {
/// @dev A new puppet contract was created.
/// @param puppet The address of the puppet contract.
event PuppetCreated(address puppet);
/// @dev Create a new, free puppet to add to the pool. Anyone can call this.
/// @return puppet The new puppet's address.
function createFreePuppet() external returns (address puppet);
/// @dev Acquire a new puppet instance. This removes the puppet from the
/// pool. If one is not available, a new one will be deployed.
/// Only callable from within.
/// @return puppet The acquired puppet.
function _acquirePuppet() external returns (IPuppet puppet);
/// @dev Release an acquired puppet instance back into the pool.
/// Only callable from within.
/// @param puppet The puppet to return to the pool.
function _releasePuppet(IPuppet puppet) external;
/// @dev Gets the number of free puppets in the pool.
/// @return count The number of free puppets in the pool.
function getFreePuppetsCount() external view returns (uint256 count);
/// @dev Check if an address is a puppet instance.
/// @param puppet The address to check.
/// @return isPuppet_ `true` if `puppet` is a puppet instance.
function isPuppet(address puppet) external view returns (bool isPuppet_);
}

View File

@@ -1,169 +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/LibSafeMathV06.sol";
import "../errors/LibPuppetRichErrors.sol";
import "../fixins/FixinCommon.sol";
import "../migrations/LibMigrate.sol";
import "../puppets/IPuppet.sol";
import "../puppets/Puppet.sol";
import "../storage/LibPuppetPoolStorage.sol";
import "./ISimpleFunctionRegistry.sol";
import "./IPuppetPool.sol";
import "./IFeature.sol";
/// @dev Feature to manage a pool of puppet workers.
contract PuppetPool is
IFeature,
IPuppetPool,
FixinCommon
{
// solhint-disable const-name-snakecase
/// @dev Name of this feature.
string constant public override FEATURE_NAME = "PuppetPool";
/// @dev Version of this feature.
uint256 constant public override FEATURE_VERSION = (1 << 64) | (0 << 32) | (0);
// solhint-enable const-name-snakecase
/// @dev The implementation address of this feature.
address private immutable _impl;
using LibSafeMathV06 for uint256;
using LibRichErrorsV06 for bytes;
constructor() public {
_impl = address(this);
}
/// @dev Initialize and register this feature. Should be delegatecalled
/// into during a `Migrate.migrate()`.
function migrate() external returns (bytes4 success) {
// Register this feature's functions.
ISimpleFunctionRegistry(address(this))
.extend(this.createFreePuppet.selector, _impl);
ISimpleFunctionRegistry(address(this))
.extend(this._acquirePuppet.selector, _impl);
ISimpleFunctionRegistry(address(this))
.extend(this._releasePuppet.selector, _impl);
ISimpleFunctionRegistry(address(this))
.extend(this.getFreePuppetsCount.selector, _impl);
ISimpleFunctionRegistry(address(this))
.extend(this.isPuppet.selector, _impl);
return LibMigrate.MIGRATE_SUCCESS;
}
/// @dev Create a new, free puppet to add to the pool. Anyone can call this.
/// @return puppet The new puppet's address.
function createFreePuppet()
external
override
returns (address puppet)
{
return address(_createPuppet(LibPuppetPoolStorage.PuppetState.Free));
}
/// @dev Acquire a new puppet instance. This removes the puppet from the
/// pool. If one is not available, a new one will be deployed.
/// Only callable from within.
/// @return puppet The acquired puppet.
function _acquirePuppet()
external
override
onlySelf
returns (IPuppet puppet)
{
LibPuppetPoolStorage.Storage storage stor = LibPuppetPoolStorage.getStorage();
uint256 numFreePuppets = stor.freePuppets.length;
if (numFreePuppets == 0) {
puppet = _createPuppet(LibPuppetPoolStorage.PuppetState.Acquired);
} else {
puppet = stor.freePuppets[numFreePuppets - 1];
stor.puppetState[address(puppet)] = LibPuppetPoolStorage.PuppetState.Acquired;
stor.freePuppets.pop();
}
}
/// @dev Release an acquired puppet instance back into the pool.
/// Only callable from within.
/// @param puppet The puppet to return to the pool.
function _releasePuppet(IPuppet puppet)
external
override
onlySelf
{
LibPuppetPoolStorage.Storage storage stor = LibPuppetPoolStorage.getStorage();
// Validate puppet state.
LibPuppetPoolStorage.PuppetState state = stor.puppetState[address(puppet)];
if (state == LibPuppetPoolStorage.PuppetState.Invalid) {
LibPuppetRichErrors.InvalidPuppetInstanceError(address(puppet)).rrevert();
} else if (state == LibPuppetPoolStorage.PuppetState.Free) {
LibPuppetRichErrors.PuppetNotAcquiredError(address(puppet)).rrevert();
}
// Return the puppet to the pool.
stor.puppetState[address(puppet)] = LibPuppetPoolStorage.PuppetState.Free;
stor.freePuppets.push(Puppet(address(uint160(address(puppet)))));
}
/// @dev Gets the number of free puppets in the pool.
/// @return count The number of free puppets in the pool.
function getFreePuppetsCount()
external
override
view
returns (uint256 count)
{
return LibPuppetPoolStorage.getStorage().freePuppets.length;
}
/// @dev Check if an address is a puppet instance.
/// @param puppet The address to check.
/// @return isPuppet_ `true` if `puppet` is a puppet instance.
function isPuppet(address puppet)
external
override
view
returns (bool isPuppet_)
{
LibPuppetPoolStorage.PuppetState state =
LibPuppetPoolStorage.getStorage().puppetState[address(puppet)];
return state != LibPuppetPoolStorage.PuppetState.Invalid;
}
/// @dev Deploy a new puppet instance with the provided state.
/// If `state` is `Free`, this will also add it to the free puppets pool.
/// @param state The state of the puppet.
/// @return puppet The new puppet instance.
function _createPuppet(LibPuppetPoolStorage.PuppetState state)
private
returns (Puppet puppet)
{
LibPuppetPoolStorage.Storage storage stor = LibPuppetPoolStorage.getStorage();
puppet = new Puppet();
puppet.addAuthorizedAddress(address(this));
stor.puppetState[address(puppet)] = state;
if (state == LibPuppetPoolStorage.PuppetState.Free) {
stor.freePuppets.push(puppet);
}
emit PuppetCreated(address(puppet));
}
}

View File

@@ -1,30 +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 "./IPuppet.sol";
/// @dev An alias for IPuppet, to differentiate from puppets used by PuppetPool.
interface ITokenSpenderPuppet is
IPuppet
{
// solhint-disable no-empty-blocks
}

View File

@@ -1,33 +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 "./ITokenSpenderPuppet.sol";
import "./Puppet.sol";
/// @dev A specialized puppet for use exclusively by the TokenSpender.
/// Essentially an alias to differentiate from puppets in PuppetPool.
contract TokenSpenderPuppet is
ITokenSpenderPuppet,
Puppet
{
// solhint-disable no-empty-blocks
}

View File

@@ -1,54 +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 "./LibStorage.sol";
import "../puppets/Puppet.sol";
/// @dev Storage helpers for the `PuppetPool` feature.
library LibPuppetPoolStorage {
/// @dev The state of a puppet instance.
enum PuppetState {
// Not a valid puppet (default)
Invalid,
// Puppet is free to be acquired.
Free,
// Puppet is currently acquired.
Acquired
}
/// @dev Storage bucket for this feature.
struct Storage {
// State of a puppet instance.
mapping(address => PuppetState) puppetState;
// Free puppet instances.
Puppet[] freePuppets;
}
/// @dev Get the storage bucket for this contract.
function getStorage() internal pure returns (Storage storage stor) {
uint256 storageSlot = LibStorage.getStorageSlot(
LibStorage.StorageId.PuppetPool
);
assembly { stor_slot := storageSlot }
}
}

View File

@@ -1,31 +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 "../src/features/PuppetPool.sol";
contract TestPuppetPool is
PuppetPool
{
modifier onlySelf() override {
_;
}
}

View File

@@ -23,6 +23,7 @@ pragma experimental ABIEncoderV2;
contract TestPuppetTarget {
event PuppetTargetCalled(
address context,
address sender,
bytes data,
uint256 value
@@ -36,6 +37,7 @@ contract TestPuppetTarget {
revert("TestPuppetTarget/REVERT");
}
emit PuppetTargetCalled(
address(this),
msg.sender,
msg.data,
msg.value

View File

@@ -1,150 +0,0 @@
import { blockchainTests, constants, expect, verifyEventsFromLogs } from '@0x/contracts-test-utils';
import { ZeroExRevertErrors } from '@0x/utils';
import { artifacts } from '../artifacts';
import { abis } from '../utils/abis';
import { fullMigrateAsync } from '../utils/migration';
import { IPuppetPoolEvents, PuppetContract, PuppetPoolContract, ZeroExContract } from '../wrappers';
blockchainTests.resets('PuppetPool feature', env => {
let zeroEx: ZeroExContract;
let feature: PuppetPoolContract;
let unmanagedPuppet: PuppetContract;
before(async () => {
const [owner] = await env.getAccountAddressesAsync();
zeroEx = await fullMigrateAsync(owner, env.provider, env.txDefaults, {
puppetPool: (await PuppetContract.deployFrom0xArtifactAsync(
artifacts.TestPuppetPool,
env.provider,
env.txDefaults,
artifacts,
)).address,
});
feature = new PuppetPoolContract(zeroEx.address, env.provider, env.txDefaults, abis);
unmanagedPuppet = await PuppetContract.deployFrom0xArtifactAsync(
artifacts.Puppet,
env.provider,
env.txDefaults,
artifacts,
);
});
async function acquirePuppetAsync(): Promise<PuppetContract> {
const puppet = new PuppetContract(
await feature._acquirePuppet().callAsync(),
env.provider,
env.txDefaults,
abis,
);
await feature._acquirePuppet().awaitTransactionSuccessAsync();
return puppet;
}
async function releasePuppetAsync(puppet: string | PuppetContract): Promise<void> {
await feature
._releasePuppet(typeof puppet === 'string' ? puppet : puppet.address)
.awaitTransactionSuccessAsync();
}
describe('_acquirePuppet() and _releasePuppet()', () => {
it('_acquirePuppet() creates a new puppet if none are available', async () => {
const acquiredPuppets = [];
while ((await feature.getFreePuppetsCount().callAsync()).gt(0)) {
acquiredPuppets.push(await acquirePuppetAsync());
}
const puppetAddress = await feature._acquirePuppet().callAsync();
const receipt = await feature._acquirePuppet().awaitTransactionSuccessAsync();
expect(puppetAddress).to.not.eq(constants.NULL_ADDRESS);
verifyEventsFromLogs(receipt.logs, [{ puppet: puppetAddress }], IPuppetPoolEvents.PuppetCreated);
});
it('_acquirePuppet() returns a free puppet if available', async () => {
const freePuppetAddess = await feature.createFreePuppet().callAsync();
await feature.createFreePuppet().awaitTransactionSuccessAsync();
// Acquire the free puppet.
const puppetAddress = await feature._acquirePuppet().callAsync();
const receipt = await feature._acquirePuppet().awaitTransactionSuccessAsync();
expect(await feature.getFreePuppetsCount().callAsync()).to.bignumber.eq(0);
expect(puppetAddress).to.eq(freePuppetAddess);
verifyEventsFromLogs(receipt.logs, [], IPuppetPoolEvents.PuppetCreated);
});
it('can release an EXISTING puppet returned by _acquirePuppet()', async () => {
const freePuppetAddess = await feature.createFreePuppet().callAsync();
await feature.createFreePuppet().awaitTransactionSuccessAsync();
// Acquire the free puppet.
const puppetAddress = await feature._acquirePuppet().callAsync();
await feature._acquirePuppet().awaitTransactionSuccessAsync();
expect(await feature.getFreePuppetsCount().callAsync()).to.bignumber.eq(0);
expect(puppetAddress).to.eq(freePuppetAddess);
await releasePuppetAsync(puppetAddress);
expect(await feature.getFreePuppetsCount().callAsync()).to.bignumber.eq(1);
});
it('can acquire and release many puppets', async () => {
const puppets = [];
for (let i = 0; i < 8; ++i) {
puppets.push(await acquirePuppetAsync());
}
expect(await feature.getFreePuppetsCount().callAsync()).to.bignumber.eq(0);
for (const puppet of puppets) {
await releasePuppetAsync(puppet);
}
expect(await feature.getFreePuppetsCount().callAsync()).to.bignumber.eq(puppets.length);
});
it('cannot release a puppet not created by the pool', async () => {
return expect(releasePuppetAsync(unmanagedPuppet)).to.revertWith(
new ZeroExRevertErrors.Puppet.InvalidPuppetInstanceError(unmanagedPuppet.address),
);
});
it('cannot release a free puppet', async () => {
const puppet = await acquirePuppetAsync();
await releasePuppetAsync(puppet);
return expect(releasePuppetAsync(puppet)).to.revertWith(
new ZeroExRevertErrors.Puppet.PuppetNotAcquiredError(puppet.address),
);
});
});
describe('createFreePuppet()', () => {
it('creates a free puppet', async () => {
const puppet = await feature.createFreePuppet().callAsync();
const receipt = await feature.createFreePuppet().awaitTransactionSuccessAsync();
verifyEventsFromLogs(receipt.logs, [{ puppet }], IPuppetPoolEvents.PuppetCreated);
expect(await feature.isPuppet(puppet).callAsync()).to.eq(true);
expect(await feature.getFreePuppetsCount().callAsync()).to.bignumber.eq(1);
});
});
describe('isPuppet()', () => {
it('returns false for a puppet not created by the pool', async () => {
expect(await feature.isPuppet(unmanagedPuppet.address).callAsync()).to.eq(false);
});
it('returns true for an acquired puppet', async () => {
const puppet = await acquirePuppetAsync();
expect(await feature.isPuppet(puppet.address).callAsync()).to.eq(true);
});
it('returns true for a released puppet', async () => {
const puppet = await acquirePuppetAsync();
await releasePuppetAsync(puppet);
expect(await feature.isPuppet(puppet.address).callAsync()).to.eq(true);
});
});
describe('puppets', () => {
it('puppet is owned by proxy contract', async () => {
const puppet = await acquirePuppetAsync();
expect(await puppet.owner().callAsync()).to.eq(zeroEx.address);
});
it('proxy contract is authorized', async () => {
const puppet = await acquirePuppetAsync();
expect(await puppet.authorized(zeroEx.address).callAsync()).to.eq(true);
});
});
});

View File

@@ -6,26 +6,27 @@ import {
randomAddress,
verifyEventsFromLogs,
} from '@0x/contracts-test-utils';
import { AuthorizableRevertErrors, hexUtils, StringRevertError, ZeroExRevertErrors } from '@0x/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 authority: string;
let puppet: PuppetContract;
let puppetTarget: TestPuppetTargetContract;
before(async () => {
[owner, authority] = await env.getAccountAddressesAsync();
[owner] = await env.getAccountAddressesAsync();
puppet = await PuppetContract.deployFrom0xArtifactAsync(
artifacts.Puppet,
env.provider,
env.txDefaults,
{
...env.txDefaults,
from: owner,
},
artifacts,
);
await puppet.addAuthorizedAddress(authority).awaitTransactionSuccessAsync();
puppetTarget = await TestPuppetTargetContract.deployFrom0xArtifactAsync(
artifacts.TestPuppetTarget,
env.provider,
@@ -34,84 +35,177 @@ blockchainTests.resets('Puppets', env => {
);
});
it('non-authority cannot call execute()', async () => {
const notAuthority = randomAddress();
const tx = puppet
.execute(randomAddress(), hexUtils.random(), getRandomInteger(0, '100e18'))
.callAsync({ from: notAuthority });
return expect(tx).to.revertWith(new AuthorizableRevertErrors.SenderNotAuthorizedError(notAuthority));
});
it('authority can call execute()', async () => {
const targetData = hexUtils.random(128);
const receipt = await puppet
.execute(puppetTarget.address, targetData, constants.ZERO_AMOUNT)
.awaitTransactionSuccessAsync({ from: authority });
verifyEventsFromLogs(
receipt.logs,
[
{
sender: puppet.address,
data: targetData,
value: constants.ZERO_AMOUNT,
},
],
TestPuppetTargetEvents.PuppetTargetCalled,
);
});
it('authority 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: authority, value: callValue });
verifyEventsFromLogs(
receipt.logs,
[
{
sender: puppet.address,
data: targetData,
value: callValue,
},
],
TestPuppetTargetEvents.PuppetTargetCalled,
);
});
const TARGET_RETURN_VALUE = hexUtils.rightPad('0x12345678');
it('puppet returns call result', async () => {
const result = await puppet
.execute(puppetTarget.address, hexUtils.random(128), constants.ZERO_AMOUNT)
.callAsync({ from: authority });
expect(result).to.eq(TARGET_RETURN_VALUE);
});
const REVERTING_DATA = '0x1337';
it('puppet wraps call revert', async () => {
const tx = puppet
.execute(puppetTarget.address, REVERTING_DATA, constants.ZERO_AMOUNT)
.callAsync({ from: authority });
return expect(tx).to.revertWith(
new ZeroExRevertErrors.Puppet.PuppetExecuteFailedError(
puppet.address,
puppetTarget.address,
REVERTING_DATA,
constants.ZERO_AMOUNT,
new StringRevertError('TestPuppetTarget/REVERT').encode(),
),
);
it('owned by deployer', () => {
return expect(puppet.owner().callAsync()).to.eventually.eq(owner);
});
it('puppet can receive ETH', async () => {
await env.web3Wrapper.sendTransactionAsync({
to: puppet.address,
from: owner,
value: 1,
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(),
),
);
});
const bal = await env.web3Wrapper.getBalanceInWeiAsync(puppet.address);
expect(bal).to.bignumber.eq(1);
});
});