-
Notifications
You must be signed in to change notification settings - Fork 0
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
feat: pool created handler #11
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GJ ser, just some comments
process(_event: ProtocolEvent<"Allo", AlloEvent>): Promise<Changeset> { | ||
throw new Error("Method not implemented."); | ||
constructor( | ||
private readonly chainId: ChainId, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can get rid of chainId as attribute, since we don't need processors to be attached to a certain chainId, just the core pipeline. I think we can just use the chainId from the event. Wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i thought but actually the InsertRound
object needs the chainId, we could attach it later but i think that's kindof unclean thing to do, we would be delegating a task to the caller
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@0xkenj1 should we pass just the chainId or a Viem Chain
type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My two cents here; probably coupling the proccessor's public interface to viem's types is not the happiest thing to do (ie what if we need to change viem for other lib?)
constructor( | ||
readonly event: ProtocolEvent<"Allo", "PoolCreated">, | ||
private readonly chainId: ChainId, | ||
dependencies: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can create a type called ProcessorDependencies
and use it everywhere, to simplify code
round?: unknown; | ||
application?: unknown; | ||
}>(metadataPointer); | ||
const parsedRoundMetadata = RoundMetadataSchema.safeParse(metadata?.round); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we pass schema validator in getMetadata
method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
forgot i implemented that jajaja
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'll come back later to this because i'll need to make adjustments since code flow should go on even if validation fails, so i need to make adjustments to the metadata provider
this makes me think if it's correct to have the validation function as arg 🤔
symbol: "USDC", //TODO: get symbol from token | ||
name: "USDC", //TODO: get name from token |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would leave these two as unknown
wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually idk what the TokenType will have, when we copy the map from the old repo we'll see
let strategyTimings: { | ||
applicationsStartTime: Date | null; | ||
applicationsEndTime: Date | null; | ||
donationsStartTime: Date | null; | ||
donationsEndTime: Date | null; | ||
} = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type StrategyTimings
?
let registrationStartTimeResolved: bigint; | ||
let registrationEndTimeResolved: bigint; | ||
let allocationStartTimeResolved: bigint; | ||
let allocationEndTimeResolved: bigint; | ||
|
||
if (viemClient.chain?.contracts?.multicall3) { | ||
const results = await viemClient.multicall({ | ||
contracts: [ | ||
{ | ||
abi: DonationVotingMerkleDistributionDirectTransferStrategy, | ||
functionName: "registrationStartTime", | ||
address: strategyId, | ||
}, | ||
{ | ||
abi: DonationVotingMerkleDistributionDirectTransferStrategy, | ||
functionName: "registrationEndTime", | ||
address: strategyId, | ||
}, | ||
{ | ||
abi: DonationVotingMerkleDistributionDirectTransferStrategy, | ||
functionName: "allocationStartTime", | ||
address: strategyId, | ||
}, | ||
{ | ||
abi: DonationVotingMerkleDistributionDirectTransferStrategy, | ||
functionName: "allocationEndTime", | ||
address: strategyId, | ||
}, | ||
], | ||
allowFailure: false, | ||
}); | ||
registrationStartTimeResolved = results[0]; | ||
registrationEndTimeResolved = results[1]; | ||
allocationStartTimeResolved = results[2]; | ||
allocationEndTimeResolved = results[3]; | ||
} else { | ||
const results = await Promise.all([ | ||
viemClient.readContract({ | ||
abi: DonationVotingMerkleDistributionDirectTransferStrategy, | ||
functionName: "registrationStartTime", | ||
address: strategyId, | ||
}), | ||
viemClient.readContract({ | ||
abi: DonationVotingMerkleDistributionDirectTransferStrategy, | ||
functionName: "registrationEndTime", | ||
address: strategyId, | ||
}), | ||
viemClient.readContract({ | ||
abi: DonationVotingMerkleDistributionDirectTransferStrategy, | ||
functionName: "allocationStartTime", | ||
address: strategyId, | ||
}), | ||
viemClient.readContract({ | ||
abi: DonationVotingMerkleDistributionDirectTransferStrategy, | ||
functionName: "allocationEndTime", | ||
address: strategyId, | ||
}), | ||
]); | ||
registrationStartTimeResolved = results[0]; | ||
registrationEndTimeResolved = results[1]; | ||
allocationStartTimeResolved = results[2]; | ||
allocationEndTimeResolved = results[3]; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let registrationStartTimeResolved: bigint; | |
let registrationEndTimeResolved: bigint; | |
let allocationStartTimeResolved: bigint; | |
let allocationEndTimeResolved: bigint; | |
if (viemClient.chain?.contracts?.multicall3) { | |
const results = await viemClient.multicall({ | |
contracts: [ | |
{ | |
abi: DonationVotingMerkleDistributionDirectTransferStrategy, | |
functionName: "registrationStartTime", | |
address: strategyId, | |
}, | |
{ | |
abi: DonationVotingMerkleDistributionDirectTransferStrategy, | |
functionName: "registrationEndTime", | |
address: strategyId, | |
}, | |
{ | |
abi: DonationVotingMerkleDistributionDirectTransferStrategy, | |
functionName: "allocationStartTime", | |
address: strategyId, | |
}, | |
{ | |
abi: DonationVotingMerkleDistributionDirectTransferStrategy, | |
functionName: "allocationEndTime", | |
address: strategyId, | |
}, | |
], | |
allowFailure: false, | |
}); | |
registrationStartTimeResolved = results[0]; | |
registrationEndTimeResolved = results[1]; | |
allocationStartTimeResolved = results[2]; | |
allocationEndTimeResolved = results[3]; | |
} else { | |
const results = await Promise.all([ | |
viemClient.readContract({ | |
abi: DonationVotingMerkleDistributionDirectTransferStrategy, | |
functionName: "registrationStartTime", | |
address: strategyId, | |
}), | |
viemClient.readContract({ | |
abi: DonationVotingMerkleDistributionDirectTransferStrategy, | |
functionName: "registrationEndTime", | |
address: strategyId, | |
}), | |
viemClient.readContract({ | |
abi: DonationVotingMerkleDistributionDirectTransferStrategy, | |
functionName: "allocationStartTime", | |
address: strategyId, | |
}), | |
viemClient.readContract({ | |
abi: DonationVotingMerkleDistributionDirectTransferStrategy, | |
functionName: "allocationEndTime", | |
address: strategyId, | |
}), | |
]); | |
registrationStartTimeResolved = results[0]; | |
registrationEndTimeResolved = results[1]; | |
allocationStartTimeResolved = results[2]; | |
allocationEndTimeResolved = results[3]; | |
} | |
const results: { | |
registrationStartTimeResolved: bigint; | |
registrationEndTimeResolved: bigint; | |
allocationStartTimeResolved: bigint; | |
allocationEndTimeResolved: bigint; | |
}; | |
const contractCalls = [ | |
{ | |
abi: DonationVotingMerkleDistributionDirectTransferStrategy, | |
functionName: "registrationStartTime", | |
address: strategyId, | |
}, | |
{ | |
abi: DonationVotingMerkleDistributionDirectTransferStrategy, | |
functionName: "registrationEndTime", | |
address: strategyId, | |
}, | |
{ | |
abi: DonationVotingMerkleDistributionDirectTransferStrategy, | |
functionName: "allocationStartTime", | |
address: strategyId, | |
}, | |
{ | |
abi: DonationVotingMerkleDistributionDirectTransferStrategy, | |
functionName: "allocationEndTime", | |
address: strategyId, | |
}, | |
]; | |
if (viemClient.chain?.contracts?.multicall3) { | |
results = await viemClient.multicall({ | |
contracts: contractCalls, | |
allowFailure: false, | |
}); | |
} else { | |
results = await Promise.all( | |
contractCalls.map((contractCall) => | |
viemClient.readContract(contractCall) | |
) | |
); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
didn't know that trick 🆒
let registrationStartTimeResolved: bigint; | ||
let registrationEndTimeResolved: bigint; | ||
|
||
if (viemClient.chain?.contracts?.multicall3) { | ||
const results = await viemClient.multicall({ | ||
contracts: [ | ||
{ | ||
abi: DirectGrantsLiteStrategy, | ||
functionName: "registrationStartTime", | ||
address: strategyAddress, | ||
}, | ||
{ | ||
abi: DirectGrantsLiteStrategy, | ||
functionName: "registrationEndTime", | ||
address: strategyAddress, | ||
}, | ||
], | ||
allowFailure: false, | ||
}); | ||
registrationStartTimeResolved = results[0]; | ||
registrationEndTimeResolved = results[1]; | ||
} else { | ||
const results = await Promise.all([ | ||
viemClient.readContract({ | ||
abi: DirectGrantsLiteStrategy, | ||
functionName: "registrationStartTime", | ||
address: strategyAddress, | ||
}), | ||
viemClient.readContract({ | ||
abi: DirectGrantsLiteStrategy, | ||
functionName: "registrationEndTime", | ||
address: strategyAddress, | ||
}), | ||
]); | ||
registrationStartTimeResolved = results[0]; | ||
registrationEndTimeResolved = results[1]; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shoulds
in tests
metadata: { | ||
pointer: string; | ||
protocol: bigint; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think the data for metadata comes as a tuple/array
metadata: { | |
pointer: string; | |
protocol: bigint; | |
}; | |
metadata: [ | |
pointer: string; | |
protocol: bigint; | |
]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aaaa thanks for the heads up
}, | ||
}; | ||
|
||
return mergeDeep(defaultEvent, overrides) as ProtocolEvent<"Allo", "PoolCreated">; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💎
} catch (error: unknown) { | ||
console.error( | ||
`An error occurred while processing the PoolCreated event. Event: ${this.event} - Error: ${error}`, | ||
); | ||
return []; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think at handlers level we just need to throw the errors, and let the processor to handle them and take actions based on the type of error (like for example retry the processing) and we avoid repeating logic of error handling on the handlers. wdyt ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
like that approach 🫡
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool stuff! Eager to see more PRs; left some random comments.
process(_event: ProtocolEvent<"Allo", AlloEvent>): Promise<Changeset> { | ||
throw new Error("Method not implemented."); | ||
constructor( | ||
private readonly chainId: ChainId, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My two cents here; probably coupling the proccessor's public interface to viem's types is not the happiest thing to do (ie what if we need to change viem for other lib?)
|
||
import type { Changeset, NewRound, PendingRoundRole } from "@grants-stack-indexer/repository"; | ||
import type { ChainId, ProtocolEvent } from "@grants-stack-indexer/shared"; | ||
import { isAlloNativeToken } from "@grants-stack-indexer/shared/"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the last /
mean something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wildcat eye to catch that 👁️
export function extractStrategyFromId(_id: Address): Strategy | undefined { | ||
const id = _id.toLowerCase(); | ||
/* eslint-disable no-fallthrough */ | ||
switch (id) { | ||
// SQFSuperfluidv1 | ||
case "0xf8a14294e80ff012e54157ec9d1b2827421f1e7f6bde38c06730b1c031b3f935": | ||
return { | ||
id: id as SanitizedStrategyId, | ||
name: "allov2.SQFSuperFluidStrategy", | ||
groups: ["allov2.SQFSuperFluidStrategy"], | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Asking to get more business context; are strategies going to be always hardcoded or do you plan to use some kind of API/service/repository to get these mappings?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we'll have a mapping in shared, from ID to the handler class actually. This is just the same class from the current indexer
I'll add the TODO to make it clear
export const calculateAmountInUsd = ( | ||
amount: bigint, | ||
tokenPriceInUsd: number, | ||
tokenDecimals: number, | ||
truncateDecimals?: number, | ||
): number => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd strongly suggest including at this point of the project, given that you are building the bases of your features, some kind of bigdecimal/bignumber lib to handle money values; anywhere you mention USD, price, etc. It'll make the development extremely robust and you won't need to worry about Number
precision, "stringification" of numbers, "numberification" of strings, etc.
There are two drawbacks that you might need to take into account:
- Operations between these values might imply some special methods (eg
a.plus(b)
) and not the js operators (a + b
). - Performance will be definitely worse, but if you are not doing 100M of operations per second it shouldn't be something to worry about.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As an bonus comment to show the degeneracy of JS numbers implementation, try doing this:
const n = 9332654729891549;
console.log(n.toString());
Check the last digit of the output. ☠️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great contribution ser 🫡
I also lean into this but we would have to change the DB data type at some point since now they handle it like this with truncation (don't ask why)
cc @0xkenj1 should we make the change now or leave it as a todo/nth for after the MVP?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as discussed offline, we ended up using bignumber and will change DB types to numeric, so we return string instead of hardcasting to number
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks good, just some optional suggestions 👍🏻
|
||
if (truncateDecimals !== undefined) { | ||
if (truncateDecimals < 0 || truncateDecimals > 18) { | ||
throw new Error("Truncate decimals must be between 0 and 18"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure if we prefer a custom error here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we do : D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you're right ser 💯
case "PoolCreated": | ||
return new PoolCreatedHandler(event, this.chainId, this.dependencies).handle(); | ||
default: | ||
throw new Error(`Unknown event name: ${event.eventName}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure if we prefer a custom error here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes we do 🤣
actually here i have my implementation for this error:
import { ContractName } from "@grants-stack-indexer/shared";
export class UnsupportedEventException extends Error {
constructor(
contract: ContractName,
public readonly eventName: string,
) {
super(`Event ${eventName} unsupported for ${contract} processor`);
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for this xd
case "PoolCreated": | ||
return new PoolCreatedHandler(event, this.chainId, this.dependencies).handle(); | ||
default: | ||
throw new Error(`Unknown event name: ${event.eventName}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes we do 🤣
actually here i have my implementation for this error:
import { ContractName } from "@grants-stack-indexer/shared";
export class UnsupportedEventException extends Error {
constructor(
contract: ContractName,
public readonly eventName: string,
) {
super(`Event ${eventName} unsupported for ${contract} processor`);
}
}
|
||
/** | ||
* Gets the strategy data for the DonationVotingMerkleDistributionDirectTransferStrategy | ||
* @param viemClient - The viem client |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* @param viemClient - The viem client | |
* @param evmProvider |
|
||
/** | ||
* Gets the strategy data for the DirectGrantsStrategy | ||
* @param viemClient - The viem client |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
|
||
if (truncateDecimals !== undefined) { | ||
if (truncateDecimals < 0 || truncateDecimals > 18) { | ||
throw new Error("Truncate decimals must be between 0 and 18"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we do : D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍🏻
🤖 Linear
Closes GIT-57 GIT-58
Description
Implements the
PoolCreatedHandler
on AlloProcessor.On
PoolCreated
event, we:TODO:
Note:
Admin/Manager roles for the pool are emitted before the pool is created so a pending round role is inserted in the db. Now that the PoolCreated event is emitted, we can convert pending roles to actual round roles.
Checklist before requesting a review