Skip to content

Commit

Permalink
Implement rlpEncode and getContractAddress (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
sz-piotr authored Feb 2, 2020
1 parent 92697eb commit 145808a
Show file tree
Hide file tree
Showing 5 changed files with 550 additions and 0 deletions.
20 changes: 20 additions & 0 deletions packages/evm/src/getContractAddress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Address } from './Address'
import { Tuple, rlpEncode, rlpEncodeNumber } from './rlp'
import { Byte } from './Byte'
import { keccak256 } from 'js-sha3'

export function getContractAddress (sender: Address, nonce: number) {
const bytes = rlpEncode(new Tuple([
addressToBytes(sender),
rlpEncodeNumber(nonce),
]))
return hashToAddress(keccak256(bytes))
}

function addressToBytes (value: Address) {
return value.match(/../g)!.map(x => parseInt(x, 16) as Byte)
}

function hashToAddress (hash: string) {
return hash.substring(24) as Address
}
72 changes: 72 additions & 0 deletions packages/evm/src/rlp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Byte } from './Byte'

export class Tuple {
constructor (public items: (Tuple | Byte[])[]) {}
}

export function rlpEncode (value: Tuple | Byte[]): Byte[] {
if (Array.isArray(value)) {
return rlpEncodeBytes(value)
} else {
return rlpEncodeTuple(value)
}
}

function rlpEncodeTuple (value: Tuple) {
const items = rlpEncodeTupleItems(value)
if (items.length < 56) {
return [
(192 + items.length) as Byte,
...items,
]
} else { // if items.length < 2^64 - should be always true
const lengthEncoded = rlpEncodeNumber(items.length)
return [
(247 + lengthEncoded.length) as Byte,
...lengthEncoded,
...items,
]
}
}

function rlpEncodeTupleItems (value: Tuple) {
const result: Byte[] = []
for (const item of value.items) {
result.push(...rlpEncode(item))
}
return result
}

function rlpEncodeBytes (value: Byte[]): Byte[] {
if (value.length === 1 && value[0] < 128) {
return value
} else if (value.length < 56) {
return [
(128 + value.length) as Byte,
...value,
]
} else { // if value.length < 2^64 - should be always true
const lengthEncoded = rlpEncodeNumber(value.length)
return [
(183 + lengthEncoded.length) as Byte,
...lengthEncoded,
...value,
]
}
}

export function rlpEncodeNumber (value: number): Byte[] {
if (value === 0) {
return []
}
let str = value.toString(16)
if (str.length % 2 !== 0) {
str = '0' + str
}
return str.match(/../g)!.map(x => parseInt(x, 16) as Byte)
}

export function rlpDecode (value: Byte[]): Tuple | Byte[] {
// TODO: implement
throw new TypeError('Not implemented!')
}
29 changes: 29 additions & 0 deletions packages/evm/test/getContractAddress.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { expect } from 'chai'
import { getContractAddress } from '../src/getContractAddress'
import { Address } from '../src/Address'

describe('getContractAddress', () => {
const testCases = [
{
sender: 'ca35b7d915458ef540ade6068dfe2f44e8fa733c',
nonce: 0,
contract: '692a70d2e424a56d2c6c27aa97d1a86395877b3a',
},
{
sender: 'ca35b7d915458ef540ade6068dfe2f44e8fa733c',
nonce: 1,
contract: 'bbf289d846208c16edc8474705c748aff07732db',
},
{
sender: '14723a09acff6d2a60dcdf7aa4aff308fddc160c',
nonce: 0,
contract: '0fdf4894a3b7c5a101686829063be52ad45bcfb7',
},
]
for (const testCase of testCases) {
it(`works for ${testCase.sender} and nonce ${testCase.nonce}`, () => {
const contract = getContractAddress(testCase.sender as Address, testCase.nonce)
expect(contract).to.equal(testCase.contract)
})
}
})
46 changes: 46 additions & 0 deletions packages/evm/test/rlp/rlp.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { expect } from 'chai'
import testCasesJSON from './testCases.json'
import { Byte } from '../../src/Byte'
import { Tuple, rlpEncode, rlpDecode } from '../../src/rlp'

const testCases = testCasesJSON.map(test => ({
name: test.name,
decoded: mapDecoded(test.decoded),
encoded: mapHex(test.encoded),
}))

type Decoded = (string | Decoded)[] | string

function mapDecoded (value: Decoded): Tuple | Byte[] {
if (Array.isArray(value)) {
return new Tuple(value.map(mapDecoded))
} else {
return mapHex(value)
}
}

function mapHex (value: string) {
const noPrefix = value.substring(2)
if (!noPrefix) {
return []
}
return noPrefix.match(/../g)!.map(x => parseInt(x, 16) as Byte)
}

describe('rlpEncode', () => {
for (const testCase of testCases) {
it(testCase.name, () => {
const result = rlpEncode(testCase.decoded)
expect(result).to.deep.equal(testCase.encoded)
})
}
})

describe.skip('rlpDecode', () => {
for (const testCase of testCases) {
it(testCase.name, () => {
const result = rlpDecode(testCase.encoded)
expect(result).to.deep.equal(testCase.decoded)
})
}
})
Loading

0 comments on commit 145808a

Please sign in to comment.