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

Add channelId argument to AssetTransferred event #595

Merged
merged 7 commits into from
Dec 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
4 changes: 2 additions & 2 deletions packages/nitro-protocol/contracts/AssetHolder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ contract AssetHolder is IAssetHolder {
}
if (_isExternalDestination(allocation[m].destination)) {
_transferAsset(_bytes32ToAddress(allocation[m].destination), payoutAmount);
emit AssetTransferred(allocation[m].destination, payoutAmount);
emit AssetTransferred(channelId, allocation[m].destination, payoutAmount);
} else {
holdings[allocation[m].destination] += payoutAmount;
}
Comment on lines 98 to 102
Copy link
Contributor

Choose a reason for hiding this comment

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

My comments above are because the event is only emitted inside a _isExternalDestination block. Do you think we ought to also emit an event when the holdings are updated? This could be the same event or a new one (with some appropriate name(s)). As it is now, no events will be emitted during e.g. a transferAll on a ledger channel that only allocates to other channels. The holdings mapping is public, however: but clients won't have a "trigger" to remind them to inspect it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We should make a decision on this at standup; it's not clear to me that it is important information for the wallet to keep track of.

Expand Down Expand Up @@ -263,7 +263,7 @@ contract AssetHolder is IAssetHolder {
if (payouts[j] > 0) {
if (_isExternalDestination(allocation[j].destination)) {
_transferAsset(_bytes32ToAddress(allocation[j].destination), payouts[j]);
emit AssetTransferred(allocation[j].destination, payouts[j]);
emit AssetTransferred(guarantorChannelId, allocation[j].destination, payouts[j]);
} else {
holdings[allocation[j].destination] += payouts[j];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ interface IAssetHolder {

/**
* @dev Indicates that `amount` assets have been transferred to the external destination denoted by `destination`.
* @param channelId The channelId of the funds being withdrawn.
* @param destination An external destination, left-padded with zeros.
* @param amount Number of assets transferred (wei or tokens).
*/
event AssetTransferred(bytes32 indexed destination, uint256 amount);
event AssetTransferred(bytes32 indexed channelId, bytes32 indexed destination, uint256 amount);

}
21 changes: 7 additions & 14 deletions packages/nitro-protocol/src/contract/asset-holder.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {BigNumber, bigNumberify, getAddress} from 'ethers/utils';
import {parseEventResult} from '../ethers-utils';

export interface DepositedEvent {
destination: string;
Expand All @@ -7,32 +8,24 @@ export interface DepositedEvent {
}

export interface AssetTransferredEvent {
channelId: string;
destination: string;
amount: BigNumber;
}

export function getDepositedEvent({eventArgs}): DepositedEvent {
const [
{
args: {destination, amountDeposited, destinationHoldings},
},
] = eventArgs.slice(-1);

export function getDepositedEvent(eventResult: any[]): DepositedEvent {
const {destination, amountDeposited, destinationHoldings} = parseEventResult(eventResult);
return {
destination,
amountDeposited: bigNumberify(amountDeposited),
destinationHoldings: bigNumberify(destinationHoldings),
};
}

export function getAssetTransferredEvent({eventArgs}): AssetTransferredEvent {
const [
{
args: {destination, amount},
},
] = eventArgs.slice(-1);

export function getAssetTransferredEvent(eventResult: any[]): AssetTransferredEvent {
const {channelId, destination, amount} = parseEventResult(eventResult);
return {
channelId,
destination,
amount: bigNumberify(amount),
};
Expand Down
5 changes: 5 additions & 0 deletions packages/nitro-protocol/src/ethers-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// The last value in a result from an ethers event emission (i.e., Contract.on(<filter>, <result>))
// is an object with keys as the names of the fields emitted by the event.
export function parseEventResult(result: any[]): {[fieldName: string]: any} {
return result.slice(-1)[0].args;
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,11 @@ describe('claimAll', () => {

// Add AssetTransferred events to expectations
Object.keys(payouts).forEach(assetHolder => {
expectedEvents = assetTransferredEventsFromPayouts(payouts, AssetHolder.address);
expectedEvents = assetTransferredEventsFromPayouts(
guarantorId,
payouts,
AssetHolder.address
);
});

// Extract logs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ describe('transferAll', () => {
if (payouts[destination] && payouts[destination].gt(0)) {
expectedEvents.push({
event: 'AssetTransferred',
args: {destination, amount: payouts[destination]},
args: {channelId, destination, amount: payouts[destination]},
});
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ describe('concludePushOutcomeAndTransferAll', () => {
// Add AssetTransferred events to expectations
Object.keys(payouts).forEach(assetHolder => {
expectedEvents = expectedEvents.concat(
assetTransferredEventsFromPayouts(payouts[assetHolder], assetHolder)
assetTransferredEventsFromPayouts(channelId, payouts[assetHolder], assetHolder)
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ describe('pushOutcomeAndTransferAll', () => {
// Add AssetTransferred events to expectations
Object.keys(payouts).forEach(assetHolder => {
expectedEvents = expectedEvents.concat(
assetTransferredEventsFromPayouts(payouts[assetHolder], assetHolder)
assetTransferredEventsFromPayouts(channelId, payouts[assetHolder], assetHolder)
);
});

Expand Down
3 changes: 2 additions & 1 deletion packages/nitro-protocol/test/test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ export function computeOutcome(outcomeShortHand: OutcomeShortHand): AllocationAs
}

export function assetTransferredEventsFromPayouts(
channelId: string,
singleAssetPayouts: AssetOutcomeShortHand,
assetHolder: string
) {
Expand All @@ -355,7 +356,7 @@ export function assetTransferredEventsFromPayouts(
assetTransferredEvents.push({
contract: assetHolder,
name: 'AssetTransferred',
values: {destination, amount: singleAssetPayouts[destination]},
values: {channelId, destination, amount: singleAssetPayouts[destination]},
});
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import SagaTester from "redux-saga-tester";
import {DepositedEvent} from "../redux/actions";
import {adjudicatorWatcher} from "../redux/sagas/adjudicator-watcher";
import {ETHAssetHolderWatcher} from "../redux/sagas/asset-holder-watcher";
import {assetHoldersWatcher} from "../redux/sagas/asset-holder-watcher";
import {depositIntoETHAssetHolder, createWatcherState, concludeGame, fiveFive} from "./test-utils";
import {getGanacheProvider} from "@statechannels/devtools";
import {bigNumberify} from "ethers/utils";
Expand Down Expand Up @@ -46,7 +46,7 @@ describe("ETHAssetHolder listener", () => {
const processId = ethers.Wallet.createRandom().address;

const sagaTester = new SagaTester({initialState: createWatcherState(processId, channelId)});
sagaTester.start(ETHAssetHolderWatcher, provider);
sagaTester.start(assetHoldersWatcher, provider);

const depositAmount = bigNumberify("0x05");
await depositIntoETHAssetHolder(provider, channelId, depositAmount.toHexString());
Expand Down Expand Up @@ -108,7 +108,7 @@ describe("ETHAssetHolder listener", () => {
const allocation = getAllocationOutcome(outcome).allocation;
const processId = ethers.Wallet.createRandom().address;
const sagaTester = new SagaTester({initialState: createWatcherState(processId, channelId)});
sagaTester.start(ETHAssetHolderWatcher, provider);
sagaTester.start(assetHoldersWatcher, provider);

await transferAll(channelId, encodeAllocation(allocation), provider);

Expand Down
3 changes: 3 additions & 0 deletions packages/wallet/src/redux/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,12 @@ export interface DisplayMessageSent {

export interface AssetTransferredEvent {
type: "WALLET.ASSET_HOLDER.ASSET_TRANSFERRED";
assetHolderAddress: string;
channelId: string;

// This is either a `channelId` or an external destination (both bytes32).
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// This is either a `channelId` or an external destination (both bytes32).
// This is either a `channelId` or an external destination (both bytes32):

destination: string;

amount: BigNumber;
}

Expand Down
18 changes: 13 additions & 5 deletions packages/wallet/src/redux/asset-holders-state/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
import {AssetHoldersState, recordDeposit} from "./state";
import * as actions from "../actions";
import {AssetHolderEventAction, DepositedEvent, AssetTransferredEvent} from "../actions";
import {unreachable} from "../../utils/reducer-utils";

import {AssetHoldersState, recordDeposit, recordAssetTransfer} from "./state";

export const assetHolderStateReducer = (
state: AssetHoldersState,
action: actions.AssetHolderEventAction
action: AssetHolderEventAction
): AssetHoldersState => {
switch (action.type) {
case "WALLET.ASSET_HOLDER.ASSET_TRANSFERRED":
throw Error("cant handle this");
return assetTransferredReducer(state, action);
case "WALLET.ASSET_HOLDER.DEPOSITED":
return depositedReducer(state, action);
default:
return unreachable(action);
}
};

const depositedReducer = (state: AssetHoldersState, action: actions.DepositedEvent) => {
const depositedReducer = (state: AssetHoldersState, action: DepositedEvent) => {
return recordDeposit(
state,
action.assetHolderAddress,
action.destination,
action.destinationHoldings
);
};

const assetTransferredReducer = (
state: AssetHoldersState,
{assetHolderAddress, channelId, destination, amount}: AssetTransferredEvent
) => {
return recordAssetTransfer(state, assetHolderAddress, channelId, destination, amount);
};
28 changes: 27 additions & 1 deletion packages/wallet/src/redux/asset-holders-state/state.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Zero} from "ethers/constants";
import {BigNumber} from "ethers/utils";
import {BigNumber, bigNumberify} from "ethers/utils";
import {Uint256} from "@statechannels/nitro-protocol/src/contract/types";

export interface AssetHoldersState {
Expand Down Expand Up @@ -82,3 +82,29 @@ export function recordDeposit(
newAssetHolderChannelState
);
}

export function recordAssetTransfer(
assetHoldersState: AssetHoldersState,
assetHolderAddress: string,
channelId: string,
// @ts-ignore
destination: string, // TODO: Should we take this into account?
Copy link
Contributor

Choose a reason for hiding this comment

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

It's possible in future that destination is removed so that it is not even part of the data emitted in the event. We might decide that it is redundant information. It was introduced mainly to make testing easier, but we could get the data from somewhere else most likely. In the case of an ERC20, for example, the Token contract itself will emit a Transfer event when the Tokens are transferred.

amount: BigNumber
): AssetHoldersState {
const assetHolderChannelState = getOrCreateAssetHolderChannelState(
assetHoldersState,
assetHolderAddress,
channelId
);
const newAssetHolderChannelState = {
...assetHolderChannelState,
holdings: bigNumberify(assetHolderChannelState.holdings)
.sub(amount)
.toHexString()
};
return setAssetHolderChannelState(
assetHoldersState,
assetHolderAddress,
newAssetHolderChannelState
);
}
74 changes: 37 additions & 37 deletions packages/wallet/src/redux/sagas/asset-holder-watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,46 @@ enum AssetHolderEventType {

interface AssetHolderEvent {
assetHolderAddress: string;
eventArgs: any;
eventResult: any[];
eventType: AssetHolderEventType;
}

export function* ETHAssetHolderWatcher(provider: Web3Provider) {
const ETHAssetHolderEventChannel = yield call(createAssetHolderEventChannel, provider);
export function* assetHoldersWatcher(provider: Web3Provider) {
const assetHolderEventChannel = yield call(createAssetHolderEventChannel, provider);
while (true) {
const event: AssetHolderEvent = yield take(ETHAssetHolderEventChannel);
if (event.eventType === AssetHolderEventType.Deposited) {
const channelSubscribers: ChannelSubscriber[] = yield select(
getAssetHolderWatcherSubscribersForChannel,
getDepositedEvent(event).destination
);
for (const subscriber of channelSubscribers) {
yield dispatchProcessEventAction(event, subscriber.processId, subscriber.protocolLocator);
}
const event: AssetHolderEvent = yield take(assetHolderEventChannel);
const {eventType, eventResult} = event;

let channelId: string;

if (eventType === AssetHolderEventType.Deposited) {
channelId = getDepositedEvent(eventResult).destination;
} else if (eventType === AssetHolderEventType.AssetTransferred) {
channelId = getAssetTransferredEvent(eventResult).channelId;
} else {
continue;
}

const channelSubscribers: ChannelSubscriber[] = yield select(
getAssetHolderWatcherSubscribersForChannel,
channelId
);

for (const {processId, protocolLocator} of channelSubscribers) {
yield dispatchProcessEventAction(event, processId, protocolLocator);
}

yield dispatchEventAction(event);
}
}

function* dispatchEventAction(event: AssetHolderEvent) {
const {eventType} = event;
function* dispatchEventAction({eventResult, assetHolderAddress, eventType}: AssetHolderEvent) {
switch (eventType) {
case AssetHolderEventType.AssetTransferred:
// FIXME: We need some new kind of technique for dealing with AssetTransferred situations
const assetTransferredEvent = getAssetTransferredEvent(event);
yield put(
actions.assetTransferredEvent({
destination: assetTransferredEvent.destination,
amount: assetTransferredEvent.amount
assetHolderAddress,
...getAssetTransferredEvent(eventResult)
})
);
break;
Expand All @@ -63,29 +72,20 @@ function* dispatchEventAction(event: AssetHolderEvent) {
}

function* dispatchProcessEventAction(
event: AssetHolderEvent,
{eventResult, assetHolderAddress, eventType}: AssetHolderEvent,
processId: string,
protocolLocator: ProtocolLocator
) {
const {eventType, assetHolderAddress} = event;
switch (eventType) {
case AssetHolderEventType.AssetTransferred:
yield put(
actions.assetTransferredEvent({
...getAssetTransferredEvent(event)
})
);
break;
case AssetHolderEventType.Deposited:
const {destination, amountDeposited, destinationHoldings} = getDepositedEvent(event);
yield put(
actions.depositedEvent({
processId,
protocolLocator,
assetHolderAddress,
destination,
amountDeposited,
destinationHoldings
...getDepositedEvent(eventResult)
})
);
break;
Expand All @@ -108,35 +108,35 @@ function* createAssetHolderEventChannel(provider: Web3Provider) {
const erc20AssetTransferredFilter = ERC20AssetHolder.filters.AssetTransferred();
const erc20DepositedFilter = ERC20AssetHolder.filters.Deposited();

ETHAssetHolder.on(ethAssetTransferredFilter, (...eventArgs) =>
ETHAssetHolder.on(ethAssetTransferredFilter, (...eventResult) =>
emitter({
assetHolderAddress: ETHAssetHolder.address,
eventType: AssetHolderEventType.AssetTransferred,
eventArgs
eventResult
})
);

ETHAssetHolder.on(ethDepositedFilter, (...eventArgs) =>
ETHAssetHolder.on(ethDepositedFilter, (...eventResult) =>
emitter({
assetHolderAddress: ETHAssetHolder.address,
eventType: AssetHolderEventType.Deposited,
eventArgs
eventResult
})
);

ERC20AssetHolder.on(erc20AssetTransferredFilter, (...eventArgs) =>
ERC20AssetHolder.on(erc20AssetTransferredFilter, (...eventResult) =>
emitter({
assetHolderAddress: ERC20AssetHolder.address,
eventType: AssetHolderEventType.AssetTransferred,
eventArgs
eventResult
})
);

ERC20AssetHolder.on(erc20DepositedFilter, (...eventArgs) =>
ERC20AssetHolder.on(erc20DepositedFilter, (...eventResult) =>
emitter({
assetHolderAddress: ERC20AssetHolder.address,
eventType: AssetHolderEventType.Deposited,
eventArgs
eventResult
})
);

Expand Down
Loading