-
Notifications
You must be signed in to change notification settings - Fork 260
fix(pulse): Miscellaneous contract fixes #2454
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
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
3 Skipped Deployments
|
|
||
// This method is expected to be implemented by the consumer to handle the price updates. | ||
// It will be called by _pulseCallback after _pulseCallback ensures that the call is | ||
// indeed from Pulse contract. |
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.
Note that if this is an external method, then anyone could have called this function with a sequenceNumber and a list of PriceFeeds (which are already parsed, and thus mockable).
Fix here ensures that only the Pulse contract can invoke the callback.
) external view returns (uint128 feeAmount); | ||
|
||
function getAccruedFees() external view returns (uint128 accruedFeesInWei); | ||
function getAccruedPythFees() |
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.
(renamed this for clarity)
@@ -85,21 +87,20 @@ abstract contract Pulse is IPulse, PulseState { | |||
req.requester = msg.sender; | |||
req.numPriceIds = uint8(priceIds.length); | |||
req.provider = provider; | |||
req.fee = SafeCast.toUint128(msg.value - _state.pythFeeInWei); |
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.
Store the fee in the request so we can credit the provider who actually fulfilled the request in executeCallback
SafeCast.toUint64(req.publishTime), | ||
SafeCast.toUint64(req.publishTime) | ||
); | ||
// TODO: should this use parsePriceFeedUpdatesUnique? also, do we need to add 1 to maxPublishTime? |
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.
Note that the tests mock the parsePriceFeedUpdates call, so we don't actually know if it's being invoked correctly. (it was definitely missing the value parameter)
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 using parsePriceFeedUpdatesUnique
here is a good idea due to its deterministic behavior
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 echo what Daniel says here. No you don't need to add 1 to it.
@@ -111,7 +112,7 @@ abstract contract Pulse is IPulse, PulseState { | |||
block.timestamp < req.publishTime + _state.exclusivityPeriodSeconds | |||
) { | |||
require( | |||
msg.sender == req.provider, | |||
providerToCredit == req.provider, |
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.
the provider
key is a cold key that shouldn't be used in hot actions. (This is why we have the feeManager as the hot key that has limited permissions -- if it is compromised, the provider can rotate it without too much damage)
Instead, make this method permissionless. Only the requested provider
is motivated to call it during the exclusivity period as that's the only person who can earn the fees for the request.
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 add a check to make sure the provider is registered?
require( | ||
msg.sender == provider || | ||
msg.sender == _state.providers[provider].feeManager, | ||
"Only provider or fee manager can invoke this 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.
the fee manager should be allowed to set fees
@@ -30,7 +52,8 @@ interface IPulse is PulseEvents { | |||
* the fee estimation unreliable. | |||
*/ | |||
function requestPriceUpdatesWithCallback( | |||
uint256 publishTime, | |||
address provider, |
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 don't like people choosing their providers. It's an extra hop for integrators to think about (what are providers? how they are different? how should i choose? ...)
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.
Yeah agreed. I think the thing to do is to add a wrapper method that chooses for users. I'm not exactly sure what the right way to choose is yet though.
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.
Well if people are gonna use the wrapper it shouldn't be public right? (I recommend finalizing what people are gonna use)
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 PR had a lot of small high context changes. I generally recommend separating bug fixes vs other changes and specially the API changes.
@@ -30,7 +52,8 @@ interface IPulse is PulseEvents { | |||
* the fee estimation unreliable. | |||
*/ | |||
function requestPriceUpdatesWithCallback( | |||
uint256 publishTime, | |||
address provider, |
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.
Well if people are gonna use the wrapper it shouldn't be public right? (I recommend finalizing what people are gonna use)
// TODO: this is going to absolutely explode gas costs. Need to do something smarter here. | ||
// possible solution is to hash the price ids and store the hash instead. | ||
// The ids themselves can be retrieved from the event. |
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 decided to move away from event because that adds a lot of complexity. We can come back to it if the cost is substantial (and the costs generally are getting lower on ETH which is the only chain that has high costs on these things)
// Note: The provider needs to set its fees to include the fee charged by the Pyth contract. | ||
// Ideally, we would be able to automatically compute the pyth fees from the priceIds, but the | ||
// fee computation on IPyth assumes it has the full updated data. |
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.
Couldn't you at least set it a constant for the actual contract? Now the provider fees are getting complicated.
// TODO: if this effect occurs here, we need to guarantee that executeCallback can never revert. | ||
// If executeCallback can revert, then funds can be permanently locked in the contract. |
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 wrong with just considering them as Pyth fee? I think we mention the risks in the interface.
SafeCast.toUint64(req.publishTime), | ||
SafeCast.toUint64(req.publishTime) | ||
); | ||
// TODO: should this use parsePriceFeedUpdatesUnique? also, do we need to add 1 to maxPublishTime? |
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 echo what Daniel says here. No you don't need to add 1 to it.
_state.accruedFeesInWei += _state.pythFeeInWei; | ||
|
||
emit PriceUpdateRequested(req, priceIds); | ||
} | ||
|
||
// TODO: does this need to be payable? Any cost paid to Pyth could be taken out of the provider's accrued fees. |
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.
good call no it's not needed.
require( | ||
_state.providers[provider].isRegistered, | ||
"Provider not registered" | ||
); | ||
|
||
// FIXME: this comment is wrong. (we're not using tx.gasprice) |
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.
remove the wrong comment then?
// Safe cast because this is a unix timestamp in seconds. | ||
publishTimes[i] = SafeCast.toUint64( | ||
priceFeeds[i].price.publishTime | ||
); |
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 it helps with costs, afaik event arguments will end up as u256 regardless.
@@ -111,7 +112,7 @@ abstract contract Pulse is IPulse, PulseState { | |||
block.timestamp < req.publishTime + _state.exclusivityPeriodSeconds | |||
) { | |||
require( | |||
msg.sender == req.provider, | |||
providerToCredit == req.provider, |
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 add a check to make sure the provider is registered?
|
||
clearRequest(sequenceNumber); | ||
|
||
// TODO: I'm pretty sure this is going to use a lot of gas because it's doing a storage lookup for each sequence number. | ||
// a better solution would be a doubly-linked list of active requests. | ||
// After successful callback, update firstUnfulfilledSeq 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.
yeah true. let's do some gas benchmarking later.
Summary
I was looking through the Pulse contract and noticed some issues:
How has this been tested?