Add decoders for broker and stop-limit data (#2484)
* Add decoders for broker and stop-limit data * update changelogs * Address comments
This commit is contained in:
@@ -9,6 +9,10 @@
|
||||
{
|
||||
"note": "Fixed the mainnet dYdX Bridge tests.",
|
||||
"pr": 2479
|
||||
},
|
||||
{
|
||||
"note": "Addeded decoders for stop-limit data",
|
||||
"pr": 2484
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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';
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
}),
|
||||
}),
|
||||
];
|
||||
});
|
||||
@@ -718,4 +712,30 @@ blockchainTests.resets('Broker <> Gods Unchained integration tests', env => {
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Data encoding/decoding tools', () => {
|
||||
const MAX_UINT8 = 2 ** 8 - 1;
|
||||
const MAX_UINT16 = 2 ** 16 - 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
|
||||
|
@@ -1,10 +1,22 @@
|
||||
import { ExchangeRevertErrors } from '@0x/contracts-exchange';
|
||||
import { blockchainTests, constants, expect, orderHashUtils } from '@0x/contracts-test-utils';
|
||||
import {
|
||||
blockchainTests,
|
||||
constants,
|
||||
expect,
|
||||
getRandomInteger,
|
||||
orderHashUtils,
|
||||
randomAddress,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { assetDataUtils } from '@0x/order-utils';
|
||||
import { SignedOrder } from '@0x/types';
|
||||
import { BigNumber, StringRevertError } from '@0x/utils';
|
||||
|
||||
import { encodeStopLimitStaticCallData } from '../../src/chainlink_utils';
|
||||
import {
|
||||
decodeChainlinkStopLimitData,
|
||||
decodeStopLimitStaticCallData,
|
||||
encodeChainlinkStopLimitData,
|
||||
encodeStopLimitStaticCallData,
|
||||
} from '../../src/chainlink_utils';
|
||||
|
||||
import { artifacts } from '../artifacts';
|
||||
import { Actor } from '../framework/actors/base';
|
||||
@@ -21,6 +33,7 @@ blockchainTests.resets('Chainlink stop-limit order tests', env => {
|
||||
let initialBalances: LocalBalanceStore;
|
||||
|
||||
let chainLinkAggregator: TestChainlinkAggregatorContract;
|
||||
let chainlinkStopLimit: ChainlinkStopLimitContract;
|
||||
|
||||
let maker: Maker;
|
||||
let taker: Taker;
|
||||
@@ -38,7 +51,7 @@ blockchainTests.resets('Chainlink stop-limit order tests', env => {
|
||||
});
|
||||
const [makerToken, takerToken] = deployment.tokens.erc20;
|
||||
|
||||
const chainlinkStopLimit = await ChainlinkStopLimitContract.deployFrom0xArtifactAsync(
|
||||
chainlinkStopLimit = await ChainlinkStopLimitContract.deployFrom0xArtifactAsync(
|
||||
artifacts.ChainlinkStopLimit,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
@@ -55,12 +68,11 @@ blockchainTests.resets('Chainlink stop-limit order tests', env => {
|
||||
[new BigNumber(1), new BigNumber(1)],
|
||||
[
|
||||
assetDataUtils.encodeERC20AssetData(makerToken.address),
|
||||
encodeStopLimitStaticCallData(
|
||||
chainlinkStopLimit.address,
|
||||
chainLinkAggregator.address,
|
||||
encodeStopLimitStaticCallData(chainlinkStopLimit.address, {
|
||||
oracle: chainLinkAggregator.address,
|
||||
minPrice,
|
||||
maxPrice,
|
||||
),
|
||||
}),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -104,50 +116,77 @@ blockchainTests.resets('Chainlink stop-limit order tests', env => {
|
||||
Actor.reset();
|
||||
});
|
||||
|
||||
it('fillOrder reverts if price < minPrice', async () => {
|
||||
await chainLinkAggregator.setPrice(minPrice.minus(1)).awaitTransactionSuccessAsync();
|
||||
const tx = taker.fillOrderAsync(order, order.takerAssetAmount);
|
||||
const expectedError = new ExchangeRevertErrors.AssetProxyTransferError(
|
||||
orderHashUtils.getOrderHashHex(order),
|
||||
order.makerAssetData,
|
||||
new StringRevertError('ChainlinkStopLimit/OUT_OF_PRICE_RANGE').encode(),
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
describe('filling stop-limit orders', () => {
|
||||
it('fillOrder reverts if price < minPrice', async () => {
|
||||
await chainLinkAggregator.setPrice(minPrice.minus(1)).awaitTransactionSuccessAsync();
|
||||
const tx = taker.fillOrderAsync(order, order.takerAssetAmount);
|
||||
const expectedError = new ExchangeRevertErrors.AssetProxyTransferError(
|
||||
orderHashUtils.getOrderHashHex(order),
|
||||
order.makerAssetData,
|
||||
new StringRevertError('ChainlinkStopLimit/OUT_OF_PRICE_RANGE').encode(),
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('fillOrder reverts price > maxPrice', async () => {
|
||||
await chainLinkAggregator.setPrice(maxPrice.plus(1)).awaitTransactionSuccessAsync();
|
||||
const tx = taker.fillOrderAsync(order, order.takerAssetAmount);
|
||||
const expectedError = new ExchangeRevertErrors.AssetProxyTransferError(
|
||||
orderHashUtils.getOrderHashHex(order),
|
||||
order.makerAssetData,
|
||||
new StringRevertError('ChainlinkStopLimit/OUT_OF_PRICE_RANGE').encode(),
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('fillOrder succeeds if price = minPrice', async () => {
|
||||
await chainLinkAggregator.setPrice(minPrice).awaitTransactionSuccessAsync();
|
||||
const receipt = await taker.fillOrderAsync(order, order.takerAssetAmount);
|
||||
const expectedBalances = LocalBalanceStore.create(initialBalances);
|
||||
expectedBalances.simulateFills([order], taker.address, receipt, deployment, DeploymentManager.protocolFee);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
});
|
||||
it('fillOrder succeeds if price = maxPrice', async () => {
|
||||
await chainLinkAggregator.setPrice(maxPrice).awaitTransactionSuccessAsync();
|
||||
const receipt = await taker.fillOrderAsync(order, order.takerAssetAmount);
|
||||
const expectedBalances = LocalBalanceStore.create(initialBalances);
|
||||
expectedBalances.simulateFills([order], taker.address, receipt, deployment, DeploymentManager.protocolFee);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
});
|
||||
it('fillOrder succeeds if minPrice < price < maxPrice', async () => {
|
||||
await chainLinkAggregator
|
||||
.setPrice(minPrice.plus(maxPrice).dividedToIntegerBy(2))
|
||||
.awaitTransactionSuccessAsync();
|
||||
const receipt = await taker.fillOrderAsync(order, order.takerAssetAmount);
|
||||
const expectedBalances = LocalBalanceStore.create(initialBalances);
|
||||
expectedBalances.simulateFills([order], taker.address, receipt, deployment, DeploymentManager.protocolFee);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
});
|
||||
});
|
||||
it('fillOrder reverts price > maxPrice', async () => {
|
||||
await chainLinkAggregator.setPrice(maxPrice.plus(1)).awaitTransactionSuccessAsync();
|
||||
const tx = taker.fillOrderAsync(order, order.takerAssetAmount);
|
||||
const expectedError = new ExchangeRevertErrors.AssetProxyTransferError(
|
||||
orderHashUtils.getOrderHashHex(order),
|
||||
order.makerAssetData,
|
||||
new StringRevertError('ChainlinkStopLimit/OUT_OF_PRICE_RANGE').encode(),
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('fillOrder succeeds if price = minPrice', async () => {
|
||||
await chainLinkAggregator.setPrice(minPrice).awaitTransactionSuccessAsync();
|
||||
const receipt = await taker.fillOrderAsync(order, order.takerAssetAmount);
|
||||
const expectedBalances = LocalBalanceStore.create(initialBalances);
|
||||
expectedBalances.simulateFills([order], taker.address, receipt, deployment, DeploymentManager.protocolFee);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
});
|
||||
it('fillOrder succeeds if price = maxPrice', async () => {
|
||||
await chainLinkAggregator.setPrice(maxPrice).awaitTransactionSuccessAsync();
|
||||
const receipt = await taker.fillOrderAsync(order, order.takerAssetAmount);
|
||||
const expectedBalances = LocalBalanceStore.create(initialBalances);
|
||||
expectedBalances.simulateFills([order], taker.address, receipt, deployment, DeploymentManager.protocolFee);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
});
|
||||
it('fillOrder succeeds if minPrice < price < maxPrice', async () => {
|
||||
await chainLinkAggregator
|
||||
.setPrice(minPrice.plus(maxPrice).dividedToIntegerBy(2))
|
||||
.awaitTransactionSuccessAsync();
|
||||
const receipt = await taker.fillOrderAsync(order, order.takerAssetAmount);
|
||||
const expectedBalances = LocalBalanceStore.create(initialBalances);
|
||||
expectedBalances.simulateFills([order], taker.address, receipt, deployment, DeploymentManager.protocolFee);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
|
||||
describe('Data encoding/decoding tools', () => {
|
||||
const MAX_INT256 = new BigNumber(2).exponentiatedBy(255).minus(1);
|
||||
|
||||
it('correctly decodes chainlink stop-limit params', async () => {
|
||||
const params = {
|
||||
oracle: randomAddress(),
|
||||
minPrice: getRandomInteger(0, MAX_INT256),
|
||||
maxPrice: getRandomInteger(0, MAX_INT256),
|
||||
};
|
||||
const encoded = encodeChainlinkStopLimitData(params);
|
||||
const decoded = decodeChainlinkStopLimitData(encoded);
|
||||
expect(decoded).to.deep.equal(params);
|
||||
});
|
||||
it('correctly decodes stop-limit assetData', async () => {
|
||||
const params = {
|
||||
oracle: randomAddress(),
|
||||
minPrice: getRandomInteger(0, MAX_INT256),
|
||||
maxPrice: getRandomInteger(0, MAX_INT256),
|
||||
};
|
||||
const encoded = encodeStopLimitStaticCallData(chainlinkStopLimit.address, params);
|
||||
const decoded = decodeStopLimitStaticCallData(encoded);
|
||||
expect(decoded).to.deep.equal(params);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user