added some sending messages docs

This commit is contained in:
spacemandev 2022-05-23 11:10:53 +02:00
parent c99e96c28a
commit 556e1231eb
32 changed files with 3308 additions and 0 deletions

View File

@ -0,0 +1,7 @@
.anchor
.DS_Store
target
**/*.rs.bk
node_modules
test-ledger

View File

@ -0,0 +1,8 @@
.anchor
.DS_Store
target
node_modules
dist
build
test-ledger

View File

@ -0,0 +1,14 @@
[features]
seeds = false
[programs.localnet]
solana = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
[registry]
url = "https://anchor.projectserum.com"
[provider]
cluster = "localnet"
wallet = "/Users/spacemandev/.config/solana/id.json"
[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

1335
projects/messenger/chains/solana/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
[workspace]
members = [
"programs/*"
]

View File

@ -0,0 +1,12 @@
// Migrations are an early feature. Currently, they're nothing more than this
// single deploy script that's invoked from the CLI, injecting a provider
// configured from the workspace's Anchor.toml.
const anchor = require("@project-serum/anchor");
module.exports = async function (provider) {
// Configure client to use the provider.
anchor.setProvider(provider);
// Add your deploy script here.
};

View File

@ -0,0 +1,19 @@
{
"scripts": {
"lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
"lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
},
"dependencies": {
"@project-serum/anchor": "^0.24.2"
},
"devDependencies": {
"chai": "^4.3.4",
"mocha": "^9.0.3",
"ts-mocha": "^8.0.0",
"@types/bn.js": "^5.1.0",
"@types/chai": "^4.3.0",
"@types/mocha": "^9.0.0",
"typescript": "^4.3.5",
"prettier": "^2.6.2"
}
}

View File

@ -0,0 +1,26 @@
[package]
name = "messenger"
version = "0.1.0"
description = "Simple messenger xdapp"
edition = "2021"
[lib]
crate-type = ["cdylib", "lib"]
name = "solana"
[features]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
cpi = ["no-entrypoint"]
default = []
[profile.release]
overflow-checks = true
[dependencies]
anchor-lang = "0.24.2"
sha3 = "0.10.1"
byteorder = "1.4.3"
borsh = "0.9.3"
hex = "0.4.3"

View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -0,0 +1 @@
pub const CORE_BRIDGE_ADDRESS: &str = "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o";

View File

@ -0,0 +1,146 @@
use anchor_lang::prelude::*;
use crate::constants::*;
use crate::state::*;
use std::str::FromStr;
use anchor_lang::solana_program::sysvar::{rent, clock};
use crate::wormhole::*;
use hex::decode;
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
seeds=[b"config".as_ref()],
payer=owner,
bump,
space=8+32+32+1024
)]
pub config: Account<'info, Config>,
#[account(mut)]
pub owner: Signer<'info>,
pub system_program: Program<'info, System>
}
#[derive(Accounts)]
#[instruction(chain_id:u16, emitter_addr:String)]
pub struct RegisterChain<'info> {
#[account(mut)]
pub owner: Signer<'info>,
pub system_program: Program<'info, System>,
#[account(
constraint = config.owner == owner.key()
)]
pub config: Account<'info, Config>,
#[account(
init,
seeds=[b"EmitterAddress".as_ref(), chain_id.to_be_bytes().as_ref()],
payer=owner,
bump,
space=8+2+256
)]
pub emitter_acc: Account<'info, EmitterAddrAccount>,
}
#[derive(Accounts)]
pub struct SendMsg<'info>{
#[account(
constraint = core_bridge.key() == Pubkey::from_str(CORE_BRIDGE_ADDRESS).unwrap()
)]
/// CHECK: If someone passes in the wrong account, Guardians won't read the message
pub core_bridge: AccountInfo<'info>,
#[account(
seeds = [
b"Bridge".as_ref()
],
bump,
seeds::program = Pubkey::from_str(CORE_BRIDGE_ADDRESS).unwrap(),
mut
)]
/// CHECK: If someone passes in the wrong account, Guardians won't read the message
pub wormhole_config: AccountInfo<'info>,
#[account(
seeds = [
b"fee_collector".as_ref()
],
bump,
seeds::program = Pubkey::from_str(CORE_BRIDGE_ADDRESS).unwrap(),
mut
)]
/// CHECK: If someone passes in the wrong account, Guardians won't read the message
pub wormhole_fee_collector: AccountInfo<'info>,
#[account(
seeds = [
b"emitter".as_ref(),
],
bump,
mut
)]
/// CHECK: If someone passes in the wrong account, Guardians won't read the message
pub wormhole_derived_emitter: AccountInfo<'info>,
#[account(
seeds = [
b"Sequence".as_ref(),
wormhole_derived_emitter.key().to_bytes().as_ref()
],
bump,
seeds::program = Pubkey::from_str(CORE_BRIDGE_ADDRESS).unwrap(),
mut
)]
/// CHECK: If someone passes in the wrong account, Guardians won't read the message
pub wormhole_sequence: AccountInfo<'info>,
#[account(mut)]
pub wormhole_message_key: Signer<'info>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
#[account(
constraint = clock.key() == clock::id()
)]
/// CHECK: The account constraint will make sure it's the right clock var
pub clock: AccountInfo<'info>,
#[account(
constraint = rent.key() == rent::id()
)]
/// CHECK: The account constraint will make sure it's the right rent var
pub rent: AccountInfo<'info>,
#[account(mut)]
pub config: Account<'info, Config>,
}
#[derive(Accounts)]
#[instruction()]
pub struct ConfirmMsg<'info>{
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
#[account(
init,
seeds=[
&decode(&emitter_acc.emitter_addr.as_str()).unwrap()[..],
emitter_acc.chain_id.to_be_bytes().as_ref(),
(PostedMessageData::try_from_slice(&core_bridge_vaa.data.borrow())?.0).sequence.to_be_bytes().as_ref()
],
payer=payer,
bump,
space=8
)]
pub processed_vaa: Account<'info, ProcessedVAA>,
pub emitter_acc: Account<'info, EmitterAddrAccount>,
/// This requires some fancy hashing, so confirm it's derived address in the function itself.
#[account(
constraint = core_bridge_vaa.to_account_info().owner == &Pubkey::from_str(CORE_BRIDGE_ADDRESS).unwrap()
)]
/// CHECK: This account is owned by Core Bridge so we trust it
pub core_bridge_vaa: AccountInfo<'info>,
#[account(mut)]
pub config: Account<'info, Config>,
}
#[derive(Accounts)]
pub struct Debug<'info>{
#[account(
constraint = core_bridge_vaa.to_account_info().owner == &Pubkey::from_str(CORE_BRIDGE_ADDRESS).unwrap()
)]
/// CHECK: This account is owned by Core Bridge so we trust it
pub core_bridge_vaa: AccountInfo<'info>,
}

View File

@ -0,0 +1,10 @@
use anchor_lang::prelude::*;
#[error_code]
pub enum MessengerError {
#[msg("Posted VAA Key Mismatch")]
VAAKeyMismatch,
#[msg("Posted VAA Emitter Chain ID or Address Mismatch")]
VAAEmitterMismatch,
}

View File

@ -0,0 +1,164 @@
use anchor_lang::prelude::*;
use anchor_lang::solana_program::instruction::Instruction;
use anchor_lang::solana_program::system_instruction::transfer;
use anchor_lang::solana_program::borsh::try_from_slice_unchecked;
use sha3::Digest;
use byteorder::{
BigEndian,
WriteBytesExt,
};
use std::io::{
Cursor,
Write,
};
use std::str::FromStr;
use hex::decode;
mod context;
mod constants;
mod state;
mod wormhole;
mod errors;
use wormhole::*;
use context::*;
use constants::*;
use errors::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod solana_project {
use anchor_lang::solana_program::program::invoke_signed;
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.config.owner = ctx.accounts.owner.key();
ctx.accounts.config.nonce = 1;
Ok(())
}
pub fn register_chain(ctx:Context<RegisterChain>, chain_id:u16, emitter_addr:String) -> Result<()> {
ctx.accounts.emitter_acc.chain_id = chain_id;
ctx.accounts.emitter_acc.emitter_addr = emitter_addr;
Ok(())
}
pub fn send_msg(ctx:Context<SendMsg>, msg:String) -> Result<()> {
//Look Up Fee
let bridge_data:BridgeData = try_from_slice_unchecked(&ctx.accounts.wormhole_config.data.borrow_mut())?;
//Send Fee
invoke_signed(
&transfer(
&ctx.accounts.payer.key(),
&ctx.accounts.wormhole_fee_collector.key(),
bridge_data.config.fee
),
&[
ctx.accounts.payer.to_account_info(),
ctx.accounts.wormhole_fee_collector.to_account_info()
],
&[]
)?;
//Send Post Msg Tx
let sendmsg_ix = Instruction {
program_id: ctx.accounts.core_bridge.key(),
accounts: vec![
AccountMeta::new(ctx.accounts.wormhole_config.key(), false),
AccountMeta::new(ctx.accounts.wormhole_message_key.key(), true),
AccountMeta::new_readonly(ctx.accounts.wormhole_derived_emitter.key(), true),
AccountMeta::new(ctx.accounts.wormhole_sequence.key(), false),
AccountMeta::new(ctx.accounts.payer.key(), true),
AccountMeta::new(ctx.accounts.wormhole_fee_collector.key(), false),
AccountMeta::new_readonly(ctx.accounts.clock.key(), false),
AccountMeta::new_readonly(ctx.accounts.rent.key(), false),
AccountMeta::new_readonly(ctx.accounts.system_program.key(), false),
],
data: (
wormhole::Instruction::PostMessage,
PostMessageData {
nonce: ctx.accounts.config.nonce,
payload: msg.as_bytes().try_to_vec()?,
consistency_level: wormhole::ConsistencyLevel::Confirmed,
},
).try_to_vec()?,
};
invoke_signed(
&sendmsg_ix,
&[
ctx.accounts.wormhole_config.to_account_info(),
ctx.accounts.wormhole_message_key.to_account_info(),
ctx.accounts.wormhole_derived_emitter.to_account_info(),
ctx.accounts.wormhole_sequence.to_account_info(),
ctx.accounts.payer.to_account_info(),
ctx.accounts.wormhole_fee_collector.to_account_info(),
ctx.accounts.clock.to_account_info(),
ctx.accounts.rent.to_account_info(),
ctx.accounts.system_program.to_account_info(),
],
&[
&[
&b"emitter".as_ref(),
&[*ctx.bumps.get("wormhole_derived_emitter").unwrap()]
]
]
)?;
ctx.accounts.config.nonce += 1;
Ok(())
}
pub fn confirm_msg(ctx:Context<ConfirmMsg>) -> Result<()> {
//Hash a VAA Extract and derive a VAA Key
let vaa = PostedMessageData::try_from_slice(&ctx.accounts.core_bridge_vaa.data.borrow())?.0;
let serialized_vaa = serialize_vaa(&vaa);
let mut h = sha3::Keccak256::default();
h.write(serialized_vaa.as_slice()).unwrap();
let vaa_hash: [u8; 32] = h.finalize().into();
let (vaa_key, _) = Pubkey::find_program_address(&[
b"PostedVAA",
&vaa_hash
], &Pubkey::from_str(CORE_BRIDGE_ADDRESS).unwrap());
if ctx.accounts.core_bridge_vaa.key() != vaa_key {
return err!(MessengerError::VAAKeyMismatch);
}
// Already checked that the SignedVaa is owned by core bridge in account constraint logic
//Check that the emitter chain and address match up with the vaa
if vaa.emitter_chain != ctx.accounts.emitter_acc.chain_id ||
vaa.emitter_address != &decode(&ctx.accounts.emitter_acc.emitter_addr.as_str()).unwrap()[..] {
return err!(MessengerError::VAAEmitterMismatch)
}
ctx.accounts.config.current_msg = String::from_utf8(vaa.payload).unwrap();
Ok(())
}
pub fn debug(ctx:Context<Debug>) -> Result<()> {
let vaa = PostedMessageData::try_from_slice(&ctx.accounts.core_bridge_vaa.data.borrow())?.0;
msg!("{:?}", vaa);
Ok(())
}
}
// Convert a full VAA structure into the serialization of its unique components, this structure is
// what is hashed and verified by Guardians.
pub fn serialize_vaa(vaa: &MessageData) -> Vec<u8> {
let mut v = Cursor::new(Vec::new());
v.write_u32::<BigEndian>(vaa.vaa_time).unwrap();
v.write_u32::<BigEndian>(vaa.nonce).unwrap();
v.write_u16::<BigEndian>(vaa.emitter_chain.clone() as u16).unwrap();
v.write(&vaa.emitter_address).unwrap();
v.write_u64::<BigEndian>(vaa.sequence).unwrap();
v.write_u8(vaa.consistency_level).unwrap();
v.write(&vaa.payload).unwrap();
v.into_inner()
}

View File

@ -0,0 +1,20 @@
use anchor_lang::prelude::*;
#[account]
#[derive(Default)]
pub struct Config{
pub owner: Pubkey,
pub nonce: u32,
pub current_msg: String
}
#[account]
#[derive(Default)]
pub struct EmitterAddrAccount{
pub chain_id: u16,
pub emitter_addr: String
}
//Empty account, we just need to check that it *exists*
#[account]
pub struct ProcessedVAA {}

View File

@ -0,0 +1,111 @@
use anchor_lang::prelude::*;
use borsh::{BorshDeserialize, BorshSerialize};
use std::{
io::Write,
};
#[derive(AnchorDeserialize, AnchorSerialize)]
pub struct PostMessageData {
/// Unique nonce for this message
pub nonce: u32,
/// Message payload
pub payload: Vec<u8>,
/// Commitment Level required for an attestation to be produced
pub consistency_level: ConsistencyLevel,
}
#[derive(AnchorDeserialize, AnchorSerialize)]
pub enum ConsistencyLevel {
Confirmed,
Finalized
}
#[derive(AnchorDeserialize, AnchorSerialize)]
pub enum Instruction{
Initialize,
PostMessage,
PostVAA,
SetFees,
TransferFees,
UpgradeContract,
UpgradeGuardianSet,
VerifySignatures,
}
#[derive(AnchorDeserialize, AnchorSerialize, Clone)]
pub struct BridgeData {
/// The current guardian set index, used to decide which signature sets to accept.
pub guardian_set_index: u32,
/// Lamports in the collection account
pub last_lamports: u64,
/// Bridge configuration, which is set once upon initialization.
pub config: BridgeConfig,
}
#[derive(AnchorDeserialize, AnchorSerialize, Clone)]
pub struct BridgeConfig {
/// Period for how long a guardian set is valid after it has been replaced by a new one. This
/// guarantees that VAAs issued by that set can still be submitted for a certain period. In
/// this period we still trust the old guardian set.
pub guardian_set_expiration_time: u32,
/// Amount of lamports that needs to be paid to the protocol to post a message
pub fee: u64,
}
#[derive(Debug)]
#[repr(transparent)]
pub struct PostedMessageData(pub MessageData);
#[derive(Debug, Default, BorshDeserialize, BorshSerialize)]
pub struct MessageData {
/// Header of the posted VAA
pub vaa_version: u8,
/// Level of consistency requested by the emitter
pub consistency_level: u8,
/// Time the vaa was submitted
pub vaa_time: u32,
/// Account where signatures are stored
pub vaa_signature_account: Pubkey,
/// Time the posted message was created
pub submission_time: u32,
/// Unique nonce for this message
pub nonce: u32,
/// Sequence number of this message
pub sequence: u64,
/// Emitter of the message
pub emitter_chain: u16,
/// Emitter of the message
pub emitter_address: [u8; 32],
/// Message payload
pub payload: Vec<u8>,
}
impl AnchorSerialize for PostedMessageData {
fn serialize<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
writer.write(b"msg")?;
BorshSerialize::serialize(&self.0, writer)
}
}
impl AnchorDeserialize for PostedMessageData {
fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
*buf = &buf[3..];
Ok(PostedMessageData(
<MessageData as BorshDeserialize>::deserialize(buf)?,
))
}
}

View File

@ -0,0 +1,16 @@
import * as anchor from "@project-serum/anchor";
import { Program } from "@project-serum/anchor";
import { Solana } from "../target/types/solana";
describe("solana", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Solana as Program<Solana>;
it("Is initialized!", async () => {
// Add your test here.
const tx = await program.methods.initialize().rpc();
console.log("Your transaction signature", tx);
});
});

View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"types": ["mocha", "chai"],
"typeRoots": ["./node_modules/@types"],
"lib": ["es2015"],
"module": "commonjs",
"target": "es6",
"esModuleInterop": true
}
}

File diff suppressed because it is too large Load Diff

1
projects/xdapp-starter/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules/

View File

View File

View File

@ -0,0 +1,18 @@
{
"name": "xdapp-starter",
"version": "1.0.0",
"description": "A simple template for getting started with xDapps.",
"main": "starter.js",
"scripts": {
"test": "sh test.sh"
},
"keywords": [],
"author": "",
"license": "MIT",
"workspaces": [],
"type": "module",
"dependencies": {
"@certusone/wormhole-sdk": "^0.3.3"
}
}

View File

View File

View File

View File

@ -0,0 +1,26 @@
# Registering Emitters: EVM
Specifying applications that our EVM application is allowed to listen to is a fairly simple process; We can create a mapping of chainId to bytes32 addresses of the relevant contracts on other chains. The chainId used here is the *wormhole* chainId, and the address used here is the address in *bytes*.
```solidity
contract Messenger {
mapping(uint16 => bytes32) _applicationContracts;
address owner;
constructor(){
owner = msg.sender;
}
/**
Registers it's sibling applications on other chains as the only ones that can send this instance messages
*/
function registerApplicationContracts(uint16 chainId, bytes32 applicationAddr) public {
require(msg.sender == owner, "Only owner can register new chains!");
_applicationContracts[chainId] = applicationAddr;
}
}
```
If you have more than one address per chainId that you want to listen to, consider making the mapping into bytes32[].

View File

@ -0,0 +1,7 @@
# Registering Emitters
This is not strictly required, but when writing xDapps, you want to listen for messages from *specific* apps on other chains, otherwise attacks could create fake applications that emit messages that *look* like what we expect, but have fake payloads.
To do this, we register the sending contract's addresses with the receving contracts. Because each VAA has the contract address asked the core bridge to *emit* the VAA, we call the sending contracts *emitters*. Also, the emitters you're listening too, *do not* need to be your own contracts. You might want to listen to the emits of a different xDapp, in which case you'd register it's address in your code.
Then, when receving messages, we can check the VAA being submitted, and make sure that the VAA being submitted came from one of the contracts we were expecting and *from the chain* we were expecting.

View File

@ -0,0 +1 @@
# Relaying Messages

View File

@ -0,0 +1,60 @@
# Sending Messages: EVM
To send messages from EVM, first we have to download the Core Bridge interfaces. We need two interfaces, [IWormhole.sol](https://github.com/certusone/wormhole/raw/dev.v2/ethereum/contracts/interfaces/IWormhole.sol) and [Structs.sol](https://github.com/certusone/wormhole/raw/dev.v2/ethereum/contracts/Structs.sol)
In your xdapp-starter, place those files in
```
- chains/
- evm/
- src/
- Wormhole/
- IWormhole.sol
- Structs.sol
```
also, let's modify the IWormhole.sol file to update the import for Structs.sol.
```solidity
// contracts/Messages.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "./Structs.sol";
..
```
Now, let's create a new contract in our src/ folder `Messenger.sol`. In this contract, we also create a uint32 nonce. You can think of this nonce like a message id, it's just a number that let's the receing contract know if it's already processed a message.
Also we'll set the consistency level here to 1, because we're just testing and want the Gaurdians to sign this VAA as soon as they see it, but if we were deploying to production, we might want to match this level to the deploy'd chain's finality gaurantees.
```solidity
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "./Wormhole/IWormhole.sol";
contract Messenger {
//This is the Tilt Devnet address constant.
//Replace this address with revelant testnet or mainnet address of the chain you're deploying too.
address private wormhole_core_bridge_address = address(0xC89Ce4735882C9F0f0FE26686c53074E09B0D550);
IWormhole core_bridge = IWormhole(wormhole_core_bridge_address);
uint32 nonce = 0;
constructor(){}
function sendMsg(bytes memory str) public returns (uint64 sequence) {
uint8 consistency_level = 1;
sequence = core_bridge.publishMessage(nonce, str, consistency_level);
nonce = nonce+1;
}
}
```

View File

@ -0,0 +1,12 @@
# Overview of Sending Messages
While the specific code varies chain by chain, the basic flow of sending a message requires your contract to interact with the Core Bridge contract deployed on each chain to emit a VAA.
To emit a VAA requires three pieces of information:
1. Nonce (u32)
- The nonce is a random number assigned to each message. This allows the receving contract a way to make sure it doesn't double process messages.
2. Consistency (u8)
- This is the number of blocks for Gaurdians to wait before they sign the message. Higher consistencies mean more security against blockchain reorgs. For example, if this is set too low, and the block you're emitting from reorgs, then it's possible that even though the message was emitted and signed by the Gaurdians and processed on the receving chain, no record of it exists on the emitting chain. If you were sending tokens across, this would allow for double spend attacks.
3. Payload (bytes[])
- This is a payload of raw bytes that you want to emit. It's up to the receiving contract to know how to parse it.

View File

@ -0,0 +1,21 @@
# xDapp Scaffold
To help you get started with cross chain development, we've provided a template project in `projects/xdapp-starter`. All the sample projects will be made using this template, so check them out if you want to get a feel for how the various modules interact with each other.
The template uses npm workspaces to setup a main project with subdirectories for each chain you want to interact with. This allows you to initalize each subdirectory using whatever scaffolding tool you want for each individual chain, and orchestration code in a common directory.
Let's break down what's in the `xdapp-starter` project:
### chains/
- This folder contains the subdirectories for chain specific code. For example, I might use the `anchor` tool to `anchor init solana-project` within the chains/ directory.
### handlers/
The handlers folder contains the js client code to deal with each chain's specific needs. They expose a common API that we can consume in `starter.js` for code cleanliness.
They all take in a context object that's made up of the
### starter.js
This file parses command line args and filters calls to chain management handlers.
### xdapp.config.json
The config file contains all the information about the network rpc nodes, accounts, and other constants used to communicate with contracts deployed to the selected chains.

View File

@ -0,0 +1,3 @@
# What is an xDapp
xDapps are *cross chain decentralized applications*. In short, unlike traditional blockchain programs which are tied to a specific chain, xDapps take advantage of *bridges* like Wormhole to be able to interact with applications from other networks.