Skip to content
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

Ethereum-compatible AccountId/Address #2501

Merged
merged 7 commits into from
Aug 22, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
## 1.30.0-beta.x

- Adjust balance retrievals to check for `system.account` for new/old determination
- Adjust `Address` <-> `LookupSource` definitions (no external impact, both in existence)
- Add types for the new `proposeParachain` module (as per Rococo)
- Adjust `Address` <-> `LookupSource` definitions (no external impact, both in existence)
- Add Ethereum-compatible `Ethereum{AccountId, LookupSource}` types, underlying `H160`

## 1.29.1 Aug 17, 2020

Expand Down
7 changes: 7 additions & 0 deletions packages/types/src/augment/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { AliveContractInfo, CodeHash, ContractCallRequest, ContractExecResult, C
import { AccountVote, AccountVoteSplit, AccountVoteStandard, Conviction, Delegations, PreimageStatus, PreimageStatusAvailable, PriorLock, PropIndex, Proposal, ProxyState, ReferendumIndex, ReferendumInfo, ReferendumInfoFinished, ReferendumInfoTo239, ReferendumStatus, Tally, Voting, VotingDelegating, VotingDirect, VotingDirectVote } from '@polkadot/types/interfaces/democracy';
import { ApprovalFlag, DefunctVoter, Renouncing, SetIndex, Vote, VoteIndex, VoteThreshold, VoterInfo } from '@polkadot/types/interfaces/elections';
import { CreatedBlock, ImportedAux } from '@polkadot/types/interfaces/engine';
import { EthereumAccountId, EthereumLookupSource } from '@polkadot/types/interfaces/ethereum';
import { Account, Log, Vicinity } from '@polkadot/types/interfaces/evm';
import { EcdsaSignature, Ed25519Signature, Extrinsic, ExtrinsicEra, ExtrinsicPayload, ExtrinsicPayloadUnknown, ExtrinsicPayloadV1, ExtrinsicPayloadV2, ExtrinsicPayloadV3, ExtrinsicPayloadV4, ExtrinsicSignatureV1, ExtrinsicSignatureV2, ExtrinsicSignatureV3, ExtrinsicSignatureV4, ExtrinsicUnknown, ExtrinsicV1, ExtrinsicV2, ExtrinsicV3, ExtrinsicV4, ImmortalEra, MortalEra, MultiSignature, Signature, SignerPayload, Sr25519Signature } from '@polkadot/types/interfaces/extrinsics';
import { AssetOptions, Owner, PermissionLatest, PermissionVersions, PermissionsV1 } from '@polkadot/types/interfaces/genericAsset';
Expand Down Expand Up @@ -1346,6 +1347,12 @@ declare module '@polkadot/types/types/registry' {
AccountValidity: AccountValidity;
'Option<AccountValidity>': Option<AccountValidity>;
'Vec<AccountValidity>': Vec<AccountValidity>;
EthereumAccountId: EthereumAccountId;
'Option<EthereumAccountId>': Option<EthereumAccountId>;
'Vec<EthereumAccountId>': Vec<EthereumAccountId>;
EthereumLookupSource: EthereumLookupSource;
'Option<EthereumLookupSource>': Option<EthereumLookupSource>;
'Vec<EthereumLookupSource>': Vec<EthereumLookupSource>;
CallMetadataV0: CallMetadataV0;
'Option<CallMetadataV0>': Option<CallMetadataV0>;
'Vec<CallMetadataV0>': Vec<CallMetadataV0>;
Expand Down
83 changes: 83 additions & 0 deletions packages/types/src/ethereum/AccountId.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2017-2020 @polkadot/types authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

/* eslint-disable @typescript-eslint/no-unsafe-member-access */

import { TypeRegistry } from '../create';
import Raw from '../codec/Raw';
import AccountId from './AccountId';

describe('EthereumAccountId', (): void => {
const registry = new TypeRegistry();

describe('defaults', (): void => {
const id = registry.createType('EthereumAccountId');

it('has a 20-byte length', (): void => {
expect(id).toHaveLength(20);
});

it('is empty by default', (): void => {
expect(id.isEmpty).toBe(true);
});

it('equals the empty address', (): void => {
expect(id.eq('0x0000000000000000000000000000000000000000')).toBe(true);
});
});

describe('decoding', (): void => {
const testDecode = (type: string, input: Uint8Array | string | AccountId, expected: string): void =>
it(`can decode from ${type}`, (): void => {
const a = registry.createType('EthereumAccountId', input);

expect(a.toString()).toBe(expected);
});

testDecode(
'AccountId',
registry.createType('EthereumAccountId', '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887'),
'0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887'
);
testDecode('hex', '0x4119b2e6c3cb618f4f0B93ac77f9Beec7ff02887', '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887');
testDecode(
'Raw',
new Raw(registry, [
1, 2, 3, 4, 5, 6, 7, 8, 1, 2,
1, 2, 3, 4, 5, 6, 7, 8, 1, 2
]),
'0x0102030405060708010201020304050607080102'
);
testDecode(
'Uint8Array',
Uint8Array.from([
1, 2, 3, 4, 5, 6, 7, 8, 1, 2,
1, 2, 3, 4, 5, 6, 7, 8, 1, 2
]),
'0x0102030405060708010201020304050607080102'
);
});

describe('encoding', (): void => {
const testEncode = (to: 'toHex' | 'toJSON' | 'toString' | 'toU8a', expected: Uint8Array | string, input = '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887'): void =>
it(`can encode ${to}`, (): void => {
const a = registry.createType('EthereumAccountId', input);

expect(a[to]()).toEqual(expected);
});

testEncode('toHex', '0x4119b2e6c3cb618f4f0b93ac77f9beec7ff02887');
testEncode('toJSON', '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887');
testEncode('toString', '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887');
testEncode('toString', '0x0000000000000000000000000000000000000000', '0x00');
testEncode('toU8a', Uint8Array.from([
0x41, 0x19, 0xb2, 0xe6, 0xc3, 0xcb, 0x61, 0x8f, 0x4f, 0x0b,
0x93, 0xac, 0x77, 0xf9, 0xbe, 0xec, 0x7f, 0xf0, 0x28, 0x87
]));

it('decodes to a non-empty value', (): void => {
expect(registry.createType('EthereumAccountId', '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887').isEmpty).toBe(false);
});
});
});
75 changes: 75 additions & 0 deletions packages/types/src/ethereum/AccountId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright 2017-2020 @polkadot/types authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

import { AnyString, AnyU8a, Registry } from '../types';

import { hexToU8a, isHex, isString, isU8a, u8aToU8a } from '@polkadot/util';
import { ethereumEncode, isEthereumAddress } from '@polkadot/util-crypto';

import U8aFixed from '../codec/U8aFixed';

/** @internal */
function decodeAccountId (value: AnyU8a | AnyString): AnyU8a {
if (isU8a(value) || Array.isArray(value)) {
return u8aToU8a(value);
} else if (isHex(value) || isEthereumAddress(value)) {
return hexToU8a(value.toString());
} else if (isString(value)) {
return u8aToU8a((value as string).toString());
}

return value;
}

/**
* @name EthereumAccountId
* @description
* A wrapper around an Ethereum-compatible AccountId. Since we are dealing with
* underlying addresses (20 bytes in length), we extend from U8aFixed which is
* just a Uint8Array wrapper with a fixed length.
*/
export default class AccountId extends U8aFixed {
constructor (registry: Registry, value: AnyU8a = new Uint8Array()) {
super(registry, decodeAccountId(value), 160);
}

public static encode (value: Uint8Array): string {
return ethereumEncode(value);
}

/**
* @description Compares the value of the input to see if there is a match
*/
public eq (other?: unknown): boolean {
return super.eq(decodeAccountId(other as AnyU8a));
}

/**
* @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information
*/
public toHuman (): string {
return this.toJSON();
}

/**
* @description Converts the Object to JSON, typically used for RPC transfers
*/
public toJSON (): string {
return this.toString();
}

/**
* @description Returns the string representation of the value
*/
public toString (): string {
return AccountId.encode(this);
}

/**
* @description Returns the base runtime type name for this instance
*/
public toRawType (): string {
return 'AccountId';
}
}
106 changes: 106 additions & 0 deletions packages/types/src/ethereum/LookupSource.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2017-2020 @polkadot/types authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

import { TypeRegistry } from '../create';
import AccountIndex from '../generic/AccountIndex';
import AccountId from './AccountId';
import Address from './LookupSource';

describe('EthereumLookupSource', (): void => {
const registry = new TypeRegistry();

const testDecode = (type: string, input: Address | AccountId | AccountIndex | number[] | Uint8Array, expected: string): void =>
it(`can decode from ${type}`, (): void => {
const a = registry.createType('EthereumLookupSource', input);

expect(a.toString()).toBe(expected);
});

describe('decoding', (): void => {
testDecode(
'Address',
registry.createType('EthereumLookupSource', '0x00a329c0648769a73afac7f9381e08fb43dbea72'),
'0x00a329c0648769A73afAc7F9381E08FB43dBEA72'
);
testDecode(
'AccountId',
registry.createType('EthereumAccountId', '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887'),
'0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887'
);
testDecode(
'AccountIndex (mixed prefixes)',
registry.createType('EthereumLookupSource', '2jpAFn'),
// NOTE Expected address here is encoded with prefix 42, input above with 68
'25GUyv'
);
testDecode(
'AccountIndex (hex)',
registry.createType('AccountIndex', '0x0100'),
'25GUyv'
);
testDecode(
'Uint8Array (with prefix 255)',
Uint8Array.from([
255,
0x41, 0x19, 0xb2, 0xe6, 0xc3, 0xcb, 0x61, 0x8f, 0x4f, 0x0b,
0x93, 0xac, 0x77, 0xf9, 0xbe, 0xec, 0x7f, 0xf0, 0x28, 0x87
]),
'0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887'
);
testDecode(
'Uint8Array (with prefix 1 byte)',
Uint8Array.from([1]),
'F7NZ'
);
testDecode(
'Uint8Array (with prefix 2 bytes)',
Uint8Array.from([0xfc, 0, 1]),
'25GUyv'
);
testDecode(
'Uint8Array (with prefix 4 bytes)',
Uint8Array.from([0xfd, 17, 18, 19, 20]),
'Mwz15xP2'
);
});

describe('encoding', (): void => {
const testEncode = (to: 'toHex' | 'toString' | 'toU8a', expected: string | Uint8Array): void =>
it(`can encode ${to}`, (): void => {
const a = registry.createType('EthereumLookupSource', '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887');

expect(a[to]()).toEqual(expected);
});

testEncode(
'toHex',
'0xff4119b2e6c3cb618f4f0b93ac77f9beec7ff02887'
);
testEncode(
'toString',
'0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887'
);
testEncode(
'toU8a',
Uint8Array.from([
255,
0x41, 0x19, 0xb2, 0xe6, 0xc3, 0xcb, 0x61, 0x8f, 0x4f, 0x0b,
0x93, 0xac, 0x77, 0xf9, 0xbe, 0xec, 0x7f, 0xf0, 0x28, 0x87
])
);
});

describe('utility', (): void => {
it('equals on AccountId', (): void => {
const addr = '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887';

expect(registry.createType('EthereumLookupSource', addr).eq(addr)).toBe(true);
});

it('equals on AccountIndex', (): void => {
// see the test below - these are equivalent (with different prefix encoding)
expect(registry.createType('EthereumLookupSource', '2jpAFn').eq('25GUyv')).toBe(true);
});
});
});
Loading