-
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: protocol provider base #12
Conversation
GRT-49 Implement Base Class for Protocol provider
Follow figma diagrams : https://www.figma.com/board/BbciqJb5spg35ZglTsRRBb/Offchain?node-id=233-2388&t=rxiyrWw25Xi7qLak-4 We are not implementing any of the current methods given that on-chain team is starting its development phase. AC:
GRT-50 Implement `getCurrentEpoch()`
This is the only method that should be implemented. AC:
|
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.
Pretty solid typing and also solid usage of vitest
mocking. 🚀
Left some comments, most of them to know your stance on some particular topics.
@@ -0,0 +1,2 @@ | |||
export type Address = `0x${string}`; |
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, had never used template literal types.
* @param rpcUrls The RPC URLs to connect to the Arbitrum chain | ||
* @param contracts The addresses of the protocol contracts that will be instantiated | ||
*/ | ||
constructor(rpcUrls: string[], contracts: ProtocolContractsAddresses) { |
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 should probably validate that the rpcUrls
is not empty, as instantiating a ProtocolProvider
with empty rpcUrls
probably makes no sense (and might fail while trying to create the actual client
?).
This would catch a potential EBO agent misconfiguration, if no rpc urls are specified for the protocol chain.
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 are right , we have arbitrum
variable that contains metadata, including some public rpc, but we want to be explicit here i think
}); | ||
|
||
describe("constructor", () => { | ||
it("should create a new ProtocolProvider instance successfully", () => { |
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.
This is an extremely small matter of preference (bias from other languages); what do you think about phrasing the it
descriptions by dropping the should
? IMO is more concise and gives you some characters to describe better the test case.
it("should create a new ProtocolProvider instance successfully", () => { | |
it("creates a new ProtocolProvider instance successfully", () => { |
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.
Im in. Probably should
is in someway redundant. I will add this to our best practices & standards doc.
}); | ||
}); | ||
}); | ||
describe("getCurrentEpoch", () => { |
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.
What's your stance on using tests also as "live/code" documentation? Apart from natspecs, I generally use tests to explicitly state the expected behavior for situations that are core to the business (sometimes tests are even more clarifying that some comments in code).
This mindset is something along the lines of "I've thought about this situation, how it impacts the product/business/service and I expect this code to behave like the test indicates as I carefully decided this to be the best option". Helps to confirm from another side that the implemented code is actually doing what I want it to do.
Taking as an example a failure in the getCurrentEpoch
, from the business side I guess that we want to specify with a test that we want expect it to fail if any of the contract calls inside the method fails, right?
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.
Love this comment. Basically you mean that we should focus more on the behaviour that we expect for the business to write our tests or in the some way being more focused on the business specification, instead of blindly write tests for the code, right ?
Will add tests for those cases.
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.
That's right.
I mean, blindly testing stuff somewhat works but it's extremely time consuming. Let's say that for this specific case, from the "business" side, we mainly care about the situation where at least one of the requests fails while getting the epoch block number; I think we don't have to blindly test what happens if the first request fails, if the second request fails and if both requests fails. Just stating with a test that we expect a failure when at least one request fails seems enough to (semantically) convey the whole message.
In an ideal world, we should read the test suite results and understand what our code intends to do.
@@ -0,0 +1,6 @@ | |||
export class RpcUrlsEmpty extends Error { |
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.
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.
+1 to no Exception
suffix. We can always alias the imports if needed.
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.
im in 🫡
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.
Yay 😃
private oracleContract: GetContractReturnType< | ||
typeof oracleAbi, | ||
typeof this.client, | ||
`0x${string}` |
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.
replace with Address
type
private epochManagerContract: GetContractReturnType< | ||
typeof epochManagerAbi, | ||
typeof this.client, | ||
`0x${string}` |
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.
noticed that viem
already have the Address
type, lets use their type to avoid code duplication
} | ||
this.client = createPublicClient({ | ||
chain: arbitrum, | ||
transport: fallback(rpcUrls.map((url) => http(url))), |
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.
nice
it("throws when current epoch block request fails", async () => { | ||
const protocolProvider = new ProtocolProvider(mockRpcUrls, mockContractAddress); | ||
const currentEpochError = new Error("Failed to get current epoch"); | ||
const currentEpochBlockError = new Error("Failed to get current epoch block"); | ||
|
||
(protocolProvider["epochManagerContract"].read.currentEpoch as Mock).mockRejectedValue( | ||
currentEpochError, | ||
); | ||
( | ||
protocolProvider["epochManagerContract"].read.currentEpochBlock as Mock | ||
).mockRejectedValue(currentEpochBlockError); | ||
|
||
await expect(protocolProvider.getCurrentEpoch()).rejects.toThrow(currentEpochError); | ||
}); |
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.
is this test case needed? Promise.all rejects on first promise rejection so i think with the individual rejection cases is enough
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 are right, just wanted to make sure how does Promise.all work in case of having 2 rejects at the same time 🤣
i will remove it
tsconfig.base.json
Outdated
@@ -18,6 +18,6 @@ | |||
"module": "NodeNext", | |||
"sourceMap": true, | |||
/* If your code doesn't run in the DOM: */ | |||
"lib": ["es2022"] | |||
"lib": ["es2022", "DOM"] |
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.
is this needed?
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, without this we didn't have console.log
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 what is missing then is devDependency: @types/node
because ebo-agent won't be a browser app right?
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.
oh, let me check that
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's goo 🚀
🤖 Linear
Closes GRT-49 GRT-50
Description