diff --git a/pages/entropy/_meta.json b/pages/entropy/_meta.json index 3701fc44..d5df338d 100644 --- a/pages/entropy/_meta.json +++ b/pages/entropy/_meta.json @@ -27,6 +27,7 @@ "generate-random-numbers": "Generate Random Numbers", "set-custom-gas-limits": "Set Custom Gas Limits", "debug-callback-failures": "Debug Callback Failures", + "test-with-mock-entropy": "Test with MockEntropy", "-- Reference Material": { "title": "Reference Material", diff --git a/pages/entropy/test-with-mock-entropy.mdx b/pages/entropy/test-with-mock-entropy.mdx new file mode 100644 index 00000000..b5c449bf --- /dev/null +++ b/pages/entropy/test-with-mock-entropy.mdx @@ -0,0 +1,340 @@ +import { Callout } from "nextra/components"; + +# Test with MockEntropy + +This guide shows how to use the MockEntropy contract for local testing of your Entropy integration. MockEntropy is a lightweight testing implementation that allows you to write fast, deterministic tests without needing network access or paying fees. + +## Why Use MockEntropy? + +MockEntropy is ideal for local testing because it provides: + +- **No Network Dependency**: Test entirely offline without connecting to a blockchain +- **Zero Fees**: All fee methods return 0, no native tokens required +- **Deterministic Random Numbers**: You control exactly what random values are returned +- **Fast Execution**: Callbacks are synchronous, no waiting for keeper transactions +- **Full Control**: Manually trigger reveals in any order to test edge cases + +MockEntropy is perfect for unit tests and CI pipelines, but always test with the real Entropy contract on a testnet before deploying to production. + +## Prerequisites + +Before following this guide, you should: + +- Complete the basic setup from the [Generate Random Numbers in EVM Contracts](/entropy/generate-random-numbers/evm) guide +- Have a Foundry-based testing environment set up +- Be familiar with implementing `IEntropyConsumer` and `entropyCallback` + +## Installation + +MockEntropy is included in the Pyth Entropy Solidity SDK v2.1.0+. Install it using npm: + +```bash copy +npm install @pythnetwork/entropy-sdk-solidity +``` + +For Foundry projects, add this line to your `remappings.txt`: + +```text copy +@pythnetwork/entropy-sdk-solidity/=node_modules/@pythnetwork/entropy-sdk-solidity +``` + +## Basic Usage + +### 1. Import MockEntropy in Your Test + +```solidity copy +import "forge-std/Test.sol"; +import "@pythnetwork/entropy-sdk-solidity/MockEntropy.sol"; +import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol"; + +``` + +### 2. Set Up MockEntropy in Your Test + +```solidity copy +contract MyEntropyTest is Test { + MockEntropy public entropy; + MyEntropyConsumer public consumer; + address public provider; + + function setUp() public { + // Use any address as the provider + provider = address(0x1234); + + // Deploy MockEntropy with the provider address + entropy = new MockEntropy(provider); + + // Deploy your consumer contract + consumer = new MyEntropyConsumer(address(entropy)); + } +} + +``` + +### 3. Request and Reveal Pattern + +The basic testing pattern involves requesting a random number, then manually revealing it with `mockReveal()`: + +```solidity copy +function testBasicRandomNumber() public { + // Request a random number + uint64 sequenceNumber = consumer.requestRandomNumber(); + + // Manually reveal with your chosen random value + bytes32 randomNumber = bytes32(uint256(42)); + entropy.mockReveal(provider, sequenceNumber, randomNumber); + + // Verify the callback was triggered with the correct value + assertEq(consumer.lastRandomNumber(), randomNumber); + assertEq(consumer.lastSequenceNumber(), sequenceNumber); +} + +``` + +The `mockReveal()` method signature is: + +```solidity +function mockReveal( + address provider, + uint64 sequenceNumber, + bytes32 randomNumber +) external +``` + +When called, it immediately triggers the `entropyCallback` on your consumer contract with the provided random number. + +## Advanced Testing Patterns + +### Pattern 1: Multiple Requests and Out-of-Order Reveals + +Test that your contract handles asynchronous callbacks correctly by revealing requests in a different order than they were made: + +```solidity copy +function testOutOfOrderReveals() public { + // Request multiple random numbers + uint64 seq1 = consumer.requestRandomNumber(); + uint64 seq2 = consumer.requestRandomNumber(); + uint64 seq3 = consumer.requestRandomNumber(); + + // Reveal in different order: 2, 3, then 1 + bytes32 random2 = bytes32(uint256(200)); + bytes32 random3 = bytes32(uint256(300)); + bytes32 random1 = bytes32(uint256(100)); + + entropy.mockReveal(provider, seq2, random2); + assertEq(consumer.lastRandomNumber(), random2); + + entropy.mockReveal(provider, seq3, random3); + assertEq(consumer.lastRandomNumber(), random3); + + entropy.mockReveal(provider, seq1, random1); + assertEq(consumer.lastRandomNumber(), random1); +} + +``` + +This pattern ensures your contract correctly handles callbacks arriving in any order. + +### Pattern 2: Testing with Multiple Providers + +Test your contract with different providers to ensure provider-specific logic works correctly: + +```solidity copy +function testMultipleProviders() public { + address provider1 = address(0x1111); + address provider2 = address(0x2222); + + // Request from different providers + uint64 seq1 = consumer.requestFromProvider(provider1, 100000); + uint64 seq2 = consumer.requestFromProvider(provider2, 100000); + + // Each provider has independent sequence numbers + assertEq(seq1, 1); + assertEq(seq2, 1); + + // Reveal from each provider + bytes32 random1 = bytes32(uint256(111)); + bytes32 random2 = bytes32(uint256(222)); + + entropy.mockReveal(provider1, seq1, random1); + assertEq(consumer.lastRandomNumber(), random1); + assertEq(consumer.lastProvider(), provider1); + + entropy.mockReveal(provider2, seq2, random2); + assertEq(consumer.lastRandomNumber(), random2); + assertEq(consumer.lastProvider(), provider2); +} + +``` + +### Pattern 3: Testing Gas Limit Configuration + +While MockEntropy doesn't enforce gas limits, you can verify that your contract correctly stores and passes gas limit parameters: + +```solidity copy +function testGasLimitStorage() public { + uint32 customGasLimit = 200000; + + // Request with custom gas limit + uint64 seq = consumer.requestWithGasLimit(customGasLimit); + + // Verify gas limit was stored correctly + EntropyStructsV2.Request memory req = entropy.getRequestV2(provider, seq); + assertEq(req.gasLimit10k, 20); // 200000 / 10000 = 20 + + // Reveal works normally + bytes32 randomNumber = bytes32(uint256(999)); + entropy.mockReveal(provider, seq, randomNumber); + assertEq(consumer.lastRandomNumber(), randomNumber); +} + +``` + + + MockEntropy does not enforce gas limits on callbacks. Make sure to test with + the real Entropy contract on a testnet before deploying to production to + verify your callback stays within gas limits. + + +### Pattern 4: Deterministic Testing with Specific Values + +Use deterministic random values to create reproducible tests and cover edge cases: + +```solidity copy +function testEdgeCaseRandomValues() public { + // Test with zero + uint64 seq1 = consumer.requestRandomNumber(); + entropy.mockReveal(provider, seq1, bytes32(0)); + assertEq(consumer.lastRandomNumber(), bytes32(0)); + + // Test with maximum value + uint64 seq2 = consumer.requestRandomNumber(); + entropy.mockReveal(provider, seq2, bytes32(type(uint256).max)); + assertEq(consumer.lastRandomNumber(), bytes32(type(uint256).max)); + + // Test with hash-based values + uint64 seq3 = consumer.requestRandomNumber(); + bytes32 hashValue = keccak256("deterministic test value"); + entropy.mockReveal(provider, seq3, hashValue); + assertEq(consumer.lastRandomNumber(), hashValue); +} + +``` + +This approach ensures your contract handles all possible random values correctly. + +## Key Differences from Real Entropy + +MockEntropy simplifies testing but has important differences from the production Entropy contract: + +### 1. No Fees Required + +All `getFeeV2()` methods return 0: + +```solidity +// MockEntropy always returns 0 +uint128 fee = entropy.getFeeV2(); // Returns 0 +uint128 feeWithGas = entropy.getFeeV2(100000); // Returns 0 +``` + +You don't need to send native tokens with your requests during testing. + +### 2. No Gas Limit Enforcement + +MockEntropy stores gas limits but does **not** enforce them on callbacks. Your callback can use any amount of gas during testing. + + + Always test on a testnet with the real Entropy contract to verify your + callback respects gas limits. A callback that works in MockEntropy tests may + fail in production if it exceeds the gas limit. + + +### 3. Synchronous Callbacks + +`mockReveal()` immediately calls `entropyCallback` in the same transaction. The real Entropy contract uses keeper transactions that arrive asynchronously. + +### 4. Manual Random Number Control + +You decide exactly what random number is revealed and when. The real Entropy contract generates cryptographically secure random numbers. + +### 5. Simplified Provider Setup + +Providers in MockEntropy are just addresses with default configuration: + +- Default fee: 1 wei (not enforced) +- Default gas limit: 100,000 +- Sequence numbers start at 1 + +No keeper infrastructure is required. + +## Best Practices + +### 1. Test Edge Cases + +Use extreme random values to ensure your contract handles all cases: + +```solidity copy +// Test boundary values +bytes32[] memory testValues = new bytes32[](3); +testValues[0] = bytes32(uint256(0)); +testValues[1] = bytes32(type(uint256).max); +testValues[2] = bytes32(uint256(1)); + +for (uint i = 0; i < testValues.length; i++) { + uint64 seq = consumer.requestRandomNumber(); + entropy.mockReveal(provider, seq, testValues[i]); + // Verify your contract handles the value correctly +} +``` + +### 2. Measure Callback Gas Usage + +Even though MockEntropy doesn't enforce limits, measure your callback's gas usage: + +```solidity copy +function testCallbackGasUsage() public { + uint64 seq = consumer.requestRandomNumber(); + + uint256 gasBefore = gasleft(); + entropy.mockReveal(provider, seq, bytes32(uint256(42))); + uint256 gasUsed = gasBefore - gasleft(); + + console.log("Callback gas used:", gasUsed); + + // Ensure it's within your target gas limit + assertTrue(gasUsed < 100000, "Callback uses too much gas"); +} + +``` + +### 3. Always Test on Testnet + +MockEntropy is useful for development, but always run integration tests on a testnet before deploying to production. This verifies: + +- Fee calculations work correctly +- Callback gas usage is within limits +- Keeper transactions are processed successfully +- Your contract integrates properly with the real Entropy infrastructure + +See the [Contract Addresses](/entropy/contract-addresses) page for testnet deployment addresses. + +### 4. Use Deterministic Values for CI + +For reproducible CI tests, use deterministic random values instead of actual randomness: + +```solidity copy +// Good for CI: deterministic and reproducible +bytes32 testRandom = keccak256(abi.encodePacked("test-seed-", testName)); + +// Avoid in CI: non-deterministic +bytes32 actualRandom = keccak256(abi.encodePacked(block.timestamp, msg.sender)); +``` + +## Additional Resources + +- [Generate Random Numbers in EVM Contracts](/entropy/generate-random-numbers/evm) - Full integration guide +- [Set Custom Gas Limits](/entropy/set-custom-gas-limits) - Gas limit configuration for production +- [Debug Callback Failures](/entropy/debug-callback-failures) - Troubleshooting callback issues +- [Contract Addresses](/entropy/contract-addresses) - Testnet and mainnet Entropy contracts +- [MockEntropy.sol](https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/ethereum/entropy_sdk/solidity/MockEntropy.sol) - Source code +- [MockEntropy.t.sol](https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/ethereum/contracts/test/MockEntropy.t.sol) - Complete test examples