Update Sp1LightClient.dispatchedSlotKey. Add MockLightClient, Sp1LightClient unit tests

ltyu/sp1-lightclient-ism
Le Yu 4 months ago
parent 59eeb98dc5
commit ef903da96e
  1. 5
      solidity/contracts/interfaces/ISP1LightClient.sol
  2. 16
      solidity/contracts/isms/ccip-read/SP1LightClientISM.sol
  3. 49
      solidity/contracts/mock/MockLightClient.sol
  4. 161
      solidity/test/isms/Sp1LightClientIsm.t.sol
  5. 2
      solidity/test/lib/StateProofHelpers.t.sol

@ -1,4 +1,7 @@
interface ISP1Lightclient {
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
interface ISP1LightClient {
/// @notice The latest slot the light client has a finalized header for.
function head() external view returns (uint256);

@ -15,7 +15,7 @@ pragma solidity >=0.8.0;
// ============ External Imports ============
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {ISP1Lightclient} from "../../interfaces/ISP1Lightclient.sol";
import {ISP1LightClient} from "../../interfaces/ISP1LightClient.sol";
// ============ Internal Imports ============
@ -34,7 +34,7 @@ contract SP1LightClientIsm is AbstractCcipReadIsm, OwnableUpgradeable {
using Message for bytes;
/// @notice LightClient to read the state root from
ISP1Lightclient public lightClient;
ISP1LightClient public lightClient;
/// @notice Source Mailbox that will dispatch a message
Mailbox public sourceMailbox;
@ -70,7 +70,7 @@ contract SP1LightClientIsm is AbstractCcipReadIsm, OwnableUpgradeable {
sourceMailbox = _sourceMailbox;
destinationMailbox = _destinationMailbox;
dispatchedHook = _dispatchedHook;
lightClient = ISP1Lightclient(_lightClient);
lightClient = ISP1LightClient(_lightClient);
dispatchedSlot = _dispatchedSlot;
offchainUrls = _offchainUrls;
}
@ -182,15 +182,7 @@ contract SP1LightClientIsm is AbstractCcipReadIsm, OwnableUpgradeable {
function dispatchedSlotKey(
uint32 _messageNonce
) public view returns (bytes32) {
return
keccak256(
abi.encode(
_messageNonce,
keccak256(
abi.encode(address(sourceMailbox), dispatchedSlot)
)
)
);
return keccak256(abi.encode(_messageNonce, dispatchedSlot));
}
/**

@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT or Apache-2.0
pragma solidity ^0.8.13;
import {ISP1LightClient} from "../interfaces/ISP1LightClient.sol";
contract MockLightClient is ISP1LightClient {
bytes32 public immutable GENESIS_VALIDATORS_ROOT;
uint256 public immutable GENESIS_TIME;
uint256 public immutable SECONDS_PER_SLOT;
uint256 public immutable SLOTS_PER_PERIOD;
uint32 public immutable SOURCE_CHAIN_ID;
uint16 public immutable FINALITY_THRESHOLD;
/// @notice The latest slot the light client has a finalized header for.
uint256 public head = 0;
/// @notice Maps from a slot to the current finalized ethereum1 execution state root.
mapping(uint256 => bytes32) public executionStateRoots;
/// @notice Maps from a period to the poseidon commitment for the sync committee.
mapping(uint256 => bytes32) public syncCommitteePoseidons;
constructor(
bytes32 genesisValidatorsRoot,
uint256 genesisTime,
uint256 secondsPerSlot,
uint256 slotsPerPeriod,
uint256 syncCommitteePeriod,
bytes32 syncCommitteePoseidon,
uint32 sourceChainId,
uint16 finalityThreshold
) {
GENESIS_VALIDATORS_ROOT = genesisValidatorsRoot;
GENESIS_TIME = genesisTime;
SECONDS_PER_SLOT = secondsPerSlot;
SLOTS_PER_PERIOD = slotsPerPeriod;
SOURCE_CHAIN_ID = sourceChainId;
FINALITY_THRESHOLD = finalityThreshold;
setSyncCommitteePoseidon(syncCommitteePeriod, syncCommitteePoseidon);
}
/// @notice Sets the sync committee poseidon for a given period.
function setSyncCommitteePoseidon(
uint256 period,
bytes32 poseidon
) internal {
syncCommitteePoseidons[period] = poseidon;
}
}

@ -0,0 +1,161 @@
// SPDX-License-Identifier: MIT or Apache-2.0
pragma solidity ^0.8.13;
import {Test, StdStorage, stdStorage} from "forge-std/Test.sol";
import {MockLightClient} from "../../contracts/mock/MockLightClient.sol";
import {Message} from "../../contracts/libs/Message.sol";
import {TypeCasts} from "../../contracts/libs/TypeCasts.sol";
import {MessageUtils} from "../isms/IsmTestUtils.sol";
import {MockMailbox} from "../../contracts/mock/MockMailbox.sol";
import {SP1LightClientIsm} from "../../contracts/isms/ccip-read/SP1LightClientIsm.sol";
import {DispatchedHook} from "../../contracts/hooks/DispatchedHook.sol";
import {ICcipReadIsm} from "../../contracts/interfaces/isms/ICcipReadIsm.sol";
import {StateProofHelpersTest} from "../lib/StateProofHelpers.t.sol";
import {ISuccinctProofsService} from "../../contracts/interfaces/ccip-gateways/ISuccinctProofsService.sol";
contract SP1LightClientIsmTest is StateProofHelpersTest {
using Message for bytes;
using stdStorage for StdStorage;
using TypeCasts for address;
address internal alice = address(0x1);
string[] urls = ["localhost:3001/telepathy-ccip/"];
MockMailbox mailbox;
SP1LightClientIsm sp1LightClientIsm;
DispatchedHook hook;
MockLightClient lightClient;
function setUp() public override {
super.setUp();
sp1LightClientIsm = new SP1LightClientIsm();
lightClient = new MockLightClient({
genesisValidatorsRoot: EMPTY_BYTES32,
genesisTime: 0,
secondsPerSlot: 12,
slotsPerPeriod: 8192,
syncCommitteePeriod: 0,
syncCommitteePoseidon: EMPTY_BYTES32,
sourceChainId: 1,
finalityThreshold: 461
});
deployCodeTo("DispatchedHook.sol", abi.encode(0), HOOK_ADDR);
mailbox = MockMailbox(MAILBOX_ADDR);
hook = DispatchedHook(HOOK_ADDR);
sp1LightClientIsm.initialize({
_sourceMailbox: mailbox,
_destinationMailbox: mailbox,
_dispatchedHook: hook,
_lightClient: address(lightClient),
_dispatchedSlot: DISPATCHED_SLOT,
_offchainUrls: urls
});
_setExecutionStateRoot();
}
// ============ Helper Functions ============
function _encodeTestMessage(
uint32 _messageNonce
) internal pure returns (bytes memory) {
return
// Use a struct as the parameter
MessageUtils.formatMessage({
_version: 0,
_nonce: _messageNonce,
_originDomain: 0,
_sender: hex"0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496",
_destinationDomain: 0,
_recipient: hex"0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496",
_messageBody: hex"00000000000000000000000000000000000000000000000000000000000000a7"
});
}
function _copyUrls() internal view returns (string[] memory offChainUrls) {
uint256 len = sp1LightClientIsm.offchainUrlsLength();
offChainUrls = new string[](len);
for (uint256 i; i < len; i++) {
offChainUrls[i] = sp1LightClientIsm.offchainUrls(i);
}
}
/// @dev This Mocks what Succinct's Prover will set, in other words, this sets the head slot and state root
function _setExecutionStateRoot() internal {
// Set the head slot
stdstore.target(address(lightClient)).sig("head()").checked_write(1);
// Set the head slot to stateRoot
stdstore
.target(address(lightClient))
.sig("executionStateRoots(uint256)")
.with_key(1)
.depth(0)
.checked_write(stateRoot);
}
function _encodeProofs() internal view returns (bytes memory) {
return abi.encode(accountProof, storageProof);
}
function testSP1LightClientIsm_setOffchainUrls_revertsWithNonOwner(
address _nonOwner
) public {
vm.assume(_nonOwner != address(this));
urls.push("localhost:3001/telepathy-ccip-2/");
vm.expectRevert("Ownable: caller is not the owner");
vm.prank(_nonOwner);
sp1LightClientIsm.setOffchainUrls(urls);
// Default to owner
sp1LightClientIsm.setOffchainUrls(urls);
assertEq(sp1LightClientIsm.offchainUrlsLength(), 2);
}
function testSP1LightClientIsm_getOffchainVerifyInfo_revertsCorrectly(
uint32 _messageNonce
) public {
bytes memory encodedMessage = _encodeTestMessage(_messageNonce);
string[] memory offChainUrls = _copyUrls();
bytes memory offChainLookupError = abi.encodeWithSelector(
ICcipReadIsm.OffchainLookup.selector,
address(sp1LightClientIsm),
offChainUrls,
abi.encodeWithSelector(
ISuccinctProofsService.getProofs.selector,
address(HOOK_ADDR),
sp1LightClientIsm.dispatchedSlotKey(_messageNonce)
), // Mailbox Addr, storageKeys
SP1LightClientIsm.process.selector,
encodedMessage
);
vm.expectRevert(offChainLookupError);
sp1LightClientIsm.getOffchainVerifyInfo(encodedMessage);
}
function testSP1LightClientIsm_verify_withMessage(
uint32 _incorrectMessageNonce
) public {
vm.assume(_incorrectMessageNonce != MESSAGE_NONCE);
// Incorrect message
bool verified = sp1LightClientIsm.verify(
_encodeProofs(),
_encodeTestMessage(_incorrectMessageNonce)
);
assertFalse(verified);
// Correct message
verified = sp1LightClientIsm.verify(
_encodeProofs(),
_encodeTestMessage(MESSAGE_NONCE)
);
assertTrue(verified);
}
}

@ -33,7 +33,7 @@ contract StateProofHelpersTest is Test {
0x8284b05f9fecfb3b8089dc7671e647563fdba6b1c6b4ce10d257a5f18fd471cf
);
address constant MAILBOX_ADDR = 0xd81BDE27ce1217C5DaF4dE611577667534f997B0;
address constant MAILBOX_ADDR = 0x46f7C5D896bbeC89bE1B19e4485e59b4Be49e9Cc;
bytes constant MESSAGE_ID =
hex"42a71a941db463ca31d30e30837b436a24fafbf1e0210e5013dcc5af8029989c";
uint256 constant ACCOUNT_PROOF_LENGTH = 9;

Loading…
Cancel
Save