@0x/contracts-zero-ex
: Revamp TransformERC20.
This commit is contained in:
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
@@ -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(
|
@@ -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_);
|
||||
}
|
@@ -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));
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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 }
|
||||
}
|
||||
}
|
@@ -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 {
|
||||
_;
|
||||
}
|
||||
}
|
@@ -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
|
||||
|
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
@@ -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);
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user