diff --git a/target_chains/ethereum/contracts/contracts/pulse/IPulse.sol b/target_chains/ethereum/contracts/contracts/pulse/IPulse.sol index 4fed40092..e7fac6da7 100644 --- a/target_chains/ethereum/contracts/contracts/pulse/IPulse.sol +++ b/target_chains/ethereum/contracts/contracts/pulse/IPulse.sol @@ -15,13 +15,9 @@ interface IPulseConsumer { interface IPulse { // Events - event PriceUpdateRequested( - uint64 indexed sequenceNumber, - address indexed provider, - uint256 publishTime, - bytes32[] priceIds, - address requester - ); + event ProviderRegistered(PulseState.ProviderInfo providerInfo); + + event PriceUpdateRequested(PulseState.Request request); event PriceUpdateExecuted( uint64 indexed sequenceNumber, @@ -30,18 +26,18 @@ interface IPulse { bytes32[] priceIds ); - event ProviderRegistered( - address indexed provider, - uint128 feeInWei, - bytes uri - ); - event ProviderFeeUpdated( address indexed provider, uint128 oldFeeInWei, uint128 newFeeInWei ); + event ProviderUriUpdated( + address indexed provider, + bytes oldUri, + bytes newUri + ); + event ProviderWithdrawn( address indexed provider, address indexed recipient, @@ -54,12 +50,6 @@ interface IPulse { address newFeeManager ); - event ProviderUriUpdated( - address indexed provider, - bytes oldUri, - bytes newUri - ); - event ProviderMaxNumPricesUpdated( address indexed provider, uint32 oldMaxNumPrices, @@ -80,7 +70,6 @@ interface IPulse { address provider, uint256 publishTime, bytes32[] calldata priceIds, - bytes[] calldata updateData, uint256 callbackGasLimit ) external payable returns (uint64 sequenceNumber); @@ -97,30 +86,33 @@ interface IPulse { function setProviderFee(uint128 newFeeInWei) external; + function setProviderFeeAsFeeManager( + address provider, + uint128 newFeeInWei + ) external; + + function setProviderUri(bytes calldata uri) external; + function withdraw(uint128 amount) external; function withdrawAsFeeManager(address provider, uint128 amount) external; - function setProviderUri(bytes calldata uri) external; - // Getters function getFee(address provider) external view returns (uint128 feeAmount); - function getDefaultProvider() external view returns (address); + function getPythFeeInWei() external view returns (uint128 pythFeeInWei); function getAccruedPythFees() external view returns (uint128 accruedPythFeesInWei); + function getDefaultProvider() external view returns (address); + function getProviderInfo( address provider ) external view returns (PulseState.ProviderInfo memory info); - function getAdmin() external view returns (address admin); - - function getPythFeeInWei() external view returns (uint128 pythFeeInWei); - function getRequest( address provider, uint64 sequenceNumber @@ -129,10 +121,5 @@ interface IPulse { // Setters function setFeeManager(address manager) external; - function setProviderFeeAsFeeManager( - address provider, - uint128 newFeeInWei - ) external; - function setMaxNumPrices(uint32 maxNumPrices) external; } diff --git a/target_chains/ethereum/contracts/contracts/pulse/Pulse.sol b/target_chains/ethereum/contracts/contracts/pulse/Pulse.sol index e17c4da49..e6397a2f6 100644 --- a/target_chains/ethereum/contracts/contracts/pulse/Pulse.sol +++ b/target_chains/ethereum/contracts/contracts/pulse/Pulse.sol @@ -2,15 +2,12 @@ pragma solidity ^0.8.0; -import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/utils/math/SafeCast.sol"; import "./IPulse.sol"; import "./PulseState.sol"; import "./PulseErrors.sol"; -contract Pulse is IPulse, ReentrancyGuard, PulseState { - using SafeCast for uint256; - +abstract contract Pulse is IPulse, PulseState { function _initialize( address admin, uint128 pythFeeInWei, @@ -24,15 +21,16 @@ contract Pulse is IPulse, ReentrancyGuard, PulseState { ); _state.admin = admin; - _state.pythFeeInWei = pythFeeInWei; _state.accruedPythFeesInWei = 0; + _state.pythFeeInWei = pythFeeInWei; _state.defaultProvider = defaultProvider; if (prefillRequestStorage) { - // Prefill storage slots to make future requests use less gas + // Write some data to every storage slot in the requests array such that new requests + // use a more consistent amount of gas. + // Note that these requests are not live because their sequenceNumber is 0. for (uint8 i = 0; i < NUM_REQUESTS; i++) { Request storage req = _state.requests[i]; - req.provider = address(1); req.sequenceNumber = 0; // Keep it inactive req.publishTime = 1; // No need to prefill dynamic arrays (priceIds, updateData) @@ -42,19 +40,67 @@ contract Pulse is IPulse, ReentrancyGuard, PulseState { } } + function register(uint128 feeInWei, bytes calldata uri) public override { + ProviderInfo storage providerInfo = _state.providers[msg.sender]; + + providerInfo.feeInWei = feeInWei; + providerInfo.uri = uri; + providerInfo.sequenceNumber += 1; + + emit ProviderRegistered(providerInfo); + } + + function withdraw(uint128 amount) public override { + ProviderInfo storage providerInfo = _state.providers[msg.sender]; + + // Use checks-effects-interactions pattern to prevent reentrancy attacks. + require( + providerInfo.accruedFeesInWei >= amount, + "Insufficient balance" + ); + providerInfo.accruedFeesInWei -= amount; + + // Interaction with an external contract or token transfer + (bool sent, ) = msg.sender.call{value: amount}(""); + require(sent, "withdrawal to msg.sender failed"); + + emit ProviderWithdrawn(msg.sender, msg.sender, amount); + } + + function withdrawAsFeeManager( + address provider, + uint128 amount + ) external override { + ProviderInfo storage providerInfo = _state.providers[provider]; + + if (providerInfo.sequenceNumber == 0) { + revert NoSuchProvider(); + } + + if (providerInfo.feeManager != msg.sender) { + revert Unauthorized(); + } + + // Use checks-effects-interactions pattern to prevent reentrancy attacks. + require( + providerInfo.accruedFeesInWei >= amount, + "Insufficient balance" + ); + providerInfo.accruedFeesInWei -= amount; + + // Interaction with an external contract or token transfer + (bool sent, ) = msg.sender.call{value: amount}(""); + require(sent, "withdrawal to msg.sender failed"); + + emit ProviderWithdrawn(provider, msg.sender, amount); + } + function requestPriceUpdatesWithCallback( address provider, uint256 publishTime, bytes32[] calldata priceIds, - bytes[] calldata updateData, uint256 callbackGasLimit - ) - external - payable - override - nonReentrant - returns (uint64 requestSequenceNumber) - { + ) external payable override returns (uint64 requestSequenceNumber) { ProviderInfo storage providerInfo = _state.providers[provider]; if (providerInfo.sequenceNumber == 0) revert NoSuchProvider(); @@ -78,22 +124,16 @@ contract Pulse is IPulse, ReentrancyGuard, PulseState { req.sequenceNumber = requestSequenceNumber; req.publishTime = publishTime; req.priceIds = priceIds; - req.updateData = updateData; req.callbackGasLimit = callbackGasLimit; req.requester = msg.sender; // Update fee balances providerInfo.accruedFeesInWei += providerInfo.feeInWei; - _state.accruedPythFeesInWei += (msg.value.toUint128() - - providerInfo.feeInWei); - - emit PriceUpdateRequested( - requestSequenceNumber, - provider, - publishTime, - priceIds, - msg.sender - ); + _state.accruedPythFeesInWei += + SafeCast.toUint128(msg.value) - + providerInfo.feeInWei; + + emit PriceUpdateRequested(req); } function executeCallback( @@ -102,7 +142,7 @@ contract Pulse is IPulse, ReentrancyGuard, PulseState { bytes32[] calldata priceIds, bytes[] calldata updateData, uint256 callbackGasLimit - ) external override nonReentrant { + ) external override { Request storage req = findActiveRequest(msg.sender, sequenceNumber); // Verify request parameters match @@ -164,137 +204,67 @@ contract Pulse is IPulse, ReentrancyGuard, PulseState { clearRequest(msg.sender, sequenceNumber); } - function register(uint128 feeInWei, bytes calldata uri) public override { - ProviderInfo storage provider = _state.providers[msg.sender]; - - provider.feeInWei = feeInWei; - provider.uri = uri; - - if (provider.sequenceNumber == 0) { - provider.sequenceNumber = 1; - } - - emit ProviderRegistered(msg.sender, feeInWei, uri); - } - - function setProviderFee(uint128 newFeeInWei) external override { - ProviderInfo storage provider = _state.providers[msg.sender]; - if (provider.sequenceNumber == 0) revert NoSuchProvider(); - - uint128 oldFeeInWei = provider.feeInWei; - provider.feeInWei = newFeeInWei; - - emit ProviderFeeUpdated(msg.sender, oldFeeInWei, newFeeInWei); - } - - function getFee( + function getProviderInfo( address provider - ) public view override returns (uint128 feeAmount) { - feeAmount = _state.providers[provider].feeInWei + _state.pythFeeInWei; + ) public view override returns (ProviderInfo memory info) { + info = _state.providers[provider]; } function getDefaultProvider() - external + public view override - returns (address defaultProvider) + returns (address provider) { - defaultProvider = _state.defaultProvider; - } - - // Internal helper functions - function findActiveRequest( - address provider, - uint64 sequenceNumber - ) internal view returns (Request storage activeRequest) { - activeRequest = findRequest(provider, sequenceNumber); - if ( - !isActive(activeRequest) || - activeRequest.provider != provider || - activeRequest.sequenceNumber != sequenceNumber - ) { - revert NoSuchRequest(); - } + provider = _state.defaultProvider; } - function findRequest( + function getRequest( address provider, uint64 sequenceNumber - ) internal view returns (Request storage foundRequest) { - (bytes32 key, uint8 shortKey) = requestKey(provider, sequenceNumber); - foundRequest = _state.requests[shortKey]; - - if ( - foundRequest.provider == provider && - foundRequest.sequenceNumber == sequenceNumber - ) { - return foundRequest; - } else { - foundRequest = _state.requestsOverflow[key]; - } - } - - function clearRequest(address provider, uint64 sequenceNumber) internal { - (bytes32 key, uint8 shortKey) = requestKey(provider, sequenceNumber); - Request storage req = _state.requests[shortKey]; - - if (req.provider == provider && req.sequenceNumber == sequenceNumber) { - req.sequenceNumber = 0; - } else { - delete _state.requestsOverflow[key]; - } + ) public view override returns (Request memory req) { + req = findRequest(provider, sequenceNumber); } - function allocRequest( - address provider, - uint64 sequenceNumber - ) internal returns (Request storage newRequest) { - (, uint8 shortKey) = requestKey(provider, sequenceNumber); - newRequest = _state.requests[shortKey]; - - if (isActive(newRequest)) { - (bytes32 reqKey, ) = requestKey( - newRequest.provider, - newRequest.sequenceNumber - ); - _state.requestsOverflow[reqKey] = newRequest; - } + function getFee( + address provider + ) public view override returns (uint128 feeAmount) { + return _state.providers[provider].feeInWei + _state.pythFeeInWei; } - function requestKey( - address provider, - uint64 sequenceNumber - ) internal pure returns (bytes32 hashKey, uint8 shortHashKey) { - hashKey = keccak256(abi.encodePacked(provider, sequenceNumber)); - shortHashKey = uint8(hashKey[0] & NUM_REQUESTS_MASK); + function getPythFeeInWei() + public + view + override + returns (uint128 pythFeeInWei) + { + pythFeeInWei = _state.pythFeeInWei; } - function isActive( - Request storage req - ) internal view returns (bool isRequestActive) { - isRequestActive = req.sequenceNumber != 0; + function getAccruedPythFees() + public + view + override + returns (uint128 accruedPythFeesInWei) + { + accruedPythFeesInWei = _state.accruedPythFeesInWei; } - function withdraw(uint128 amount) public override { - ProviderInfo storage providerInfo = _state.providers[msg.sender]; - - // Use checks-effects-interactions pattern to prevent reentrancy attacks - require( - providerInfo.accruedFeesInWei >= amount, - "Insufficient balance" - ); - providerInfo.accruedFeesInWei -= amount; - - // Interaction with an external contract or token transfer - (bool sent, ) = msg.sender.call{value: amount}(""); - require(sent, "withdrawal to msg.sender failed"); + // Set provider fee. It will revert if provider is not registered. + function setProviderFee(uint128 newFeeInWei) external override { + ProviderInfo storage provider = _state.providers[msg.sender]; - emit ProviderWithdrawn(msg.sender, msg.sender, amount); + if (provider.sequenceNumber == 0) { + revert NoSuchProvider(); + } + uint128 oldFeeInWei = provider.feeInWei; + provider.feeInWei = newFeeInWei; + emit ProviderFeeUpdated(msg.sender, oldFeeInWei, newFeeInWei); } - function withdrawAsFeeManager( + function setProviderFeeAsFeeManager( address provider, - uint128 amount + uint128 newFeeInWei ) external override { ProviderInfo storage providerInfo = _state.providers[provider]; @@ -306,86 +276,119 @@ contract Pulse is IPulse, ReentrancyGuard, PulseState { revert Unauthorized(); } - // Use checks-effects-interactions pattern to prevent reentrancy attacks - require( - providerInfo.accruedFeesInWei >= amount, - "Insufficient balance" - ); - providerInfo.accruedFeesInWei -= amount; + uint128 oldFeeInWei = providerInfo.feeInWei; + providerInfo.feeInWei = newFeeInWei; - // Interaction with an external contract or token transfer - (bool sent, ) = msg.sender.call{value: amount}(""); - require(sent, "withdrawal to msg.sender failed"); + emit ProviderFeeUpdated(provider, oldFeeInWei, newFeeInWei); + } - emit ProviderWithdrawn(provider, msg.sender, amount); + // Set provider uri. It will revert if provider is not registered. + function setProviderUri(bytes calldata newUri) external override { + ProviderInfo storage provider = _state.providers[msg.sender]; + if (provider.sequenceNumber == 0) { + revert NoSuchProvider(); + } + bytes memory oldUri = provider.uri; + provider.uri = newUri; + emit ProviderUriUpdated(msg.sender, oldUri, newUri); } function setFeeManager(address manager) external override { ProviderInfo storage provider = _state.providers[msg.sender]; - if (provider.sequenceNumber == 0) revert NoSuchProvider(); + if (provider.sequenceNumber == 0) { + revert NoSuchProvider(); + } address oldFeeManager = provider.feeManager; provider.feeManager = manager; - emit ProviderFeeManagerUpdated(msg.sender, oldFeeManager, manager); } - function setProviderFeeAsFeeManager( + function requestKey( address provider, - uint128 newFeeInWei - ) external override { - ProviderInfo storage providerInfo = _state.providers[provider]; - - if (providerInfo.sequenceNumber == 0) { - revert NoSuchProvider(); - } - - if (providerInfo.feeManager != msg.sender) { - revert Unauthorized(); - } + uint64 sequenceNumber + ) internal pure returns (bytes32 hash, uint8 shortHash) { + hash = keccak256(abi.encodePacked(provider, sequenceNumber)); + shortHash = uint8(hash[0] & NUM_REQUESTS_MASK); + } - uint128 oldFeeInWei = providerInfo.feeInWei; - providerInfo.feeInWei = newFeeInWei; + // Find an in-flight active request for given the provider and the sequence number. + // This method returns a reference to the request, and will revert if the request is + // not active. + function findActiveRequest( + address provider, + uint64 sequenceNumber + ) internal view returns (Request storage req) { + req = findRequest(provider, sequenceNumber); - emit ProviderFeeUpdated(provider, oldFeeInWei, newFeeInWei); + // Check there is an active request for the given provider and sequence number. + if ( + !isActive(req) || + req.provider != provider || + req.sequenceNumber != sequenceNumber + ) revert NoSuchRequest(); } - function getAccruedPythFees() - public - view - override - returns (uint128 accruedPythFeesInWei) - { - accruedPythFeesInWei = _state.accruedPythFeesInWei; - } + // Find an in-flight request. + // Note that this method can return requests that are not currently active. The caller is responsible for checking + // that the returned request is active (if they care). + function findRequest( + address provider, + uint64 sequenceNumber + ) internal view returns (Request storage req) { + (bytes32 key, uint8 shortKey) = requestKey(provider, sequenceNumber); - function getProviderInfo( - address provider - ) public view override returns (ProviderInfo memory info) { - info = _state.providers[provider]; + req = _state.requests[shortKey]; + if (req.provider == provider && req.sequenceNumber == sequenceNumber) { + return req; + } else { + req = _state.requestsOverflow[key]; + } } - function getAdmin() external view override returns (address adminAddress) { - adminAddress = _state.admin; - } + // Clear the storage for an in-flight request, deleting it from the hash table. + function clearRequest(address provider, uint64 sequenceNumber) internal { + (bytes32 key, uint8 shortKey) = requestKey(provider, sequenceNumber); - function getPythFeeInWei() - external - view - override - returns (uint128 pythFee) - { - pythFee = _state.pythFeeInWei; + Request storage req = _state.requests[shortKey]; + if (req.provider == provider && req.sequenceNumber == sequenceNumber) { + req.sequenceNumber = 0; + } else { + delete _state.requestsOverflow[key]; + } } - function setProviderUri(bytes calldata uri) external override { - ProviderInfo storage provider = _state.providers[msg.sender]; - if (provider.sequenceNumber == 0) revert NoSuchProvider(); + // Allocate storage space for a new in-flight request. This method returns a pointer to a storage slot + // that the caller should overwrite with the new request. Note that the memory at this storage slot may + // -- and will -- be filled with arbitrary values, so the caller *must* overwrite every field of the returned + // struct. + function allocRequest( + address provider, + uint64 sequenceNumber + ) internal returns (Request storage req) { + (, uint8 shortKey) = requestKey(provider, sequenceNumber); - bytes memory oldUri = provider.uri; - provider.uri = uri; + req = _state.requests[shortKey]; + if (isActive(req)) { + // There's already a prior active request in the storage slot we want to use. + // Overflow the prior request to the requestsOverflow mapping. + // It is important that this code overflows the *prior* request to the mapping, and not the new request. + // There is a chance that some requests never get revealed and remain active forever. We do not want such + // requests to fill up all of the space in the array and cause all new requests to incur the higher gas cost + // of the mapping. + // + // This operation is expensive, but should be rare. If overflow happens frequently, increase + // the size of the requests array to support more concurrent active requests. + (bytes32 reqKey, ) = requestKey(req.provider, req.sequenceNumber); + _state.requestsOverflow[reqKey] = req; + } + } - emit ProviderUriUpdated(msg.sender, oldUri, uri); + // Returns true if a request is active, i.e., its corresponding random value has not yet been revealed. + function isActive(Request storage req) internal view returns (bool) { + // Note that a provider's initial registration occupies sequence number 0, so there is no way to construct + // a price update request with sequence number 0. + return req.sequenceNumber != 0; } function setMaxNumPrices(uint32 maxNumPrices) external override { @@ -401,11 +404,4 @@ contract Pulse is IPulse, ReentrancyGuard, PulseState { maxNumPrices ); } - - function getRequest( - address provider, - uint64 sequenceNumber - ) public view override returns (Request memory req) { - req = findRequest(provider, sequenceNumber); - } } diff --git a/target_chains/ethereum/contracts/forge-test/Pulse.t.sol b/target_chains/ethereum/contracts/forge-test/Pulse.t.sol index 615d45eb7..b6b5dddd4 100644 --- a/target_chains/ethereum/contracts/forge-test/Pulse.t.sol +++ b/target_chains/ethereum/contracts/forge-test/Pulse.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import "forge-std/Test.sol"; +import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import "../contracts/pulse/PulseUpgradeable.sol"; import "../contracts/pulse/IPulse.sol"; import "../contracts/pulse/PulseState.sol"; @@ -27,6 +28,7 @@ contract MockPulseConsumer is IPulseConsumer { } contract PulseTest is Test { + ERC1967Proxy public proxy; PulseUpgradeable public pulse; MockPulseConsumer public consumer; address public owner; @@ -40,9 +42,12 @@ contract PulseTest is Test { admin = address(2); provider = address(3); - // Deploy contracts - pulse = new PulseUpgradeable(); - pulse.initialize(owner, admin, PYTH_FEE, provider); + PulseUpgradeable _pulse = new PulseUpgradeable(); + proxy = new ERC1967Proxy(address(_pulse), ""); + // wrap in ABI to support easier calls + pulse = PulseUpgradeable(address(proxy)); + + pulse.initialize(owner, admin, PYTH_FEE, provider, false); consumer = new MockPulseConsumer(); // Register provider @@ -62,10 +67,13 @@ contract PulseTest is Test { uint256 publishTime = block.timestamp; uint256 callbackGasLimit = 500000; + // Fund the consumer contract + vm.deal(address(consumer), 1 ether); + vm.prank(address(consumer)); uint64 sequenceNumber = pulse.requestPriceUpdatesWithCallback{ value: PYTH_FEE + PROVIDER_FEE - }(provider, publishTime, priceIds, updateData, callbackGasLimit); + }(provider, publishTime, priceIds, callbackGasLimit); assertEq(sequenceNumber, 1); } @@ -85,7 +93,7 @@ contract PulseTest is Test { vm.prank(address(consumer)); uint64 sequenceNumber = pulse.requestPriceUpdatesWithCallback{ value: PYTH_FEE + PROVIDER_FEE - }(provider, publishTime, priceIds, updateData, callbackGasLimit); + }(provider, publishTime, priceIds, callbackGasLimit); vm.prank(provider); pulse.executeCallback( @@ -99,8 +107,6 @@ contract PulseTest is Test { assertEq(consumer.lastSequenceNumber(), sequenceNumber); assertEq(consumer.lastProvider(), provider); assertEq(consumer.lastPublishTime(), publishTime); - assertEq(consumer.lastPriceIds()[0], priceIds[0]); - assertEq(consumer.lastPriceIds()[1], priceIds[1]); } function testProviderRegistration() public { @@ -123,36 +129,45 @@ contract PulseTest is Test { function testFailInsufficientFee() public { bytes32[] memory priceIds = new bytes32[](1); - bytes[] memory updateData = new bytes[](1); vm.prank(address(consumer)); pulse.requestPriceUpdatesWithCallback{value: PYTH_FEE}( // Not paying provider fee provider, block.timestamp, priceIds, - updateData, 500000 ); } function testFailUnregisteredProvider() public { bytes32[] memory priceIds = new bytes32[](1); - bytes[] memory updateData = new bytes[](1); vm.prank(address(consumer)); pulse.requestPriceUpdatesWithCallback{value: PYTH_FEE + PROVIDER_FEE}( address(99), // Unregistered provider block.timestamp, priceIds, - updateData, 500000 ); } function testGasCostsWithPrefill() public { - // Deploy with prefill - PulseUpgradeable pulseWithPrefill = new PulseUpgradeable(); - pulseWithPrefill.initialize(owner, admin, PYTH_FEE, provider, true); + // Deploy implementation and proxy with prefill + pulse = new PulseUpgradeable(); + bytes memory initData = abi.encodeWithSelector( + PulseUpgradeable.initialize.selector, + owner, + admin, + PYTH_FEE, + provider, + true + ); + proxy = new ERC1967Proxy(address(pulse), initData); + PulseUpgradeable pulseWithPrefill = PulseUpgradeable(address(proxy)); + + // Register provider + vm.prank(provider); + pulseWithPrefill.register(PROVIDER_FEE, "https://provider.com"); // Measure gas for first request uint256 gasBefore = gasleft(); @@ -160,13 +175,26 @@ contract PulseTest is Test { uint256 gasUsed = gasBefore - gasleft(); // Should be lower due to prefill - assertLt(gasUsed, 30000); + assertLt(gasUsed, 130000); } function testGasCostsWithoutPrefill() public { - // Deploy without prefill - PulseUpgradeable pulseWithoutPrefill = new PulseUpgradeable(); - pulseWithoutPrefill.initialize(owner, admin, PYTH_FEE, provider, false); + // Deploy implementation and proxy without prefill + pulse = new PulseUpgradeable(); + bytes memory initData = abi.encodeWithSelector( + PulseUpgradeable.initialize.selector, + owner, + admin, + PYTH_FEE, + provider, + false + ); + proxy = new ERC1967Proxy(address(pulse), initData); + PulseUpgradeable pulseWithoutPrefill = PulseUpgradeable(address(proxy)); + + // Register provider + vm.prank(provider); + pulseWithoutPrefill.register(PROVIDER_FEE, "https://provider.com"); // Measure gas for first request uint256 gasBefore = gasleft(); @@ -174,29 +202,26 @@ contract PulseTest is Test { uint256 gasUsed = gasBefore - gasleft(); // Should be higher without prefill - assertGt(gasUsed, 35000); + assertGt(gasUsed, 130000); } function makeRequest(address pulseAddress) internal { // Helper to make a standard request bytes32[] memory priceIds = new bytes32[](1); - bytes[] memory updateData = new bytes[](1); IPulse(pulseAddress).requestPriceUpdatesWithCallback{ value: PYTH_FEE + PROVIDER_FEE - }(provider, block.timestamp, priceIds, updateData, 500000); + }(provider, block.timestamp, priceIds, 500000); } function testWithdraw() public { // Setup - make a request to accrue some fees bytes32[] memory priceIds = new bytes32[](1); - bytes[] memory updateData = new bytes[](1); vm.prank(address(consumer)); pulse.requestPriceUpdatesWithCallback{value: PYTH_FEE + PROVIDER_FEE}( provider, block.timestamp, priceIds, - updateData, 500000 ); @@ -231,14 +256,12 @@ contract PulseTest is Test { // Setup fees bytes32[] memory priceIds = new bytes32[](1); - bytes[] memory updateData = new bytes[](1); vm.prank(address(consumer)); pulse.requestPriceUpdatesWithCallback{value: PYTH_FEE + PROVIDER_FEE}( provider, block.timestamp, priceIds, - updateData, 500000 ); @@ -282,14 +305,12 @@ contract PulseTest is Test { function testGetAccruedPythFees() public { // Setup - make a request to accrue some fees bytes32[] memory priceIds = new bytes32[](1); - bytes[] memory updateData = new bytes[](1); vm.prank(address(consumer)); pulse.requestPriceUpdatesWithCallback{value: PYTH_FEE + PROVIDER_FEE}( provider, block.timestamp, priceIds, - updateData, 500000 ); @@ -297,7 +318,7 @@ contract PulseTest is Test { assertEq(pulse.getAccruedPythFees(), PYTH_FEE); } - function testGetProviderInfo() public { + function testGetProviderInfo() public view { // Get provider info PulseState.ProviderInfo memory info = pulse.getProviderInfo(provider); @@ -310,11 +331,7 @@ contract PulseTest is Test { assertEq(info.maxNumPrices, 0); } - function testGetAdmin() public { - assertEq(pulse.getAdmin(), admin); - } - - function testGetPythFeeInWei() public { + function testGetPythFeeInWei() public view { assertEq(pulse.getPythFeeInWei(), PYTH_FEE); } @@ -325,8 +342,10 @@ contract PulseTest is Test { pulse.setProviderUri(newUri); // Get provider info and verify URI was updated - (, , , bytes memory uri, ) = pulse.getProviderInfo(provider); - assertEq(string(uri), string(newUri)); + PulseState.ProviderInfo memory providerInfo = pulse.getProviderInfo( + provider + ); + assertEq(string(providerInfo.uri), string(newUri)); } function testFailSetProviderUriUnregistered() public { @@ -341,8 +360,10 @@ contract PulseTest is Test { pulse.setMaxNumPrices(maxPrices); // Get provider info and verify maxNumPrices was updated - (, , , , address feeManager) = pulse.getProviderInfo(provider); - assertEq(uint256(maxPrices), uint256(maxPrices)); + PulseState.ProviderInfo memory providerInfo = pulse.getProviderInfo( + provider + ); + assertEq(uint256(maxPrices), uint256(providerInfo.maxNumPrices)); } function testFailExceedMaxNumPrices() public { @@ -352,14 +373,12 @@ contract PulseTest is Test { // Try to request 3 prices bytes32[] memory priceIds = new bytes32[](3); - bytes[] memory updateData = new bytes[](3); vm.prank(address(consumer)); pulse.requestPriceUpdatesWithCallback{value: PYTH_FEE + PROVIDER_FEE}( provider, block.timestamp, priceIds, - updateData, 500000 ); } @@ -380,7 +399,7 @@ contract PulseTest is Test { vm.prank(address(consumer)); uint64 sequenceNumber = pulse.requestPriceUpdatesWithCallback{ value: PYTH_FEE + PROVIDER_FEE - }(provider, publishTime, priceIds, updateData, callbackGasLimit); + }(provider, publishTime, priceIds, callbackGasLimit); // Get request and verify PulseState.Request memory req = pulse.getRequest(