Skip to content
This repository has been archived by the owner on Oct 11, 2024. It is now read-only.

orderwatch,rpc: Supply parsed contract events in emitted OrderEvents #420

Merged
merged 17 commits into from
Oct 3, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

This changelog is a work in progress and may contain notes for versions which have not actually been released. Check the [Releases](https://github.com/0xProject/0x-mesh/releases) page to see full release notes and more information about the latest released versions.

## v4.1.0-beta
## v5.0.0-beta

### Breaking changes 🛠

- Removes the `txHashes` key in the `OrderEvent`s emitted from the `orders` JSON-RPC subscription and replaced it with `contractEvents`, an array of decoded order-relevant contract events. Parsing these events allows callers to find every discrete order fill/cancel event. ([#420](https://github.com/0xProject/0x-mesh/pull/420))
- Renames the `Kind` key in `OrderEvent` to `EndState` to better elucidate that it represents the aggregate change to the orders state since it was last re-validated. As an end state, it does not capture any possible intermediate states the order might have been in since the last re-validation. Intermediate states can be inferred from the `contractEvents` included ([#420](https://github.com/0xProject/0x-mesh/pull/420))

### Features ✅

Expand Down
282 changes: 278 additions & 4 deletions browser/ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,185 @@ interface WrapperSignedOrder {
signature: string;
}

export interface ERC20TransferEvent {
from: string;
to: string;
value: BigNumber;
}

interface WrapperERC20TransferEvent {
from: string;
to: string;
value: string;
}

export interface ERC20ApprovalEvent {
owner: string;
spender: string;
value: BigNumber;
}

interface WrapperERC20ApprovalEvent {
owner: string;
spender: string;
value: string;
}

export interface ERC721TransferEvent {
from: string;
to: string;
tokenId: BigNumber;
}

interface WrapperERC721TransferEvent {
from: string;
to: string;
tokenId: string;
}

export interface ERC721ApprovalEvent {
owner: string;
approved: string;
tokenId: BigNumber;
}

interface WrapperERC721ApprovalEvent {
owner: string;
approved: string;
tokenId: string;
}

export interface ERC721ApprovalForAllEvent {
owner: string;
operator: string;
approved: boolean;
}

export interface ExchangeFillEvent {
makerAddress: string;
takerAddress: string;
senderAddress: string;
feeRecipientAddress: string;
makerAssetFilledAmount: BigNumber;
takerAssetFilledAmount: BigNumber;
makerFeePaid: BigNumber;
takerFeePaid: BigNumber;
orderHash: string;
makerAssetData: string;
takerAssetData: string;
}

interface WrapperExchangeFillEvent {
makerAddress: string;
takerAddress: string;
senderAddress: string;
feeRecipientAddress: string;
makerAssetFilledAmount: string;
takerAssetFilledAmount: string;
makerFeePaid: string;
takerFeePaid: string;
orderHash: string;
makerAssetData: string;
takerAssetData: string;
}

export interface ExchangeCancelEvent {
makerAddress: string;
senderAddress: string;
feeRecipientAddress: string;
orderHash: string;
makerAssetData: string;
takerAssetData: string;
}

export interface ExchangeCancelUpToEvent {
makerAddress: string;
senderAddress: string;
orderEpoch: BigNumber;
}

interface WrapperExchangeCancelUpToEvent {
makerAddress: string;
senderAddress: string;
orderEpoch: string;
}

export interface WethWithdrawalEvent {
owner: string;
value: BigNumber;
}

interface WrapperWethWithdrawalEvent {
owner: string;
value: string;
}

export interface WethDepositEvent {
owner: string;
value: BigNumber;
}

interface WrapperWethDepositEvent {
owner: string;
value: string;
}

enum ContractEventKind {
ERC20TransferEvent = 'ERC20TransferEvent',
ERC20ApprovalEvent = 'ERC20ApprovalEvent',
ERC721TransferEvent = 'ERC721TransferEvent',
ERC721ApprovalEvent = 'ERC721ApprovalEvent',
ExchangeFillEvent = 'ExchangeFillEvent',
ExchangeCancelEvent = 'ExchangeCancelEvent',
ExchangeCancelUpToEvent = 'ExchangeCancelUpToEvent',
WethDepositEvent = 'WethDepositEvent',
WethWithdrawalEvent = 'WethWithdrawalEvent',
}

type WrapperContractEventParameters = WrapperERC20TransferEvent | WrapperERC20ApprovalEvent | WrapperERC721TransferEvent | WrapperERC721ApprovalEvent | WrapperExchangeFillEvent | WrapperExchangeCancelUpToEvent | WrapperWethWithdrawalEvent | WrapperWethDepositEvent | ERC721ApprovalForAllEvent | ExchangeCancelEvent;

type ContractEventParameters = ERC20TransferEvent | ERC20ApprovalEvent | ERC721TransferEvent | ERC721ApprovalEvent | ExchangeFillEvent | ExchangeCancelUpToEvent | WethWithdrawalEvent | WethDepositEvent | ERC721ApprovalForAllEvent | ExchangeCancelEvent;

export interface ContractEvent {
blockHash: string;
txHash: string;
txIndex: number;
logIndex: number;
isRemoved: string;
address: string;
kind: ContractEventKind;
parameters: ContractEventParameters;
}

// The type for order events exposed by MeshWrapper.
interface WrapperContractEvent {
blockHash: string;
txHash: string;
txIndex: number;
logIndex: number;
isRemoved: string;
address: string;
kind: string;
parameters: WrapperContractEventParameters;
}

export enum OrderEventEndState {
Invalid = 'INVALID',
Added = 'ADDED',
Filled = 'FILLED',
FullyFilled = 'FULLY_FILLED',
Cancelled = 'CANCELLED',
Expired = 'EXPIRED',
Unfunded = 'UNFUNDED',
FillabilityIncreased = 'FILLABILITY_INCREASED',
}

interface WrapperOrderEvent {
orderHash: string;
signedOrder: WrapperSignedOrder;
kind: string;
endState: OrderEventEndState;
fillableTakerAssetAmount: string;
txHashes: string[];
contractEvents: WrapperContractEvent[];
}

/**
Expand All @@ -141,9 +313,9 @@ interface WrapperOrderEvent {
export interface OrderEvent {
orderHash: string;
signedOrder: SignedOrder;
kind: string;
endState: OrderEventEndState;
fillableTakerAssetAmount: BigNumber;
txHashes: string[];
contractEvents: ContractEvent[];
}

// The type for validation results exposed by MeshWrapper.
Expand Down Expand Up @@ -367,6 +539,107 @@ function wrapperSignedOrderToSignedOrder(wrapperSignedOrder: WrapperSignedOrder)
};
}

function wrapperContractEventsToContractEvents(wrapperContractEvents: WrapperContractEvent[]): ContractEvent[] {
const contractEvents: ContractEvent[] = [];
if (wrapperContractEvents === null) {
return contractEvents;
}
wrapperContractEvents.forEach(wrapperContractEvent => {
const kind = wrapperContractEvent.kind as ContractEventKind;
const rawParameters = wrapperContractEvent.parameters;
let parameters: ContractEventParameters;
switch (kind) {
case ContractEventKind.ERC20TransferEvent:
const erc20TransferEvent = rawParameters as WrapperERC20TransferEvent;
parameters = {
from: erc20TransferEvent.from,
to: erc20TransferEvent.to,
value: new BigNumber(erc20TransferEvent.value),
};
break;
case ContractEventKind.ERC20ApprovalEvent:
const erc20ApprovalEvent = rawParameters as WrapperERC20ApprovalEvent;
parameters = {
owner: erc20ApprovalEvent.owner,
spender: erc20ApprovalEvent.spender,
value: new BigNumber(erc20ApprovalEvent.value),
};
break;
case ContractEventKind.ERC721TransferEvent:
const erc721TransferEvent = rawParameters as WrapperERC721TransferEvent;
parameters = {
from: erc721TransferEvent.from,
to: erc721TransferEvent.to,
tokenId: new BigNumber(erc721TransferEvent.tokenId),
};
break;
case ContractEventKind.ERC721ApprovalEvent:
const erc721ApprovalEvent = rawParameters as WrapperERC721ApprovalEvent;
parameters = {
owner: erc721ApprovalEvent.owner,
approved: erc721ApprovalEvent.approved,
tokenId: new BigNumber(erc721ApprovalEvent.tokenId),
};
break;
case ContractEventKind.ExchangeFillEvent:
const exchangeFillEvent = rawParameters as WrapperExchangeFillEvent;
parameters = {
makerAddress: exchangeFillEvent.makerAddress,
takerAddress: exchangeFillEvent.takerAddress,
senderAddress: exchangeFillEvent.senderAddress,
feeRecipientAddress: exchangeFillEvent.feeRecipientAddress,
makerAssetFilledAmount: new BigNumber(exchangeFillEvent.makerAssetFilledAmount),
takerAssetFilledAmount: new BigNumber(exchangeFillEvent.takerAssetFilledAmount),
makerFeePaid: new BigNumber(exchangeFillEvent.makerFeePaid),
takerFeePaid: new BigNumber(exchangeFillEvent.takerFeePaid),
orderHash: exchangeFillEvent.orderHash,
makerAssetData: exchangeFillEvent.makerAssetData,
takerAssetData: exchangeFillEvent.takerAssetData,
};
break;
case ContractEventKind.ExchangeCancelEvent:
parameters = rawParameters as ExchangeCancelEvent;
break;
case ContractEventKind.ExchangeCancelUpToEvent:
const exchangeCancelUpToEvent = rawParameters as WrapperExchangeCancelUpToEvent;
parameters = {
makerAddress: exchangeCancelUpToEvent.makerAddress,
senderAddress: exchangeCancelUpToEvent.senderAddress,
orderEpoch: new BigNumber(exchangeCancelUpToEvent.orderEpoch),
};
break;
case ContractEventKind.WethDepositEvent:
const wethDepositEvent = rawParameters as WrapperWethDepositEvent;
parameters = {
owner: wethDepositEvent.owner,
value: new BigNumber(wethDepositEvent.value),
};
break;
case ContractEventKind.WethWithdrawalEvent:
const wethWithdrawalEvent = rawParameters as WrapperWethWithdrawalEvent;
parameters = {
owner: wethWithdrawalEvent.owner,
value: new BigNumber(wethWithdrawalEvent.value),
};
break;
default:
throw new Error(`Unrecognized ContractEventKind: ${kind}`);
}
const contractEvent: ContractEvent = {
blockHash: wrapperContractEvent.blockHash,
txHash: wrapperContractEvent.txHash,
txIndex: wrapperContractEvent.txIndex,
logIndex: wrapperContractEvent.logIndex,
isRemoved: wrapperContractEvent.isRemoved,
address: wrapperContractEvent.address,
kind,
parameters,
};
contractEvents.push(contractEvent);
});
return contractEvents;
}

function signedOrderToWrapperSignedOrder(signedOrder: SignedOrder): WrapperSignedOrder {
return {
...signedOrder,
Expand All @@ -384,6 +657,7 @@ function wrapperOrderEventToOrderEvent(wrapperOrderEvent: WrapperOrderEvent): Or
...wrapperOrderEvent,
signedOrder: wrapperSignedOrderToSignedOrder(wrapperOrderEvent.signedOrder),
fillableTakerAssetAmount: new BigNumber(wrapperOrderEvent.fillableTakerAssetAmount),
contractEvents: wrapperContractEventsToContractEvents(wrapperOrderEvent.contractEvents),
};
}

Expand Down
3 changes: 2 additions & 1 deletion core/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/0xProject/0x-mesh/p2p"
"github.com/0xProject/0x-mesh/zeroex"
"github.com/0xProject/0x-mesh/zeroex/ordervalidator"
"github.com/ethereum/go-ethereum/rpc"
log "github.com/sirupsen/logrus"
"github.com/xeipuuv/gojsonschema"
)
Expand Down Expand Up @@ -192,7 +193,7 @@ func (app *App) validateOrders(orders []*zeroex.SignedOrder) (*ordervalidator.Va
validMeshOrders = append(validMeshOrders, order)
}
areNewOrders := true
zeroexResults := app.orderValidator.BatchValidate(validMeshOrders, areNewOrders)
zeroexResults := app.orderValidator.BatchValidate(validMeshOrders, areNewOrders, rpc.LatestBlockNumber)
zeroexResults.Accepted = append(zeroexResults.Accepted, results.Accepted...)
zeroexResults.Rejected = append(zeroexResults.Rejected, results.Rejected...)
return zeroexResults, nil
Expand Down
6 changes: 4 additions & 2 deletions docs/db_syncing.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ When first connecting the DB and Mesh node, we first need to make sure both have

#### 1. Subscribe to Mesh

Subscribe to the Mesh node's `orders` subscription over a WS connection. This can be done using our [golang](https://godoc.org/github.com/0xProject/0x-mesh/rpc) or [Typescript/Javascript](json_rpc_clients/typescript/README.md) clients or any other JSON-RPC WebSocket client. Whenever you receive an order event from this subscription, make the appropriate updates to your DB. Each order event has an associated [OrderEventKind](https://godoc.org/github.com/0xProject/0x-mesh/zeroex#pkg-constants).
Subscribe to the Mesh node's `orders` subscription over a WS connection. This can be done using our [golang](https://godoc.org/github.com/0xProject/0x-mesh/rpc) or [Typescript/Javascript](json_rpc_clients/typescript/README.md) clients or any other JSON-RPC WebSocket client. Whenever you receive an order event from this subscription, make the appropriate updates to your DB. Each order event has an associated [OrderEventEndState](https://godoc.org/github.com/0xProject/0x-mesh/zeroex#pkg-constants).

| Kind | DB operation |
| End state | DB operation |
|--------------------------------------------|---------------------------------|
| ADDED | Insert |
| FILLED | Update |
Expand All @@ -25,6 +25,8 @@ Subscribe to the Mesh node's `orders` subscription over a WS connection. This ca

**Note:** Updates refer to updating the order's `fillableTakerAssetAmount` in the DB.

**Note 2:** If we receive any event other than `ADDED` and `FILLABILITY_INCREASED` for an order we do not find in our database, we ignore the event and noop.

#### 2. Get all orders currently stored in Mesh

There might have been orders stored in Mesh that the DB doesn't know about at this time. Because of this, we must fetch all currently stored orders in the Mesh node and upsert them in the database. This can be done using the [mesh_getOrders](rpc_api.md#mesh_getorders) JSON-RPC method. This method creates a snapshot of the Mesh node's internal DB of orders when first called, and allows for subsequent paginated requests against this snapshot. Because we are already subscribed to order events, any new orders added/removed after the snapshot is made will be discovered via that subscription.
Expand Down
Loading