Skip to content

Commit 8d9f689

Browse files
committed
pulsse fixes
1 parent dd205a0 commit 8d9f689

File tree

5 files changed

+60
-23
lines changed

5 files changed

+60
-23
lines changed

target_chains/ethereum/contracts/contracts/pulse/Pulse.sol

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ abstract contract Pulse is IPulse, PulseState {
5656
// TODO: there can be a separate wrapper function that defaults the provider (or uses the cheapest or something).
5757
function requestPriceUpdatesWithCallback(
5858
address provider,
59-
uint256 publishTime,
59+
uint64 publishTime,
6060
bytes32[] calldata priceIds,
6161
uint256 callbackGasLimit
6262
) external payable override returns (uint64 requestSequenceNumber) {
@@ -65,7 +65,7 @@ abstract contract Pulse is IPulse, PulseState {
6565
"Provider not registered"
6666
);
6767

68-
// FIXME: this comment is wrong.
68+
// FIXME: this comment is wrong. (we're not using tx.gasprice)
6969
// NOTE: The 60-second future limit on publishTime prevents a DoS vector where
7070
// attackers could submit many low-fee requests for far-future updates when gas prices
7171
// are low, forcing executors to fulfill them later when gas prices might be much higher.
@@ -93,7 +93,6 @@ abstract contract Pulse is IPulse, PulseState {
9393
for (uint8 i = 0; i < priceIds.length; i++) {
9494
req.priceIds[i] = priceIds[i];
9595
}
96-
9796
_state.accruedFeesInWei += _state.pythFeeInWei;
9897

9998
emit PriceUpdateRequested(req, priceIds);
@@ -129,7 +128,6 @@ abstract contract Pulse is IPulse, PulseState {
129128
}
130129
}
131130

132-
// Parse price feeds first to measure gas usage
133131
// TODO: should this use parsePriceFeedUpdatesUnique? also, do we need to add 1 to maxPublishTime?
134132
IPyth pyth = IPyth(_state.pyth);
135133
uint256 pythFee = pyth.getUpdateFee(updateData);
@@ -142,7 +140,6 @@ abstract contract Pulse is IPulse, PulseState {
142140
SafeCast.toUint64(req.publishTime)
143141
);
144142

145-
clearRequest(sequenceNumber);
146143
// TODO: if this effect occurs here, we need to guarantee that executeCallback can never revert.
147144
// If executeCallback can revert, then funds can be permanently locked in the contract.
148145
// TODO: there also needs to be some penalty mechanism in case the expected provider doesn't execute the callback.
@@ -153,6 +150,18 @@ abstract contract Pulse is IPulse, PulseState {
153150
_state.providers[providerToCredit].accruedFeesInWei += SafeCast
154151
.toUint128(msg.value - pythFee);
155152

153+
clearRequest(sequenceNumber);
154+
155+
// 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.
156+
// a better solution would be a doubly-linked list of active requests.
157+
// After successful callback, update firstUnfulfilledSeq if needed
158+
while (
159+
_state.firstUnfulfilledSeq < _state.currentSequenceNumber &&
160+
!isActive(findRequest(_state.firstUnfulfilledSeq))
161+
) {
162+
_state.firstUnfulfilledSeq++;
163+
}
164+
156165
try
157166
IPulseConsumer(req.requester)._pulseCallback{
158167
gas: req.callbackGasLimit
@@ -164,7 +173,7 @@ abstract contract Pulse is IPulse, PulseState {
164173
// Explicit revert/require
165174
emit PriceUpdateCallbackFailed(
166175
sequenceNumber,
167-
msg.sender,
176+
providerToCredit,
168177
priceIds,
169178
req.requester,
170179
reason
@@ -173,21 +182,12 @@ abstract contract Pulse is IPulse, PulseState {
173182
// Out of gas or other low-level errors
174183
emit PriceUpdateCallbackFailed(
175184
sequenceNumber,
176-
msg.sender,
185+
providerToCredit,
177186
priceIds,
178187
req.requester,
179188
"low-level error (possibly out of gas)"
180189
);
181190
}
182-
183-
// 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.
184-
// After successful callback, update firstUnfulfilledSeq if needed
185-
while (
186-
_state.firstUnfulfilledSeq < _state.currentSequenceNumber &&
187-
!isActive(findRequest(_state.firstUnfulfilledSeq))
188-
) {
189-
_state.firstUnfulfilledSeq++;
190-
}
191191
}
192192

193193
function emitPriceUpdate(
@@ -198,7 +198,7 @@ abstract contract Pulse is IPulse, PulseState {
198198
int64[] memory prices = new int64[](priceFeeds.length);
199199
uint64[] memory conf = new uint64[](priceFeeds.length);
200200
int32[] memory expos = new int32[](priceFeeds.length);
201-
uint256[] memory publishTimes = new uint256[](priceFeeds.length);
201+
uint64[] memory publishTimes = new uint256[](priceFeeds.length);
202202

203203
for (uint i = 0; i < priceFeeds.length; i++) {
204204
prices[i] = priceFeeds[i].price.price;

target_chains/ethereum/contracts/contracts/pulse/PulseErrors.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pragma solidity ^0.8.0;
44

55
error NoSuchProvider();
66
error NoSuchRequest();
7+
// TODO: add expected / provided values
78
error InsufficientFee();
89
error Unauthorized();
910
error InvalidCallbackGas();

target_chains/ethereum/contracts/contracts/pulse/PulseEvents.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ interface PulseEvents {
1313
int64[] prices,
1414
uint64[] conf,
1515
int32[] expos,
16-
uint256[] publishTimes
16+
uint64[] publishTimes
1717
);
1818

1919
event FeesWithdrawn(address indexed recipient, uint128 amount);

target_chains/ethereum/contracts/contracts/pulse/PulseState.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ contract PulseState {
1111

1212
struct Request {
1313
uint64 sequenceNumber;
14-
uint256 publishTime;
14+
uint64 publishTime;
1515
// TODO: this is going to absolutely explode gas costs. Need to do something smarter here.
16+
// possible solution is to hash the price ids and store the hash instead.
17+
// The ids themselves can be retrieved from the event.
1618
bytes32[MAX_PRICE_IDS] priceIds;
1719
uint8 numPriceIds; // Actual number of price IDs used
1820
uint256 callbackGasLimit;

target_chains/ethereum/contracts/forge-test/Pulse.t.sol

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,22 @@ import "../contracts/pulse/PulseEvents.sol";
1212
import "../contracts/pulse/PulseErrors.sol";
1313

1414
contract MockPulseConsumer is IPulseConsumer {
15+
address private _pulse;
1516
uint64 public lastSequenceNumber;
1617
PythStructs.PriceFeed[] private _lastPriceFeeds;
1718

19+
constructor(address pulse) {
20+
_pulse = pulse;
21+
}
22+
23+
function getPulse() internal view override returns (address) {
24+
return _pulse;
25+
}
26+
1827
function pulseCallback(
1928
uint64 sequenceNumber,
2029
PythStructs.PriceFeed[] memory priceFeeds
21-
) external override {
30+
) internal override {
2231
lastSequenceNumber = sequenceNumber;
2332
for (uint i = 0; i < priceFeeds.length; i++) {
2433
_lastPriceFeeds.push(priceFeeds[i]);
@@ -35,25 +44,46 @@ contract MockPulseConsumer is IPulseConsumer {
3544
}
3645

3746
contract FailingPulseConsumer is IPulseConsumer {
47+
address private _pulse;
48+
49+
constructor(address pulse) {
50+
_pulse = pulse;
51+
}
52+
53+
function getPulse() internal view override returns (address) {
54+
return _pulse;
55+
}
56+
3857
function pulseCallback(
3958
uint64,
4059
PythStructs.PriceFeed[] memory
41-
) external pure override {
60+
) internal pure override {
4261
revert("callback failed");
4362
}
4463
}
4564

4665
contract CustomErrorPulseConsumer is IPulseConsumer {
4766
error CustomError(string message);
4867

68+
address private _pulse;
69+
70+
constructor(address pulse) {
71+
_pulse = pulse;
72+
}
73+
74+
function getPulse() internal view override returns (address) {
75+
return _pulse;
76+
}
77+
4978
function pulseCallback(
5079
uint64,
5180
PythStructs.PriceFeed[] memory
52-
) external pure override {
81+
) internal pure override {
5382
revert CustomError("callback failed");
5483
}
5584
}
5685

86+
// FIXME: this shouldn't be IPulseConsumer.
5787
contract PulseTest is Test, PulseEvents, IPulseConsumer {
5888
ERC1967Proxy public proxy;
5989
PulseUpgradeable public pulse;
@@ -1187,11 +1217,15 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer {
11871217
);
11881218
}
11891219

1220+
function getPulse() internal view override returns (address) {
1221+
return address(pulse);
1222+
}
1223+
11901224
// Mock implementation of pulseCallback
11911225
function pulseCallback(
11921226
uint64 sequenceNumber,
11931227
PythStructs.PriceFeed[] memory priceFeeds
1194-
) external override {
1228+
) internal override {
11951229
// Just accept the callback, no need to do anything with the data
11961230
// This prevents the revert we're seeing
11971231
}

0 commit comments

Comments
 (0)