-
Notifications
You must be signed in to change notification settings - Fork 0
feat(sdk): keystore full example #144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Mechanix97
wants to merge
33
commits into
main
Choose a base branch
from
mecha/add-examples
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
8e316a8
Init
Mechanix97 cc29521
Merge branch 'main' into mecha/add-examples
Mechanix97 7c9e1b3
Add solidity contract
Mechanix97 619d447
Deploy contract
Mechanix97 e2652f1
Try deploy contract
Mechanix97 55ea9be
Add transfer
Mechanix97 3293c71
Fix gas
Mechanix97 1bfec25
Add call params and example
Mechanix97 e25a609
Add git ignore
Mechanix97 b3900ba
Change call data
Mechanix97 d714e98
Remove comment
Mechanix97 2d986cb
Last commit
Mechanix97 0af470c
Untrack `solc_out`
ilitteri 8fc17b5
Rename contract file
ilitteri 7f2c418
Add `.gitignore` to SDK
ilitteri 09167bd
Reorder keystore example structure + download deps and compile contract
ilitteri 764342f
Fix transfer impl
ilitteri 03327f2
Fix deployment
ilitteri 2deb488
add call
Mechanix97 6638278
Use send tx
Mechanix97 0053423
fix gitignore
Mechanix97 b3dcd42
Add comments
Mechanix97 2a7b330
Fix simple usage
Mechanix97 c11f069
Add RPC url
Mechanix97 c86157a
Fix docs
Mechanix97 9db0d66
Merge branch 'main' into mecha/add-examples
Mechanix97 5ba6d87
Add cargo lock
Mechanix97 3afe6b3
Remove dep
Mechanix97 466c044
Recover address
Mechanix97 9ed9e1f
Improve comments
Mechanix97 ed1f35f
add private key in docs
Mechanix97 ef901a7
Have mandatory pk
Mechanix97 c58dda8
strip prefix
Mechanix97 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
examples/keystore/contracts/lib | ||
examples/keystore/contracts/solc_out |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.7; | ||
|
||
import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; | ||
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; | ||
import "@openzeppelin/contracts/utils/Address.sol"; | ||
|
||
contract RecoverSigner { | ||
using MessageHashUtils for bytes32; | ||
using Address for address; | ||
|
||
event RecoveredSigner( | ||
address signer | ||
); | ||
|
||
function recoverSigner( | ||
bytes32 message, | ||
bytes memory signature | ||
) public { | ||
bytes32 hash = message.toEthSignedMessageHash(); | ||
address signer = ECDSA.recover(hash, signature); | ||
emit RecoveredSigner(signer); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,305 @@ | ||
use clap::Parser; | ||
use ethrex_common::{Bytes, H160, H256, U256}; | ||
use keccak_hash::keccak; | ||
use rex_sdk::calldata::{Value, encode_calldata}; | ||
use rex_sdk::client::eth::get_address_from_secret_key; | ||
use rex_sdk::client::{EthClient, Overrides}; | ||
use rex_sdk::{ | ||
keystore::{create_new_keystore, load_keystore_from_path}, | ||
sign::sign_hash, | ||
transfer, wait_for_transaction_receipt, | ||
}; | ||
use secp256k1::SecretKey; | ||
use std::fs::read_to_string; | ||
use std::path::PathBuf; | ||
use std::process::Command; | ||
use std::str::FromStr; | ||
|
||
#[derive(Parser)] | ||
struct ExampleArgs { | ||
#[arg( | ||
long, | ||
env = "PRIVATE_KEY", | ||
help = "The private key to derive the address from." | ||
)] | ||
private_key: String, | ||
#[arg(long, default_value = "http://localhost:8545", env = "RPC_URL")] | ||
rpc_url: String, | ||
} | ||
|
||
#[tokio::main] | ||
async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
let args = ExampleArgs::parse(); | ||
|
||
// 1. Download contract deps and compile contract. | ||
setup(); | ||
|
||
// 2. Create a new keystore named "RexTest" in the "ContractKeystores" directory. | ||
create_new_keystore(None, Some("RexTest"), "LambdaClass")?; | ||
|
||
// 3. Load the keystore with the password. | ||
let keystore_secret_key = load_keystore_from_path(None, "RexTest", "LambdaClass")?; | ||
let keystore_address = get_address_from_secret_key(&keystore_secret_key)?; | ||
|
||
println!("\nKeystore loaded successfully:"); | ||
println!( | ||
"\tPrivate Key: 0x{}", | ||
hex::encode(keystore_secret_key.secret_bytes()) | ||
); | ||
println!("\tAddress: {keystore_address:#x}"); | ||
|
||
// Connect the client to a node | ||
let eth_client = EthClient::new(&args.rpc_url); | ||
|
||
// 4. Fund the keystore account. | ||
let pk = &args | ||
.private_key | ||
.strip_prefix("0x") | ||
.unwrap_or(&args.private_key); | ||
let rich_wallet_pk = SecretKey::from_str(&pk)?; | ||
let rich_wallet_address = get_address_from_secret_key(&rich_wallet_pk)?; | ||
let amount = U256::from_dec_str("1000000000000000000").expect("Failed to parse amount"); | ||
let transfer_tx_hash = transfer( | ||
amount, | ||
rich_wallet_address, | ||
keystore_address, | ||
rich_wallet_pk, | ||
ð_client, | ||
Overrides::default(), | ||
) | ||
.await?; | ||
|
||
let transfer_receipt = | ||
wait_for_transaction_receipt(transfer_tx_hash, ð_client, 10, true).await?; | ||
|
||
println!("\nFunds transferred successfully:"); | ||
println!("\tTransfer tx hash: {transfer_tx_hash:#x}"); | ||
println!("\tTransfer receipt: {transfer_receipt:?}"); | ||
|
||
// 5. Deploy the signer recovery example contract with the keystore account. | ||
let bytecode_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) | ||
.join("examples/keystore/contracts/solc_out") | ||
.join("RecoverSigner.bin"); | ||
let bytecode = hex::decode(read_to_string(bytecode_path)?)?; | ||
let (contract_tx_hash, deployed_address) = eth_client | ||
.deploy( | ||
keystore_address, | ||
keystore_secret_key, | ||
Bytes::from(bytecode), | ||
Overrides::default(), | ||
) | ||
.await?; | ||
|
||
let contract_deploy_receipt = | ||
wait_for_transaction_receipt(contract_tx_hash, ð_client, 10, true).await?; | ||
|
||
println!("\nContract deployed successfully:"); | ||
println!("\tContract deployment tx hash: {contract_tx_hash:#x}"); | ||
println!("\tContract deployment address: {deployed_address:#x}"); | ||
println!("\tContract deployment receipt: {contract_deploy_receipt:?}"); | ||
|
||
// Get the current block (for later). | ||
let from_block = eth_client.get_block_number().await?; | ||
|
||
// 6. Prepare the calldata to call the example contract. | ||
// i. Prepare a message. | ||
let message = H256::random(); | ||
let prefix = "\x19Ethereum Signed Message:\n32"; | ||
let mut hash_input = Vec::new(); | ||
hash_input.extend_from_slice(prefix.as_bytes()); | ||
hash_input.extend_from_slice(message.as_bytes()); | ||
let hash = keccak(&hash_input); | ||
|
||
// ii. Sign the hash of the message with the keystore private key. | ||
let signature = sign_hash(hash, keystore_secret_key); | ||
|
||
// iii. ABI-encode the parameters. | ||
let raw_function_signature = "recoverSigner(bytes32,bytes)"; | ||
let arguments = vec![ | ||
Value::FixedBytes(Bytes::from(message.to_fixed_bytes().to_vec())), | ||
Value::Bytes(Bytes::from(signature)), | ||
]; | ||
let calldata = encode_calldata(raw_function_signature, &arguments).unwrap(); | ||
|
||
// 7. Prepare and send the transaction for calling the example contract. | ||
let tx = eth_client | ||
.build_eip1559_transaction( | ||
deployed_address, | ||
keystore_address, | ||
calldata.into(), | ||
Overrides { | ||
value: Some(U256::from_dec_str("0")?), | ||
nonce: Some(1), | ||
chain_id: Some(9), | ||
gas_limit: Some(2000000), | ||
max_fee_per_gas: Some(2000000), | ||
max_priority_fee_per_gas: Some(20000), | ||
..Default::default() | ||
}, | ||
10, | ||
) | ||
.await?; | ||
|
||
let sent_tx_hash = eth_client | ||
.send_eip1559_transaction(&tx, &keystore_secret_key) | ||
.await?; | ||
|
||
let sent_tx_receipt = | ||
wait_for_transaction_receipt(sent_tx_hash, ð_client, 100, true).await?; | ||
|
||
println!("\nTx sent successfully:"); | ||
println!("\tTx hash: {sent_tx_hash:#x}"); | ||
println!("\tTx receipt: {sent_tx_receipt:?}"); | ||
|
||
// Get the new current block. | ||
let to_block = eth_client.get_block_number().await?; | ||
|
||
// 8. Get the log emitted by the contract call execution. | ||
let logs = eth_client | ||
Mechanix97 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.get_logs_from_signature( | ||
from_block, | ||
to_block, | ||
deployed_address, | ||
"RecoveredSigner(address)", | ||
) | ||
.await?; | ||
|
||
println!("\tTx Logs: {:?}", logs); | ||
|
||
// 9. Compare it with the expected one. | ||
let address_bytes = &logs[0].log.data[logs[0].log.data.len() - 20..]; | ||
let recovered_address = H160::from_str(&hex::encode(address_bytes))?; | ||
assert_eq!(recovered_address, keystore_address); | ||
|
||
println!("\nAddress recovered successfully!"); | ||
println!("\tRecovered address: {recovered_address:#x}"); | ||
|
||
Ok(()) | ||
} | ||
|
||
fn setup() { | ||
download_contract_deps(); | ||
compile_contracts(); | ||
} | ||
|
||
fn download_contract_deps() { | ||
println!("Downloading contract dependencies"); | ||
|
||
let root_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); | ||
|
||
let lib_path = root_path.join("examples/keystore/contracts/lib"); | ||
|
||
if !lib_path.exists() { | ||
std::fs::create_dir_all(&lib_path).expect("Failed to create lib directory"); | ||
} | ||
|
||
git_clone( | ||
"https://github.com/OpenZeppelin/openzeppelin-contracts.git", | ||
lib_path | ||
.join("openzeppelin-contracts") | ||
.to_str() | ||
.expect("Failed to get str from path"), | ||
None, | ||
true, | ||
); | ||
|
||
println!("Contract dependencies downloaded"); | ||
} | ||
|
||
pub fn git_clone(repository_url: &str, outdir: &str, branch: Option<&str>, submodules: bool) { | ||
println!("Cloning repository: {repository_url} into {outdir}"); | ||
|
||
let mut git_cmd = Command::new("git"); | ||
|
||
let git_clone_cmd = git_cmd.arg("clone").arg(repository_url); | ||
|
||
if let Some(branch) = branch { | ||
git_clone_cmd.arg("--branch").arg(branch); | ||
} | ||
|
||
if submodules { | ||
git_clone_cmd.arg("--recurse-submodules"); | ||
} | ||
|
||
git_clone_cmd | ||
.arg(outdir) | ||
.spawn() | ||
.expect("Failed to spawn git clone command") | ||
.wait() | ||
.expect("Failed to wait for git clone command"); | ||
|
||
println!("Repository cloned successfully"); | ||
} | ||
|
||
fn compile_contracts() { | ||
println!("Compiling contracts"); | ||
|
||
let root_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); | ||
|
||
let contracts_path = root_path.join("examples/keystore/contracts"); | ||
|
||
compile_contract(contracts_path, "RecoverSigner.sol", false); | ||
|
||
println!("Contracts compiled"); | ||
} | ||
|
||
pub fn compile_contract(general_contracts_path: PathBuf, contract_path: &str, runtime_bin: bool) { | ||
let bin_flag = if runtime_bin { | ||
"--bin-runtime" | ||
} else { | ||
"--bin" | ||
}; | ||
|
||
// Both the contract path and the output path are relative to where the Makefile is. | ||
if !Command::new("solc") | ||
.arg(bin_flag) | ||
.arg( | ||
"@openzeppelin/contracts=".to_string() | ||
+ general_contracts_path | ||
.join("lib") | ||
.join("openzeppelin-contracts") | ||
.join("lib") | ||
.join("openzeppelin-contracts") | ||
.join("contracts") | ||
.to_str() | ||
.expect("Failed to get str from path"), | ||
) | ||
.arg( | ||
"@openzeppelin/contracts=".to_string() | ||
+ general_contracts_path | ||
.join("lib") | ||
.join("openzeppelin-contracts") | ||
.join("contracts") | ||
.to_str() | ||
.expect("Failed to get str from path"), | ||
) | ||
.arg( | ||
general_contracts_path | ||
.join(contract_path) | ||
.to_str() | ||
.expect("Failed to get str from path"), | ||
) | ||
.arg("--via-ir") | ||
.arg("-o") | ||
.arg( | ||
general_contracts_path | ||
.join("solc_out") | ||
.to_str() | ||
.expect("Failed to get str from path"), | ||
) | ||
.arg("--overwrite") | ||
.arg("--allow-paths") | ||
.arg( | ||
general_contracts_path | ||
.to_str() | ||
.expect("Failed to get str from path"), | ||
) | ||
.spawn() | ||
.expect("Failed to spawn solc command") | ||
.wait() | ||
.expect("Failed to wait for solc command") | ||
.success() | ||
{ | ||
panic!("Failed to compile {contract_path}"); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.