Skip to content

Commit

Permalink
Working Gravity V2 test
Browse files Browse the repository at this point in the history
This patch adds metadata to the test environment tokens, this is
required in order to properly handle the ERC20 deploy event and send
Cosmos tokens to Ethereum.

With that modification to the testing environment we can make a pretty
straightforward test to validate the rest of the functionality of V2,
which works quite well, credit to Jehan's testing.
  • Loading branch information
jkilpatr committed Feb 28, 2021
1 parent 71f7e3e commit ecc64ce
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 50 deletions.
4 changes: 2 additions & 2 deletions orchestrator/cosmos_peggy/src/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,9 +304,9 @@ pub async fn send_to_eth(
.expect("Invalid private key!")
.to_address();
let tx_info = maybe_get_optional_tx_info(our_address, None, None, None, contact).await?;
if amount.denom != fee.denom || !amount.denom.contains("peggy") {
if amount.denom != fee.denom {
return Err(JsonRpcError::BadInput(format!(
"{} {} is an invalid denom set for SendToEth",
"{} {} is an invalid denom set for SendToEth you must pay fees in the same token your sending",
amount.denom, fee.denom,
)));
}
Expand Down
3 changes: 2 additions & 1 deletion orchestrator/test_runner/src/happy_path.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::get_chain_id;
use crate::get_fee;
use crate::get_test_token_name;
use crate::{
Expand Down Expand Up @@ -167,7 +168,7 @@ pub async fn delegate_tokens(delegate_address: &str, amount: &str) {
amount,
"--home=/validator1",
// this is defined in /tests/container-scripts/setup-validator.sh
"--chain-id=peggy-test",
&format!("--chain-id={}", get_chain_id()),
"--keyring-backend=test",
"--yes",
"--from=validator1",
Expand Down
150 changes: 139 additions & 11 deletions orchestrator/test_runner/src/happy_path_v2.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
//! This is the happy path test for Cosmos to Ethereum asset transfers, meaning assets originated on Cosmos

use std::time::{Duration, Instant};

use crate::get_fee;
use crate::get_test_token_name;
use crate::utils::get_user_key;
use crate::utils::send_one_eth;
use crate::{COSMOS_NODE_GRPC, TOTAL_TIMEOUT};
use actix::Arbiter;
use clarity::Address as EthAddress;
use clarity::PrivateKey as EthPrivateKey;
use clarity::{abi::Token, Address as EthAddress};
use clarity::{PrivateKey as EthPrivateKey, Uint256};
use contact::client::Contact;
use deep_space::private_key::PrivateKey as CosmosPrivateKey;
use cosmos_peggy::send::{send_request_batch, send_to_eth};
use deep_space::{coin::Coin, private_key::PrivateKey as CosmosPrivateKey};
use ethereum_peggy::{deploy_erc20::deploy_erc20, utils::get_event_nonce};
use orchestrator::main_loop::orchestrator_main_loop;
use peggy_proto::peggy::query_client::QueryClient as PeggyQueryClient;
use peggy_proto::peggy::{query_client::QueryClient as PeggyQueryClient, QueryDenomToErc20Request};
use tokio::time::delay_for;
use tonic::transport::Channel;
use web30::client::Web3;
Expand All @@ -22,15 +28,19 @@ pub async fn happy_path_test_v2(
peggy_address: EthAddress,
validator_out: bool,
) {
let mut grpc_client = grpc_client;
let starting_event_nonce =
get_event_nonce(peggy_address, keys[0].1.to_public_key().unwrap(), web30)
.await
.unwrap();

let token_to_send_to_eth = "footoken".to_string();

deploy_erc20(
"footoken-a".to_string(),
"footoken-b".to_string(),
"foo".to_string(),
18,
token_to_send_to_eth.clone(),
token_to_send_to_eth.clone(),
token_to_send_to_eth.clone(),
6,
peggy_address,
web30,
Some(TOTAL_TIMEOUT),
Expand Down Expand Up @@ -80,8 +90,126 @@ pub async fn happy_path_test_v2(
}
}

delay_for(TOTAL_TIMEOUT).await;
let start = Instant::now();
// the erc20 representing the cosmos asset on Ethereum
let mut erc20_contract = None;
while Instant::now() - start < TOTAL_TIMEOUT {
let res = grpc_client
.denom_to_erc20(QueryDenomToErc20Request {
denom: token_to_send_to_eth.clone(),
})
.await;
if let Ok(res) = res {
let erc20 = res.into_inner().erc20;
info!(
"Successfully adopted {} token contract of {}",
token_to_send_to_eth, erc20
);
erc20_contract = Some(erc20);
break;
}
delay_for(Duration::from_secs(1)).await;
}
if erc20_contract.is_none() {
panic!(
"Cosmos did not adopt the ERC20 contract for {} it must be invalid in some way",
token_to_send_to_eth
);
}
let erc20_contract: EthAddress = erc20_contract.unwrap().parse().unwrap();

// one foo token
let amount_to_bridge: Uint256 = 1_000_000u64.into();
let send_to_user_coin = Coin {
denom: token_to_send_to_eth.clone(),
amount: amount_to_bridge.clone() + 100u8.into(),
};
let send_to_eth_coin = Coin {
denom: token_to_send_to_eth.clone(),
amount: amount_to_bridge.clone(),
};

let user = get_user_key();
// send the user some footoken
contact
.create_and_send_transaction(
send_to_user_coin.clone(),
get_fee(),
user.cosmos_address,
keys[0].0,
None,
None,
None,
)
.await
.unwrap();

// TODO make sure that Cosmos adopts the contract
// TODO generate batch of footoken to send over to ethereum, check that it gets there
let balances = contact.get_balances(user.cosmos_address).await.unwrap();
let mut found = false;
for coin in balances.result {
if coin.denom == token_to_send_to_eth.clone() {
found = true;
break;
}
}
if !found {
panic!(
"Failed to send {} to the user address",
token_to_send_to_eth
);
}
info!(
"Sent some {} to user address {}",
token_to_send_to_eth, user.cosmos_address
);
// send the user some eth, they only need this to check their
// erc20 balance, so a pretty minor usecase
send_one_eth(user.eth_address, web30).await;
info!("Sent 1 eth to user address {}", user.eth_address);

let res = send_to_eth(
user.cosmos_key,
user.eth_address,
send_to_eth_coin,
get_fee(),
contact,
)
.await
.unwrap();
info!("Send to eth res {:?}", res);
info!(
"Locked up {} {} to send to Cosmos",
amount_to_bridge, token_to_send_to_eth
);

let res = send_request_batch(keys[0].0, token_to_send_to_eth.clone(), get_fee(), contact)
.await
.unwrap();
info!("Batch request res {:?}", res);
info!("Sent batch request to move things along");

info!("Waiting for batch to be signed and relayed to Ethereum");
let start = Instant::now();
while Instant::now() - start < TOTAL_TIMEOUT {
let balance = web30
.get_erc20_balance(erc20_contract, user.eth_address)
.await;
if balance.is_err() {
continue;
}
let balance = balance.unwrap();
if balance == amount_to_bridge {
info!(
"Successfully bridged {} Cosmos asset {} to Ethereum!",
amount_to_bridge, token_to_send_to_eth
);
break;
} else if balance != 0u8.into() {
panic!(
"Expected {} {} but got {} instead",
amount_to_bridge, token_to_send_to_eth, balance
);
}
delay_for(Duration::from_secs(1)).await;
}
}
4 changes: 4 additions & 0 deletions orchestrator/test_runner/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ pub fn get_test_token_name() -> String {
"footoken".to_string()
}

pub fn get_chain_id() -> String {
"peggy-test".to_string()
}

pub fn one_eth() -> Uint256 {
1000000000000000000u128.into()
}
Expand Down
37 changes: 2 additions & 35 deletions orchestrator/test_runner/src/transaction_stress_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ use clarity::Address as EthAddress;
use clarity::PrivateKey as EthPrivateKey;
use contact::client::Contact;
use cosmos_peggy::send::{send_request_batch, send_to_eth};
use deep_space::coin::Coin;
use deep_space::private_key::PrivateKey as CosmosPrivateKey;
use deep_space::{address::Address as CosmosAddress, coin::Coin};
use ethereum_peggy::{send_to_cosmos::send_to_cosmos, utils::get_tx_batch_nonce};
use futures::future::join_all;
use orchestrator::main_loop::orchestrator_main_loop;
use peggy_proto::peggy::query_client::QueryClient as PeggyQueryClient;
use rand::Rng;
use std::{
collections::HashSet,
time::{Duration, Instant},
Expand All @@ -33,18 +32,6 @@ const TIMEOUT: Duration = Duration::from_secs(120);
/// Batches executed = erc20_addresses.len() * (NUM_USERS / 100)
const NUM_USERS: usize = 100;

pub struct BridgeUserKey {
// the starting addresses that get Eth balances to send across the bridge
pub eth_address: EthAddress,
pub eth_key: EthPrivateKey,
// the cosmos addresses that get the funds and send them on to the dest eth addresses
pub cosmos_address: CosmosAddress,
pub cosmos_key: CosmosPrivateKey,
// the location tokens are sent back to on Ethereum
pub eth_dest_address: EthAddress,
pub eth_dest_key: EthPrivateKey,
}

/// Perform a stress test by sending thousands of
/// transactions and producing large batches
#[allow(clippy::too_many_arguments)]
Expand Down Expand Up @@ -75,27 +62,7 @@ pub async fn transaction_stress_test(
// Generate 100 user keys to send ETH and multiple types of tokens
let mut user_keys = Vec::new();
for _ in 0..NUM_USERS {
let mut rng = rand::thread_rng();
let secret: [u8; 32] = rng.gen();
// the starting location of the funds
let eth_key = EthPrivateKey::from_slice(&secret).unwrap();
let eth_address = eth_key.to_public_key().unwrap();
// the destination on cosmos that sends along to the final ethereum destination
let cosmos_key = CosmosPrivateKey::from_secret(&secret);
let cosmos_address = cosmos_key.to_public_key().unwrap().to_address();
let mut rng = rand::thread_rng();
let secret: [u8; 32] = rng.gen();
// the final destination of the tokens back on Ethereum
let eth_dest_key = EthPrivateKey::from_slice(&secret).unwrap();
let eth_dest_address = eth_key.to_public_key().unwrap();
user_keys.push(BridgeUserKey {
eth_address,
eth_key,
cosmos_address,
cosmos_key,
eth_dest_key,
eth_dest_address,
})
user_keys.push(get_user_key());
}
// the sending eth addresses need Ethereum to send ERC20 tokens to the bridge
let sending_eth_addresses: Vec<EthAddress> = user_keys.iter().map(|i| i.eth_address).collect();
Expand Down
37 changes: 37 additions & 0 deletions orchestrator/test_runner/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use deep_space::address::Address as CosmosAddress;
use deep_space::coin::Coin;
use deep_space::private_key::PrivateKey as CosmosPrivateKey;
use futures::future::join_all;
use rand::Rng;
use web30::{client::Web3, types::SendTxOption};

use crate::TOTAL_TIMEOUT;
Expand Down Expand Up @@ -171,3 +172,39 @@ pub async fn send_eth_bulk(amount: Uint256, destinations: &[EthAddress], web3: &
}
join_all(wait_for_txid).await;
}

pub fn get_user_key() -> BridgeUserKey {
let mut rng = rand::thread_rng();
let secret: [u8; 32] = rng.gen();
// the starting location of the funds
let eth_key = EthPrivateKey::from_slice(&secret).unwrap();
let eth_address = eth_key.to_public_key().unwrap();
// the destination on cosmos that sends along to the final ethereum destination
let cosmos_key = CosmosPrivateKey::from_secret(&secret);
let cosmos_address = cosmos_key.to_public_key().unwrap().to_address();
let mut rng = rand::thread_rng();
let secret: [u8; 32] = rng.gen();
// the final destination of the tokens back on Ethereum
let eth_dest_key = EthPrivateKey::from_slice(&secret).unwrap();
let eth_dest_address = eth_key.to_public_key().unwrap();
BridgeUserKey {
eth_address,
eth_key,
cosmos_address,
cosmos_key,
eth_dest_key,
eth_dest_address,
}
}

pub struct BridgeUserKey {
// the starting addresses that get Eth balances to send across the bridge
pub eth_address: EthAddress,
pub eth_key: EthPrivateKey,
// the cosmos addresses that get the funds and send them on to the dest eth addresses
pub cosmos_address: CosmosAddress,
pub cosmos_key: CosmosPrivateKey,
// the location tokens are sent back to on Ethereum
pub eth_dest_address: EthAddress,
pub eth_dest_key: EthPrivateKey,
}
12 changes: 11 additions & 1 deletion tests/container-scripts/setup-validators.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,17 @@ STARTING_VALIDATOR=1
STARTING_VALIDATOR_HOME="--home /validator$STARTING_VALIDATOR"
# todo add git hash to chain name
$BIN init $STARTING_VALIDATOR_HOME --chain-id=$CHAIN_ID validator1
mv /validator$STARTING_VALIDATOR/config/genesis.json /genesis.json


## Modify generated genesis.json to our liking by editing fields using jq
## we could keep a hardcoded genesis file around but that would prevent us from
## testing the generated one with the default values provided by the module.

# add in denom metadata for both native tokens
jq '.app_state.bank.denom_metadata += [{"base": "footoken", display: "footoken", "description": "A non-staking test token", "denom_units": [{"denom": "footoken", "exponent": 6}]}, {"base": "stake", display: "stake", "description": "A staking test token", "denom_units": [{"denom": "stake", "exponent": 6}]}]' /validator$STARTING_VALIDATOR/config/genesis.json > /edited-genesis.json

mv /edited-genesis.json /genesis.json


# copy over the legacy RPC enabled config for the first validator
# this is the only validator we want taking up this port
Expand Down

0 comments on commit ecc64ce

Please sign in to comment.