Skip to content

Commit

Permalink
feat: support evm address (#668)
Browse files Browse the repository at this point in the history
Adds an `EvmAddress` type, equivalent to the Sway std lib type.

Co-authored-by: hal3e <hal3e.git@gmail.com>
Co-authored-by: iqdecay <victor@berasconsulting.com>
Co-authored-by: Rodrigo Araújo <rod.dearaujo@gmail.com>
  • Loading branch information
4 people committed Nov 9, 2022
1 parent ccd3e16 commit e1a68d4
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 6 deletions.
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
- [Structs and enums](./types/custom_types.md)
- [String](./types/string.md)
- [Bits256](./types/bits256.md)
- [EvmAddress](./types/evm_address.md)
- [Vectors](./types/vectors.md)
- [API](./getting-started/api.md)
- [Debugging](./debugging/debugging.md)
Expand Down
15 changes: 15 additions & 0 deletions docs/src/types/evm_address.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# EvmAddress

In the Rust SDK, Ethereum Virtual Machine (EVM) addresses can be represented with the 'EvmAddress' type. Its definition matches with the Sway standard library type with the same name and will be converted accordingly when interacting with contracts:

```rust,ignore
{{#include ../../../packages/fuels-core/src/types/bits_256.rs:evm_address}}
```

Here's an example:

```rust,ignore
{{#include ../../../packages/fuels/tests/bindings.rs:evm_address_arg}}
```

> **Note:** when creating an `EvmAddress` from `Bits256`, the first 12 bytes will be cleared because an evm address is only 20 bytes long.
1 change: 1 addition & 0 deletions packages/fuels-core/src/code_gen/abigen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ impl Abigen {
"Vec",
"raw untyped ptr",
"RawVec",
"EvmAddress",
]
.into_iter()
.any(|e| e == name))
Expand Down
112 changes: 106 additions & 6 deletions packages/fuels-core/src/types/bits_256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,77 @@ impl Tokenizable for Bits256 {
}
}

#[derive(Debug, PartialEq, Eq, Copy, Clone)]
// ANCHOR: evm_address
pub struct EvmAddress {
// An evm address is only 20 bytes, the first 12 bytes should be set to 0
value: Bits256,
}
// ANCHOR_END: evm_address

impl EvmAddress {
// sets the leftmost 12 bytes to zero
fn clear_12_bytes(bytes: [u8; 32]) -> [u8; 32] {
let mut bytes = bytes;
bytes[..12].copy_from_slice(&[0u8; 12]);

bytes
}
}

impl From<Bits256> for EvmAddress {
fn from(b256: Bits256) -> Self {
let value = Bits256(Self::clear_12_bytes(b256.0));

Self { value }
}
}

impl Parameterize for EvmAddress {
fn param_type() -> ParamType {
ParamType::Struct {
fields: vec![ParamType::B256],
generics: vec![],
}
}
}

impl Tokenizable for EvmAddress {
fn from_token(t: Token) -> Result<Self, Error>
where
Self: Sized,
{
if let Token::Struct(tokens) = &t {
let first_token = tokens.iter().next();
if let Some(Token::B256(data)) = first_token {
return Ok(EvmAddress::from(Bits256(*data)));
}
}

Err(Error::InstantiationError(format!(
"EvmAddress could not be constructed from the given token. Reason: Expected Struct(B256) got: {t:?}",
)))
}

fn into_token(self) -> Token {
Token::Struct(vec![Bits256(self.value.0).into_token()])
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::Tokenizable;
use fuels_types::param_types::ParamType;

#[test]
fn test_param_type() {
fn test_param_type_b256() {
assert_eq!(Bits256::param_type(), ParamType::B256);
}

#[test]
fn test_from_token() -> Result<(), Error> {
let data = [0u8; 32];
fn test_from_token_b256() -> Result<(), Error> {
let data = [1u8; 32];
let token = Token::B256(data);

let bits256 = Bits256::from_token(token)?;
Expand All @@ -74,8 +131,8 @@ mod tests {
}

#[test]
fn test_into_token() {
let data = [0u8; 32];
fn test_into_token_b256() {
let data = [1u8; 32];
let bits256 = Bits256(data);

let token = bits256.into_token();
Expand All @@ -84,7 +141,7 @@ mod tests {
}

#[test]
fn from_hex_str() -> Result<(), Error> {
fn from_hex_str_b256() -> Result<(), Error> {
// ANCHOR: from_hex_str
let hex_str = "0101010101010101010101010101010101010101010101010101010101010101";

Expand All @@ -102,4 +159,47 @@ mod tests {

Ok(())
}

#[test]
fn test_param_type_evm_addr() {
assert_eq!(
EvmAddress::param_type(),
ParamType::Struct {
fields: vec![ParamType::B256],
generics: vec![]
}
);
}

#[test]
fn test_from_token_evm_addr() -> Result<(), Error> {
let data = [1u8; 32];
let token = Token::Struct(vec![Token::B256(data)]);

let evm_address = EvmAddress::from_token(token)?;

let expected_data = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1,
];

assert_eq!(evm_address.value.0, expected_data);

Ok(())
}

#[test]
fn test_into_token_evm_addr() {
let data = [1u8; 32];
let evm_address = EvmAddress::from(Bits256(data));

let token = evm_address.into_token();

let expected_data = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1,
];

assert_eq!(token, Token::Struct(vec![Token::B256(expected_data)]));
}
}
68 changes: 68 additions & 0 deletions packages/fuels/tests/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,74 @@ async fn compile_bindings_b256_input() {
);
}

#[tokio::test]
async fn compile_bindings_evm_address_input() {
abigen!(
SimpleContract,
r#"
{
"types": [
{
"typeId": 0,
"type": "()",
"components": [],
"typeParameters": null
},
{
"typeId": 1,
"type": "struct EvmAddress",
"components": null,
"typeParameters": null
}
],
"functions": [
{
"inputs": [
{
"name": "arg",
"type": 1,
"typeArguments": null
}
],
"name": "takes_evm_address",
"output": {
"name": "",
"type": 0,
"typeArguments": null
}
}
]
}
"#,
);

let wallet = launch_provider_and_get_wallet().await;

let contract_instance = SimpleContract::new(null_contract_id(), wallet);

let mut hasher = Sha256::new();
hasher.update("test string".as_bytes());

// ANCHOR: evm_address_arg
let b256 = Bits256(hasher.finalize().into());
let arg = EvmAddress::from(b256);

let call_handler = contract_instance.methods().takes_evm_address(arg);
// ANCHOR_END: evm_address_arg

let encoded_args = call_handler.contract_call.encoded_args.resolve(0);
let encoded = format!(
"{}{}",
hex::encode(call_handler.contract_call.encoded_selector),
hex::encode(&encoded_args)
);

assert_eq!(
"000000006ef3f9a50000000000000000000000005b44e4cb4e2c2298f4ac457ba8f82743f31e930b",
encoded
);
}

#[tokio::test]
async fn compile_bindings_struct_input() {
// Generates the bindings from the an ABI definition inline.
Expand Down
61 changes: 61 additions & 0 deletions packages/fuels/tests/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,67 @@ async fn test_tuples() -> Result<(), Error> {
Ok(())
}

#[tokio::test]
async fn test_evm_address() -> Result<(), Error> {
setup_contract_test!(
contract_instance,
wallet,
"packages/fuels/tests/types/evm_address"
);

{
let b256 = Bits256::from_hex_str(
"0x1616060606060606060606060606060606060606060606060606060606060606",
)?;
let evm_address = EvmAddress::from(b256);

assert!(
contract_instance
.methods()
.evm_address_as_input(evm_address)
.call()
.await?
.value
);
}

{
let b256 = Bits256::from_hex_str(
"0x0606060606060606060606060606060606060606060606060606060606060606",
)?;
let expected_evm_address = EvmAddress::from(b256);

assert_eq!(
contract_instance
.methods()
.evm_address_from_literal()
.call()
.await?
.value,
expected_evm_address
);
}

{
let b256 = Bits256::from_hex_str(
"0x0606060606060606060606060606060606060606060606060606060606060606",
)?;
let expected_evm_address = EvmAddress::from(b256);

assert_eq!(
contract_instance
.methods()
.evm_address_from_argument(b256)
.call()
.await?
.value,
expected_evm_address
);
}

Ok(())
}

#[tokio::test]
async fn test_array() -> Result<(), Error> {
setup_contract_test!(
Expand Down
7 changes: 7 additions & 0 deletions packages/fuels/tests/types/evm_address/Forc.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
name = "evm_address"

[dependencies]
25 changes: 25 additions & 0 deletions packages/fuels/tests/types/evm_address/src/main.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
contract;

use std::vm::evm::evm_address::EvmAddress;

abi EvmTest {
fn evm_address_as_input(evm_addr: EvmAddress) -> bool;
fn evm_address_from_literal() -> EvmAddress;
fn evm_address_from_argument(raw_address: b256) -> EvmAddress;
}

impl EvmTest for Contract {
fn evm_address_as_input(evm_addr: EvmAddress) -> bool {
let evm_addr2 = EvmAddress::from(0x1616060606060606060606060606060606060606060606060606060606060606);

evm_addr == evm_addr2
}

fn evm_address_from_literal() -> EvmAddress {
EvmAddress::from(0x0606060606060606060606060606060606060606060606060606060606060606)
}

fn evm_address_from_argument(raw_address: b256) -> EvmAddress {
EvmAddress::from(raw_address)
}
}

0 comments on commit e1a68d4

Please sign in to comment.