Skip to content

Commit

Permalink
feat(@embark/specialconfigs): introduce dynamic addresses (#1873)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
0x-r4bbit authored and iurimatias committed Sep 6, 2019
1 parent 022a3c1 commit 86ee867
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 4 deletions.
5 changes: 4 additions & 1 deletion packages/embark-contracts-manager/src/index.js
Expand Up @@ -323,7 +323,10 @@ class ContractsManager {
contract.type = 'file';
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;
}

Expand Down
13 changes: 13 additions & 0 deletions packages/embark-deployment/src/index.js
Expand Up @@ -66,6 +66,19 @@ class Deployment {
Object.values(contracts).forEach((contract) => {
function deploy(result, callback) {
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) => {
if (err) {
errors.push(err);
Expand Down
21 changes: 21 additions & 0 deletions packages/embark-specialconfigs/src/functionConfigs.js
Expand Up @@ -9,6 +9,17 @@ class FunctionConfigs {
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) {
try {
const beforeDeployFn = this.config.contractsConfig.beforeDeploy;
Expand Down Expand Up @@ -76,6 +87,10 @@ class FunctionConfigs {
// TODO: for this to work correctly we need to add a default from address to the contract
if (contract.deploy === false) continue;
// 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);
args.contracts[contract.className] = contractInstance;
}
Expand All @@ -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;
5 changes: 5 additions & 0 deletions packages/embark-specialconfigs/src/index.js
Expand Up @@ -15,13 +15,18 @@ class SpecialConfigs {
this.listConfigs = new ListConfigs(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:afterAll', this.afterAllDeployAction.bind(this));
this.embark.registerActionForEvent("deployment:contract:deployed", this.doOnDeployAction.bind(this));
this.embark.registerActionForEvent("deployment:contract:shouldDeploy", this.deployIfAction.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) {
if (typeof this.config.contractsConfig.beforeDeploy !== 'function') {
return this.listConfigs.beforeAllDeployAction(cb);
Expand Down
2 changes: 1 addition & 1 deletion packages/embark-utils/src/index.js
Expand Up @@ -183,7 +183,7 @@ function prepareContractsConfig(config) {
config.contracts[contractName].gasPrice = getWeiBalanceFromString(gasPrice);
}

if (address) {
if (address && typeof address === 'string') {
config.contracts[contractName].address = extendZeroAddressShorthand(address);
}

Expand Down
27 changes: 25 additions & 2 deletions site/source/docs/contracts_configuration.md
Expand Up @@ -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.

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.
- **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.
Expand Down Expand Up @@ -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

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.
Expand Down Expand Up @@ -541,7 +564,7 @@ deploy: {
}
```

Which will result in
Which will result in

```
SimpleStorage > onDeploy > Hello from onDeploy!
Expand Down

0 comments on commit 86ee867

Please sign in to comment.