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 8 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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

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.
fabioberger marked this conversation as resolved.
Show resolved Hide resolved

### Features ✅

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

// The type for order events exposed by MeshWrapper.
interface WrapperOrderEvent {
export interface ERC20TransferEvent {
from: string;
to: string;
value: BigNumber;
}

export interface WrapperERC20TransferEvent {
fabioberger marked this conversation as resolved.
Show resolved Hide resolved
from: string;
to: string;
value: string;
}

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

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

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

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

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

export 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;
signedOrder: WrapperSignedOrder;
kind: string;
fillableTakerAssetAmount: string;
txHashes: string[];
makerAssetData: string;
takerAssetData: string;
}

export 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;
}

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

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

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

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

export 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;

/**
fabioberger marked this conversation as resolved.
Show resolved Hide resolved
* Order events are fired by Mesh whenever an order is added, canceled, expired,
* or filled.
*/
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.
export interface WrapperContractEvent {
blockHash: string;
txHash: string;
txIndex: number;
logIndex: number;
isRemoved: string;
address: string;
kind: string;
parameters: WrapperContractEventParameters;
}

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

export interface WrapperOrderEvent {
orderHash: string;
signedOrder: WrapperSignedOrder;
kind: OrderEventKind;
fillableTakerAssetAmount: string;
contractEvents: WrapperContractEvent[];
}

export interface OrderEvent {
orderHash: string;
signedOrder: SignedOrder;
kind: string;
kind: OrderEventKind;
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
2 changes: 2 additions & 0 deletions docs/db_syncing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
22 changes: 20 additions & 2 deletions docs/rpc_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ Gets certain configurations and stats about a Mesh node.

Allows the caller to subscribe to a stream of `OrderEvents`. An `OrderEvent` contains either newly discovered orders found by Mesh via the P2P network, or updates to the fillability of a previously discovered order (e.g., if an order gets filled, cancelled, expired, etc...). `OrderEvent`s _do not_ correspond 1-to-1 to smart contract events. Rather, an `OrderEvent` about an orders fillability change represents the aggregate change to it's fillability given _all_ the transactions included within the most recently mined block.

**Example:** If an order is both `filled` and `cancelled` within a single block, only a cancellation `OrderEvent` will be emitted (since this is the final state of the order after this block is mined). The cancellation `OrderEvent` _will_ however list the hashes of all transactions that impacted the order's fillability within the block. Using these `txHashes`, one can fetch all the individual smart contract events impacting to an order if they are needed.
**Example:** If an order is both `filled` and `cancelled` within a single block, only a cancellation `OrderEvent` will be emitted (since this is the final state of the order after this block is mined). The cancellation `OrderEvent` _will_ however list the contract events intercepted that could have impacted this orders fillability. This list will include both the fill event and cancellation event.
fabioberger marked this conversation as resolved.
Show resolved Hide resolved

Mesh has implemented subscriptions in the [same manner as Geth](https://github.com/ethereum/go-ethereum/wiki/RPC-PUB-SUB). In order to start a subscription, you must send the following payload:

Expand Down Expand Up @@ -267,7 +267,25 @@ Mesh has implemented subscriptions in the [same manner as Geth](https://github.c
},
"kind": "CANCELLED",
"fillableTakerAssetAmount": 0,
"txHashes": ["0x9e6830a7044b39e107f410e4f765995fd0d3d69d5c3b3582a1701b9d68167560"]
"contractEvents": [
{
"blockHash": "0x1be2eb6174dbf0458686bdae44c9a330d9a9eb563962512a7be545c4ec11a4d2",
"txHash": "0xbcce172374dbf0458686bdae44c9a330d9a9eb563962512a7be545c4ec232e3a",
"txIndex": 23,
"logIndex": 0,
"isRemoved": false,
"address": "0x4f833a24e1f95d70f028921e27040ca56e09ab0b",
"kind": "ExchangeCancelEvent",
"parameters": {
"makerAddress": "0x50f84bbee6fb250d6f49e854fa280445369d64d9",
"senderAddress": "0x0000000000000000000000000000000000000000",
"feeRecipientAddress": "0xa258b39954cef5cb142fd567a46cddb31a670124",
"orderHash": "0x96e6eb6174dbf0458686bdae44c9a330d9a9eb563962512a7be545c4ecc13fd4",
"makerAssetData": "0xf47261b00000000000000000000000000f5d2fb29fb7d3cfee444a200298f468908cc942",
"takerAssetData": "0xf47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
}
}
]
}
]
}
Expand Down
Loading