Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proxy colonies M3] feat: proxy colonies M3 #332

Draft
wants to merge 2 commits into
base: feat/M2-proxy-colonies
Choose a base branch
from
Draft
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
3 changes: 2 additions & 1 deletion apps/main-chain/src/eventListeners/colony.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ export const setupListenersForColony = (
[ContractEventsSignatures.ExpenditureAdded]: handleExpenditureAdded,
[ContractEventsSignatures.ExpenditureRecipientSet]:
handleExpenditureRecipientSet,
[ContractEventsSignatures.ExpenditurePayoutSet]: handleExpenditurePayoutSet,
[ContractEventsSignatures.ExpenditurePayoutSetOld]:
handleExpenditurePayoutSet,
[ContractEventsSignatures.ExpenditureLocked]: handleExpenditureLocked,
[ContractEventsSignatures.ExpenditureCancelled]: handleExpenditureCancelled,
[ContractEventsSignatures.ExpenditureFinalized]: handleExpenditureFinalized,
Expand Down
163 changes: 155 additions & 8 deletions apps/main-chain/src/handlers/actions/oneTxPayment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import {
GetColonyExtensionQueryVariables,
} from '@joincolony/graphql';
import rpcProvider from '~provider';
import { ContractEvent, ContractEventsSignatures } from '@joincolony/blocks';
import {
ContractEvent,
ContractEventsSignatures,
ProxyColonyEvents,
} from '@joincolony/blocks';
import { NotificationCategory } from '~types/notifications';
import {
getCachedColonyClient,
Expand All @@ -23,13 +27,15 @@ import {
} from '~utils';
import { getAmountLessFee, getNetworkInverseFee } from '~utils/networkFee';
import { sendPermissionsActionNotifications } from '~utils/notifications';
import blockManager from '~blockManager';
import { getAndSyncMultiChainInfo } from '~utils/crossChain';

const PAYOUT_CLAIMED_SIGNATURE_HASH = utils.id(
ContractEventsSignatures.PayoutClaimed,
);

const EXPENDITURE_PAYOUT_SET = utils.id(
ContractEventsSignatures.ExpenditurePayoutSet,
const EXPENDITURE_PAYOUT_SET_OLD = utils.id(
ContractEventsSignatures.ExpenditurePayoutSetOld,
);

enum ExpenditureStatus {
Expand Down Expand Up @@ -58,6 +64,7 @@ interface ExpenditureSlot {
export interface MultiPayment {
amount: string;
networkFee?: string;
chainId?: string;
tokenAddress: string;
recipientAddress: string;
}
Expand Down Expand Up @@ -97,15 +104,18 @@ export default async (oneTxPaymentEvent: ContractEvent): Promise<void> => {
);
break;
case 6:
handlerV6(
case 7:
case 8:
case 9:
handlerV6ToV9(
oneTxPaymentEvent,
colonyAddress,
colonyClient,
networkInverseFee,
);
break;
default:
handlerV6(
handlerV10(
oneTxPaymentEvent,
colonyAddress,
colonyClient,
Expand Down Expand Up @@ -179,7 +189,7 @@ const handlerV1ToV5 = async (
);

const expenditurePayoutLogs = receipt.logs.filter((log) =>
log.topics.includes(EXPENDITURE_PAYOUT_SET),
log.topics.includes(EXPENDITURE_PAYOUT_SET_OLD),
);

const expenditurePayoutEvents = await Promise.all(
Expand Down Expand Up @@ -222,7 +232,7 @@ const handlerV1ToV5 = async (
}
};

const handlerV6 = async (
const handlerV6ToV9 = async (
event: ContractEvent,
colonyAddress: string,
colonyClient: AnyColonyClient,
Expand All @@ -241,7 +251,7 @@ const handlerV6 = async (
);

const expenditurePayoutLogs = receipt.logs.filter((log) =>
log.topics.includes(EXPENDITURE_PAYOUT_SET),
log.topics.includes(EXPENDITURE_PAYOUT_SET_OLD),
);

const expenditurePayoutEvents = await Promise.all(
Expand Down Expand Up @@ -314,3 +324,140 @@ const handlerV6 = async (
});
}
};

const PAYOUT_CLAIMED = utils.id(
ContractEventsSignatures.ExpenditurePayoutClaimedNew,
);
export const PAYOUT_CLAIMED_INTERFACE = new utils.Interface([
'event PayoutClaimed(address agent, uint256 id, uint256 slot, uint256 chainId, address token, uint256 tokenPayout)',
]);

const handlerV10 = async (
event: ContractEvent,
colonyAddress: string,
colonyClient: AnyColonyClient,
networkFee: string,
): Promise<void> => {
const { blockNumber, transactionHash } = event;
const [initiatorAddress, expenditureId] = event.args;
const receipt = await rpcProvider
.getProviderInstance()
.getTransactionReceipt(event.transactionHash);

// multiple OneTxPayments use expenditures at the contract level
const expenditure: Expenditure = await colonyClient.getExpenditure(
expenditureId,
{ blockTag: blockNumber },
);

const paymentClaimedLogs = receipt.logs.filter((log) =>
log.topics.includes(PAYOUT_CLAIMED),
);

const payoutClaimedEvents = await Promise.all(
paymentClaimedLogs.map((log) =>
blockManager.mapLogToContractEvent(log, PAYOUT_CLAIMED_INTERFACE),
),
);

const payments: MultiPayment[] = await Promise.all(
payoutClaimedEvents.filter(notNull).map(async ({ args }) => {
const [, expenditureId, slotId, chainId, tokenAddress, amount] = args;
const expenditureSlot: ExpenditureSlot =
await colonyClient.getExpenditureSlot(expenditureId, slotId, {
blockTag: blockNumber,
});

const amountLessFee = getAmountLessFee(amount, networkFee);
const fee = BigNumber.from(amount).sub(amountLessFee);

const payment: MultiPayment = {
amount: amountLessFee.toString(),
networkFee: fee.toString(),
chainId,
tokenAddress,
recipientAddress: expenditureSlot.recipient,
};
return payment;
}),
);

// this is assuming the following:
// the simple payment action only has 1 recipient anyways, so we can use that chainId for the action one
// if we have multiple, then the UI needs to get it from the payments array

const firstPaymentData = payments?.[0];
const targetChainId = firstPaymentData?.chainId
? firstPaymentData.chainId
: rpcProvider.getChainId();

const hasMultiplePayments = payments.length > 1;
let actionFields: ActionFields = {
type: hasMultiplePayments
? ColonyActionType.MultiplePayment
: ColonyActionType.Payment,
fromDomainId: getDomainDatabaseId(
colonyAddress,
toNumber(expenditure.domainId),
),
initiatorAddress,
};

// we only do cross chain action completion for simple payments, since multi payment need to be done per payout
if (payments.length === 1) {
let multiChainInfoId;

if (targetChainId !== rpcProvider.getChainId()) {
const wormholeLogs = receipt.logs.filter((log) =>
log.topics.includes(
utils.id(ContractEventsSignatures.LogMessagePublished),
),
);

const wormholeEvents = await Promise.all(
wormholeLogs.map((log) =>
blockManager.mapLogToContractEvent(log, ProxyColonyEvents),
),
);

const wormholeEvent = wormholeEvents[0];
if (wormholeEvent) {
multiChainInfoId = await getAndSyncMultiChainInfo(
wormholeEvent,
transactionHash,
Number(targetChainId),
);
}
}

const { tokenAddress, amount, recipientAddress, networkFee } = payments[0];
actionFields = {
...actionFields,
tokenAddress,
amount,
networkFee,
recipientAddress,
paymentId: toNumber(expenditureId),
targetChainId: Number(targetChainId),
multiChainInfoId,
};
} else {
actionFields = {
...actionFields,
paymentId: toNumber(expenditureId),
payments,
};
}

await writeActionFromEvent(event, colonyAddress, actionFields);

if (firstPaymentData) {
sendPermissionsActionNotifications({
mentions: [firstPaymentData.recipientAddress],
creator: initiatorAddress,
colonyAddress,
transactionHash,
notificationCategory: NotificationCategory.Payment,
});
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const createEditExpenditureAction = async (
topics: [
[
utils.id(ContractEventsSignatures.ExpenditureStateChanged),
utils.id(ContractEventsSignatures.ExpenditurePayoutSet),
utils.id(ContractEventsSignatures.ExpenditurePayoutSetOld),
],
],
});
Expand Down Expand Up @@ -163,7 +163,7 @@ export const createEditExpenditureAction = async (
updatedStatus = decodedStatus;
}
} else if (
actionEvent.signature === ContractEventsSignatures.ExpenditurePayoutSet
actionEvent.signature === ContractEventsSignatures.ExpenditurePayoutSetOld
) {
const {
slot,
Expand Down
55 changes: 6 additions & 49 deletions apps/main-chain/src/handlers/proxyColonies/proxyColonyRequested.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,13 @@ import {
ProxyColoniesListener,
ProxyColonyEvents,
} from '@joincolony/blocks';
import {
getMultiChainInfoId,
output,
upsertMultiChainInfo,
} from '@joincolony/utils';
import { output } from '@joincolony/utils';
import { utils } from 'ethers';
import blockManager from '~blockManager';
import rpcProvider from '~provider';
import { writeActionFromEvent } from '~utils/actions/writeAction';
import {
ColonyActionType,
CreateMultiChainInfoInput,
UpdateMultiChainInfoInput,
} from '@joincolony/graphql';
import amplifyClient from '~amplifyClient';
import { ColonyActionType } from '@joincolony/graphql';
import { getAndSyncMultiChainInfo } from '~utils/crossChain';

// @NOTE this one listens to the ProxyColonyRequested event on the colony, not the network!
export const handleProxyColonyRequested: EventHandler = async (
Expand Down Expand Up @@ -68,45 +60,10 @@ export const handleProxyColonyRequested: EventHandler = async (
return;
}

const { sender, sequence } = wormholeEvent.args;
const emitterAddress = sender.toString();
const emitterSequence = sequence.toString();

if (!emitterAddress || !sequence) {
output('Missing arguments on the LogMessagePublished events');
return;
}

// we could technically use this one, but we should use the one of the created one, just so we have all the core logic in the upsertMultiChainInfo helper
const existingMultiChainInfoId = getMultiChainInfoId(
const multiChainInfoId = await getAndSyncMultiChainInfo(
wormholeEvent,
transactionHash,
destinationChainId.toNumber(),
);

const createMultiChainInfoInput: CreateMultiChainInfoInput = {
id: existingMultiChainInfoId,
completedOnMainChain: true,
completedOnProxyChain: false,
wormholeInfo: {
sequence: emitterSequence,
emitterAddress,
},
};

const updateMultiChainInfoInput: UpdateMultiChainInfoInput = {
id: existingMultiChainInfoId,
completedOnMainChain: true,
wormholeInfo: {
sequence: emitterSequence,
emitterAddress,
},
};

const multiChainInfoId = await upsertMultiChainInfo(
amplifyClient,
existingMultiChainInfoId,
createMultiChainInfoInput,
updateMultiChainInfoInput,
Number(destinationChainId),
);

await writeActionFromEvent(event, colonyAddress, {
Expand Down
57 changes: 57 additions & 0 deletions apps/main-chain/src/utils/crossChain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { ContractEvent } from '@joincolony/blocks';
import {
CreateMultiChainInfoInput,
UpdateMultiChainInfoInput,
} from '@joincolony/graphql';
import {
getMultiChainInfoId,
output,
upsertMultiChainInfo,
} from '@joincolony/utils';
import amplifyClient from '~amplifyClient';

export const getAndSyncMultiChainInfo = async (
wormholeEvent: ContractEvent,
txHash: string,
chainId: number,
): Promise<string | undefined> => {
const { sender, sequence } = wormholeEvent.args;
const emitterAddress = sender.toString();
const emitterSequence = sequence.toString();

if (!emitterAddress || !sequence) {
output('Missing arguments on the LogMessagePublished events');
return undefined;
}

// we could technically use this one, but we should use the one of the created one, just so we have all the core logic in the upsertMultiChainInfo helper
const existingMultiChainInfoId = getMultiChainInfoId(txHash, chainId);

const createMultiChainInfoInput: CreateMultiChainInfoInput = {
id: existingMultiChainInfoId,
completedOnMainChain: true,
completedOnProxyChain: false,
wormholeInfo: {
sequence: emitterSequence,
emitterAddress,
},
};

const updateMultiChainInfoInput: UpdateMultiChainInfoInput = {
id: existingMultiChainInfoId,
completedOnMainChain: true,
wormholeInfo: {
sequence: emitterSequence,
emitterAddress,
},
};

const multiChainInfoId = await upsertMultiChainInfo(
amplifyClient,
existingMultiChainInfoId,
createMultiChainInfoInput,
updateMultiChainInfoInput,
);

return multiChainInfoId;
};
Loading