feat(@embark/specialconfigs): introduce dynamic addresses (#1873)

* fix embarkjs generation

fix ens setProvider

fix embarkjs objects

fix generated embarkjs provider

generate contracts

fix embarkjs-ens

* address some of the issues in the code review

* feat(@embark/specialconfigs): introduce dynamic addresses

This commit introduces a new Smart Contract configuration addressHandler
that lets users define a function to "lazily" compute the address of the
Smart Contract in question.

This is useful when a third-party takes care of deploying a dependency
Smart Contract, but the address of that Smart Contract only being available
at run-time.

Example:
```
deploy: {
  SimpleStorage: {
    fromIndex: 0,
    args: [100],
  },
  OtherContract: {
    deps: ['SimpleStorage'],
    address: async (deps) => {
      // use `deps.contracts.SimpleStorage` to determine address
    },
    abiDefinition: ABI
  },
}
```

In the example above, OtherContract will be deployed after SimpleStorage
because it uses the deps property to define Smart Contracts that it depends
on. All dependencies are exposed on the addressHandler function parameter
similar to deployment hooks.

Closes #1690
This commit is contained in:
Pascal Precht 2019-09-06 21:02:51 +02:00 committed by Iuri Matias
parent 022a3c11ec
commit 86ee867689
6 changed files with 69 additions and 4 deletions

View File

@ -323,7 +323,10 @@ class ContractsManager {
contract.type = 'file'; contract.type = 'file';
contract.className = className; contract.className = className;
if (contract.address) { if (contract.address && typeof contract.address === 'function') {
contract.addressHandler = contract.address;
delete contract.addres;
} else if (contract.address && typeof contract.address === 'string') {
contract.deployedAddress = contract.address; contract.deployedAddress = contract.address;
} }

View File

@ -66,6 +66,19 @@ class Deployment {
Object.values(contracts).forEach((contract) => { Object.values(contracts).forEach((contract) => {
function deploy(result, callback) { function deploy(result, callback) {
if (typeof result === 'function') callback = result; if (typeof result === 'function') callback = result;
if (contract.addressHandler) {
return self.events.request('deployment:contract:address', {contract}, (err, address) => {
if (err) {
errors.push(err);
} else {
contract.address = address;
contract.deployedAddress = address;
self.logger.info(__('{{contractName}} already deployed at {{address}}', {contractName: contract.className.bold.cyan, address: contract.address.bold.cyan}));
self.events.emit("deployment:contract:deployed", contract);
}
callback();
});
}
self.deployContract(contract, (err) => { self.deployContract(contract, (err) => {
if (err) { if (err) {
errors.push(err); errors.push(err);

View File

@ -9,6 +9,17 @@ class FunctionConfigs {
this.config = embark.config; this.config = embark.config;
} }
async executeContractAddressHandler(contract, cb) {
try {
const logger = Utils.createLoggerWithPrefix(this.logger, 'addressHandler >');
const dependencies = await this.getDependenciesObject(logger);
const address = await contract.addressHandler(dependencies);
cb(null, address);
} catch (err) {
cb(new Error(`Error running addressHandler for ${contract.className}: ${err.message}`));
}
}
async beforeAllDeployAction(cb) { async beforeAllDeployAction(cb) {
try { try {
const beforeDeployFn = this.config.contractsConfig.beforeDeploy; const beforeDeployFn = this.config.contractsConfig.beforeDeploy;
@ -76,6 +87,10 @@ class FunctionConfigs {
// TODO: for this to work correctly we need to add a default from address to the contract // TODO: for this to work correctly we need to add a default from address to the contract
if (contract.deploy === false) continue; if (contract.deploy === false) continue;
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
const contractRegisteredInVM = await this.checkContractRegisteredInVM(contract);
if (!contractRegisteredInVM) {
await this.events.request2("embarkjs:contract:runInVM", contract);
}
let contractInstance = await this.events.request2("runcode:eval", contract.className); let contractInstance = await this.events.request2("runcode:eval", contract.className);
args.contracts[contract.className] = contractInstance; args.contracts[contract.className] = contractInstance;
} }
@ -90,6 +105,12 @@ class FunctionConfigs {
}); });
} }
async checkContractRegisteredInVM(contract) {
const checkContract = `
return typeof ${contract.className} !== 'undefined';
`;
return await this.events.request2('runcode:eval', checkContract);
}
} }
module.exports = FunctionConfigs; module.exports = FunctionConfigs;

View File

@ -15,6 +15,7 @@ class SpecialConfigs {
this.listConfigs = new ListConfigs(embark); this.listConfigs = new ListConfigs(embark);
this.functionConfigs = new FunctionConfigs(embark); this.functionConfigs = new FunctionConfigs(embark);
this.events.setCommandHandler('deployment:contract:address', this.executeAddressHandlerForContract.bind(this));
this.embark.registerActionForEvent('deployment:deployContracts:beforeAll', this.beforeAllDeployAction.bind(this)); this.embark.registerActionForEvent('deployment:deployContracts:beforeAll', this.beforeAllDeployAction.bind(this));
this.embark.registerActionForEvent('deployment:deployContracts:afterAll', this.afterAllDeployAction.bind(this)); this.embark.registerActionForEvent('deployment:deployContracts:afterAll', this.afterAllDeployAction.bind(this));
this.embark.registerActionForEvent("deployment:contract:deployed", this.doOnDeployAction.bind(this)); this.embark.registerActionForEvent("deployment:contract:deployed", this.doOnDeployAction.bind(this));
@ -22,6 +23,10 @@ class SpecialConfigs {
this.embark.registerActionForEvent('deployment:contract:beforeDeploy', this.beforeDeployAction.bind(this)); this.embark.registerActionForEvent('deployment:contract:beforeDeploy', this.beforeDeployAction.bind(this));
} }
async executeAddressHandlerForContract(params, cb) {
return this.functionConfigs.executeContractAddressHandler(params.contract, cb);
}
async beforeAllDeployAction(_params, cb) { async beforeAllDeployAction(_params, cb) {
if (typeof this.config.contractsConfig.beforeDeploy !== 'function') { if (typeof this.config.contractsConfig.beforeDeploy !== 'function') {
return this.listConfigs.beforeAllDeployAction(cb); return this.listConfigs.beforeAllDeployAction(cb);

View File

@ -183,7 +183,7 @@ function prepareContractsConfig(config) {
config.contracts[contractName].gasPrice = getWeiBalanceFromString(gasPrice); config.contracts[contractName].gasPrice = getWeiBalanceFromString(gasPrice);
} }
if (address) { if (address && typeof address === 'string') {
config.contracts[contractName].address = extendZeroAddressShorthand(address); config.contracts[contractName].address = extendZeroAddressShorthand(address);
} }

View File

@ -131,7 +131,7 @@ production: {
In order to give users full control over which Smart Contracts should be deployed, Embark comes with a configuration feature called "deployment strategies". Deployment strategies tell Embark whether it should deploy all of the user's Smart Contracts (and its (3rd-party) dependencies, or just deploy individual Smart Contracts. In order to give users full control over which Smart Contracts should be deployed, Embark comes with a configuration feature called "deployment strategies". Deployment strategies tell Embark whether it should deploy all of the user's Smart Contracts (and its (3rd-party) dependencies, or just deploy individual Smart Contracts.
There are two possible strategy options: There are two possible strategy options:
- **implicit** - This is the default. Using the `implicit` strategy, Embark tries to deploy all Smart Contracts configured in the `deploy` configuration, including its (3rd-party) dependencies. - **implicit** - This is the default. Using the `implicit` strategy, Embark tries to deploy all Smart Contracts configured in the `deploy` configuration, including its (3rd-party) dependencies.
- **explicit** - Setting this option to `explicit` tells Embark to deploy the Smart Contracts specified in the `deploy` configuration without their dependencies. This can be combined with [disabling deployment](#Disabling-deployment) of individual Smart Contracts for fine control. - **explicit** - Setting this option to `explicit` tells Embark to deploy the Smart Contracts specified in the `deploy` configuration without their dependencies. This can be combined with [disabling deployment](#Disabling-deployment) of individual Smart Contracts for fine control.
@ -202,6 +202,29 @@ module.exports = {
} }
``` ```
## Dynamic Addresses
There are scenarios in which we want to configure a Smart Contract that is already deployed by a third-party, but its address can only be computed at run-time. For such cases, Embark supports specifying a function as `address`, which returns the address we're interested in. Usually, other Smart Contract instances are needed to perform that computation, so the [`deps` configuration](#Deployment-hooks) comes in handy as well:
```
deploy: {
SimpleStorage: {
fromIndex: 0,
args: [100],
},
OtherContract: {
deps: ['SimpleStorage'],
address: async (deps) => {
// use `deps.contracts.SimpleStorage` to determine and return address
},
abiDefinition: ABI
},
}
```
In the example above, `OtherContract` will be deployed after `SimpleStorage` because it uses the `deps` property to define Smart Contracts that it depends on. All dependencies are exposed on the `address` function parameter similar to [deployment hooks](#Deployment-hooks). Also, notice that the `abiDefinition` is only needed if the Smart Contracts bytecode isn't already on disk.
## Configuring source files ## Configuring source files
By default Embark will look for Smart Contracts inside the folder that's configured in the application's [embark.json](configuration.html#contracts), the default being the `contracts` folder. However, if we want to change the location to look for a single Smart Contract's source, or need to compile a third-party Smart Contract to get hold of its ABI, we can do so by using the `file` property. By default Embark will look for Smart Contracts inside the folder that's configured in the application's [embark.json](configuration.html#contracts), the default being the `contracts` folder. However, if we want to change the location to look for a single Smart Contract's source, or need to compile a third-party Smart Contract to get hold of its ABI, we can do so by using the `file` property.
@ -541,7 +564,7 @@ deploy: {
} }
``` ```
Which will result in Which will result in
``` ```
SimpleStorage > onDeploy > Hello from onDeploy! SimpleStorage > onDeploy > Hello from onDeploy!