// SPDX-License-Identifier: Apache-2.0// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>pragmasolidity 0.8.25;import {ECDSA} from"openzeppelin/utils/cryptography/ECDSA.sol";
import {SubstrateMerkleProof} from"./utils/SubstrateMerkleProof.sol";
import {Bitfield} from"./utils/Bitfield.sol";
import {Uint16Array, createUint16Array} from"./utils/Uint16Array.sol";
import {Math} from"./utils/Math.sol";
import {MMRProof} from"./utils/MMRProof.sol";
import {ScaleCodec} from"./utils/ScaleCodec.sol";
/**
* @title BeefyClient
*
* High-level documentation at https://docs.snowbridge.network/architecture/verification/polkadot
*
* To submit new commitments, relayers must call the following methods sequentially:
* 1. submitInitial: Setup the session for the interactive submission
* 2. commitPrevRandao: Commit to a random seed for generating a validator subsampling
* 3. createFinalBitfield: Generate the validator subsampling
* 4. submitFinal: Complete submission after providing the request validator signatures
*
*/contractBeefyClient{
usingMathforuint16;
usingMathforuint256;
/* Events *//**
* @dev Emitted when the MMR root is updated
* @param mmrRoot the updated MMR root
* @param blockNumber the beefy block number of the updated MMR root
*/eventNewMMRRoot(bytes32 mmrRoot, uint64 blockNumber);
/**
* @dev Emitted when a new ticket has been created
* @param relayer The relayer who created the ticket
* @param blockNumber the parent block number of the candidate MMR root
*/eventNewTicket(address relayer, uint64 blockNumber);
/* Types *//**
* @dev The Commitment, with its payload, is the core thing we are trying to verify with
* this contract. It contains an MMR root that commits to the polkadot history, including
* past blocks and parachain blocks and can be used to verify both polkadot and parachain blocks.
*/structCommitment {
// Relay chain block numberuint32 blockNumber;
// ID of the validator set that signed the commitmentuint64 validatorSetID;
// The payload of the new commitment in beefy justifications (in// our case, this is a new MMR root for all past polkadot blocks)
PayloadItem[] payload;
}
/**
* @dev Each PayloadItem is a piece of data signed by validators at a particular block.
*/structPayloadItem {
// An ID that references a description of the data in the payload item.// Known payload ids can be found [upstream](https://github.com/paritytech/substrate/blob/fe1f8ba1c4f23931ae89c1ada35efb3d908b50f5/primitives/consensus/beefy/src/payload.rs#L27).bytes2 payloadID;
// The contents of the payload itembytes data;
}
/**
* @dev The ValidatorProof is a proof used to verify a commitment signature
*/structValidatorProof {
// The parity bit to specify the intended solutionuint8 v;
// The x component on the secp256k1 curvebytes32 r;
// The challenge solutionbytes32 s;
// Leaf index of the validator address in the merkle treeuint256 index;
// Validator addressaddress account;
// Merkle proof for the validatorbytes32[] proof;
}
/**
* @dev A ticket tracks working state for the interactive submission of new commitments
*/structTicket {
// The block number this ticket was issueduint64 blockNumber;
// Length of the validator set that signed the commitmentuint32 validatorSetLen;
// The number of signatures requireduint32 numRequiredSignatures;
// The PREVRANDAO seed selected for this ticket sessionuint256 prevRandao;
// Hash of a bitfield claiming which validators have signedbytes32 bitfieldHash;
}
/// @dev The MMRLeaf describes the leaf structure of the MMRstructMMRLeaf {
// Version of the leaf typeuint8 version;
// Parent number of the block this leaf describesuint32 parentNumber;
// Parent hash of the block this leaf describesbytes32 parentHash;
// Validator set id that will be part of consensus for the next blockuint64 nextAuthoritySetID;
// Length of that validator setuint32 nextAuthoritySetLen;
// Merkle root of all public keys in that validator setbytes32 nextAuthoritySetRoot;
// Merkle root of all parachain headers in this blockbytes32 parachainHeadsRoot;
}
/**
* @dev The ValidatorSet describes a BEEFY validator set
*/structValidatorSet {
// Identifier for the setuint128 id;
// Number of validators in the setuint128 length;
// Merkle root of BEEFY validator addressesbytes32 root;
}
/**
* @dev The ValidatorSetState describes a BEEFY validator set along with signature usage counters
*/structValidatorSetState {
// Identifier for the setuint128 id;
// Number of validators in the setuint128 length;
// Merkle root of BEEFY validator addressesbytes32 root;
// Number of times a validator signature has been used
Uint16Array usageCounters;
}
/* State *//// @dev The latest verified MMR rootbytes32public latestMMRRoot;
/// @dev The block number in the relay chain in which the latest MMR root was emitteduint64public latestBeefyBlock;
/// @dev State of the current validator set
ValidatorSetState public currentValidatorSet;
/// @dev State of the next validator set
ValidatorSetState public nextValidatorSet;
/// @dev Pending tickets for commitment submissionmapping(bytes32 ticketID => Ticket) public tickets;
/* Constants *//**
* @dev Beefy payload id for MMR Root payload items:
* https://github.com/paritytech/substrate/blob/fe1f8ba1c4f23931ae89c1ada35efb3d908b50f5/primitives/consensus/beefy/src/payload.rs#L33
*/bytes2publicconstant MMR_ROOT_ID =bytes2("mh");
/**
* @dev Minimum delay in number of blocks that a relayer must wait between calling
* submitInitial and commitPrevRandao. In production this should be set to MAX_SEED_LOOKAHEAD:
* https://eth2book.info/altair/part3/config/preset#max_seed_lookahead
*/uint256publicimmutable randaoCommitDelay;
/**
* @dev after randaoCommitDelay is reached, relayer must
* call commitPrevRandao within this number of blocks.
* Without this expiration, relayers can roll the dice infinitely to get the subsampling
* they desire.
*/uint256publicimmutable randaoCommitExpiration;
/**
* @dev Minimum number of signatures required to validate a new commitment. This parameter
* is calculated based on `randaoCommitExpiration`. See ~/scripts/beefy_signature_sampling.py
* for the calculation.
*/uint256publicimmutable minNumRequiredSignatures;
/* Errors */errorInvalidBitfield();
errorInvalidBitfieldLength();
errorInvalidCommitment();
errorInvalidMMRLeaf();
errorInvalidMMRLeafProof();
errorInvalidMMRRootLength();
errorInvalidSignature();
errorInvalidTicket();
errorInvalidValidatorProof();
errorInvalidValidatorProofLength();
errorCommitmentNotRelevant();
errorNotEnoughClaims();
errorPrevRandaoAlreadyCaptured();
errorPrevRandaoNotCaptured();
errorStaleCommitment();
errorTicketExpired();
errorWaitPeriodNotOver();
constructor(uint256 _randaoCommitDelay,
uint256 _randaoCommitExpiration,
uint256 _minNumRequiredSignatures,
uint64 _initialBeefyBlock,
ValidatorSet memory _initialValidatorSet,
ValidatorSet memory _nextValidatorSet
) {
if (_nextValidatorSet.id != _initialValidatorSet.id +1) {
revert("invalid-constructor-params");
}
randaoCommitDelay = _randaoCommitDelay;
randaoCommitExpiration = _randaoCommitExpiration;
minNumRequiredSignatures = _minNumRequiredSignatures;
latestBeefyBlock = _initialBeefyBlock;
currentValidatorSet.id = _initialValidatorSet.id;
currentValidatorSet.length= _initialValidatorSet.length;
currentValidatorSet.root = _initialValidatorSet.root;
currentValidatorSet.usageCounters = createUint16Array(currentValidatorSet.length);
nextValidatorSet.id = _nextValidatorSet.id;
nextValidatorSet.length= _nextValidatorSet.length;
nextValidatorSet.root = _nextValidatorSet.root;
nextValidatorSet.usageCounters = createUint16Array(nextValidatorSet.length);
}
/* External Functions *//**
* @dev Begin submission of commitment
* @param commitment contains the commitment signed by the validators
* @param bitfield a bitfield claiming which validators have signed the commitment
* @param proof a proof that a single validator from currentValidatorSet has signed the commitment
*/functionsubmitInitial(Commitment calldata commitment, uint256[] calldata bitfield, ValidatorProof calldata proof)
external{
if (commitment.blockNumber <= latestBeefyBlock) {
revert StaleCommitment();
}
ValidatorSetState storage vset;
uint16 signatureUsageCount;
if (commitment.validatorSetID == currentValidatorSet.id) {
signatureUsageCount = currentValidatorSet.usageCounters.get(proof.index);
currentValidatorSet.usageCounters.set(proof.index, signatureUsageCount.saturatingAdd(1));
vset = currentValidatorSet;
} elseif (commitment.validatorSetID == nextValidatorSet.id) {
signatureUsageCount = nextValidatorSet.usageCounters.get(proof.index);
nextValidatorSet.usageCounters.set(proof.index, signatureUsageCount.saturatingAdd(1));
vset = nextValidatorSet;
} else {
revert InvalidCommitment();
}
// Check if merkle proof is valid based on the validatorSetRoot and if proof is included in bitfieldif (!isValidatorInSet(vset, proof.account, proof.index, proof.proof) ||!Bitfield.isSet(bitfield, proof.index))
{
revert InvalidValidatorProof();
}
// Check if validatorSignature is correct, ie. check if it matches// the signature of senderPublicKey on the commitmentHashbytes32 commitmentHash =keccak256(encodeCommitment(commitment));
if (ECDSA.recover(commitmentHash, proof.v, proof.r, proof.s) != proof.account) {
revert InvalidSignature();
}
// For the initial submission, the supplied bitfield should claim that more than// two thirds of the validator set have sign the commitmentif (Bitfield.countSetBits(bitfield) < computeQuorum(vset.length)) {
revert NotEnoughClaims();
}
tickets[createTicketID(msg.sender, commitmentHash)] = Ticket({
blockNumber: uint64(block.number),
validatorSetLen: uint32(vset.length),
numRequiredSignatures: uint32(
computeNumRequiredSignatures(vset.length, signatureUsageCount, minNumRequiredSignatures)
),
prevRandao: 0,
bitfieldHash: keccak256(abi.encodePacked(bitfield))
});
emit NewTicket(msg.sender, commitment.blockNumber);
}
/**
* @dev Capture PREVRANDAO
* @param commitmentHash contains the commitmentHash signed by the validators
*/functioncommitPrevRandao(bytes32 commitmentHash) external{
bytes32 ticketID = createTicketID(msg.sender, commitmentHash);
Ticket storage ticket = tickets[ticketID];
if (ticket.blockNumber ==0) {
revert InvalidTicket();
}
if (ticket.prevRandao !=0) {
revert PrevRandaoAlreadyCaptured();
}
// relayer must wait `randaoCommitDelay` blocksif (block.number< ticket.blockNumber + randaoCommitDelay) {
revert WaitPeriodNotOver();
}
// relayer can capture within `randaoCommitExpiration` blocksif (block.number> ticket.blockNumber + randaoCommitDelay + randaoCommitExpiration) {
delete tickets[ticketID];
revert TicketExpired();
}
// Post-merge, the difficulty opcode now returns PREVRANDAO
ticket.prevRandao =block.prevrandao;
}
/**
* @dev Submit a commitment and leaf for final verification
* @param commitment contains the full commitment that was used for the commitmentHash
* @param bitfield claiming which validators have signed the commitment
* @param proofs a struct containing the data needed to verify all validator signatures
* @param leaf an MMR leaf provable using the MMR root in the commitment payload
* @param leafProof an MMR leaf proof
* @param leafProofOrder a bitfield describing the order of each item (left vs right)
*/functionsubmitFinal(
Commitment calldata commitment,
uint256[] calldata bitfield,
ValidatorProof[] calldata proofs,
MMRLeaf calldata leaf,
bytes32[] calldata leafProof,
uint256 leafProofOrder
) external{
bytes32 commitmentHash =keccak256(encodeCommitment(commitment));
bytes32 ticketID = createTicketID(msg.sender, commitmentHash);
validateTicket(ticketID, commitment, bitfield);
bool is_next_session =false;
ValidatorSetState storage vset;
if (commitment.validatorSetID == nextValidatorSet.id) {
is_next_session =true;
vset = nextValidatorSet;
} elseif (commitment.validatorSetID == currentValidatorSet.id) {
vset = currentValidatorSet;
} else {
revert InvalidCommitment();
}
verifyCommitment(commitmentHash, ticketID, bitfield, vset, proofs);
bytes32 newMMRRoot = ensureProvidesMMRRoot(commitment);
if (is_next_session) {
if (leaf.nextAuthoritySetID != nextValidatorSet.id +1) {
revert InvalidMMRLeaf();
}
bool leafIsValid =
MMRProof.verifyLeafProof(newMMRRoot, keccak256(encodeMMRLeaf(leaf)), leafProof, leafProofOrder);
if (!leafIsValid) {
revert InvalidMMRLeafProof();
}
currentValidatorSet = nextValidatorSet;
nextValidatorSet.id = leaf.nextAuthoritySetID;
nextValidatorSet.length= leaf.nextAuthoritySetLen;
nextValidatorSet.root = leaf.nextAuthoritySetRoot;
nextValidatorSet.usageCounters = createUint16Array(leaf.nextAuthoritySetLen);
}
latestMMRRoot = newMMRRoot;
latestBeefyBlock = commitment.blockNumber;
delete tickets[ticketID];
emit NewMMRRoot(newMMRRoot, commitment.blockNumber);
}
/**
* @dev Verify that the supplied MMR leaf is included in the latest verified MMR root.
* @param leafHash contains the merkle leaf to be verified
* @param proof contains simplified mmr proof
* @param proofOrder a bitfield describing the order of each item (left vs right)
*/functionverifyMMRLeafProof(bytes32 leafHash, bytes32[] calldata proof, uint256 proofOrder)
externalviewreturns (bool)
{
return MMRProof.verifyLeafProof(latestMMRRoot, leafHash, proof, proofOrder);
}
/**
* @dev Helper to create an initial validator bitfield.
* @param bitsToSet contains indexes of all signed validators, should be deduplicated
* @param length of validator set
*/functioncreateInitialBitfield(uint256[] calldata bitsToSet, uint256 length)
externalpurereturns (uint256[] memory)
{
if (length < bitsToSet.length) {
revert InvalidBitfieldLength();
}
return Bitfield.createBitfield(bitsToSet, length);
}
/**
* @dev Helper to create a final bitfield, with subsampled validator selections
* @param commitmentHash contains the commitmentHash signed by the validators
* @param bitfield claiming which validators have signed the commitment
*/functioncreateFinalBitfield(bytes32 commitmentHash, uint256[] calldata bitfield)
externalviewreturns (uint256[] memory)
{
Ticket storage ticket = tickets[createTicketID(msg.sender, commitmentHash)];
if (ticket.bitfieldHash !=keccak256(abi.encodePacked(bitfield))) {
revert InvalidBitfield();
}
return Bitfield.subsample(ticket.prevRandao, bitfield, ticket.numRequiredSignatures, ticket.validatorSetLen);
}
/* Internal Functions */// Creates a unique ticket ID for a new interactive prover-verifier sessionfunctioncreateTicketID(address account, bytes32 commitmentHash) internalpurereturns (bytes32 value) {
assembly {
mstore(0x00, account)
mstore(0x20, commitmentHash)
value :=keccak256(0x0, 0x40)
}
}
/**
* @dev Calculates the number of required signatures for `submitFinal`.
* @param validatorSetLen The length of the validator set
* @param signatureUsageCount A counter of the number of times the validator signature was previously used in a call to `submitInitial` within the session.
* @param minRequiredSignatures The minimum amount of signatures to verify
*/// For more details on the calculation, read the following:// 1. https://docs.snowbridge.network/architecture/verification/polkadot#signature-sampling// 2. https://hackmd.io/9OedC7icR5m-in_moUZ_WQfunctioncomputeNumRequiredSignatures(uint256 validatorSetLen,
uint256 signatureUsageCount,
uint256 minRequiredSignatures
) internalpurereturns (uint256) {
// Start with the minimum number of signatures.uint256 numRequiredSignatures = minRequiredSignatures;
// Add signatures based on the number of validators in the validator set.
numRequiredSignatures += Math.log2(validatorSetLen, Math.Rounding.Ceil);
// Add signatures based on the signature usage count.
numRequiredSignatures +=1+ (2* Math.log2(signatureUsageCount, Math.Rounding.Ceil));
// Never require more signatures than a 2/3 majorityreturn Math.min(numRequiredSignatures, computeQuorum(validatorSetLen));
}
/**
* @dev Calculates 2/3 majority required for quorum for a given number of validators.
* @param numValidators The number of validators in the validator set.
*/functioncomputeQuorum(uint256 numValidators) internalpurereturns (uint256) {
return numValidators - (numValidators -1) /3;
}
/**
* @dev Verify commitment using the supplied signature proofs
*/functionverifyCommitment(bytes32 commitmentHash,
bytes32 ticketID,
uint256[] calldata bitfield,
ValidatorSetState storage vset,
ValidatorProof[] calldata proofs
) internalview{
Ticket storage ticket = tickets[ticketID];
// Verify that enough signature proofs have been supplieduint256 numRequiredSignatures = ticket.numRequiredSignatures;
if (proofs.length!= numRequiredSignatures) {
revert InvalidValidatorProofLength();
}
// Generate final bitfield indicating which validators need to be included in the proofs.uint256[] memory finalbitfield =
Bitfield.subsample(ticket.prevRandao, bitfield, numRequiredSignatures, vset.length);
for (uint256 i =0; i < proofs.length; i++) {
ValidatorProof calldata proof = proofs[i];
// Check that validator is in bitfieldif (!Bitfield.isSet(finalbitfield, proof.index)) {
revert InvalidValidatorProof();
}
// Check that validator is actually in a validator setif (!isValidatorInSet(vset, proof.account, proof.index, proof.proof)) {
revert InvalidValidatorProof();
}
// Check that validator signed the commitmentif (ECDSA.recover(commitmentHash, proof.v, proof.r, proof.s) != proof.account) {
revert InvalidSignature();
}
// Ensure no validator can appear more than once in bitfield
Bitfield.unset(finalbitfield, proof.index);
}
}
// Ensure that the commitment provides a new MMR rootfunctionensureProvidesMMRRoot(Commitment calldata commitment) internalpurereturns (bytes32) {
for (uint256 i =0; i < commitment.payload.length; i++) {
if (commitment.payload[i].payloadID == MMR_ROOT_ID) {
if (commitment.payload[i].data.length!=32) {
revert InvalidMMRRootLength();
} else {
returnbytes32(commitment.payload[i].data);
}
}
}
revert CommitmentNotRelevant();
}
functionencodeCommitment(Commitment calldata commitment) internalpurereturns (bytesmemory) {
returnbytes.concat(
encodeCommitmentPayload(commitment.payload),
ScaleCodec.encodeU32(commitment.blockNumber),
ScaleCodec.encodeU64(commitment.validatorSetID)
);
}
functionencodeCommitmentPayload(PayloadItem[] calldata items) internalpurereturns (bytesmemory) {
bytesmemory payload = ScaleCodec.checkedEncodeCompactU32(items.length);
for (uint256 i =0; i < items.length; i++) {
payload =bytes.concat(
payload, items[i].payloadID, ScaleCodec.checkedEncodeCompactU32(items[i].data.length), items[i].data
);
}
return payload;
}
functionencodeMMRLeaf(MMRLeaf calldata leaf) internalpurereturns (bytesmemory) {
returnbytes.concat(
ScaleCodec.encodeU8(leaf.version),
ScaleCodec.encodeU32(leaf.parentNumber),
leaf.parentHash,
ScaleCodec.encodeU64(leaf.nextAuthoritySetID),
ScaleCodec.encodeU32(leaf.nextAuthoritySetLen),
leaf.nextAuthoritySetRoot,
leaf.parachainHeadsRoot
);
}
/**
* @dev Checks if a validators address is a member of the merkle tree
* @param vset The validator set
* @param account The address of the validator to check for inclusion in `vset`.
* @param index The leaf index of the account in the merkle tree of validator set addresses.
* @param proof Merkle proof required for validation of the address
* @return true if the validator is in the set
*/functionisValidatorInSet(ValidatorSetState storage vset, address account, uint256 index, bytes32[] calldata proof)
internalviewreturns (bool)
{
bytes32 hashedLeaf =keccak256(abi.encodePacked(account));
return SubstrateMerkleProof.verify(vset.root, hashedLeaf, index, vset.length, proof);
}
/**
* @dev Basic validation of a ticket for submitFinal
*/functionvalidateTicket(bytes32 ticketID, Commitment calldata commitment, uint256[] calldata bitfield)
internalview{
Ticket storage ticket = tickets[ticketID];
if (ticket.blockNumber ==0) {
// submitInitial hasn't been called yetrevert InvalidTicket();
}
if (ticket.prevRandao ==0) {
// commitPrevRandao hasn't been called yetrevert PrevRandaoNotCaptured();
}
if (commitment.blockNumber <= latestBeefyBlock) {
// ticket is obsoleterevert StaleCommitment();
}
if (ticket.bitfieldHash !=keccak256(abi.encodePacked(bitfield))) {
// The provided claims bitfield isn't the same one that was// passed to submitInitialrevert InvalidBitfield();
}
}
}
Contract Source Code
File 2 of 11: Bitfield.sol
// SPDX-License-Identifier: Apache-2.0// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>pragmasolidity 0.8.25;import {Bits} from"./Bits.sol";
libraryBitfield{
usingBitsforuint256;
/**
* @dev Constants used to efficiently calculate the hamming weight of a bitfield. See
* https://en.wikipedia.org/wiki/Hamming_weight#Efficient_implementation for an explanation of those constants.
*/uint256internalconstant M1 =0x5555555555555555555555555555555555555555555555555555555555555555;
uint256internalconstant M2 =0x3333333333333333333333333333333333333333333333333333333333333333;
uint256internalconstant M4 =0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f;
uint256internalconstant M8 =0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff;
uint256internalconstant M16 =0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff;
uint256internalconstant M32 =0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffff;
uint256internalconstant M64 =0x0000000000000000ffffffffffffffff0000000000000000ffffffffffffffff;
uint256internalconstant M128 =0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff;
uint256internalconstant ONE =uint256(1);
/**
* @notice Core subsampling algorithm. Draws a random number, derives an index in the bitfield, and sets the bit if it is in the `prior` and not
* yet set. Repeats that `n` times.
* @param seed Source of randomness for selecting validator signatures.
* @param prior Bitfield indicating which validators claim to have signed the commitment.
* @param n Number of unique bits in prior that must be set in the result. Must be <= number of set bits in `prior`.
* @param length Length of the bitfield prior to draw bits from. Must be <= prior.length * 256.
*/functionsubsample(uint256 seed, uint256[] memory prior, uint256 n, uint256 length)
internalpurereturns (uint256[] memory bitfield)
{
bitfield =newuint256[](prior.length);
uint256 found =0;
for (uint256 i =0; found < n;) {
uint256 index = makeIndex(seed, i, length);
// require randomly selected bit to be set in prior and not yet set in bitfieldif (!isSet(prior, index) || isSet(bitfield, index)) {
unchecked {
i++;
}
continue;
}
set(bitfield, index);
unchecked {
found++;
i++;
}
}
return bitfield;
}
/**
* @dev Helper to create a bitfield.
*/functioncreateBitfield(uint256[] calldata bitsToSet, uint256 length)
internalpurereturns (uint256[] memory bitfield)
{
// Calculate length of uint256 array based on rounding up to number of uint256 neededuint256 arrayLength = (length +255) /256;
bitfield =newuint256[](arrayLength);
for (uint256 i =0; i < bitsToSet.length; i++) {
set(bitfield, bitsToSet[i]);
}
return bitfield;
}
/**
* @notice Calculates the number of set bits by using the hamming weight of the bitfield.
* The algorithm below is implemented after https://en.wikipedia.org/wiki/Hamming_weight#Efficient_implementation.
* Further improvements are possible, see the article above.
*/functioncountSetBits(uint256[] memoryself) internalpurereturns (uint256) {
unchecked {
uint256 count =0;
for (uint256 i =0; i <self.length; i++) {
uint256 x =self[i];
x = (x & M1) + ((x >>1) & M1); //put count of each 2 bits into those 2 bits
x = (x & M2) + ((x >>2) & M2); //put count of each 4 bits into those 4 bits
x = (x & M4) + ((x >>4) & M4); //put count of each 8 bits into those 8 bits
x = (x & M8) + ((x >>8) & M8); //put count of each 16 bits into those 16 bits
x = (x & M16) + ((x >>16) & M16); //put count of each 32 bits into those 32 bits
x = (x & M32) + ((x >>32) & M32); //put count of each 64 bits into those 64 bits
x = (x & M64) + ((x >>64) & M64); //put count of each 128 bits into those 128 bits
x = (x & M128) + ((x >>128) & M128); //put count of each 256 bits into those 256 bits
count += x;
}
return count;
}
}
functionisSet(uint256[] memoryself, uint256 index) internalpurereturns (bool) {
uint256 element = index >>8;
returnself[element].bit(uint8(index)) ==1;
}
functionset(uint256[] memoryself, uint256 index) internalpure{
uint256 element = index >>8;
self[element] =self[element].setBit(uint8(index));
}
functionunset(uint256[] memoryself, uint256 index) internalpure{
uint256 element = index >>8;
self[element] =self[element].clearBit(uint8(index));
}
functionmakeIndex(uint256 seed, uint256 iteration, uint256 length) internalpurereturns (uint256 index) {
assembly {
mstore(0x00, seed)
mstore(0x20, iteration)
index :=mod(keccak256(0x00, 0x40), length)
}
}
}
Contract Source Code
File 3 of 11: Bits.sol
// SPDX-License-Identifier: Apache-2.0// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>// Code from https://github.com/ethereum/solidity-examplespragmasolidity 0.8.25;libraryBits{
uint256internalconstant ONE =uint256(1);
uint256internalconstant ONES =type(uint256).max;
// Sets the bit at the given 'index' in 'self' to '1'.// Returns the modified value.functionsetBit(uint256self, uint8 index) internalpurereturns (uint256) {
returnself| (ONE << index);
}
// Sets the bit at the given 'index' in 'self' to '0'.// Returns the modified value.functionclearBit(uint256self, uint8 index) internalpurereturns (uint256) {
returnself&~(ONE << index);
}
// Sets the bit at the given 'index' in 'self' to:// '1' - if the bit is '0'// '0' - if the bit is '1'// Returns the modified value.functiontoggleBit(uint256self, uint8 index) internalpurereturns (uint256) {
returnself^ (ONE << index);
}
// Get the value of the bit at the given 'index' in 'self'.functionbit(uint256self, uint8 index) internalpurereturns (uint8) {
returnuint8((self>> index) &1);
}
// Check if the bit at the given 'index' in 'self' is set.// Returns:// 'true' - if the value of the bit is '1'// 'false' - if the value of the bit is '0'functionbitSet(uint256self, uint8 index) internalpurereturns (bool) {
return (self>> index) &1==1;
}
// Checks if the bit at the given 'index' in 'self' is equal to the corresponding// bit in 'other'.// Returns:// 'true' - if both bits are '0' or both bits are '1'// 'false' - otherwisefunctionbitEqual(uint256self, uint256 other, uint8 index) internalpurereturns (bool) {
return ((self^ other) >> index) &1==0;
}
// Get the bitwise NOT of the bit at the given 'index' in 'self'.functionbitNot(uint256self, uint8 index) internalpurereturns (uint8) {
returnuint8(1- ((self>> index) &1));
}
// Computes the bitwise AND of the bit at the given 'index' in 'self', and the// corresponding bit in 'other', and returns the value.functionbitAnd(uint256self, uint256 other, uint8 index) internalpurereturns (uint8) {
returnuint8(((self& other) >> index) &1);
}
// Computes the bitwise OR of the bit at the given 'index' in 'self', and the// corresponding bit in 'other', and returns the value.functionbitOr(uint256self, uint256 other, uint8 index) internalpurereturns (uint8) {
returnuint8(((self| other) >> index) &1);
}
// Computes the bitwise XOR of the bit at the given 'index' in 'self', and the// corresponding bit in 'other', and returns the value.functionbitXor(uint256self, uint256 other, uint8 index) internalpurereturns (uint8) {
returnuint8(((self^ other) >> index) &1);
}
// Gets 'numBits' consecutive bits from 'self', starting from the bit at 'startIndex'.// Returns the bits as a 'uint'.// Requires that:// - '0 < numBits <= 256'// - 'startIndex < 256'// - 'numBits + startIndex <= 256'functionbits(uint256self, uint8 startIndex, uint16 numBits) internalpurereturns (uint256) {
require(0< numBits && startIndex <256&& startIndex + numBits <=256, "out of bounds");
return (self>> startIndex) & (ONES >> (256- numBits));
}
// Computes the index of the highest bit set in 'self'.// Returns the highest bit set as an 'uint8'.// Requires that 'self != 0'.functionhighestBitSet(uint256self) internalpurereturns (uint8 highest) {
require(self!=0, "should not be zero");
uint256 val =self;
for (uint8 i =128; i >=1; i >>=1) {
if (val & (((ONE << i) -1) << i) !=0) {
highest += i;
val >>= i;
}
}
}
// Computes the index of the lowest bit set in 'self'.// Returns the lowest bit set as an 'uint8'.// Requires that 'self != 0'.functionlowestBitSet(uint256self) internalpurereturns (uint8 lowest) {
require(self!=0, "should not be zero");
uint256 val =self;
for (uint8 i =128; i >=1; i >>=1) {
if (val & ((ONE << i) -1) ==0) {
lowest += i;
val >>= i;
}
}
}
}
Contract Source Code
File 4 of 11: ECDSA.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol)pragmasolidity ^0.8.0;import"../Strings.sol";
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/libraryECDSA{
enumRecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
InvalidSignatureV // Deprecated in v4.8
}
function_throwError(RecoverError error) privatepure{
if (error == RecoverError.NoError) {
return; // no error: do nothing
} elseif (error == RecoverError.InvalidSignature) {
revert("ECDSA: invalid signature");
} elseif (error == RecoverError.InvalidSignatureLength) {
revert("ECDSA: invalid signature length");
} elseif (error == RecoverError.InvalidSignatureS) {
revert("ECDSA: invalid signature 's' value");
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature` or error string. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*
* _Available since v4.3._
*/functiontryRecover(bytes32 hash, bytesmemory signature) internalpurereturns (address, RecoverError) {
if (signature.length==65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them// currently is to use assembly./// @solidity memory-safe-assemblyassembly {
r :=mload(add(signature, 0x20))
s :=mload(add(signature, 0x40))
v :=byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength);
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/functionrecover(bytes32 hash, bytesmemory signature) internalpurereturns (address) {
(address recovered, RecoverError error) = tryRecover(hash, signature);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*
* _Available since v4.3._
*/functiontryRecover(bytes32 hash, bytes32 r, bytes32 vs) internalpurereturns (address, RecoverError) {
bytes32 s = vs &bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
uint8 v =uint8((uint256(vs) >>255) +27);
return tryRecover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*
* _Available since v4.2._
*/functionrecover(bytes32 hash, bytes32 r, bytes32 vs) internalpurereturns (address) {
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*
* _Available since v4.3._
*/functiontryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internalpurereturns (address, RecoverError) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most// signatures from current libraries generate a unique signature with an s-value in the lower half order.//// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept// these malleable signatures as well.if (uint256(s) >0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS);
}
// If the signature is valid (and not malleable), return the signer addressaddress signer =ecrecover(hash, v, r, s);
if (signer ==address(0)) {
return (address(0), RecoverError.InvalidSignature);
}
return (signer, RecoverError.NoError);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/functionrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internalpurereturns (address) {
(address recovered, RecoverError error) = tryRecover(hash, v, r, s);
_throwError(error);
return recovered;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/functiontoEthSignedMessageHash(bytes32 hash) internalpurereturns (bytes32 message) {
// 32 is the length in bytes of hash,// enforced by the type signature above/// @solidity memory-safe-assemblyassembly {
mstore(0x00, "\x19Ethereum Signed Message:\n32")
mstore(0x1c, hash)
message :=keccak256(0x00, 0x3c)
}
}
/**
* @dev Returns an Ethereum Signed Message, created from `s`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/functiontoEthSignedMessageHash(bytesmemory s) internalpurereturns (bytes32) {
returnkeccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
}
/**
* @dev Returns an Ethereum Signed Typed Data, created from a
* `domainSeparator` and a `structHash`. This produces hash corresponding
* to the one signed with the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
* JSON-RPC method as part of EIP-712.
*
* See {recover}.
*/functiontoTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internalpurereturns (bytes32 data) {
/// @solidity memory-safe-assemblyassembly {
let ptr :=mload(0x40)
mstore(ptr, "\x19\x01")
mstore(add(ptr, 0x02), domainSeparator)
mstore(add(ptr, 0x22), structHash)
data :=keccak256(ptr, 0x42)
}
}
/**
* @dev Returns an Ethereum Signed Data with intended validator, created from a
* `validator` and `data` according to the version 0 of EIP-191.
*
* See {recover}.
*/functiontoDataWithIntendedValidatorHash(address validator, bytesmemory data) internalpurereturns (bytes32) {
returnkeccak256(abi.encodePacked("\x19\x00", validator, data));
}
}
Contract Source Code
File 5 of 11: MMRProof.sol
// SPDX-License-Identifier: Apache-2.0// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>pragmasolidity 0.8.25;libraryMMRProof{
errorProofSizeExceeded();
uint256internalconstant MAXIMUM_PROOF_SIZE =256;
/**
* @dev Verify inclusion of a leaf in an MMR
* @param root MMR root hash
* @param leafHash leaf hash
* @param proof an array of hashes
* @param proofOrder a bitfield describing the order of each item (left vs right)
*/functionverifyLeafProof(bytes32 root, bytes32 leafHash, bytes32[] calldata proof, uint256 proofOrder)
internalpurereturns (bool)
{
// Size of the proof is bounded, since `proofOrder` can only contain `MAXIMUM_PROOF_SIZE` orderings.if (proof.length> MAXIMUM_PROOF_SIZE) {
revert ProofSizeExceeded();
}
bytes32 acc = leafHash;
for (uint256 i =0; i < proof.length; i++) {
acc = hashPairs(acc, proof[i], (proofOrder >> i) &1);
}
return root == acc;
}
functionhashPairs(bytes32 x, bytes32 y, uint256 order) internalpurereturns (bytes32 value) {
assembly {
switch order
case0 {
mstore(0x00, x)
mstore(0x20, y)
}
default {
mstore(0x00, y)
mstore(0x20, x)
}
value :=keccak256(0x0, 0x40)
}
}
}
Contract Source Code
File 6 of 11: Math.sol
// SPDX-License-Identifier: MIT// SPDX-FileCopyrightText: 2023 OpenZeppelin// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>// Code from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/Math.solpragmasolidity 0.8.25;/**
* @dev Standard math utilities missing in the Solidity language.
*/libraryMath{
enumRounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Returns the largest of two numbers.
*/functionmax(uint256 a, uint256 b) internalpurereturns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/functionmin(uint256 a, uint256 b) internalpurereturns (uint256) {
return a < b ? a : b;
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/functionlog2(uint256 value) internalpurereturns (uint256) {
uint256 result =0;
unchecked {
if (value >>128>0) {
value >>=128;
result +=128;
}
if (value >>64>0) {
value >>=64;
result +=64;
}
if (value >>32>0) {
value >>=32;
result +=32;
}
if (value >>16>0) {
value >>=16;
result +=16;
}
if (value >>8>0) {
value >>=8;
result +=8;
}
if (value >>4>0) {
value >>=4;
result +=4;
}
if (value >>2>0) {
value >>=2;
result +=2;
}
if (value >>1>0) {
result +=1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/functionlog2(uint256 value, Rounding rounding) internalpurereturns (uint256) {
unchecked {
uint256 result =log2(value);
return result + (unsignedRoundsUp(rounding) &&1<< result < value ? 1 : 0);
}
}
/**
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
*/functionunsignedRoundsUp(Rounding rounding) internalpurereturns (bool) {
returnuint8(rounding) %2==1;
}
/**
* @dev Safely adds two unsigned 16-bit integers, preventing overflow by saturating to max uint16.
*/functionsaturatingAdd(uint16 a, uint16 b) internalpurereturns (uint16) {
unchecked {
uint16 c = a + b;
if (c < a) {
return0xFFFF;
}
return c;
}
}
/**
* @dev Safely subtracts two unsigned 256-bit integers, preventing overflow by saturating to min uint256.
*/functionsaturatingSub(uint256 a, uint256 b) internalpurereturns (uint256) {
unchecked {
if (b >= a) {
return0;
}
return a - b;
}
}
}
Contract Source Code
File 7 of 11: ScaleCodec.sol
// SPDX-License-Identifier: Apache-2.0// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>pragmasolidity 0.8.25;libraryScaleCodec{
errorUnsupportedCompactEncoding();
uint256internalconstant MAX_COMPACT_ENCODABLE_UINT =2**30-1;
// Sources:// * https://ethereum.stackexchange.com/questions/15350/how-to-convert-an-bytes-to-address-in-solidity/50528// * https://graphics.stanford.edu/~seander/bithacks.html#ReverseParallelfunctionreverse256(uint256 input) internalpurereturns (uint256 v) {
v = input;
// swap bytes
v = ((v &0xFF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00) >>8)
| ((v &0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) <<8);
// swap 2-byte long pairs
v = ((v &0xFFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000) >>16)
| ((v &0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) <<16);
// swap 4-byte long pairs
v = ((v &0xFFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000) >>32)
| ((v &0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) <<32);
// swap 8-byte long pairs
v = ((v &0xFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF0000000000000000) >>64)
| ((v &0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) <<64);
// swap 16-byte long pairs
v = (v >>128) | (v <<128);
}
functionreverse128(uint128 input) internalpurereturns (uint128 v) {
v = input;
// swap bytes
v = ((v &0xFF00FF00FF00FF00FF00FF00FF00FF00) >>8) | ((v &0x00FF00FF00FF00FF00FF00FF00FF00FF) <<8);
// swap 2-byte long pairs
v = ((v &0xFFFF0000FFFF0000FFFF0000FFFF0000) >>16) | ((v &0x0000FFFF0000FFFF0000FFFF0000FFFF) <<16);
// swap 4-byte long pairs
v = ((v &0xFFFFFFFF00000000FFFFFFFF00000000) >>32) | ((v &0x00000000FFFFFFFF00000000FFFFFFFF) <<32);
// swap 8-byte long pairs
v = (v >>64) | (v <<64);
}
functionreverse64(uint64 input) internalpurereturns (uint64 v) {
v = input;
// swap bytes
v = ((v &0xFF00FF00FF00FF00) >>8) | ((v &0x00FF00FF00FF00FF) <<8);
// swap 2-byte long pairs
v = ((v &0xFFFF0000FFFF0000) >>16) | ((v &0x0000FFFF0000FFFF) <<16);
// swap 4-byte long pairs
v = (v >>32) | (v <<32);
}
functionreverse32(uint32 input) internalpurereturns (uint32 v) {
v = input;
// swap bytes
v = ((v &0xFF00FF00) >>8) | ((v &0x00FF00FF) <<8);
// swap 2-byte long pairs
v = (v >>16) | (v <<16);
}
functionreverse16(uint16 input) internalpurereturns (uint16 v) {
v = input;
// swap bytes
v = (v >>8) | (v <<8);
}
functionencodeU256(uint256 input) internalpurereturns (bytes32) {
returnbytes32(reverse256(input));
}
functionencodeU128(uint128 input) internalpurereturns (bytes16) {
returnbytes16(reverse128(input));
}
functionencodeU64(uint64 input) internalpurereturns (bytes8) {
returnbytes8(reverse64(input));
}
functionencodeU32(uint32 input) internalpurereturns (bytes4) {
returnbytes4(reverse32(input));
}
functionencodeU16(uint16 input) internalpurereturns (bytes2) {
returnbytes2(reverse16(input));
}
functionencodeU8(uint8 input) internalpurereturns (bytes1) {
returnbytes1(input);
}
// Supports compact encoding of integers in [0, uint32.MAX]functionencodeCompactU32(uint32 value) internalpurereturns (bytesmemory) {
if (value <=2**6-1) {
// add single byte flagreturnabi.encodePacked(uint8(value <<2));
} elseif (value <=2**14-1) {
// add two byte flag and create little endian encodingreturnabi.encodePacked(ScaleCodec.reverse16(uint16(((value <<2) +1))));
} elseif (value <=2**30-1) {
// add four byte flag and create little endian encodingreturnabi.encodePacked(ScaleCodec.reverse32(uint32((value <<2)) +2));
} else {
returnabi.encodePacked(uint8(3), ScaleCodec.reverse32(value));
}
}
functioncheckedEncodeCompactU32(uint256 value) internalpurereturns (bytesmemory) {
if (value >type(uint32).max) {
revert UnsupportedCompactEncoding();
}
return encodeCompactU32(uint32(value));
}
}
Contract Source Code
File 8 of 11: SignedMath.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)pragmasolidity ^0.8.0;/**
* @dev Standard signed math utilities missing in the Solidity language.
*/librarySignedMath{
/**
* @dev Returns the largest of two signed numbers.
*/functionmax(int256 a, int256 b) internalpurereturns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/functionmin(int256 a, int256 b) internalpurereturns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/functionaverage(int256 a, int256 b) internalpurereturns (int256) {
// Formula from the book "Hacker's Delight"int256 x = (a & b) + ((a ^ b) >>1);
return x + (int256(uint256(x) >>255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/functionabs(int256 n) internalpurereturns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`returnuint256(n >=0 ? n : -n);
}
}
}
Contract Source Code
File 9 of 11: Strings.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)pragmasolidity ^0.8.0;import"./math/Math.sol";
import"./math/SignedMath.sol";
/**
* @dev String operations.
*/libraryStrings{
bytes16privateconstant _SYMBOLS ="0123456789abcdef";
uint8privateconstant _ADDRESS_LENGTH =20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/functiontoString(uint256 value) internalpurereturns (stringmemory) {
unchecked {
uint256 length = Math.log10(value) +1;
stringmemory buffer =newstring(length);
uint256 ptr;
/// @solidity memory-safe-assemblyassembly {
ptr :=add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assemblyassembly {
mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
}
value /=10;
if (value ==0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/functiontoString(int256 value) internalpurereturns (stringmemory) {
returnstring(abi.encodePacked(value <0 ? "-" : "", toString(SignedMath.abs(value))));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/functiontoHexString(uint256 value) internalpurereturns (stringmemory) {
unchecked {
return toHexString(value, Math.log256(value) +1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/functiontoHexString(uint256 value, uint256 length) internalpurereturns (stringmemory) {
bytesmemory buffer =newbytes(2* length +2);
buffer[0] ="0";
buffer[1] ="x";
for (uint256 i =2* length +1; i >1; --i) {
buffer[i] = _SYMBOLS[value &0xf];
value >>=4;
}
require(value ==0, "Strings: hex length insufficient");
returnstring(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/functiontoHexString(address addr) internalpurereturns (stringmemory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
/**
* @dev Returns true if the two strings are equal.
*/functionequal(stringmemory a, stringmemory b) internalpurereturns (bool) {
returnkeccak256(bytes(a)) ==keccak256(bytes(b));
}
}
Contract Source Code
File 10 of 11: SubstrateMerkleProof.sol
// SPDX-License-Identifier: Apache-2.0// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>pragmasolidity 0.8.25;// Used to verify merkle proofs generated by https://github.com/paritytech/substrate/tree/master/utils/binary-merkle-treelibrarySubstrateMerkleProof{
/**
* @notice Verify that a specific leaf element is part of the Merkle Tree at a specific position in the tree
*
* The tree would have been constructed using
* https://paritytech.github.io/substrate/master/binary_merkle_tree/fn.merkle_root.html
*
* This implementation adapted from
* https://paritytech.github.io/substrate/master/binary_merkle_tree/fn.verify_proof.html
*
* @param root the root of the merkle tree
* @param leaf the leaf which needs to be proven
* @param position the position of the leaf, index starting with 0
* @param width the width or number of leaves in the tree
* @param proof the array of proofs to help verify the leaf's membership, ordered from leaf to root
* @return a boolean value representing the success or failure of the operation
*/functionverify(bytes32 root, bytes32 leaf, uint256 position, uint256 width, bytes32[] calldata proof)
internalpurereturns (bool)
{
if (position >= width) {
returnfalse;
}
return root == computeRoot(leaf, position, width, proof);
}
functioncomputeRoot(bytes32 leaf, uint256 position, uint256 width, bytes32[] calldata proof)
internalpurereturns (bytes32)
{
bytes32 node = leaf;
unchecked {
for (uint256 i =0; i < proof.length; i++) {
if (position &1==1|| position +1== width) {
node = efficientHash(proof[i], node);
} else {
node = efficientHash(node, proof[i]);
}
position = position >>1;
width = ((width -1) >>1) +1;
}
return node;
}
}
functionefficientHash(bytes32 a, bytes32 b) internalpurereturns (bytes32 value) {
/// @solidity memory-safe-assemblyassembly {
mstore(0x00, a)
mstore(0x20, b)
value :=keccak256(0x00, 0x40)
}
}
}
Contract Source Code
File 11 of 11: Uint16Array.sol
// SPDX-License-Identifier: Apache-2.0// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>pragmasolidity 0.8.25;/**
* @title A utility library for 16 bit counters packed in 256 bit array.
* @dev The BeefyClient needs to store a count of how many times a validators signature is used. In solidity
* a uint16 would take up as much space as a uin256 in storage, making storing counters for 1000 validators
* expensive in terms of gas. The BeefyClient only needs 16 bits per counter. This library allows us to pack
* 16 uint16 into a single uint256 and save 16x storage.
*
* Layout of 32 counters (2 uint256)
* We store all counts in a single large uint256 array and convert from index from the logical uint16 array
* to the physical uint256 array.
*
* 0 1 2
* uint256[] |-- -- -- -- -- -- -- -- -- -- -- -- YY -- -- --|-- -- -- -- -- -- XX -- -- -- -- -- -- -- -- --|
* uint16[] |--|--|--|--|--|--|--|--|--|--|--|--|YY|--|--|--|--|--|--|--|--|--|XX|--|--|--|--|--|--|--|--|--|
* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
*
* Logical Index Layout
* We use the first 4
* |-------...---------|----|
* 256 4 0
* ^index ^bit-index
*
* In the above table counter YY is at logical index 12 in the uint16 array. It will convert to a physical
* index of 0 in the physical uint256 array and then to bit-index of 192 to 207 of that uint256. In the
* above table counter XX is at logical index 22. It will convert to a physical index of 1 in the array and
* then to bit-index 96 to 111 of uint256[1].
*/using {get, set} forUint16Arrayglobal;
errorIndexOutOfBounds();
/**
* @dev stores the backing array and the length.
*/structUint16Array {
uint256[] data;
uint256 length;
}
/**
* @dev Creates a new counter which can store at least `length` counters.
* @param length The amount of counters.
*/functioncreateUint16Array(uint256 length) purereturns (Uint16Array memory) {
// create space for `length` elements and round up if needed.uint256 bufferLength = length /16+ (length %16==0 ? 0 : 1);
return Uint16Array({data: newuint256[](bufferLength), length: length});
}
/**
* @dev Gets the counter at the logical index
* @param self The array.
* @param index The logical index.
*/functionget(Uint16Array storageself, uint256 index) viewreturns (uint16) {
if (index >=self.length) {
revert IndexOutOfBounds();
}
// Right-shift the index by 4. This truncates the first 4 bits (bit-index) leaving us with the index// into the array.uint256 element = index >>4;
// Mask out the first 4 bits of the logical index to give us the bit-index.uint8 inside =uint8(index) &0x0F;
// find the element in the array, shift until its bit index and mask to only take the first 16 bits.returnuint16((self.data[element] >> (16* inside)) &0xFFFF);
}
/**
* @dev Sets the counter at the logical index.
* @param self The array.
* @param index The logical index of the counter in the array.
* @param value The value to set the counter to.
*/functionset(Uint16Array storageself, uint256 index, uint16 value) {
if (index >=self.length) {
revert IndexOutOfBounds();
}
// Right-shift the index by 4. This truncates the first 4 bits (bit-index) leaving us with the index// into the array.uint256 element = index >>4;
// Mask out the first 4 bytes of the logical index to give us the bit-index.uint8 inside =uint8(index) &0x0F;
// Create a zero mask which will clear the existing value at the bit-index.uint256 zero =~(uint256(0xFFFF) << (16* inside));
// Shift the value to the bit index.uint256 shiftedValue =uint256(value) << (16* inside);
// Take the element, apply the zero mask to clear the existing value, and then apply the shifted value with bitwise or.self.data[element] =self.data[element] & zero | shiftedValue;
}