Skip to content

Commit

Permalink
feat: support EvmAddress (#995)
Browse files Browse the repository at this point in the history
* feat: create evm address interface

* feat: create evm address clas

* feat: unit tests for evm address wrapper class

* chore: linting

* chore: linting

* feat: support from public key in evm address

* feat: remove evm address class and unit tests

* feat: create types for B256 Evm and Evm address

* feat: create address utility function to convert a b256 to an evm b256

* feat: add address function to return as evm addres

* feat: add unit tests for address to evm address and utility function

* feat: create snippets util function generate test wallet and deploy a contract

* test: create docs snippets tests for evm address

* chore: remove redundant gitignore from evm address doc snippet contract

* test: rename evm address docs snippet test

* docs: create documentation for evm address

* docs: modify docs snippets for evm address

* docs: add evm address to docs config

* test: alter evm address test asserttions

* docs: update evm address title capitalisation

* feat: add evm address type to typegen

* test: add unit test for evm address type with forc project

* test: add evm address to full typegen test suit

* feat: add ignore evm struct to struct typegen

* feat: add evm address type to supported types in typegen

* chore: changeset

* feat: add forc projects to workspace

* test: add address import to dts test

* Update packages/abi-typegen/test/fixtures/forc-projects/evm-address/Forc.toml

Co-authored-by: Sérgio Torres <30977845+Torres-ssf@users.noreply.github.com>

* feat: remove redundant gitignore from evm address cargo

* feat: remove address import for evm address typegen

* chore: force rebuild

* feat: DRY evm address contract in docs snippets

* refactor: DRY EVM Address sway contract in snippets

* test: remove address import assert from typegen evm test

* feat: support from evm address in address

---------

Co-authored-by: danielbate <--global>
Co-authored-by: Sérgio Torres <30977845+Torres-ssf@users.noreply.github.com>
  • Loading branch information
danielbate and Torres-ssf authored May 29, 2023
1 parent df7573f commit ac9362c
Show file tree
Hide file tree
Showing 25 changed files with 349 additions and 5 deletions.
7 changes: 7 additions & 0 deletions .changeset/shiny-socks-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@fuel-ts/abi-typegen": patch
"@fuel-ts/address": patch
"@fuel-ts/interfaces": patch
---

Support EVM Address type
1 change: 1 addition & 0 deletions apps/docs-snippets/projects/Forc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ members = [
"return-true-predicate",
"echo-employee-data-vector",
"whitelisted-address-predicate",
"echo-evm-address",
]
7 changes: 7 additions & 0 deletions apps/docs-snippets/projects/echo-evm-address/Forc.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[project]
authors = ["FuelLabs"]
entry = "main.sw"
license = "Apache-2.0"
name = "echo-evm-address"

[dependencies]
26 changes: 26 additions & 0 deletions apps/docs-snippets/projects/echo-evm-address/src/main.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// #region evm-address-1
contract;

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

configurable {
B256_ADDR: b256 = 0xbebd3baab326f895289ecbd4210cf886ce41952316441ae4cac35f00f0e882a6,
}

abi EvmTest {
fn echo_address() -> EvmAddress;
fn echo_address_comparison(evm_addr: EvmAddress) -> bool;
}

impl EvmTest for Contract {
fn echo_address() -> EvmAddress {
return EvmAddress::from(B256_ADDR);
}

fn echo_address_comparison(evm_addr: EvmAddress) -> bool {
let evm_addr2 = EvmAddress::from(B256_ADDR);

evm_addr == evm_addr2
}
}
// #endregion evm-address-1
1 change: 1 addition & 0 deletions apps/docs-snippets/projects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export enum SnippetProjectEnum {
RETURN_TRUE_PREDICATE = 'return-true-predicate',
ECHO_EMPLOYEE_DATA_VECTOR = 'echo-employee-data-vector',
WHITELISTED_ADDRESS_PREDICATE = 'whitelisted-address-predicate',
ECHO_EVM_ADDRESS = 'echo-evm-address',
}

export const getSnippetProjectArtifacts = (project: SnippetProjectEnum) =>
Expand Down
74 changes: 74 additions & 0 deletions apps/docs-snippets/src/guide/types/evm-address.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import type { B256AddressEvm, Contract, EvmAddress } from 'fuels';
import { Address } from 'fuels';

import { SnippetProjectEnum } from '../../../projects';
import { createAndDeployContractFromProject } from '../../utils';

describe('EvMAddress', () => {
let contract: Contract;
const Bits256: B256AddressEvm =
'0x000000000000000000000000210cf886ce41952316441ae4cac35f00f0e882a6';

beforeAll(async () => {
contract = await createAndDeployContractFromProject(SnippetProjectEnum.ECHO_EVM_ADDRESS);
});

it('should demonstrate typed evm address example', () => {
// #region evm-address-1
// #context import type { EvmAddress } from 'fuels';

const evmAddress: EvmAddress = {
value: Bits256,
};
// #endregion evm-address-1

expect(evmAddress.value).toBe(Bits256);
});

it('should create an Evm Address from a B256Address', async () => {
// #region evm-address-2
// #context import type { EvmAddress } from 'fuels';
// #context import { Address } from 'fuels';

const b256Address = '0xbebd3baab326f895289ecbd4210cf886ce41952316441ae4cac35f00f0e882a6';

const address = Address.fromB256(b256Address);

const evmAddress: EvmAddress = address.toEvmAddress();
// #endregion evm-address-2

const { value } = await contract.functions.echo_address_comparison(evmAddress).get();

expect(value).toBeTruthy();
});

it('should pass an evm address to a contract', async () => {
// #region evm-address-3
// #context import type { EvmAddress } from 'fuels';

const evmAddress: EvmAddress = {
value: Bits256,
};

const { value } = await contract.functions.echo_address_comparison(evmAddress).get();

expect(value).toBeTruthy();
// #endregion evm-address-3
});

it('should retrieve an evm address from a contract', async () => {
// #region evm-address-4
// #context import type { EvmAddress } from 'fuels';

const evmAddress: EvmAddress = {
value: Bits256,
};

const { value } = await contract.functions.echo_address().get();

expect(value).toEqual(evmAddress);
// #endregion evm-address-4

expect(value.value).toEqual(Bits256);
});
});
17 changes: 16 additions & 1 deletion apps/docs-snippets/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CoinQuantityLike } from 'fuels';
import type { CoinQuantityLike, Contract } from 'fuels';
import {
FUEL_NETWORK_URL,
NativeAssetId,
Expand All @@ -7,8 +7,12 @@ import {
Wallet,
WalletUnlocked,
coinQuantityfy,
ContractFactory,
} from 'fuels';

import type { SnippetProjectEnum } from '../projects';
import { getSnippetProjectArtifacts } from '../projects';

export const getTestWallet = async () => {
// create a provider using the Fuel network URL
const provider = new Provider(FUEL_NETWORK_URL);
Expand Down Expand Up @@ -53,3 +57,14 @@ export const getTestWallet = async () => {
// return the test wallet
return testWallet;
};

export const createAndDeployContractFromProject = async (
project: SnippetProjectEnum
): Promise<Contract> => {
const wallet = await getTestWallet();
const { abiContents, binHelixfied } = getSnippetProjectArtifacts(project);

const contractFactory = new ContractFactory(binHelixfied, abiContents, wallet);

return contractFactory.deployContract();
};
4 changes: 4 additions & 0 deletions apps/docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export default defineConfig({
text: 'Address',
link: '/guide/types/address',
},
{
text: 'Evm Address',
link: '/guide/types/evm-address',
},
{
text: 'Arrays',
link: '/guide/types/arrays',
Expand Down
25 changes: 25 additions & 0 deletions apps/docs/src/guide/types/evm-address.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# EvmAddress

An Ethereum Virtual Machine (EVM) Address can be represented using the `EvmAddress` type. It's definition matches the Sway standard library type being a `Struct` wrapper around an inner `Bits256` value.

<<< @/../../docs-snippets/src/guide/types/evm-address.test.ts#evm-address-1{ts:line-numbers}

## Creating an EVM Address

An EVM Address only has 20 bytes therefore the first 12 bytes of the `Bits256` value are set to 0. Within the SDK, an `Address` can be instantiated and converted to an EVM Address using the `toEvmAddress()` function:

<<< @/../../docs-snippets/src/guide/types/evm-address.test.ts#evm-address-2{ts:line-numbers}

## Using an EVM Address

The `EvmAddress` type can be integrated with your contract calls. Consider the following contract that can compare and return an EVM Address:

<<< @/../../docs-snippets/projects/echo-evm-address/src/main.sw#evm-address-1{ts:line-numbers}

The `EvmAddress` type can be used with the SDK and passed to the contract function as follows:

<<< @/../../docs-snippets/src/guide/types/evm-address.test.ts#evm-address-3{ts:line-numbers}

And to validate the returned value:

<<< @/../../docs-snippets/src/guide/types/evm-address.test.ts#evm-address-4{ts:line-numbers}
36 changes: 36 additions & 0 deletions packages/abi-typegen/src/abi/types/EvmAddressType.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { getProjectResources, ForcProjectsEnum } from '../../../test/fixtures/forc-projects/index';
import type { IRawAbiTypeRoot } from '../../index';
import { findType } from '../../utils/findType';
import { makeType } from '../../utils/makeType';
import * as parseTypeArgumentsMod from '../../utils/parseTypeArguments';

import { EvmAddressType } from './EvmAddressType';
import { StructType } from './StructType';
import { VectorType } from './VectorType';

describe('EvmAddressType.ts', () => {
test('should properly parse type attributes', () => {
const parseTypeArguments = jest.spyOn(parseTypeArgumentsMod, 'parseTypeArguments');

const project = getProjectResources(ForcProjectsEnum.EVM_ADDRESS);

const rawTypes = project.abiContents.types;
const types = rawTypes.map((rawAbiType: IRawAbiTypeRoot) => makeType({ rawAbiType }));

const suitableForEvmAddress = EvmAddressType.isSuitableFor({ type: EvmAddressType.swayType });
const suitableForStruct = EvmAddressType.isSuitableFor({ type: StructType.swayType });
const suitableForVector = EvmAddressType.isSuitableFor({ type: VectorType.swayType });

expect(suitableForEvmAddress).toEqual(true);
expect(suitableForStruct).toEqual(false);
expect(suitableForVector).toEqual(false);

parseTypeArguments.mockClear();

const evmAddress = findType({ types, typeId: 1 }) as EvmAddressType;

expect(evmAddress.attributes.inputLabel).toEqual('EvmAddress');
expect(evmAddress.attributes.outputLabel).toEqual('EvmAddress');
expect(evmAddress.requiredFuelsMembersImports).toStrictEqual(['EvmAddress']);
});
});
28 changes: 28 additions & 0 deletions packages/abi-typegen/src/abi/types/EvmAddressType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { IType } from '../../types/interfaces/IType';

import { AType } from './AType';

export class EvmAddressType extends AType implements IType {
public static swayType = 'struct EvmAddress';

public name = 'evmAddress';

static MATCH_REGEX: RegExp = /^struct EvmAddress$/m;

static isSuitableFor(params: { type: string }) {
return EvmAddressType.MATCH_REGEX.test(params.type);
}

public parseComponentsAttributes(_params: { types: IType[] }) {
const capitalizedName = 'EvmAddress';

this.attributes = {
inputLabel: capitalizedName,
outputLabel: capitalizedName,
};

this.requiredFuelsMembersImports = [capitalizedName];

return this.attributes;
}
}
3 changes: 3 additions & 0 deletions packages/abi-typegen/src/abi/types/StructType.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { findType } from '../../utils/findType';
import { makeType } from '../../utils/makeType';
import * as parseTypeArgumentsMod from '../../utils/parseTypeArguments';

import { EvmAddressType } from './EvmAddressType';
import { StructType } from './StructType';
import { U16Type } from './U16Type';

Expand All @@ -19,9 +20,11 @@ describe('StructType.ts', () => {

const suitableForStruct = StructType.isSuitableFor({ type: StructType.swayType });
const suitableForU16 = StructType.isSuitableFor({ type: U16Type.swayType });
const suitableForEvmAddress = StructType.isSuitableFor({ type: EvmAddressType.swayType });

expect(suitableForStruct).toEqual(true);
expect(suitableForU16).toEqual(false);
expect(suitableForEvmAddress).toEqual(false);

// validating `struct C`, with nested `typeArguments`
parseTypeArguments.mockClear();
Expand Down
2 changes: 1 addition & 1 deletion packages/abi-typegen/src/abi/types/StructType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export class StructType extends AType implements IType {
public name = 'struct';

static MATCH_REGEX: RegExp = /^struct (.+)$/m;
static IGNORE_REGEX: RegExp = /^struct (Vec|RawVec)$/m;
static IGNORE_REGEX: RegExp = /^struct (Vec|RawVec|EvmAddress)$/m;

static isSuitableFor(params: { type: string }) {
const isAMatch = StructType.MATCH_REGEX.test(params.type);
Expand Down
2 changes: 1 addition & 1 deletion packages/abi-typegen/src/utils/supportedTypes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import { supportedTypes } from './supportedTypes';

describe('supportedTypes.ts', () => {
test('should export all supported types', () => {
expect(supportedTypes.length).toEqual(16);
expect(supportedTypes.length).toEqual(17);
});
});
2 changes: 2 additions & 0 deletions packages/abi-typegen/src/utils/supportedTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { B256Type } from '../abi/types/B256Type';
import { B512Type } from '../abi/types/B512Type';
import { BoolType } from '../abi/types/BoolType';
import { EnumType } from '../abi/types/EnumType';
import { EvmAddressType } from '../abi/types/EvmAddressType';
import { GenericType } from '../abi/types/GenericType';
import { OptionType } from '../abi/types/OptionType';
import { RawUntypedPtr } from '../abi/types/RawUntypedPtr';
Expand Down Expand Up @@ -32,4 +33,5 @@ export const supportedTypes = [
U64Type,
U8Type,
VectorType,
EvmAddressType,
];
1 change: 1 addition & 0 deletions packages/abi-typegen/test/fixtures/forc-projects/Forc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"./enum-of-structs",
"./enum-simple",
"./enum-simple-native",
"./evm-address",
"./fn-void",
"./full",
"./minimal",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[project]
authors = ["FuelLabs"]
entry = "main.sw"
license = "Apache-2.0"
name = "evm-address"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
contract;

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

abi EvmAddressTest {
fn main(raw_address: b256) -> EvmAddress;
}

impl EvmAddressTest for Contract {
fn main(raw_address: b256) -> EvmAddress {
EvmAddress::from(raw_address)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
contract;

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

enum MyEnum {
Checked: (),
Pending: ()
Expand Down Expand Up @@ -36,6 +38,7 @@ abi MyContract {
fn types_vector_option(x: Vec<StructWithMultiOption>) -> Vec<StructWithMultiOption>;
fn types_option(x: Option<u8>) -> Option<u8>;
fn types_option_geo(x: Option<MyStruct>) -> Option<MyStruct>;
fn types_evm_address(x: EvmAddress) -> EvmAddress;
}

impl MyContract for Contract {
Expand All @@ -55,4 +58,5 @@ impl MyContract for Contract {
fn types_vector_option(x: Vec<StructWithMultiOption>) -> Vec<StructWithMultiOption> { x }
fn types_option(x: Option<u8>) -> Option<u8> { x }
fn types_option_geo(x: Option<MyStruct>) -> Option<MyStruct> { x }
fn types_evm_address(x: EvmAddress) -> EvmAddress { EvmAddress::from(0x0606060606060606060606060606060606060606060606060606060606060606) }
}
1 change: 1 addition & 0 deletions packages/abi-typegen/test/fixtures/forc-projects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export enum ForcProjectsEnum {
ENUM_OF_STRUCTS = 'enum-of-structs',
ENUM_SIMPLE = 'enum-simple',
ENUM_SIMPLE_NATIVE = 'enum-simple-native',
EVM_ADDRESS = 'evm-address',
FN_VOID = 'fn-void',
FULL = 'full',
MINIMAL = 'minimal',
Expand Down
Loading

0 comments on commit ac9362c

Please sign in to comment.