Broker contracts

This commit is contained in:
Michael Zhu
2020-01-24 14:42:52 -08:00
parent 5bbbae5b23
commit a98c95b514
13 changed files with 494 additions and 9 deletions

View File

@@ -1,5 +1,217 @@
/*
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;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
import "@0x/contracts-exchange-forwarder/contracts/src/libs/LibAssetDataTransfer.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "@0x/contracts-utils/contracts/src/Refundable.sol";
import "./interfaces/IBroker.sol";
import "./interfaces/IPropertyValidator.sol";
// TODO(mzhu25): Implement, you know, the whole thing
contract Broker { } // solhint-disable-line no-empty-blocks
// solhint-disable space-after-comma
contract Broker is
Refundable,
IBroker
{
bytes[] internal _cachedAssetData;
uint256 internal _cacheIndex;
address internal _sender;
address internal _EXCHANGE; // solhint-disable-line var-name-mixedcase
using LibSafeMath for uint256;
using LibBytes for bytes;
using LibAssetDataTransfer for bytes;
/// @param exchange Address of the 0x Exchange contract.
constructor (address exchange)
public
{
_EXCHANGE = exchange;
}
/// @dev A payable fallback function that makes this contract "payable". This is necessary to allow
/// this contract to gracefully handle refunds from the Exchange.
function ()
external
payable
{} // solhint-disable-line no-empty-blocks
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata /* ids */,
uint256[] calldata amounts,
bytes calldata data
)
external
{
require(from == address(this), "INVALID_FROM_ADDRESS");
require(amounts.length == 1, "MUST_PROVIDE_ONE_AMOUNT");
uint256 remainingAmount = amounts[0];
require(_cachedAssetData.length.safeSub(_cacheIndex) >= remainingAmount, "TOO_FEW_BROKERED_ASSETS_PROVIDED");
while (remainingAmount != 0) {
bytes memory assetToTransfer = _cachedAssetData[_cacheIndex];
_cacheIndex++;
// Decode validator and params from `data`
(address validator, bytes memory propertyData) = abi.decode(
data,
(address, bytes)
);
// Execute staticcall
(bool success, bytes memory returnData) = validator.staticcall(abi.encodeWithSelector(
IPropertyValidator(0).checkBrokerAsset.selector,
assetToTransfer,
propertyData
));
// Revert with returned data if staticcall is unsuccessful
if (!success) {
assembly {
revert(add(returnData, 32), mload(returnData))
}
}
// Perform the transfer
assetToTransfer.transferERC721Token(
_sender,
to,
1
);
remainingAmount--;
}
}
function brokerTrade(
bytes[] memory brokeredAssets,
LibOrder.Order memory order,
uint256 takerAssetFillAmount,
bytes memory signature,
bytes4 fillFunctionSelector
)
public
payable
refundFinalBalance
returns (LibFillResults.FillResults memory fillResults)
{
// Cache the taker-supplied asset data
_cachedAssetData = brokeredAssets;
// Cache the sender's address
_sender = msg.sender;
// Sanity-check the provided function selector
require(
fillFunctionSelector == IExchange(address(0)).fillOrder.selector ||
fillFunctionSelector == IExchange(address(0)).fillOrKillOrder.selector,
"UNRECOGNIZED_FUNCTION_SELECTOR"
);
// Perform the fill
bytes memory fillCalldata = abi.encodeWithSelector(
fillFunctionSelector,
order,
takerAssetFillAmount,
signature
);
// solhint-disable-next-line avoid-call-value
(bool didSucceed, bytes memory returnData) = _EXCHANGE.call.value(msg.value)(fillCalldata);
if (didSucceed) {
fillResults = abi.decode(returnData, (LibFillResults.FillResults));
} else {
assembly {
revert(add(returnData, 32), mload(returnData))
}
}
// Transfer maker asset to taker
order.makerAssetData.transferOut(fillResults.makerAssetFilledAmount);
// Clear storage
delete _cachedAssetData;
_cacheIndex = 0;
_sender = address(0);
return fillResults;
}
function batchBrokerTrade(
bytes[] memory brokeredAssets,
LibOrder.Order[] memory orders,
uint256[] memory takerAssetFillAmounts,
bytes[] memory signatures,
bytes4 batchFillFunctionSelector
)
public
payable
refundFinalBalance
returns (LibFillResults.FillResults[] memory fillResults)
{
// Cache the taker-supplied asset data
_cachedAssetData = brokeredAssets;
// Cache the sender's address
_sender = msg.sender;
// Sanity-check the provided function selector
require(
batchFillFunctionSelector == IExchange(address(0)).batchFillOrders.selector ||
batchFillFunctionSelector == IExchange(address(0)).batchFillOrKillOrders.selector ||
batchFillFunctionSelector == IExchange(address(0)).batchFillOrdersNoThrow.selector,
"UNRECOGNIZED_FUNCTION_SELECTOR"
);
// Perform the batch fill
bytes memory batchFillCalldata = abi.encodeWithSelector(
batchFillFunctionSelector,
orders,
takerAssetFillAmounts,
signatures
);
// solhint-disable-next-line avoid-call-value
(bool didSucceed, bytes memory returnData) = _EXCHANGE.call.value(msg.value)(batchFillCalldata);
if (didSucceed) {
// solhint-disable-next-line indent
fillResults = abi.decode(returnData, (LibFillResults.FillResults[]));
} else {
assembly {
revert(add(returnData, 32), mload(returnData))
}
}
// Transfer maker assets to taker
for (uint256 i = 0; i < orders.length; i++) {
orders[i].makerAssetData.transferOut(fillResults[i].makerAssetFilledAmount);
}
// Clear storage
delete _cachedAssetData;
_cacheIndex = 0;
_sender = address(0);
return fillResults;
}
}

View File

@@ -0,0 +1,59 @@
/*
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;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
// solhint-disable space-after-comma
interface IBroker {
function brokerTrade(
bytes[] calldata brokeredAssets,
LibOrder.Order calldata order,
uint256 takerAssetFillAmount,
bytes calldata signature,
bytes4 fillFunctionSelector
)
external
payable
returns (LibFillResults.FillResults memory fillResults);
function batchBrokerTrade(
bytes[] calldata brokeredAssets,
LibOrder.Order[] calldata orders,
uint256[] calldata takerAssetFillAmounts,
bytes[] calldata signatures,
bytes4 batchFillFunctionSelector
)
external
payable
returns (LibFillResults.FillResults[] memory fillResults);
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata /* ids */,
uint256[] calldata amounts,
bytes calldata data
)
external;
}

View File

@@ -0,0 +1,29 @@
/*
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;
pragma experimental ABIEncoderV2;
interface IGodsUnchained {
function getDetails(uint256 tokenId)
external
view
returns (uint16 proto, uint8 quality);
}

View File

@@ -0,0 +1,31 @@
/*
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;
pragma experimental ABIEncoderV2;
interface IPropertyValidator {
function checkBrokerAsset(
bytes calldata assetData,
bytes calldata propertyData
)
external
view;
}

View File

@@ -0,0 +1,61 @@
/*
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;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "../interfaces/IGodsUnchained.sol";
import "../interfaces/IPropertyValidator.sol";
contract GodsUnchainedValidator is
IPropertyValidator
{
IGodsUnchained internal GODS_UNCHAINED; // solhint-disable-line var-name-mixedcase
using LibBytes for bytes;
constructor(address _godsUnchained)
public
{
GODS_UNCHAINED = IGodsUnchained(_godsUnchained);
}
function checkBrokerAsset(
bytes calldata assetData,
bytes calldata propertyData
)
external
view
{
(uint16 expectedProto, uint8 expectedQuality) = abi.decode(
propertyData,
(uint16, uint8)
);
// Decode and validate asset data.
address token = assetData.readAddress(16);
require(token == address(GODS_UNCHAINED), "TOKEN_ADDRESS_MISMATCH");
uint256 tokenId = assetData.readUint256(36);
(uint16 proto, uint8 quality) = GODS_UNCHAINED.getDetails(tokenId);
require(proto == expectedProto, "PROTO_MISMATCH");
require(quality == expectedQuality, "QUALITY_MISMATCH");
}
}

View File

@@ -0,0 +1,55 @@
/*
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;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc721/contracts/test/DummyERC721Token.sol";
import "../src/interfaces/IGodsUnchained.sol";
contract TestGodsUnchained is
IGodsUnchained,
DummyERC721Token
{
mapping (uint256 => uint16) internal _protoByTokenId;
mapping (uint256 => uint8) internal _qualityByTokenId;
constructor (
string memory _name,
string memory _symbol
)
public
DummyERC721Token(_name, _symbol)
{} // solhint-disable-line no-empty-blocks
function setTokenProperties(uint256 tokenId, uint16 proto, uint8 quality)
external
{
_protoByTokenId[tokenId] = proto;
_qualityByTokenId[tokenId] = quality;
}
function getDetails(uint256 tokenId)
external
view
returns (uint16 proto, uint8 quality)
{
return (_protoByTokenId[tokenId], _qualityByTokenId[tokenId]);
}
}

View File

@@ -39,7 +39,7 @@
},
"config": {
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(Broker).json"
"abis": "./test/generated-artifacts/@(Broker|GodsUnchainedValidator|IBroker|IGodsUnchained|IPropertyValidator).json"
},
"repository": {
"type": "git",
@@ -52,10 +52,9 @@
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
"devDependencies": {
"@0x/abi-gen": "^5.1.0",
"@0x/contracts-asset-proxy": "^3.1.1",
"@0x/contracts-erc1155": "^2.0.4",
"@0x/contracts-erc721": "^3.0.4",
"@0x/contracts-exchange": "^3.1.0",
"@0x/contracts-exchange-forwarder": "^4.0.4",
"@0x/contracts-exchange-libs": "^4.1.0",
"@0x/contracts-gen": "^2.0.4",
"@0x/contracts-test-utils": "^5.1.1",

View File

@@ -6,4 +6,14 @@
import { ContractArtifact } from 'ethereum-types';
import * as Broker from '../generated-artifacts/Broker.json';
export const artifacts = { Broker: Broker as ContractArtifact };
import * as GodsUnchainedValidator from '../generated-artifacts/GodsUnchainedValidator.json';
import * as IBroker from '../generated-artifacts/IBroker.json';
import * as IGodsUnchained from '../generated-artifacts/IGodsUnchained.json';
import * as IPropertyValidator from '../generated-artifacts/IPropertyValidator.json';
export const artifacts = {
Broker: Broker as ContractArtifact,
IBroker: IBroker as ContractArtifact,
IGodsUnchained: IGodsUnchained as ContractArtifact,
IPropertyValidator: IPropertyValidator as ContractArtifact,
GodsUnchainedValidator: GodsUnchainedValidator as ContractArtifact,
};

View File

@@ -1,2 +1,2 @@
export { artifacts } from './artifacts';
export { BrokerContract } from './wrappers';
export { BrokerContract, GodsUnchainedValidatorContract } from './wrappers';

View File

@@ -4,3 +4,7 @@
* -----------------------------------------------------------------------------
*/
export * from '../generated-wrappers/broker';
export * from '../generated-wrappers/gods_unchained_validator';
export * from '../generated-wrappers/i_broker';
export * from '../generated-wrappers/i_gods_unchained';
export * from '../generated-wrappers/i_property_validator';

View File

@@ -6,4 +6,14 @@
import { ContractArtifact } from 'ethereum-types';
import * as Broker from '../test/generated-artifacts/Broker.json';
export const artifacts = { Broker: Broker as ContractArtifact };
import * as GodsUnchainedValidator from '../test/generated-artifacts/GodsUnchainedValidator.json';
import * as IBroker from '../test/generated-artifacts/IBroker.json';
import * as IGodsUnchained from '../test/generated-artifacts/IGodsUnchained.json';
import * as IPropertyValidator from '../test/generated-artifacts/IPropertyValidator.json';
export const artifacts = {
Broker: Broker as ContractArtifact,
IBroker: IBroker as ContractArtifact,
IGodsUnchained: IGodsUnchained as ContractArtifact,
IPropertyValidator: IPropertyValidator as ContractArtifact,
GodsUnchainedValidator: GodsUnchainedValidator as ContractArtifact,
};

View File

@@ -4,3 +4,7 @@
* -----------------------------------------------------------------------------
*/
export * from '../test/generated-wrappers/broker';
export * from '../test/generated-wrappers/gods_unchained_validator';
export * from '../test/generated-wrappers/i_broker';
export * from '../test/generated-wrappers/i_gods_unchained';
export * from '../test/generated-wrappers/i_property_validator';

View File

@@ -2,6 +2,17 @@
"extends": "../../tsconfig",
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
"files": ["generated-artifacts/Broker.json", "test/generated-artifacts/Broker.json"],
"files": [
"generated-artifacts/Broker.json",
"generated-artifacts/GodsUnchainedValidator.json",
"generated-artifacts/IBroker.json",
"generated-artifacts/IGodsUnchained.json",
"generated-artifacts/IPropertyValidator.json",
"test/generated-artifacts/Broker.json",
"test/generated-artifacts/GodsUnchainedValidator.json",
"test/generated-artifacts/IBroker.json",
"test/generated-artifacts/IGodsUnchained.json",
"test/generated-artifacts/IPropertyValidator.json"
],
"exclude": ["./deploy/solc/solc_bin"]
}