Skip to content

Commit

Permalink
feat: eth wallet contract implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
birchmd committed Mar 21, 2024
1 parent f2f6ed0 commit 69066bb
Show file tree
Hide file tree
Showing 30 changed files with 8,434 additions and 1,612 deletions.
5,173 changes: 5,173 additions & 0 deletions runtime/near-wallet-contract/implementation/Cargo.lock

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions runtime/near-wallet-contract/implementation/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[workspace.package]
authors = ["Aurora Labs <[email protected]>"]
version = "0.1.0"
edition = "2021"
homepage = "https://github.com/aurora-is-near/eth-wallet-contract"
repository = "https://github.com/aurora-is-near/eth-wallet-contract"
license = "CC0-1.0"


[workspace.dependencies]
aurora-engine-transactions = { git = "https://github.com/aurora-is-near/aurora-engine.git", rev = "c03a2d8610cd27a9decb91b3bddb107db2177b29", default-features = false, features = ["contract"]}
base64 = "0.21"
ethabi = { version = "18", default-features = false }
hex = "0.4"
near-sdk = { version = "5.0" }
once_cell = "1.18"
serde = { version = "1", features = ["derive"] }

# dev-dependencies
anyhow = "1"
aurora-engine-types = { git = "https://github.com/aurora-is-near/aurora-engine.git", rev = "c03a2d8610cd27a9decb91b3bddb107db2177b29", default-features = false }
near-crypto = "0.21"
near-workspaces = "0.10"
rlp = { version = "0.5", default-features = false }
serde_json = "1"
sha3 = "0.10"
tokio = { version = "1", features = ["full"] }

[workspace]
resolver = "2"
members = [
"address-registrar",
"wallet-contract",
]

[profile.release]
panic = 'abort'
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "eth-address-registrar"
version.workspace = true
edition.workspace = true
homepage.workspace = true
repository.workspace = true
license.workspace = true

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
hex.workspace = true
near-sdk.workspace = true
serde.workspace = true
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use near_sdk::{
borsh::{BorshDeserialize, BorshSerialize},
env, near_bindgen,
store::{lookup_map::Entry, LookupMap},
AccountId, BorshStorageKey, PanicOnDefault,
};

type Address = [u8; 20];

#[derive(BorshSerialize, BorshStorageKey)]
#[borsh(crate = "near_sdk::borsh")]
enum StorageKey {
Addresses,
}

#[near_bindgen]
#[derive(PanicOnDefault, BorshDeserialize, BorshSerialize)]
#[borsh(crate = "near_sdk::borsh")]
pub struct AddressRegistrar {
pub addresses: LookupMap<Address, AccountId>,
}

#[near_bindgen]
impl AddressRegistrar {
#[init]
pub fn new() -> Self {
Self {
addresses: LookupMap::new(StorageKey::Addresses),
}
}

pub fn register(&mut self, account_id: AccountId) -> Option<String> {
let address = account_id_to_address(&account_id);

match self.addresses.entry(address) {
Entry::Vacant(entry) => {
let address = format!("0x{}", hex::encode(address));
let log_message = format!("Added entry {} -> {}", address, account_id);
entry.insert(account_id);
env::log_str(&log_message);
Some(address)
}
Entry::Occupied(entry) => {
let log_message = format!(
"Address collision between {} and {}. Keeping the former.",
entry.get(),
account_id
);
env::log_str(&log_message);
None
}
}
}

pub fn lookup(&self, address: String) -> Option<AccountId> {
let address = {
let mut buf = [0u8; 20];
hex::decode_to_slice(address.strip_prefix("0x").unwrap_or(&address), &mut buf)
.unwrap_or_else(|_| env::panic_str("Invalid hex encoding"));
buf
};
self.addresses.get(&address).cloned()
}

pub fn get_address(&self, account_id: AccountId) -> String {
let address = account_id_to_address(&account_id);
format!("0x{}", hex::encode(address))
}
}

fn account_id_to_address(account_id: &AccountId) -> Address {
let hash = near_sdk::env::keccak256_array(account_id.as_bytes());
let mut result = [0u8; 20];
result.copy_from_slice(&hash[12..32]);
result
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "eth-wallet-contract"
version.workspace = true
edition.workspace = true
homepage.workspace = true
repository.workspace = true
license.workspace = true

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
aurora-engine-transactions.workspace = true
base64.workspace = true
ethabi.workspace = true
hex.workspace = true
near-sdk.workspace = true
once_cell.workspace = true
serde.workspace = true
serde_json.workspace = true

[dev-dependencies]
anyhow.workspace = true
aurora-engine-types.workspace = true
near-crypto.workspace = true
near-workspaces.workspace = true
rlp.workspace = true
sha3.workspace = true
tokio.workspace = true
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
todo
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
use std::fmt;

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Error {
AccountNonceExhausted,
AccountId(AccountIdError),
Relayer(RelayerError),
User(UserError),
Caller(CallerError),
}

/// Errors that should never happen when the Eth Implicit accounts feature
/// is available on Near. These errors relate to parsing a 20-byte address
/// from a Near account ID.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum AccountIdError {
AccountIdTooShort,
Missing0xPrefix,
InvalidHex,
}

/// Errors which should never happen if the relayer is honest.
/// If these errors happen then we should ban the relayer (revoke their access key).
/// An external caller (as opposed to a relayer with a Function Call access key) may
/// also trigger these errors by passing bad arguments, but this is not an issue
/// (there is no ban list for external callers) because they are paying the gas fees
/// for their own mistakes.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum RelayerError {
/// Relayers should always check the nonce before sending
InvalidNonce,
/// Relayers should always encode the transaction correctly
InvalidBase64,
/// Relayers should always send valid transactions
TxParsing(aurora_engine_transactions::Error),
/// Relayers should always send correctly signed transactions
InvalidSender,
/// Relayers should always give the correct target account
InvalidTarget,
/// Relayers should always check the transaction is signed with the correct chain id.
InvalidChainId,
}

/// Errors that arise from problems in the data signed by the user
/// (i.e. in the Ethereum transaction itself). A careful power-user
/// should never see these errors because they can review the data
/// they are signing. If a user does see these errors then there is
/// likely a bug in the front-end code that is constructing the Ethereum
/// transaction to be signed.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum UserError {
EvmDeployDisallowed,
ValueTooLarge,
UnknownPublicKeyKind,
InvalidEd25519Key,
InvalidSecp256k1Key,
InvalidAccessKeyAccountId,
UnsupportedAction(UnsupportedAction),
UnknownFunctionSelector,
InvalidAbiEncodedData,
ExcessYoctoNear,
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum UnsupportedAction {
AddFullAccessKey,
CreateAccount,
Delegate,
DeleteAccount,
DeployContract,
Stake,
}

/// Errors that arise from external accounts calling the Wallet Contract.
/// The `rlp_execute` function is intentionally public so that any account
/// can pay for the fees on behalf of a Wallet Contract key holder.
/// These errors are not a big deal from the perspective of the Wallet Contract
/// because the cost for executing such erroneous transactions are paid for
/// by that external caller.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum CallerError {
InsufficientAttachedValue,
}

impl From<aurora_engine_transactions::Error> for Error {
fn from(value: aurora_engine_transactions::Error) -> Self {
Self::Relayer(RelayerError::TxParsing(value))
}
}

impl fmt::Display for AccountIdError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::AccountIdTooShort => f.write_str("Error: account ID too short"),
Self::Missing0xPrefix => f.write_str("Error: account ID missing 0x"),
Self::InvalidHex => f.write_str("Error: account ID is not valid hex encoding"),
}
}
}

impl fmt::Display for RelayerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TxParsing(e) => std::write!(f, "Error parsing RLP {}", e.as_str()),
Self::InvalidSender => f.write_str("Error: signature is not from account owner"),
Self::InvalidBase64 => f.write_str("Error: invalid base64 encoding"),
Self::InvalidTarget => {
f.write_str("Error: target does not match to in signed transaction")
}
Self::InvalidNonce => f.write_str("Error: invalid nonce value"),
Self::InvalidChainId => f.write_str("Error: invalid chain id value"),
}
}
}

impl fmt::Display for UserError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EvmDeployDisallowed => {
f.write_str("Error: transactions deploying EVM contracts not allowed")
}
Self::ValueTooLarge => {
f.write_str("Error: transaction value must be representable by 128 bits")
}
Self::UnknownPublicKeyKind => f.write_str("Error: unknown public key kind"),
Self::InvalidEd25519Key => f.write_str("Error: invalid ED25519 public key"),
Self::InvalidSecp256k1Key => f.write_str("Error: invalid SECP256k1 public key"),
Self::InvalidAccessKeyAccountId => f.write_str(
"Error: attempt to add function call access key with invalid account id",
),
Self::UnsupportedAction(a) => {
std::write!(f, "Error unsupported action {:?}", a)
}
Self::UnknownFunctionSelector => f.write_str("Error: unknown function selector"),
Self::InvalidAbiEncodedData => {
f.write_str("Error: invalid ABI encoding in transaction data")
}
Self::ExcessYoctoNear => f.write_str(
"Error: only at most 1_000_000 yoctoNear can be included directly in an action",
),
}
}
}

impl fmt::Display for CallerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InsufficientAttachedValue => {
f.write_str("Error: external calls must attach Near to pay for their transactions")
}
}
}
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::AccountNonceExhausted => f.write_str("Error: no nonce values remain"),
Self::AccountId(e) => e.fmt(f),
Self::Relayer(e) => e.fmt(f),
Self::User(e) => e.fmt(f),
Self::Caller(e) => e.fmt(f),
}
}
}

impl std::error::Error for Error {}
Loading

0 comments on commit 69066bb

Please sign in to comment.