Skip to content

Commit

Permalink
Add IncrementingNonceAddressGenerator for deployments on live networks
Browse files Browse the repository at this point in the history
  • Loading branch information
hobofan committed Dec 18, 2018
1 parent dfb697b commit f9f045d
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 16 deletions.
35 changes: 27 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"ganache-cli": "6.1.8",
"jest": "^23.6.0",
"keccak": "1.4.0",
"rlp": "2.1.0",
"rlp": "^2.1.0",
"solidity-coverage": "0.5.11",
"solparse": "2.2.5",
"truffle": "beta",
Expand Down
95 changes: 88 additions & 7 deletions tools/deployment_tools.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const Web3 = require('web3');
const fs = require('fs');
const path = require('path');
const rlp = require('rlp');

const STATE_NEW = 'new';
const STATE_LINKED = 'linked';
Expand Down Expand Up @@ -120,8 +121,7 @@ class Contract {
}

/**
* Performs all linking for a contract and returns the linked bytecode.
* Assumes that the addresses of all linked dependencies have previously been set.
* Returns the previously linked bytecode.
*
* @returns {string} The linked bytecode of the contract.
*/
Expand Down Expand Up @@ -217,7 +217,9 @@ class Contract {
}

/**
* Replaces all linking placeholder in this contract's bytecode with address.
* Replaces all linking placeholder in this contract's bytecode with addresses.
* Assumes that the addresses of all linked dependencies have previously been set.
*
* See {@link Contract#_linkBytecodeReplacement}.
*/
_linkBytecode() {
Expand Down Expand Up @@ -327,6 +329,49 @@ class IncrementingAddressGenerator {
}
}

/**
* A AddressGenerator that returns addresses based on the provided `from` address and
* that address's current transaction nonce (which is auto-incremented).
*
* Suitable for deployment on a running network.
*/
class IncrementingNonceAddressGenerator {
/**
* @param {string} fromAddress The address that is used for deployment.
* @param {number} startingNonce The first nonce to used for generating addresses.
* This should be equivalent to the number of transactions made
* from the `fromAddress`.
*/
constructor(fromAddress, startingNonce) {
if (typeof fromAddress === 'undefined') {
throw new Error('"fromAddress" address not provided');
}
if (typeof startingNonce === 'undefined') {
throw new Error('"startingNonce" not provided');
}

this.fromAddress = fromAddress;
this.nextNonce = startingNonce;
}

/**
* Returns the next available address.
*
* @return {string} Next address that will be used for a contract created by
* a transaction from `fromAddress` at the current transaction
* count (= nonce).
*/
generateAddress() {
const addressBytes = Web3.utils.sha3(rlp.encode([this.fromAddress, this.nextNonce]))
.slice(12)
.substring(14);
const address = `0x${addressBytes}`;
this.nextNonce += 1;

return address;
}
}

/**
* A contract registry that allows for registering contracts and planning deployment.
*/
Expand Down Expand Up @@ -384,13 +429,10 @@ class ContractRegistry {
const output = {
};

Object.values(this.contracts)
this.contracts
.forEach((contract) => {
const address = contract.getAddress();
const constructor = contract.constructorData;
if (!constructor) {
throw new Error(`constructorData for contract ${contract.contractName} is missing. This probably means that .instantiate() has not been called for the contract`);
}

output[address] = {
balance: '0',
Expand All @@ -401,6 +443,44 @@ class ContractRegistry {
return output;
}

/**
* Generate transaction objects that can be passed as an argument
* to `web3.eth.sendTransaction()`.
* This allows for deployment on a live network.
*
* @param {string} fromAddress See {@ IncrementingNonceAddressGenerator#constructor}.
* @param {number} startingNonce See {@ IncrementingNonceAddressGenerator#constructor}.
*
* @returns {Array.<object>} The transaction objects that can be used for deployment.
*/
toLiveTransactionObjects(fromAddress, startingNonce) {
const addressGenerator = new IncrementingNonceAddressGenerator(fromAddress, startingNonce);

this._orderContracts();
this.contracts.forEach(contract => contract.setAddress(addressGenerator));
this.contracts.forEach(contract => contract.instantiate());

const transactionObjects = [];
this.contracts
.forEach((contract, i) => {
const transactionObject = {
// fields for web3.eth.sendTransaction()
from: fromAddress,
data: contract.constructorData,
nonce: startingNonce + i,
};
const deploymentObject = {
transactionObject,
// metadata
address: contract.getAddress(),
contractName: contract.contractName,
};
transactionObjects.push(deploymentObject);
});

return transactionObjects;
}

/**
* Orders all contracts in the order of sequential deployment.
* This is determined by the amount of transitive dependencies (including self).
Expand All @@ -415,4 +495,5 @@ module.exports = {
Contract,
ContractRegistry,
IncrementingAddressGenerator,
IncrementingNonceAddressGenerator,
};
46 changes: 46 additions & 0 deletions tools/test/deployment_tools.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const Contract = require('../deployment_tools.js').Contract;
const ContractRegistry = require('../deployment_tools.js').ContractRegistry;
const IncrementingNonceAddressGenerator = require('../deployment_tools.js').IncrementingNonceAddressGenerator;

const EIP20TokenFile = require('../../build/contracts/EIP20Token.json');

Expand Down Expand Up @@ -115,4 +116,49 @@ describe('ContractRegistry', () => {
expect(Object.values(output)[1].constructor).toContain(EIP20Token.getAddress().slice(2));
});
});

describe('toLiveTransactionObjects', () => {
test('returns correctly formed transaction objects', () => {
const EIP20Token = Contract.loadTruffleContract(
'EIP20Token',
['MYT', 'MyToken', 18],
{ rootDir },
);

const registry = new ContractRegistry();
registry.addContract(EIP20Token);

const output = registry.toLiveTransactionObjects('0xc02345a911471fd46c47c4d3c2e5c85f5ae93d13', 0);
expect(output[0]).toEqual({
from: '0xc02345a911471fd46c47c4d3c2e5c85f5ae93d13',
nonce: 0,
data: EIP20Token.constructorData,

address: '0x5eceb671884153e2e312f8c5ae8e38fdc473c18d',
contractName: 'EIP20Token',
});
});
});
});

describe('IncrementingNonceAddressGenerator', () => {
test('generates correct first address', () => {
const generator = new IncrementingNonceAddressGenerator('0xc02345a911471fd46c47c4d3c2e5c85f5ae93d13', 0);
const address = generator.generateAddress();

expect(address).toEqual('0x5eceb671884153e2e312f8c5ae8e38fdc473c18d');
});

test('generates multiple addresses correctly', () => {
const generator = new IncrementingNonceAddressGenerator('0xc02345a911471fd46c47c4d3c2e5c85f5ae93d13', 0);
const address1 = generator.generateAddress();
const address2 = generator.generateAddress();
const address3 = generator.generateAddress();
const address4 = generator.generateAddress();

expect(address1).toEqual('0x5eceb671884153e2e312f8c5ae8e38fdc473c18d');
expect(address2).toEqual('0x20e8a23a99c26334aed05051d6e5c6cdf50d63f6');
expect(address3).toEqual('0xf0cd575450fc03b90eead03d65e79741a19665e4');
expect(address4).toEqual('0x10ef71366ad76d6bddddc66677c38e137aa564db');
});
});

0 comments on commit f9f045d

Please sign in to comment.