Skip to content

Commit

Permalink
Fix issues and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
philogicae committed Sep 24, 2024
1 parent 4ac26d5 commit 0023c67
Show file tree
Hide file tree
Showing 19 changed files with 460 additions and 320 deletions.
4 changes: 2 additions & 2 deletions packages/account/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ export type SignableMessage = {
export type EphAccount = {
address: string
publicKey: string
privateKey?: string
mnemonic?: string
privateKey: string
mnemonic: string
}
37 changes: 24 additions & 13 deletions packages/avalanche/__tests__/account.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Decimal } from 'decimal.js'

import { EphAccount } from '../../account/src'
import { EthereumMockProvider } from '../../evm/src'
import { HashedMessage, ItemType, PostContent, PostMessageBuilder, prepareAlephMessage } from '../../message/src'
Expand All @@ -7,9 +9,10 @@ async function createEphemeralAvax(): Promise<EphAccount> {
const keypair = await avalanche.getKeyPair()

return {
address: keypair.getAddressString(),
address: avalanche.getEVMAddress(keypair),
publicKey: keypair.getPublicKey().toString('hex'),
privateKey: keypair.getPrivateKey().toString('hex'),
mnemonic: '',
}
}

Expand All @@ -20,6 +23,12 @@ describe('Avalanche accounts', () => {
ephemeralAccount = await createEphemeralAvax()
})

it('should import an avalanche account using a private key', async () => {
const accountFromPrivate = await avalanche.importAccountFromPrivateKey(ephemeralAccount.privateKey)

expect(ephemeralAccount.address).toStrictEqual(accountFromPrivate.address)
})

it('should retrieved an avalanche keypair from an hexadecimal private key', async () => {
const { account, privateKey } = await avalanche.newAccount()

Expand All @@ -33,23 +42,19 @@ describe('Avalanche accounts', () => {

it('should throw Error to get a Keypair', async () => {
const fakePrivateKey = 'a'
const fct = async () => await avalanche.importAccountFromPrivateKey(fakePrivateKey)
const fct = async () => await avalanche.importAccountFromPrivateKey(fakePrivateKey, avalanche.ChainType.X_CHAIN)

await expect(fct).rejects.toThrow('Invalid private key')
})

it('should import an avalanche accounts using a provider', async () => {
const { address, privateKey } = ephemeralAccount
if (!privateKey) throw Error('Can not retrieve privateKey inside ephemeralAccount.json')

const provider = new EthereumMockProvider({
address,
privateKey,
networkVersion: 31,
it('should import an avalanche account using a provider', async () => {
const mockProvider = new EthereumMockProvider({
address: '0x1234567890AbcdEF1234567890aBcdef12345678',
privateKey: '0x1234567890AbcdEF1234567890aBcdef12345678',
networkVersion: 43114,
})

const accountFromProvider = await avalanche.getAccountFromProvider(provider)
expect(accountFromProvider.address).toStrictEqual(address)
const accountFromProvider = await avalanche.getAccountFromProvider(mockProvider)
expect(accountFromProvider.address).toStrictEqual(mockProvider.getAddress())
})

// @todo: Fix this test! We should unit test the cosmos account features, not to send messages to the network and if so, at least mock the backend....
Expand Down Expand Up @@ -159,4 +164,10 @@ describe('Avalanche accounts', () => {
expect(verif).toStrictEqual(false)
expect(verifB).toStrictEqual(false)
})

it('should retrieve ALEPH balance for each account', async () => {
const accountFromPrivate = await avalanche.importAccountFromPrivateKey(ephemeralAccount.privateKey!)

expect(await accountFromPrivate.getALEPHBalance()).toStrictEqual(new Decimal(0))
})
})
85 changes: 56 additions & 29 deletions packages/avalanche/src/account.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,48 @@
import { BinTools, Buffer as AvaBuff, AvalancheCore as Avalanche } from 'avalanche'
import { KeyPair, KeyChain } from 'avalanche/dist/apis/avm'
import { SignableMessage } from '@aleph-sdk/account'

Check failure on line 1 in packages/avalanche/src/account.ts

View workflow job for this annotation

GitHub Actions / lint

Unable to resolve path to module '@aleph-sdk/account'
import { Blockchain } from '@aleph-sdk/core'

Check failure on line 2 in packages/avalanche/src/account.ts

View workflow job for this annotation

GitHub Actions / lint

Unable to resolve path to module '@aleph-sdk/core'
import { ChainData, ChainMetadata, ChangeRpcParam, EVMAccount, hexToDec, JsonRPCWallet, RpcId } from '@aleph-sdk/evm'

Check failure on line 3 in packages/avalanche/src/account.ts

View workflow job for this annotation

GitHub Actions / lint

Unable to resolve path to module '@aleph-sdk/evm'
import { Buffer as AvaBuff, AvalancheCore as Avalanche, BinTools } from 'avalanche'
import { KeyChain, KeyPair } from 'avalanche/dist/apis/avm'
import { KeyPair as EVMKeyPair } from 'avalanche/dist/apis/evm'
import { ethers, providers } from 'ethers'
import { privateToAddress } from 'ethereumjs-util'
import { Blockchain } from '@aleph-sdk/core'
import { SignableMessage, BaseProviderWallet } from '@aleph-sdk/account'
import { ChangeRpcParam, RpcId, EVMAccount, JsonRPCWallet, ChainData } from '@aleph-sdk/evm'
import { providers, utils, Wallet } from 'ethers'

import { digestMessage, verifyAvalanche } from './verify'

/**
* AvalancheAccount implements the Account class for the Avalanche protocol.
* It is used to represent an Avalanche account when publishing a message on the Aleph network.
*/
export class AvalancheAccount extends EVMAccount {
public declare readonly wallet?: BaseProviderWallet
public readonly keyPair?: KeyPair | EVMKeyPair

constructor(
signerOrWallet: KeyPair | EVMKeyPair | BaseProviderWallet | ethers.providers.JsonRpcProvider,
signerOrWallet: KeyPair | EVMKeyPair | Wallet | providers.JsonRpcProvider | JsonRPCWallet,
address: string,
publicKey?: string,
rpcId?: number,
) {
super(address, publicKey)
if (signerOrWallet instanceof ethers.providers.JsonRpcProvider) this.wallet = new JsonRPCWallet(signerOrWallet)
else if (signerOrWallet instanceof KeyPair || signerOrWallet instanceof EVMKeyPair) this.keyPair = signerOrWallet
else this.wallet = signerOrWallet
this.selectedRpcId = rpcId || RpcId.AVAX
if (signerOrWallet instanceof KeyPair || signerOrWallet instanceof EVMKeyPair) {
this.keyPair = signerOrWallet
signerOrWallet = new Wallet(signerOrWallet.getPrivateKey().toString('hex'))
}
if (signerOrWallet instanceof providers.JsonRpcProvider) this.wallet = new JsonRPCWallet(signerOrWallet)
else if (signerOrWallet instanceof Wallet && !signerOrWallet.provider) {
const network = ChainMetadata[rpcId || this.selectedRpcId]
const provider = new providers.JsonRpcProvider(network.rpcUrls.at(0), {
name: network.chainName,
chainId: hexToDec(network.chainId),
})
this.wallet = new JsonRPCWallet(signerOrWallet.connect(provider))
} else if (signerOrWallet instanceof JsonRPCWallet) {
this.wallet = signerOrWallet
}
}

override getChain(): Blockchain {
if (this.keyPair) return Blockchain.AVAX
if (this.wallet) return Blockchain.ETH
if (this.keyPair || this.wallet) return Blockchain.AVAX

throw new Error('Cannot determine chain')
}
Expand All @@ -45,6 +58,11 @@ export class AvalancheAccount extends EVMAccount {
override async askPubKey(): Promise<void> {
if (this.publicKey) return
if (!this.wallet) throw Error('PublicKey Error: No providers are setup')

if (this.wallet instanceof Wallet) {
this.publicKey = this.wallet.publicKey
return
}
this.publicKey = await this.wallet.getPublicKey()
return
}
Expand Down Expand Up @@ -91,11 +109,11 @@ export enum ChainType {
* @param chain Avalanche chain type: c-chain | x-chain
* @returns key chains
*/
async function getKeyChain(chain = ChainType.X_CHAIN) {
async function getKeyChain(chain = ChainType.C_CHAIN) {
return new KeyChain(new Avalanche().getHRP(), chain)
}

export async function getKeyPair(privateKey?: string, chain = ChainType.X_CHAIN): Promise<KeyPair> {
export async function getKeyPair(privateKey?: string, chain = ChainType.C_CHAIN): Promise<KeyPair> {
const keyChain = await getKeyChain(chain)
const keyPair = keyChain.makeKey()

Expand Down Expand Up @@ -123,14 +141,19 @@ export async function getKeyPair(privateKey?: string, chain = ChainType.X_CHAIN)
*/
export async function importAccountFromPrivateKey(
privateKey: string,
chain = ChainType.X_CHAIN,
chain = ChainType.C_CHAIN,
): Promise<AvalancheAccount> {
const keyPair = await getKeyPair(privateKey, chain)
return new AvalancheAccount(keyPair, keyPair.getAddressString(), keyPair.getPublicKey().toString('hex'))
if (chain === ChainType.X_CHAIN) {
const keyPair = await getKeyPair(privateKey, chain)
return new AvalancheAccount(keyPair, keyPair.getAddressString(), keyPair.getPublicKey().toString('hex'))
} else {
const wallet = new Wallet(privateKey)
return new AvalancheAccount(wallet, wallet.address, wallet.publicKey)
}
}

/**
* Imports an ethereum account given a mnemonic and the 'ethers' package.
* Imports an Avalanche account given a mnemonic and the 'ethers' package.
*
* It creates an avalanche wallet containing information about the account, extracted in the AvalancheAccount constructor.
*
Expand All @@ -141,33 +164,37 @@ export async function importAccountFromPrivateKey(
export async function importAccountFromMnemonic(
mnemonic: string,
derivationPath = "m/44'/60'/0'/0/0",
chain = ChainType.X_CHAIN,
chain = ChainType.C_CHAIN,
): Promise<AvalancheAccount> {
const wallet = ethers.Wallet.fromMnemonic(mnemonic, derivationPath)
const wallet = Wallet.fromMnemonic(mnemonic, derivationPath)

return await importAccountFromPrivateKey(wallet.privateKey, chain)
}

/**
* Get an account from a Web3 provider (ex: Metamask)
*
* @param {providers.ExternalProvider | ethers.providers.Web3Provider} provider
* @param {providers.ExternalProvider | providers.Web3Provider} provider
* @param requestedRpc Use this params to change the RPC endpoint;
*/
export async function getAccountFromProvider(
provider: ethers.providers.ExternalProvider | ethers.providers.Web3Provider,
provider: providers.ExternalProvider | providers.Web3Provider,
requestedRpc: ChangeRpcParam = RpcId.AVAX,
): Promise<AvalancheAccount> {
const ETHprovider =
provider instanceof ethers.providers.Web3Provider ? provider : new providers.Web3Provider(provider)
const ETHprovider = provider instanceof providers.Web3Provider ? provider : new providers.Web3Provider(provider)
const jrw = new JsonRPCWallet(ETHprovider)

const chainId = Number((typeof requestedRpc === 'number' ? ChainData[requestedRpc] : requestedRpc).chainId)
if (chainId !== (await jrw.provider.getNetwork()).chainId) await jrw.changeNetwork(requestedRpc)
await jrw.connect()

if (jrw.address) {
return new AvalancheAccount(jrw, jrw.address)
return new AvalancheAccount(
jrw,
jrw.address,
undefined,
typeof requestedRpc === 'number' ? requestedRpc : undefined,
)
}
throw new Error('Insufficient permissions')
}
Expand All @@ -182,18 +209,18 @@ export async function getAccountFromProvider(
* @returns A Promise that resolves to the EVM-style address of the account
* @throws An error if the current signer is not associated with the C-Chain
*/
function getEVMAddress(keypair: EVMKeyPair): string {
export function getEVMAddress(keypair: EVMKeyPair): string {
const pkHex = keypair.getPrivateKey().toString('hex')
const pkBuffNative = Buffer.from(pkHex, 'hex')
const ethAddress = privateToAddress(pkBuffNative).toString('hex')
return `0x${ethAddress}`
return utils.getAddress(`0x${ethAddress}`)
}

/**
* Creates a new Avalanche account using a randomly generated privateKey
*/
export async function newAccount(
chain = ChainType.X_CHAIN,
chain = ChainType.C_CHAIN,
): Promise<{ account: AvalancheAccount; privateKey: string }> {
const keypair = await getKeyPair(undefined, chain)
const privateKey = keypair.getPrivateKey().toString('hex')
Expand Down
77 changes: 25 additions & 52 deletions packages/base/__tests__/account.test.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,7 @@
import * as bip39 from 'bip39'
import { ethers } from 'ethers'

import { EphAccount } from '../../account/src'
import { createEphemeralEth, EthereumMockProvider } from '../../evm/src'
import { ItemType, PostMessageBuilder, prepareAlephMessage } from '../../message/src'
import * as base from '../src'
import { PostMessageBuilder, prepareAlephMessage, ItemType } from '../../message/src'
import { EthereumMockProvider } from '@aleph-sdk/evm'
import { EphAccount } from '@aleph-sdk/account'

async function createEphemeralEth(): Promise<EphAccount> {
const mnemonic = bip39.generateMnemonic()
const { address, publicKey, privateKey } = ethers.Wallet.fromMnemonic(mnemonic)

return {
address,
publicKey,
privateKey: privateKey.substring(2),
mnemonic,
}
}

describe('Ethereum accounts', () => {
let ephemeralAccount: EphAccount
Expand All @@ -26,62 +11,50 @@ describe('Ethereum accounts', () => {
})

it('should import a base account using a mnemonic', () => {
const { account, mnemonic } = base.newAccount()
const accountFromMnemonic = base.importAccountFromMnemonic(mnemonic)
const accountFromMnemonic = base.importAccountFromMnemonic(ephemeralAccount.mnemonic)

expect(account.address).toStrictEqual(accountFromMnemonic.address)
expect(ephemeralAccount.address).toStrictEqual(accountFromMnemonic.address)
})

it('should import a base account using a private key', () => {
const mnemonic = bip39.generateMnemonic()
const wallet = ethers.Wallet.fromMnemonic(mnemonic)
const accountFromPrivate = base.importAccountFromPrivateKey(wallet.privateKey)
const accountFromPrivate = base.importAccountFromPrivateKey(ephemeralAccount.privateKey)

expect(wallet.address).toStrictEqual(accountFromPrivate.address)
expect(ephemeralAccount.address).toStrictEqual(accountFromPrivate.address)
})

it('should import a base account using a provider', async () => {
const { address, privateKey } = ephemeralAccount
if (!privateKey) throw Error('Can not retrieve privateKey inside ephemeralAccount.json')

const provider = new EthereumMockProvider({
address,
privateKey,
networkVersion: 31,
const mockProvider = new EthereumMockProvider({
address: '0x1234567890AbcdEF1234567890aBcdef12345678',
privateKey: '0x1234567890AbcdEF1234567890aBcdef12345678',
networkVersion: 8453,
})

const accountFromProvider = await base.getAccountFromProvider(provider)
const accountFromPrivate = base.importAccountFromPrivateKey(privateKey)

expect(accountFromProvider.address).toStrictEqual(accountFromPrivate.address)
const accountFromProvider = await base.getAccountFromProvider(mockProvider)
expect(accountFromProvider.address).toStrictEqual(mockProvider.getAddress())
})

it('should get the same signed message for each account', async () => {
const { address, privateKey } = ephemeralAccount
if (!privateKey) throw Error('Can not retrieve privateKey inside ephemeralAccount.json')

const provider = new EthereumMockProvider({
address,
privateKey,
networkVersion: 31,
})
const { account, mnemonic } = base.newAccount()
const accountFromProvider = await base.getAccountFromProvider(provider)
const accountFromPrivate = await base.importAccountFromMnemonic(mnemonic)
const accountFromMnemonic = base.importAccountFromMnemonic(ephemeralAccount.mnemonic!)
const accountFromPrivate = base.importAccountFromPrivateKey(ephemeralAccount.privateKey!)

const builtMessage = PostMessageBuilder({
account,
account: accountFromMnemonic,
channel: 'TEST',
storageEngine: ItemType.inline,
timestamp: Date.now() / 1000,
content: { address: account.address, time: 15, type: '' },
content: { address: accountFromMnemonic.address, time: 15, type: '' },
})

const hashedMessage = await prepareAlephMessage({
message: builtMessage,
})

expect(account.sign(hashedMessage)).toStrictEqual(accountFromPrivate.sign(hashedMessage))
expect(account.sign(hashedMessage)).toStrictEqual(accountFromProvider.sign(hashedMessage))
expect(accountFromMnemonic.sign(hashedMessage)).toStrictEqual(accountFromPrivate.sign(hashedMessage))
})

it('should retrieve ALEPH balance for each account', async () => {
const accountFromMnemonic = base.importAccountFromMnemonic(ephemeralAccount.mnemonic!)
const accountFromPrivate = base.importAccountFromPrivateKey(ephemeralAccount.privateKey!)

expect(await accountFromMnemonic.getALEPHBalance()).toStrictEqual(await accountFromPrivate.getALEPHBalance())
})
})
Loading

0 comments on commit 0023c67

Please sign in to comment.