This is a demo of using Wormhole Queries to provide a Solana Stake Pool rate on an EVM chain.
The tests use Jito SOL as an example, though the code is generally applicable to any Stake Pool account.
Learn more about developing with Queries in the docs.
The oracle contract at ./src/StakePoolRate.sol
is an immutable QueryResponse processor, which accepts valid queries for the designated Stake Pool account via updatePool()
and provides the last totalActiveStake
and poolTokenSupply
via getRate()
as long as the last update is not older than the configured allowedStaleness
.
address _wormhole
- The address of the Wormhole core contract on this chain. Used to verify guardian signatures.bytes32 _stakePoolAccount
- The 32-byte address in hex of the Stake Pool account on Solana. Only queries for that account will be accepted, essentially making this a 1-1 mirror of that account (at least for thetotalActiveStake
andpoolTokenSupply
fields).uint64 _allowedUpdateStaleness
- The time in seconds behind the current block time for which updates will be accepted (i.e.updatePool
will not revert).uint64 _allowedRateStaleness
- The time in seconds behind the current block time for which rates are valid (i.e.getRate
will not revert).
The updatePool
method takes in the response
and signatures
from a Wormhole Query, performs validation, and updates the lastUpdateSolanaSlotNumber
, lastUpdateSolanaBlockTime
, totalActiveStake
, and poolTokenSupply
fields.
The validation includes
- Verifying the guardian signatures
- Parsing the query response
- Response includes exactly 2 results
- Response is for the Solana (Wormhole) chain id
- Request commitment level is for
finalized
- Request data slice is for the applicable fields
- Request account 0 is the configured
stakePoolAccount
- Response account 0's owner is Stake Pool program (
SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy
) - Request account 1 is the Clock account (
SysvarC1ock11111111111111111111111111111111
) - Response slot number is at least
lastUpdateSolanaSlotNumber
- Response time is at least
block.timestamp - allowedUpdateStaleness
- The last update epoch from the stake pool account matches the current epoch in the Clock account
This also emits the following event
event RateUpdated(
uint64 indexed epoch,
uint64 solanaSlotNumber,
uint64 solanaBlockTime,
uint64 totalActiveStake,
uint64 poolTokenSupply,
uint256 calculatedRate
);
Returns the rate scaled to 1e18 as long as the last updated time is not stale. Effectively, (totalActiveStake * (10 ** 18)) / poolTokenSupply
.
./test/StakePoolRate.t.sol
tests the following
reverse
method of the contract, which converts theu64
fields stored in the Solana account from little-endian (Borsch) to big-endian (Solidity)updatePool
positive test case, in which submitting a valid query updates the fields accordingly
forge test
./ts-test/mock.ts
performs fork testing by forking Ethereum mainnet, overriding the guardian set on the core contract, and mocking the Query Proxy / Guardian responses.
# Install dependencies
npm ci
# Generate bindings
forge build
npx typechain --target=ethers-v6 ./out/**/*.json
# Start anvil
anvil --fork-url https://ethereum.publicnode.com
# Override guardian set
npx @wormhole-foundation/wormhole-cli evm hijack -a 0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B -g 0xbeFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe
npx tsx ./ts-test/mock.ts
The contract can be deployed with
forge create StakePoolRate --private-key <YOUR_PRIVATE_KEY> --constructor-args <WORMHOLE_CORE_BRIDGE_ADDRESS> <STAKE_POOL_ADDRESS_HEX> <ALLOWED_UPDATE_STALENESS> <ALLOWED_RATE_STALENESS>
So the deploy corresponding to the above integration test might look like
forge create StakePoolRate --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --constructor-args 0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B 0x048a3e08c3b495be17f45427d89bec5b80c7e2695c1864d76743db39bed346d6 1800 2592000
⚠ This software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.