Skip to content

Commit

Permalink
Feat(gas-station): improove logging, fix naming, change CLI args, fix…
Browse files Browse the repository at this point in the history
… readme accordingly, add script to deploy test gas-station, create payer and run bridge
  • Loading branch information
gBaGu committed Jan 25, 2023
1 parent 723331b commit d5cd1fe
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 57 deletions.
60 changes: 33 additions & 27 deletions cli/src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use evm_state::{self as evm, FromKey};
use solana_clap_utils::input_parsers::signer_of;
use solana_clap_utils::input_validators::is_valid_signer;
use solana_clap_utils::keypair::{DefaultSigner, SignerIndex};
use solana_client::rpc_response::Response;
use solana_evm_loader_program::{instructions::FeePayerType, scope::evm::gweis_to_lamports};
use solana_remote_wallet::remote_wallet::RemoteWalletManager;

Expand Down Expand Up @@ -83,41 +84,37 @@ impl EvmSubCommands for App<'_, '_> {
SubCommand::with_name("create-gas-station-payer")
.about("Create payer account for gas station program")
.display_order(3)
.arg(
Arg::with_name("payer_account")
.arg(Arg::with_name("payer_account")
.index(1)
.value_name("ACCOUNT_KEYPAIR")
.takes_value(true)
.required(true)
.validator(is_valid_signer)
.help("Keypair of the payer storage account"),
).arg(
Arg::with_name("payer_owner")
.index(2)
.value_name("ACCOUNT_KEYPAIR")
.takes_value(true)
.required(true)
.validator(is_valid_signer)
.help("Keypair of the owner account"),
)
.help("Keypair of the payer storage account"))
.arg(Arg::with_name("gas-station-key")
.index(3)
.index(2)
.takes_value(true)
.required(true)
.value_name("PROGRAM ID")
.help("Public key of gas station program"))
.arg(Arg::with_name("lamports")
.index(4)
.index(3)
.takes_value(true)
.required(true)
.value_name("AMOUNT")
.help("Amount in lamports to transfer to created account"))
.arg(Arg::with_name("filters_path")
.index(5)
.index(4)
.takes_value(true)
.required(true)
.value_name("PATH")
.help("Path to json file with filter to store in payer storage"))
.arg(Arg::with_name("payer_owner")
.index(5)
.value_name("ACCOUNT_KEYPAIR")
.takes_value(true)
.validator(is_valid_signer)
.help("Keypair of the owner account"))
)


Expand Down Expand Up @@ -379,13 +376,20 @@ fn create_gas_station_payer<P: AsRef<Path>>(
let filters: Vec<TxFilter> = serde_json::from_reader(file)
.map_err(|e| custom_error(format!("Unable to decode json: {:?}", e)))?;

let create_owner_ix = system_instruction::create_account(
&cli_pubkey,
&payer_owner_pubkey,
rpc_client.get_minimum_balance_for_rent_exemption(0)?,
0,
&system_program::id(),
);
let mut instructions = vec![];
if let Response { value: None, .. } =
rpc_client.get_account_with_commitment(&payer_owner_pubkey, CommitmentConfig::default())?
{
let create_owner_ix = system_instruction::create_account(
&cli_pubkey,
&payer_owner_pubkey,
rpc_client.get_minimum_balance_for_rent_exemption(0)?,
0,
&system_program::id(),
);
info!("Add instruction to create owner: {}", payer_owner_pubkey);
instructions.push(create_owner_ix);
}
let state_size = evm_gas_station::get_state_size(&filters);
let minimum_balance = rpc_client.get_minimum_balance_for_rent_exemption(state_size)?;
let create_storage_ix = evm_gas_station::create_storage_account(
Expand All @@ -395,6 +399,8 @@ fn create_gas_station_payer<P: AsRef<Path>>(
&filters,
&gas_station_key,
);
info!("Add instruction to create storage: {}", payer_storage_pubkey);
instructions.push(create_storage_ix);
let register_payer_ix = evm_gas_station::register_payer(
gas_station_key,
cli_pubkey,
Expand All @@ -403,10 +409,10 @@ fn create_gas_station_payer<P: AsRef<Path>>(
transfer_amount,
filters,
);
let message = Message::new(
&[create_owner_ix, create_storage_ix, register_payer_ix],
Some(&cli_pubkey),
);
info!("Add instruction to register payer: gas-station={}, signer={}, storage={}, owner={}",
gas_station_key, cli_pubkey, payer_storage_pubkey, payer_owner_pubkey);
instructions.push(register_payer_ix);
let message = Message::new(&instructions, Some(&cli_pubkey));
let latest_blockhash = rpc_client.get_latest_blockhash()?;

let mut tx = Transaction::new_unsigned(message);
Expand Down Expand Up @@ -591,7 +597,7 @@ pub fn parse_evm_subcommand(
signers.push(signer);
2
})
.unwrap();
.unwrap_or(0);

let gas_station_key = value_t_or_exit!(matches, "gas-station-key", Pubkey);
let lamports = value_t_or_exit!(matches, "lamports", u64);
Expand Down
71 changes: 45 additions & 26 deletions evm-utils/evm-bridge/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,9 @@ impl EvmBridge {
self.redirect_to_proxy_filters = redirect_to_proxy_filters
.into_iter()
.map(|mut item| {
let (payer_key, _) = Pubkey::find_program_address(&[item.owner.as_ref()], &gas_station_program_id);
item.payer = payer_key;
let (payer_key, _) =
Pubkey::find_program_address(&[item.owner.as_ref()], &gas_station_program_id);
item.gas_station_payer = payer_key;
item
})
.collect();
Expand Down Expand Up @@ -1012,39 +1013,50 @@ pub(crate) fn from_client_error(client_error: ClientError) -> evm_rpc::Error {

#[derive(thiserror::Error, Debug, PartialEq)]
pub enum ParseEvmContractToPayerKeysError {
#[error("Evm contract string is invalid")]
InvalidEvmContract,
#[error("Input format is invalid, provide string of the next format: \"<evm contract address>:<payer pubkey>\"")]
InvalidFormat,
#[error("Invalid pubkey")]
InvalidPubkey,
#[error("Evm contract string is invalid: `{0}`")]
InvalidEvmContract(String),
#[error("Input format is invalid: `{0}`, provide string of the next format: \"<evm contract address>:<owner pubkey>:<storage pubkey>\"")]
InvalidFormat(String),
#[error("Invalid pubkey: `{0}`")]
InvalidPubkey(String),
}

#[derive(Debug)]
struct EvmContractToPayerKeys {
contract: Address,
owner: Pubkey,
payer: Pubkey,
gas_station_payer: Pubkey,
storage_acc: Pubkey,
}

impl FromStr for EvmContractToPayerKeys {
type Err = ParseEvmContractToPayerKeysError;

fn from_str(s: &str) -> StdResult<Self, Self::Err> {
let (addr, keys) = s
.split_once(':')
.ok_or(ParseEvmContractToPayerKeysError::InvalidFormat)?;
let (key1, key2) = keys
.split_once(':')
.ok_or(ParseEvmContractToPayerKeysError::InvalidFormat)?;
let contract = Address::from_str(addr)
.map_err(|_| ParseEvmContractToPayerKeysError::InvalidEvmContract)?;
let owner = Pubkey::from_str(key1)
.map_err(|_| ParseEvmContractToPayerKeysError::InvalidPubkey)?;
let storage_acc = Pubkey::from_str(key2)
.map_err(|_| ParseEvmContractToPayerKeysError::InvalidPubkey)?;
Ok(Self { contract, owner, payer: Pubkey::default(), storage_acc })
let (contract, keys) =
s.split_once(':')
.ok_or(ParseEvmContractToPayerKeysError::InvalidFormat(
s.to_string(),
))?;
let (owner, storage_acc) =
keys.split_once(':')
.ok_or(ParseEvmContractToPayerKeysError::InvalidFormat(
s.to_string(),
))?;
let contract = Address::from_str(contract).map_err(|_| {
ParseEvmContractToPayerKeysError::InvalidEvmContract(contract.to_string())
})?;
let owner = Pubkey::from_str(owner)
.map_err(|_| ParseEvmContractToPayerKeysError::InvalidPubkey(owner.to_string()))?;
let storage_acc = Pubkey::from_str(storage_acc).map_err(|_| {
ParseEvmContractToPayerKeysError::InvalidPubkey(storage_acc.to_string())
})?;
Ok(Self {
contract,
owner,
gas_station_payer: Pubkey::default(),
storage_acc,
})
}
}

Expand Down Expand Up @@ -1078,7 +1090,7 @@ struct Args {
#[structopt(long = "gas-station")]
gas_station_program_id: Option<Pubkey>,
#[structopt(long = "redirect-to-proxy")]
redirect_contracts_to_proxy: Option<Vec<EvmContractToPayerKeys>>,
redirect_contracts_to_proxy: Vec<EvmContractToPayerKeys>,
}

impl Args {
Expand Down Expand Up @@ -1173,9 +1185,16 @@ async fn main(args: Args) -> StdResult<(), Box<dyn std::error::Error>> {
min_gas_price,
);
meta.set_whitelist(whitelist);
if let Some(redirect_list) = args.redirect_contracts_to_proxy {
let gas_station_program_id = args.gas_station_program_id.expect("gas-station program id is missing");
meta.set_redirect_to_proxy_filters(gas_station_program_id, redirect_list);
if !args.redirect_contracts_to_proxy.is_empty() {
let gas_station_program_id = args
.gas_station_program_id
.expect("gas-station program id is missing");
info!("Redirecting evm transaction to gas station: {}, filters: {:?}",
gas_station_program_id, args.redirect_contracts_to_proxy);
meta.set_redirect_to_proxy_filters(
gas_station_program_id,
args.redirect_contracts_to_proxy,
);
}
let meta = Arc::new(meta);

Expand Down
4 changes: 3 additions & 1 deletion evm-utils/evm-bridge/src/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,8 @@ async fn process_tx(
.iter()
.find(|val| matches!(tx.action, TransactionAction::Call(addr) if addr == val.contract))
{
info!("Bridge filters matched. Sending transaction {} to gas station {}",
tx.tx_id_hash(), bridge.gas_station_program_id.unwrap());
let tx = evm_gas_station::evm_types::Transaction {
nonce: tx.nonce,
gas_price: tx.gas_price,
Expand All @@ -622,7 +624,7 @@ async fn process_tx(
bridge.gas_station_program_id.unwrap(),
bridge.key.pubkey(),
val.storage_acc,
val.payer,
val.gas_station_payer,
)]
} else {
bridge.make_send_tx_instructions(&tx, &meta_keys)
Expand Down
10 changes: 8 additions & 2 deletions evm-utils/programs/gas_station/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,29 @@ Deploy program:

On testnet you can use deployed gas station program: **99GZJejoz8L521qEgjDbQ4ucheCsw17SL8FWCYXmDCH7**

As a test evm contract you can use this one: **0x507AAe92E8a024feDCbB521d11EC406eEfB4488F**.
It has one method that accepts uint256. Valid input data will be *0x6057361d* as a method selector following by encoded uint256
Example input data (passing 1 as an argument): *0x6057361d0000000000000000000000000000000000000000000000000000000000000001*

### Register payer

Given you have successfully deployed gas station program your next step is
to register payer account that will be paying for incoming evm transactions
if they meet its filter conditions:

``velas evm create-gas-station-payer -u <public rpc> -k <signer keypair>
<storage keypair> <owner keypair> <program id> <lamports> <filters file>``
<storage keypair> <program id> <lamports> <filters file> [<owner keypair>]``

Where:

- *signer keypair* - path to keypair of an account that will pay for this transaction
- *storage keypair* - path to keypair of payer storage account that will hold filters data
- *owner keypair* - keypair of payer owner account that will have write access to payer storage (for a future use)
- *program id* - gas station program id
- *lamports* - amount of tokens (above rent exemption) that will be transferred to gas station pda account to pay for future evm transactions
- *filters file* - path to JSON file with filters to store in payer storage
- *owner keypair* - (**OPTIONAL**) keypair of payer owner account that will have write access to payer storage (for a future use)
Default value: *signer keypair*
*Only one registered payer per owner supported*

Example *filters file*:
```
Expand Down
34 changes: 34 additions & 0 deletions evm-utils/programs/gas_station/quickstart.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/bash

evm_contract=0x507AAe92E8a024feDCbB521d11EC406eEfB4488F;

signer_keypair=$1;
out_dir=$2;
project_root=$3;

gas_station_keypair=$out_dir/gas_station_keypair.json;
payer_info_storage_keypair=$out_dir/payer_storage_keypair.json;

mkdir -p "$out_dir"
velas-keygen new -o "$payer_info_storage_keypair"
velas-keygen new -o "$gas_station_keypair"

owner_key=$(velas -u t -k "$signer_keypair" address)
storage_key=$(velas -u t -k "$payer_info_storage_keypair" address)
gas_station_key=$(velas -u t -k "$gas_station_keypair" address)
echo "Keys used: signer/owner: $owner_key, storage: $storage_key, gas_station: $gas_station_key"

echo Building..
"$project_root"/cargo-build-bpf -- -p evm-gas-station
echo Deploying..
velas program deploy -u t -k "$signer_keypair" --program-id "$gas_station_keypair" "$project_root"/target/deploy/evm_gas_station.so

echo "Registering payer.."
gas_station_filter=$out_dir/gas_station_filter.json
echo "[{ \"InputStartsWith\": [ \"$evm_contract\", [96, 87, 54, 29] ] }]" > "$gas_station_filter"
velas evm create-gas-station-payer -u t -k "$signer_keypair" \
"$payer_info_storage_keypair" "$gas_station_key" 100000 "$gas_station_filter"

echo "Starting bridge.."
RUST_LOG=info evm-bridge "$signer_keypair" https://api.testnet.velas.com 127.0.0.1:8545 111 \
--gas-station "$gas_station_key" --redirect-to-proxy "$evm_contract:$owner_key:$storage_key"
2 changes: 1 addition & 1 deletion evm-utils/programs/gas_station/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ fn process_register_payer(
system_program.clone(),
],
&[&[owner.as_ref(), &[bump_seed]]],
)?;
)?; // TODO: map into something readable
msg!("PDA created: {}", payer_acc);

payer.owner = owner;
Expand Down

0 comments on commit d5cd1fe

Please sign in to comment.