Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

Add decoders for broker and stop-limit data #2484

Merged
merged 3 commits into from
Feb 15, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions contracts/broker/CHANGELOG.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
[
{
"version": "1.1.0",
"changes": [
{
"note": "Added decoders for broker data",
"pr": 2484
}
]
},
{
"timestamp": 1581204851,
"version": "1.0.2",
Expand Down
95 changes: 56 additions & 39 deletions contracts/broker/src/gods_unchained_utils.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,59 @@
import { assetDataUtils } from '@0x/order-utils';
import { ERC1155AssetData } from '@0x/types';
import { AbiEncoder, BigNumber } from '@0x/utils';

export const godsUnchainedUtils = {
/**
* Encodes the given proto and quality into the bytes format expected by the GodsUnchainedValidator.
*/
encodePropertyData(proto: BigNumber, quality: BigNumber): string {
return AbiEncoder.create([{ name: 'proto', type: 'uint16' }, { name: 'quality', type: 'uint8' }]).encode({
proto,
quality,
});
},
/**
* Encodes the given proto and quality into ERC1155 asset data to be used as the takerAssetData
* of a property-based GodsUnchained order. Must also provide the addresses of the Broker,
* GodsUnchained, and GodsUnchainedValidator contracts. The optional bundleSize parameter specifies
* how many cards are expected for each "unit" of the takerAssetAmount. For example, If the
* takerAssetAmount is 3 and the bundleSize is 2, the taker must provide 2, 4, or 6 cards
* with the given proto and quality to fill the order. If an odd number is provided, the fill fails.
*/
encodeBrokerAssetData(
brokerAddress: string,
godsUnchainedAddress: string,
validatorAddress: string,
proto: BigNumber,
quality: BigNumber,
bundleSize: number = 1,
): string {
const dataEncoder = AbiEncoder.create([
{ name: 'godsUnchainedAddress', type: 'address' },
{ name: 'validatorAddress', type: 'address' },
{ name: 'propertyData', type: 'bytes' },
]);
const propertyData = AbiEncoder.create([
{ name: 'proto', type: 'uint16' },
{ name: 'quality', type: 'uint8' },
]).encode({ proto, quality });
const data = dataEncoder.encode({ godsUnchainedAddress, validatorAddress, propertyData });
return assetDataUtils.encodeERC1155AssetData(brokerAddress, [], [new BigNumber(bundleSize)], data);
},
};
export interface GodsUnchainedProperties {
proto: BigNumber;
quality: BigNumber;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would the abi encoder accept these if they were just numbers?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yeah, changed to BigNumber | number

}

const propertyDataEncoder = AbiEncoder.create([{ name: 'proto', type: 'uint16' }, { name: 'quality', type: 'uint8' }]);
const brokerDataEncoder = AbiEncoder.create([
{ name: 'godsUnchainedAddress', type: 'address' },
{ name: 'validatorAddress', type: 'address' },
{ name: 'propertyData', type: 'bytes' },
]);

/**
* Encodes the given proto and quality into the bytes format expected by the GodsUnchainedValidator.
*/
export function encodePropertyData(properties: GodsUnchainedProperties): string {
return propertyDataEncoder.encode(properties);
}

/**
* Encodes the given proto and quality into ERC1155 asset data to be used as the takerAssetData
* of a property-based GodsUnchained order. Must also provide the addresses of the Broker,
* GodsUnchained, and GodsUnchainedValidator contracts. The optional bundleSize parameter specifies
* how many cards are expected for each "unit" of the takerAssetAmount. For example, If the
* takerAssetAmount is 3 and the bundleSize is 2, the taker must provide 2, 4, or 6 cards
* with the given proto and quality to fill the order. If an odd number is provided, the fill fails.
*/
export function encodeBrokerAssetData(
brokerAddress: string,
godsUnchainedAddress: string,
validatorAddress: string,
properties: GodsUnchainedProperties,
bundleSize: number = 1,
): string {
const propertyData = propertyDataEncoder.encode(properties);
const brokerData = brokerDataEncoder.encode({ godsUnchainedAddress, validatorAddress, propertyData });
return assetDataUtils.encodeERC1155AssetData(brokerAddress, [], [new BigNumber(bundleSize)], brokerData);
}

/**
* Decodes proto and quality from the bytes format expected by the GodsUnchainedValidator.
*/
export function decodePropertyData(propertyData: string): GodsUnchainedProperties {
return propertyDataEncoder.decode(propertyData);
}

/**
* Decodes proto and quality from the ERC1155 takerAssetData of a property-based GodsUnchained order.
*/
export function decodeBrokerAssetData(brokerAssetData: string): GodsUnchainedProperties {
// tslint:disable-next-line:no-unnecessary-type-assertion
const { callbackData: brokerData } = assetDataUtils.decodeAssetDataOrThrow(brokerAssetData) as ERC1155AssetData;
const { propertyData } = brokerDataEncoder.decode(brokerData);
return decodePropertyData(propertyData);
}
2 changes: 1 addition & 1 deletion contracts/broker/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export { artifacts } from './artifacts';
export { BrokerContract, GodsUnchainedValidatorContract, TestGodsUnchainedContract } from './wrappers';
export { godsUnchainedUtils } from './gods_unchained_utils';
export * from './gods_unchained_utils';
export { BrokerRevertErrors } from '@0x/utils';
export {
ContractArtifact,
Expand Down
4 changes: 2 additions & 2 deletions contracts/broker/test/gods_unchained_validator_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { blockchainTests, constants, expect, getRandomInteger } from '@0x/contra
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';

import { godsUnchainedUtils } from '../src/gods_unchained_utils';
import { encodePropertyData } from '../src/gods_unchained_utils';

import { artifacts } from './artifacts';
import { GodsUnchainedValidatorContract, TestGodsUnchainedContract } from './wrappers';
Expand Down Expand Up @@ -33,7 +33,7 @@ blockchainTests.resets('GodsUnchainedValidator unit tests', env => {
describe('checkBrokerAsset', () => {
const proto = new BigNumber(42);
const quality = new BigNumber(7);
const propertyData = godsUnchainedUtils.encodePropertyData(proto, quality);
const propertyData = encodePropertyData({ proto, quality });

it('succeeds if assetData proto and quality match propertyData', async () => {
const tokenId = getRandomInteger(0, constants.MAX_UINT256);
Expand Down
4 changes: 4 additions & 0 deletions contracts/integrations/CHANGELOG.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
{
"note": "Fixed the mainnet dYdX Bridge tests.",
"pr": 2479
},
{
"note": "Addeded decoders for stop-limit data",
"pr": 2484
}
]
},
Expand Down
55 changes: 39 additions & 16 deletions contracts/integrations/src/chainlink_utils.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,58 @@
import { constants } from '@0x/contracts-test-utils';
import { assetDataUtils } from '@0x/order-utils';
import { StaticCallAssetData } from '@0x/types';
import { AbiEncoder, BigNumber } from '@0x/utils';

export interface StopLimitParameters {
oracle: string;
minPrice: BigNumber;
maxPrice: BigNumber;
}

const stopLimitDataEncoder = AbiEncoder.create([
{ name: 'oracle', type: 'address' },
{ name: 'minPrice', type: 'int256' },
{ name: 'maxPrice', type: 'int256' },
]);

const stopLimitMethodEncoder = AbiEncoder.createMethod('checkStopLimit', [{ name: 'stopLimitData', type: 'bytes' }]);

/**
* Encodes the given stop limit data parameters into the bytes format expected by the
* ChainlinkStopLimit contract.
*/
export function encodeChainlinkStopLimitData(oracle: string, minPrice: BigNumber, maxPrice: BigNumber): string {
const encoder = AbiEncoder.create([
{ name: 'oracle', type: 'address' },
{ name: 'minPrice', type: 'int256' },
{ name: 'maxPrice', type: 'int256' },
]);
return encoder.encode({ oracle, minPrice, maxPrice });
export function encodeChainlinkStopLimitData(params: StopLimitParameters): string {
return stopLimitDataEncoder.encode(params);
}

/**
* Encodes the given stop limit data parameters into StaticCall asset data so that it can be used
* in a 0x order.
*/
export function encodeStopLimitStaticCallData(
chainlinkStopLimitAddress: string,
oracle: string,
minPrice: BigNumber,
maxPrice: BigNumber,
): string {
const staticCallData = AbiEncoder.createMethod('checkStopLimit', [{ name: 'stopLimitData', type: 'bytes' }]).encode(
{ stopLimitData: encodeChainlinkStopLimitData(oracle, minPrice, maxPrice) },
);
export function encodeStopLimitStaticCallData(chainlinkStopLimitAddress: string, params: StopLimitParameters): string {
const staticCallData = stopLimitMethodEncoder.encode({
stopLimitData: encodeChainlinkStopLimitData(params),
});
return assetDataUtils.encodeStaticCallAssetData(
chainlinkStopLimitAddress,
staticCallData,
constants.KECCAK256_NULL,
);
}

/**
* Decodes stop limit data parameters from the bytes format expected by the ChainlinkStopLimit contract.
*/
export function decodeChainlinkStopLimitData(stopLimitData: string): StopLimitParameters {
return stopLimitDataEncoder.decode(stopLimitData);
}

/**
* Decodes stop limit data parameters from stop limit StaticCall asset data.
*/
export function decodeStopLimitStaticCallData(assetData: string): StopLimitParameters {
// tslint:disable-next-line:no-unnecessary-type-assertion
const { staticCallData } = assetDataUtils.decodeAssetDataOrThrow(assetData) as StaticCallAssetData;
const stopLimitData = stopLimitMethodEncoder.strictDecode<string>(staticCallData);
return decodeChainlinkStopLimitData(stopLimitData);
}
66 changes: 43 additions & 23 deletions contracts/integrations/test/broker/broker_test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import {
artifacts as BrokerArtifacts,
BrokerContract,
godsUnchainedUtils,
decodeBrokerAssetData,
decodePropertyData,
encodeBrokerAssetData,
encodePropertyData,
GodsUnchainedValidatorContract,
TestGodsUnchainedContract,
} from '@0x/contracts-broker';
import { DummyERC721TokenContract } from '@0x/contracts-erc721';
import { ExchangeFunctionName, ExchangeRevertErrors } from '@0x/contracts-exchange';
import { ReferenceFunctions } from '@0x/contracts-exchange-libs';
import { blockchainTests, constants, expect } from '@0x/contracts-test-utils';
import { blockchainTests, constants, expect, getRandomInteger, randomAddress } from '@0x/contracts-test-utils';
import { assetDataUtils } from '@0x/order-utils';
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
Expand Down Expand Up @@ -72,13 +75,10 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
deployment.tokens.weth.address,
);

const takerAssetData = godsUnchainedUtils.encodeBrokerAssetData(
broker.address,
godsUnchained.address,
validator.address,
makerSpecifiedProto,
makerSpecifiedQuality,
);
const takerAssetData = encodeBrokerAssetData(broker.address, godsUnchained.address, validator.address, {
proto: makerSpecifiedProto,
quality: makerSpecifiedQuality,
});

const orderConfig = {
feeRecipientAddress: constants.NULL_ADDRESS,
Expand Down Expand Up @@ -530,22 +530,16 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {

orders = [
await maker.signOrderAsync({
takerAssetData: godsUnchainedUtils.encodeBrokerAssetData(
broker.address,
godsUnchained.address,
validator.address,
firstOrderProto,
firstOrderQuality,
),
takerAssetData: encodeBrokerAssetData(broker.address, godsUnchained.address, validator.address, {
proto: firstOrderProto,
quality: firstOrderQuality,
}),
}),
await maker.signOrderAsync({
takerAssetData: godsUnchainedUtils.encodeBrokerAssetData(
broker.address,
godsUnchained.address,
validator.address,
secondOrderProto,
secondOrderQuality,
),
takerAssetData: encodeBrokerAssetData(broker.address, godsUnchained.address, validator.address, {
proto: secondOrderProto,
quality: secondOrderQuality,
}),
}),
];
});
Expand Down Expand Up @@ -718,4 +712,30 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
balanceStore.assertEquals(expectedBalances);
});
});

describe('Data encoding/decoding tools', () => {
const MAX_UINT8 = new BigNumber(2).exponentiatedBy(8).minus(1);
const MAX_UINT16 = new BigNumber(2).exponentiatedBy(16).minus(1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Seems clearer to just do like new BigNumber(2**8-1)


it('correctly decodes property data', async () => {
const properties = {
proto: getRandomInteger(0, MAX_UINT16),
quality: getRandomInteger(0, MAX_UINT8),
};
const encoded = encodePropertyData(properties);
const decoded = decodePropertyData(encoded);
expect(decoded.proto).to.bignumber.equal(properties.proto);
expect(decoded.quality).to.bignumber.equal(properties.quality);
});
it('correctly decodes broker asset data', async () => {
const properties = {
proto: getRandomInteger(0, MAX_UINT16),
quality: getRandomInteger(0, MAX_UINT8),
};
const encoded = encodeBrokerAssetData(randomAddress(), randomAddress(), randomAddress(), properties);
const decoded = decodeBrokerAssetData(encoded);
expect(decoded.proto).to.bignumber.equal(properties.proto);
expect(decoded.quality).to.bignumber.equal(properties.quality);
});
});
}); // tslint:disable-line:max-file-line-count
Loading