From f8087ae39ce7631425b6b4763347a4f9618f9730 Mon Sep 17 00:00:00 2001 From: Richard Moore Date: Fri, 3 Jan 2020 18:20:15 -0500 Subject: [PATCH] Added utility function to compute CREATE2 addresses (#697). --- src.ts/utils/address.ts | 46 ++++++++++++++++++++++++++++++- src.ts/utils/index.ts | 3 +- tests/test-account.js | 61 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 2 deletions(-) diff --git a/src.ts/utils/address.ts b/src.ts/utils/address.ts index 63a3fa9b4..c6501c79b 100644 --- a/src.ts/utils/address.ts +++ b/src.ts/utils/address.ts @@ -3,7 +3,7 @@ // We use this for base 36 maths import BN from 'bn.js'; -import { arrayify, stripZeros, hexlify } from './bytes'; +import { arrayify, concat, stripZeros, hexlify } from './bytes'; import { BigNumber } from './bignumber'; import { keccak256 } from './keccak256'; import { encode } from './rlp'; @@ -140,3 +140,47 @@ export function getContractAddress(transaction: { from: string, nonce: Arrayish ])).substring(26)); } +export type Create2Options = { + from: string, + salt: Arrayish, + initCode?: Arrayish, + initCodeHash?: Arrayish, +}; + +// See: https://eips.ethereum.org/EIPS/eip-1014 +export function getCreate2Address(options: Create2Options): string { + let initCodeHash = options.initCodeHash; + if (options.initCode) { + if (initCodeHash) { + if (keccak256(options.initCode) !== initCodeHash) { + errors.throwError("initCode/initCodeHash mismatch", errors.INVALID_ARGUMENT, { + arg: "options", value: options + }); + } + } else { + initCodeHash = keccak256(options.initCode); + } + } + + if (!initCodeHash) { + errors.throwError("missing initCode or initCodeHash", errors.INVALID_ARGUMENT, { + arg: "options", value: options + }); + } + + const from = getAddress(options.from); + + const salt = arrayify(options.salt); + if (salt.length !== 32) { + errors.throwError("invalid salt", errors.INVALID_ARGUMENT, { + arg: "options", value: options + }); + } + + return getAddress("0x" + keccak256(concat([ + "0xff", + from, + salt, + initCodeHash + ])).substring(26)); +} diff --git a/src.ts/utils/index.ts b/src.ts/utils/index.ts index f2a82e05d..f224ba6d6 100644 --- a/src.ts/utils/index.ts +++ b/src.ts/utils/index.ts @@ -1,7 +1,7 @@ 'use strict'; import { AbiCoder, defaultAbiCoder, formatSignature, formatParamType, parseSignature, parseParamType } from './abi-coder'; -import { getAddress, getContractAddress, getIcapAddress } from './address'; +import { getAddress, getContractAddress, getCreate2Address, getIcapAddress } from './address'; import * as base64 from './base64'; import { BigNumber, bigNumberify } from './bignumber'; import { arrayify, concat, hexDataSlice, hexDataLength, hexlify, hexStripZeros, hexZeroPad, isHexString, joinSignature, padZeros, splitSignature, stripZeros } from './bytes'; @@ -103,6 +103,7 @@ export { getAddress, getIcapAddress, getContractAddress, + getCreate2Address, formatEther, parseEther, diff --git a/tests/test-account.js b/tests/test-account.js index e78bf251a..64ea7bc8d 100644 --- a/tests/test-account.js +++ b/tests/test-account.js @@ -36,3 +36,64 @@ describe('Checksum and ICAP address generation', function() { }); }); }); + +describe("Create2 Address Generation", function() { + var tests = [ + { + name: "Example 0", + from: "0x0000000000000000000000000000000000000000", + salt: "0x0000000000000000000000000000000000000000000000000000000000000000", + initCode: "0x00", + expected: "0x4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38" + }, + { + name: "Example 1", + from: "0xdeadbeef00000000000000000000000000000000", + salt: "0x0000000000000000000000000000000000000000000000000000000000000000", + initCode: "0x00", + expected: "0xB928f69Bb1D91Cd65274e3c79d8986362984fDA3" + }, + { + name: "Example 2", + from: "0xdeadbeef00000000000000000000000000000000", + salt: "0x000000000000000000000000feed000000000000000000000000000000000000", + initCode: "0x00", + expected: "0xD04116cDd17beBE565EB2422F2497E06cC1C9833" + }, + { + name: "Example 3", + from: "0x0000000000000000000000000000000000000000", + salt: "0x0000000000000000000000000000000000000000000000000000000000000000", + initCode: "0xdeadbeef", + expected: "0x70f2b2914A2a4b783FaEFb75f459A580616Fcb5e" + }, + { + name: "Example 4", + from: "0x00000000000000000000000000000000deadbeef", + salt: "0x00000000000000000000000000000000000000000000000000000000cafebabe", + initCode: "0xdeadbeef", + expected: "0x60f3f640a8508fC6a86d45DF051962668E1e8AC7" + }, + { + name: "Example 5", + from: "0x00000000000000000000000000000000deadbeef", + salt: "0x00000000000000000000000000000000000000000000000000000000cafebabe", + initCode: "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", + expected: "0x1d8bfDC5D46DC4f61D6b6115972536eBE6A8854C" + }, + { + name: "Example 6", + from: "0x0000000000000000000000000000000000000000", + salt: "0x0000000000000000000000000000000000000000000000000000000000000000", + initCode: "0x", + expected: "0xE33C0C7F7df4809055C3ebA6c09CFe4BaF1BD9e0" + }, + ]; + + tests.forEach(function(test) { + it("correctly computes the Create2 address - " + test.name, function() { + const address = ethers.utils.getCreate2Address(test); + assert.equal(address, test.expected, "correctly computes Create2 address"); + }); + }); +});