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

Add mint tokens motion support #16

Merged
merged 3 commits into from
Mar 17, 2023
Merged

Add mint tokens motion support #16

merged 3 commits into from
Mar 17, 2023

Conversation

willm30
Copy link
Contributor

@willm30 willm30 commented Feb 10, 2023

Description

This pr adds block-ingestor support for the MotionCreated event, specifically when it wraps the mint tokens action.

I have coupled the MotionCreated event listeners with the Voting Reputation extension, such that as soon as it is initialised, or if trackExtensions determines that it is initialised on block-ingestor start up, it will also listen for MotionCreated events.

Testing

To test, you will need to:

  1. Be on this cdapp branch: Port root motion saga colonyCDapp#261. Make sure the block ingestor is commented out in in colony-cdapp-env-orchestration.yaml:
  # block-ingestor:
  #   container_name: 'ingestor'
  #   image: colony-cdapp-dev-env/block-ingestor:latest
  #   volumes:
  #     - '../amplify/mock-data:/colonyCDapp/amplify/mock-data'
  #   ports:
  #     - '10001:10001'
  #   depends_on:
  #     network-contracts:
  #       condition: service_healthy
  #     amplify:
  #       condition: service_healthy
  #   links:
  #     - "network-contracts:network-contracts.docker"
  #     - "amplify:amplify.docker"
  1. Add reputation to the user. This can be done by:
  • Copying the following into a new tab and ensuring it says the reputation miner is on: http://localhost:3001/reputation/monitor/toggle
  • In a new terminal window, run npm run truffle console
  • Paste the following:
    • c = await IColony.at('{colonyAddress}') (where colony address is the address of the colony you're testing out)
      Then
    • c.emitDomainReputationReward(1, accounts[0], '1000000000000000000') (make sure you're logged in with dev wallet 1).
    • Wait for the reputation mining cycle to complete and you should see the reputation update in the ui.
  1. Install and initialise the Voting Reputation (Governance) extension
  2. Click the "Test Mint Tokens" button. If all is set up correctly, you should be redirected to a motions screen.

Testing should reveal that only one motion is created in the db, and it should be created in the colony you clicked "test mint tokens" in.

You should also terminate the block-ingestor and restart it, and you should see a listener attached to the MotionCreated event as a result of trackExtensions determining that the Voting Reputation Extn is installed.

Notes

In my dev env, I've been noticing that the event listener attached to the MotionCreated event is firing for each colony, as well as often multiple times for the same colony.

I have guarded against the motion being associated with the wrong colony in the db, but you may see errors in the block ingestor terminal when creating a motion in the db, because it may attempt to create the same motion twice. This should not be a problem and should not actually create more than one motion per tx hash. You can verify this in the ui as well as at localhost:20002 by ensuring only one motion is created per click.

I have also noticed that sometimes when you click the button, it redirects but the block ingestor doesn't pick up the event and so the action page doesn't load. If this happens, go back to Colony Home and refresh & try again. We suspect this may be the result of hot reloading the block ingestor and therefore only a problem in dev.

Finally, I've noticed that a couple of TokensMinted events get picked up straight after, with an amount of undefined and 0. I've added logic to guard against making a db mutatation when this is the case.

Troubleshooting

If you are experiencing an incorrent nonce error at colony create when running the block ingestor separately, it may be caused by the await provider.getNetwork() call on line 45 of index.ts. This is just a theory but commenting it out and the getChainId() call on the line below solved it for me the first time I tried it.

@willm30 willm30 changed the base branch from master to port/motions February 10, 2023 20:48
@willm30 willm30 force-pushed the add/root-motion branch 6 times, most recently from 2b6d12f to 8e6a07a Compare February 13, 2023 18:41
@willm30 willm30 self-assigned this Feb 13, 2023
this.queue.push(event);
verbose(
'Event added to the queue:',
event?.signature || ContractEventsSignatures.UnknownEvent,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changes in this file are just from prettier & correcting the "Uknown" event typo

* It basically does away with all the boilerplate of setting up topics, setting
* the event listener, parsing the received event
*/
export const eventListenerGenerator = async (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Have moved all the listeners to their own file to separate them from the helpers they use, especially now I've written a few for eventListenerGenerator

* Voting Reputation Client specific event listener,
* which uses `eventListenerGenerator` under the hood
*/
export const addMotionEventListener = async (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the only addition, aside from refactoring event listener generator

*/
export const eventListenerGenerator = async (
eventSignature: ContractEventsSignatures,
export const getClientAndProviderForELG = async (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Written a few helper functions for the event listener generator, since the exact logic seems to change depending on the client type. E.g. which client / provider to use, which filter to use, which contract address to use in the event, when to add the event to the queue...

I expect splitting up the various parts of adding an event listener will make it easier to extend as we continue porting over functionality.

* In the case of Voting Rep, only add the event to the queue if the colony address
* associated with the event is the same as the colony address the listener is associated with.
*/
const { colonyId } = await query('getColonyExtension', {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using this to get the colony the voting reputation extension is installed in. This is a guard against the motion being written to the db with the wrong colony (which is possible when the behaviour I'm seeing in development occurs, where the event fires on all colonies)

import { verbose } from './logger';
import { getColonyTokenAddress, getColonyTokenData } from './tokens';

export const getParsedActionFromMotion = async (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is how to get the underlying action associated with the motion, e.g mint tokens

const amount = actionArgs[0].toString();
const tokenAddress = await getColonyTokenAddress(colonyAddress);
const tokenData = await getColonyTokenData(tokenAddress, colonyClient);
await mutate('createColonyAction', {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This adds all the data the ui needs for the mint token motion

@willm30 willm30 force-pushed the add/root-motion branch 2 times, most recently from 5890066 to 32f56c9 Compare February 14, 2023 12:14
'tokens were minted in colony:',
colonyAddress,
);
if (amount && amount.toString() !== '0') {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added this because the TokensMinted event is picked up after the MotionCreated event, twice actually. The amounts are undefined and 0, so guarding against that here.

Would be interested to know why this happens @rdig?

Copy link
Member

Choose a reason for hiding this comment

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

Is the motion set to very short timeout ? Ie: it finishes instantly ? Otherwise I can't really explain this.

The underlying action / event doesn't get triggered until the motion finishes (and that is done manually).

If that isn't the case I don't really understand what's going on. Maybe ask @area for his input ?

@willm30 willm30 marked this pull request as ready for review February 14, 2023 12:15
@willm30 willm30 requested a review from a team February 14, 2023 12:16
Copy link
Collaborator

@jakubcolony jakubcolony left a comment

Choose a reason for hiding this comment

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

I got it to work in conjunction with CDapp#263, but I'm only commenting for now - I had a chat with Raul today and I'll need to refactor the way we handle events that comprise an action and that will probably require some changes to this PR subsequently

src/domains.ts Outdated
Comment on lines 1 to 2
export const formatDomain = (colonyAddress: string, domainId: string) =>
`${colonyAddress}_${domainId.toString()}`;
Copy link
Collaborator

Choose a reason for hiding this comment

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

That could live in the utils folder

Comment on lines 145 to 147
if (Extension.VotingReputation === extensionId) {
addMotionEventListener(ContractEventsSignatures.MotionCreated, colony);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

In eventProcessor, this listener is added on ExtensionInitialised event, and here you add it for all installed (but not necessarily initialised) extensions. Is that intentional? If not, you could move it a few lines below, there's a check for whether the extension is currently initialised

Copy link
Member

Choose a reason for hiding this comment

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

☝️

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, have moved down

src/types.ts Outdated
Comment on lines 78 to 81
/*
* Contract calls
*/
export enum ColonyActions {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could the name be more indicative that these are the contract calls?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've gone for ColonyOperations. Preferable?

src/types.ts Outdated
Comment on lines 75 to 89
export enum ColonyMotionType {
MintTokensMotion = 'MINT_TOKENS_MOTION',
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I noticed in the corresponding CDapp PR you keep those merged, should we also stick to that idea here?

Copy link
Member

@rdig rdig left a comment

Choose a reason for hiding this comment

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

Left some comments, but otherwise seems good.

'tokens were minted in colony:',
colonyAddress,
);
if (amount && amount.toString() !== '0') {
Copy link
Member

Choose a reason for hiding this comment

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

Is the motion set to very short timeout ? Ie: it finishes instantly ? Otherwise I can't really explain this.

The underlying action / event doesn't get triggered until the motion finishes (and that is done manually).

If that isn't the case I don't really understand what's going on. Maybe ask @area for his input ?

@@ -61,7 +61,7 @@ const startIngestor = async (): Promise<void> => {
/*
* Setup all listeners we care about
*/
await blockListener();
Copy link
Member

Choose a reason for hiding this comment

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

Not saying it's something wrong with this, I'm just curious on why you removed 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.

This one's not an async function

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rdig I think you asked this on another pr as well so just tagging you here in case you missed my response. Not using await since there's nothing to await.

Comment on lines 145 to 147
if (Extension.VotingReputation === extensionId) {
addMotionEventListener(ContractEventsSignatures.MotionCreated, colony);
}
Copy link
Member

Choose a reason for hiding this comment

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

☝️


try {
return colonyClient.interface.parseTransaction({
data: motion.action,
Copy link
Member

Choose a reason for hiding this comment

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

If I remember correctly the motion's data (action) is available in the event, and if that is the case we don't have to call getMotion to get it anymore.

Please check if that's the case, I might be wrong though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rdig I don't think so, the event emits motionId, creator, and domainId but not the underlying action

@willm30 willm30 force-pushed the add/root-motion branch 2 times, most recently from e042c49 to 70c25ed Compare February 27, 2023 13:16
@willm30 willm30 force-pushed the add/root-motion branch 2 times, most recently from c3aa3fb to 5ea6b1d Compare March 2, 2023 13:03
* It basically does away with all the boilerplate of setting up topics, setting
* the event listener, parsing the received event
*/
export const eventListenerGenerator = async (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved to own file as I have added a few helpers

* Network Client specific event listener,
* which uses `eventListenerGenerator` under the hood
*/
export const addNetworkEventListener = async (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved to '~utils/eventListeners'

contractAddress,
);

const filter = getEventFilter(eventSignature, contractAddress, clientType);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wrote some helpers for this. Should make it easier to extend.

@willm30 willm30 requested a review from jakubcolony March 2, 2023 13:13
@willm30
Copy link
Contributor Author

willm30 commented Mar 2, 2023

@jakubcolony Have rebased this to account for changes to event processor

@jakubcolony
Copy link
Collaborator

@willm30 I pulled your latest changes and I'm seeing those errors when trying to create mint tokens motion. Looks like you're trying to write some fields (tokenSymbol, fromDomain) that have been modified since:

errors: [
    {
      message: 'Variable "$input" got invalid value { id: "0x97e6da42f5d3b2d5e02a7288d5b0d222322c72fe5bdfa6aba36547eeed8f868f", colonyId: "0xA4b4F5d9DBe383206Bd11C0e447F4462B9ea6cAE", type: "MINT_TOKENS_MOTION", isMotion: true, tokenSymbol: "JKB", fromDomain: "0xA4b4F5d9DBe383206Bd11C0e447F4462B9ea6cAE_1", initiatorAddress: "0xb77D57F4959eAfA0339424b83FcFaf9c15407461", amount: "1", blockNumber: 1100, tokenAddress: "0x8c60Ab84fa41a5E826BB91C30680e3b5faF8972B" }; Field "tokenSymbol" is not defined by type CreateColonyActionInput.',
      locations: [Array]
    },
    {
      message: 'Variable "$input" got invalid value { id: "0x97e6da42f5d3b2d5e02a7288d5b0d222322c72fe5bdfa6aba36547eeed8f868f", colonyId: "0xA4b4F5d9DBe383206Bd11C0e447F4462B9ea6cAE", type: "MINT_TOKENS_MOTION", isMotion: true, tokenSymbol: "JKB", fromDomain: "0xA4b4F5d9DBe383206Bd11C0e447F4462B9ea6cAE_1", initiatorAddress: "0xb77D57F4959eAfA0339424b83FcFaf9c15407461", amount: "1", blockNumber: 1100, tokenAddress: "0x8c60Ab84fa41a5E826BB91C30680e3b5faF8972B" }; Field "fromDomain" is not defined by type CreateColonyActionInput. Did you mean fromDomainId or toDomainId?',
      locations: [Array]
    }
  ]

@willm30
Copy link
Contributor Author

willm30 commented Mar 9, 2023

@jakubcolony Yep I will need to update this as I rebased the cdapp branch.

Copy link
Collaborator

@jakubcolony jakubcolony left a comment

Choose a reason for hiding this comment

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

@willm30 Nice work overall, but after pulling the latest from port/colony-motions I can't create a colony to test this, due to the following error from GetFullColonyByName query:

"Cannot query field \"color\" on type \"Domain\". Did you mean \"colony\" or \"colonyId\"?"

Also, we don't have a linter yet, but do you have an eslint extension installed? I'm seeing a few eslint warnings here and there, e.g. utils/motions.ts:
image

@@ -0,0 +1 @@
export { default as handleMotionCreated } from './motionCreated';
Copy link
Collaborator

Choose a reason for hiding this comment

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

Looks like there's an extra space before motions:
image

import {
getParsedActionFromMotion,
writeMintTokensMotionToDB,
} from '~utils/motions';
Copy link
Collaborator

Choose a reason for hiding this comment

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

You could export the motions stuff from ~utils and import it together with verbose

Comment on lines 1 to 2
export const formatDomain = (colonyAddress: string, domainId: string) =>
`${colonyAddress}_${domainId.toString()}`;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just a heads up, there should be a getDomainDatabaseId util in port/actions

@@ -5,3 +5,4 @@ export * from './extensions';
export * from './numbers';
export * from './tokens';
export * from './actions';
export * from './eventListeners';
Copy link
Collaborator

Choose a reason for hiding this comment

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

Matter of personal preferece I guess, but I would consider changing utils/events.ts to events directory and sticking all your extracted utils in there. events and eventListeners seem closely tied

Comment on lines 13 to 19
export const getColonyTokenData = async (
tokenAddress: string,
colonyClient: AnyColonyClient,
): Promise<{ name: string; decimals: number; symbol: string }> => {
const tokenClient = await getTokenClient(tokenAddress, colonyClient.provider);
return await tokenClient.getTokenInfo();
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this used anywhere? I can see it imported in utils/motions but doesn't seem to be called

@willm30
Copy link
Contributor Author

willm30 commented Mar 16, 2023

@jakubcolony I have rebased the cdapp after you rebased actions and have rebased this branch too on latest actions. I'm not experiencing problems creating a colony and all seems to have been integrated correctly. But let me know if after building both branches afresh, you still have issues.

Copy link
Collaborator

@jakubcolony jakubcolony left a comment

Choose a reason for hiding this comment

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

Nice work! 💪

parsedAction: TransactionDescription,
) => {
const { name, args: actionArgs } = parsedAction;
const amount = actionArgs[0].toString();
Copy link
Collaborator

@jakubcolony jakubcolony Mar 17, 2023

Choose a reason for hiding this comment

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

Could this element of the array be undefined? If so, consider adding optional chaining

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jakubcolony No, don't expect so. The front end should not be passing through an undefined amount of tokens to mint.

It maps to the motion params field in the current set up, for reference:

motionparams

Copy link
Collaborator

Choose a reason for hiding this comment

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

Is that a safe assumption to make, that it will never be broken by anyone?
What about calling it directly from a contract?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jakubcolony I don't believe calling it directly from a contract will be a problem either. You need to pass an "encoded action" to the createMotion call, and in order for the encode action function to succeed, it must receive a complete set of arguments to the underlying action call (by 'complete' I mean correct length, each of which is the correct type).

@rdig Can a malformed event find its way into the block ingestor? I.e. an event without the expected set of arguments?

Copy link
Member

Choose a reason for hiding this comment

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

No, not really. If that happens it'll be the event of the year on ethereum :)

While I approve of your proactive thinking, we shouldn't be worrying about this for now

Copy link
Collaborator

Choose a reason for hiding this comment

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

Good to know, thanks 👍

@willm30 willm30 merged commit b4fc7dc into port/motions Mar 17, 2023
@willm30 willm30 deleted the add/root-motion branch March 17, 2023 17:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants