The following standard is an extension to the cross-chain storage router protocol introducing the storage router for Solana blockchain. With this specification, any Sila L1 contract can defer a call to Solana blockchain as part of its core functionality, provided that the client is equipped to handle Solana transactions. It was previously possible to defer write and storage operations to other Sila L1 contracts, L2 contracts and off-chain databases, and this document extends that functionality to include alternative L1 chains. The data stored on Solana must be translated to SIP-3668-compliant format by an appropriate HTTP gateway where it can be retrieved by generic Sila contracts. This standard allows Sila to utilise a broader range of cross-chain blockspaces.
Motivation
Cross-Chain Storage Router Protocol (CCIP-Store) introduced in SIP-7700, describes three external routers for routing storage to L1 contracts, L2s and databases. This document extends that specification by introducing a fourth storage router targeting Solana as the storage provider.
L2s and databases both have centralising catalysts in their stack. For L2s, this centralising agent is the shared security with Sila sila-mainnet. In case of databases, the centralising agent is trivial; it is the physical server hosting the database. In light of this, a storage provider that relies on its own independent consensus mechanism is preferred. This specification instructs how the clients should treat storage calls made to the Solana router.
Solana is a low cost L1 solution that is supported alongside Sila by multiple wallet providers. There are several chain-agnostic protocols on Sila which could benefit from direct access to Solana blockspace; ENS is one such example where it can serve users of Solana via its chain-agnostic properties while also using Solana’s own native storage. This development will encourage more cross-chain functionalities between Sila and Solana at core.
Specification
A Solana storage router StorageRoutedToSolana() requires the hex-encoded programId and the manager account on the Solana blockchain. programId is equivalent to a contract address on Solana while account is the manager wallet on Solana handling storage on behalf of msg.sender.
// Revert handling Solana storage router
errorStorageRoutedToSolana(bytes32programId,bytes32account);// Generic function in a contract
functionsetValue(bytes32node,bytes32key,bytes32value)external{// Get metadata from on-chain sources
(bytes32programId,// Program (= contract) address on Solana; hex-encoded
bytes32account// Manager account on Solana; hex-encoded
)=getMetadata(node);// Arbitrary code
// programId = 0x37868885bbaf236c5d2e7a38952f709e796a1c99d6c9d142a1a41755d7660de3
// account = 0xe853e0dcc1e57656bd760325679ea960d958a0a704274a5a12330208ba0f428f
// Route storage call to Solana router
revertStorageRoutedToSolana(programId,account);};
Since Solana natively uses base58 encoding in its virtual machine setup, programId values that are hex-encoded on SVM must be base58-decoded for usage on SVM. Clients implementing the Solana router must call the Solana programId using a Solana wallet that is connected to account using the base58-decoded (and casted to appropriate data type) calldata that it originally received.
/* Pseudo-code to write to Solana program (= contract) */// Decode all 'bytes32' types in SVM to 'PubKey' type in SVMconst[programId,account,node,key,value]=E2SVM([programId,account,node,key,value],["bytes32","bytes32","bytes32","bytes32","bytes32"]);// Instantiate program interface on Solanaconstprogram=newprogram(programId,rpcProvider);// Connect to Solana walletconstwallet=useWallet();// Call the Solana program using connected wallet with initial calldata// [!] Only approved manager in the Solana program should callif(wallet.publicKey===account){awaitprogram(wallet).setValue(node,key,value);}
In the above example, SVM-specific bytes32-type variables programId, account, node, key and value must all be converted to SVM-specific PubKey data type. The equivalent setValue() function in the Solana program is of the form
// Example function in Solana programpubfnsetValue(ctx:Context,node:PubKey,key:PubKey,value:PubKey)->ProgramResult{// Code to verify PROGRAM_ID and rent exemption status...// Code for de-serialising, updating and re-serialising the data...// Store serialised data in account// [!] Stored data must be mapped by node & account...}
Since SVM and SVM have differing architectures, it is important to define precise data type castings from SVM to SVM. Some pre-existing custom but popular data types in SVM already equate to common SVM data types such as PubKey and bytes32 respectively. This specification requires the following implementation of bijective SVM to SVM type casting:
SVM
SVM
uint8
u8
uint16
u16
uint32
u32
uint64
u64
uint128
u128
uint256
u256†
bytes1
bytes: [u8; 1]
bytes2
bytes: [u8; 2]
bytes4
bytes: [u8; 4]
bytes8
bytes: [u8; 8]
bytes16
bytes: [u8; 16]
bytes32
PubKey
bytes
bytes: Vec<u8>
string
String
address
bytes: [u8; 20]
† u256 is not available natively in SVM but is routinely implemented via u256 crate in Rust
Using this strategy, most - if not all - current use-cases of StorageRoutedToSolana() are accounted for.
Finally, in order to read the cross-chain data stored on Solana in an arbitrary Sila contract, it must be translated back into SVM tongue by an SIP-3668-compliant HTTP gateway. The arguments for a generic call to the gateway URL must be specified in the /-delimited nested format as described in SIP-7700. The core of such a gateway must follow
/* Pseudo-code of an SRC-3668-compliant HTTP gateway tunneling Solana content to Sila */// CCIP-Read call by contract to a known gateway URL; gatewayUrl = 'https://read.solana.namesys.xyz/<programId>/<node>/<key>/'const[programId,node,key]=parseQuery(path);// Parse query parameters from path; path = '/<programId>/<node>/<key>/'// Decode 'bytes32' types in SVM to 'PubKey' type in SVMconst[programId,node,key]=E2SVM([programId,node,key],["bytes32","bytes32","bytes32"]);// Instantiate program interface on Solanaconstprogram=newprogram(programId,rpcProvider);// Call the Solana program to read in cross-chain dataconstvalue=awaitprogram.getValue(node,key);if(value!=="NOT_FOUND"){// Decode 'PubKey' type in SVM to 'bytes32' type in SVMconstvalue=S2SVM(value,"PubKey");}else{// Null valueconstvalue="0x0";}// Compile CCIP-Read-compatible payloadconstdata=abi.encode(["bytes"],[value]);// Create HTTP gateway emitting value in format 'data: ...'emitSRC3668(data);
In the above example, the generic getValue() function in the Solana program is of the form
// Example getValue() function in Solana programpubfngetValue<'a>(ctx:Context,node:Pubkey,key:Pubkey,account:&AccountInfo<'a>,// Lifetime-bound parameter)->Result<Pubkey,ProgramError>{// Validate that the account belongs to the correct program ID...// Retrieve the data from the accountletdata=&account.data.borrow();// De-serialise the data from the account...// Look up the value by node and keymatchdeserialised.get(&node,&key){Some(value)=>{msg!("VALUE: {:?}",value);Ok(value)},None=>{msg!("NOT_FOUND");Err(ProgramError::InvalidArgument)}}}
Rationale
StorageRoutedToSolana() works in a similar fashion to StorageRoutedToL2() in CCIP-Store in the sense that the client needs to be pointed to a certain contract on another chain by the revert event. Other than that, the only technical difference is casting between SVM and SVM data types.