Skip to content

Commit

Permalink
feat(plugins/specialconfigs): adds support for Smart Contract args as…
Browse files Browse the repository at this point in the history
… functions

This commit introduces a new feature that enables users to calculate Smart Contract
constructor arguments lazily using an (async) function. Similar to normal Smart Contract
configurations, the return or resolved value from that function has to be either a list
of arguments in the order as they are needed for the constructor, or as an object with
named members that match the arguments individually.

```
...
development: {
  deploy: {
    SimpleStorage: {
      args: async ({ contracts, web3, logger}) => {
        // do something with `contracts` and `web3` to determine
        // arguments
        let someValue = await ...;
        return [someValue];

        // or
        return {
          initialValue: someValue
        };
      }
    }
  }
}
...
```

Closes #2270
  • Loading branch information
0x-r4bbit committed Mar 3, 2020
1 parent 8de6cf9 commit a4a0e9d
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 9 deletions.
6 changes: 6 additions & 0 deletions dapps/tests/app/config/contracts.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ module.exports = {
args: [100],
onDeploy: ["SimpleStorage.methods.setRegistar('embark.eth').send()"]
},
SimpleStorageArgsFn: {
instanceOf: 'SimpleStorage',
args: async ({ contracts, web3, logger }) => {
return [5000];
}
},
SimpleStorageTest: {
//file: "./some_folder/test_contract.sol",
args: [1000, 'embark.eth']
Expand Down
6 changes: 5 additions & 1 deletion packages/core/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,11 @@ export function prepareContractsConfig(config) {
config.contracts[contractName].address = extendZeroAddressShorthand(address);
}

if (args && args.length) {
if (typeof args === 'function') {
config.contracts[contractName].args = args;
}

if (args && Array.isArray(args)) {
config.contracts[contractName].args = args.map((val) => {
if (typeof val === "string") {
return extendZeroAddressShorthand(val);
Expand Down
3 changes: 3 additions & 0 deletions packages/plugins/ens/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,9 @@ class ENS {
});

function checkArgs(args, done) {
if (typeof args === 'function') {
return done(null, args);
}
if (Array.isArray(args)) {
async.map(args, (arg, next) => {
if (Array.isArray(arg)) {
Expand Down
15 changes: 14 additions & 1 deletion packages/plugins/specialconfigs/src/functionConfigs.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,22 @@ class FunctionConfigs {
}
}

async determineSmartContractArgs(params, cb) {
const contract = params.contract;
const argsFn = contract.args;
try {
const logger = Utils.createLoggerWithPrefix(this.logger, 'determineArgs >');
const dependencies = await this.getDependenciesObject(logger);
const args = await argsFn(dependencies);
params.contract.args = args;
cb();
} catch (e) {
cb(new Error(`Error running args function for ${contract.className}: ${e.message || e}`));
}
}

async getDependenciesObject(logger) {
let contracts = await this.events.request2("contracts:list");

let args = { contracts: {}, logger};
for (let contract of contracts) {
// TODO: for this to work correctly we need to add a default from address to the contract
Expand Down
14 changes: 11 additions & 3 deletions packages/plugins/specialconfigs/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class SpecialConfigs {
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));
this.embark.registerActionForEvent('deployment:contract:determineArgs', this.determineSmartContractArgs.bind(this));
}

async executeAddressHandlerForContract(params, cb) {
Expand All @@ -40,10 +41,17 @@ class SpecialConfigs {
}

async beforeDeployAction(params, cb) {
if (typeof params.contract.beforeDeploy !== 'function') {
return this.listConfigs.beforeDeployAction(params, cb);
if (params.contract.beforeDeploy) {
return this.functionConfigs.beforeDeployAction(params, cb);
}
return this.functionConfigs.beforeDeployAction(params, cb);
cb();
}

async determineSmartContractArgs(params, cb) {
if (typeof params.contract.args === 'function') {
return this.functionConfigs.determineSmartContractArgs(params, cb);
}
cb();
}

async doOnDeployAction(params, cb) {
Expand Down
4 changes: 0 additions & 4 deletions packages/plugins/specialconfigs/src/listConfigs.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,6 @@ class ListConfigs {
});
}

async beforeDeployAction(_params, cb) {
return cb();
}

runOnDeployCode(onDeployCode, callback, silent) {
const logFunction = silent ? this.logger.trace.bind(this.logger) : this.logger.info.bind(this.logger);
async.each(onDeployCode, (cmd, eachCb) => {
Expand Down
5 changes: 5 additions & 0 deletions packages/stack/deployment/src/contract_deployer.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ class ContractDeployer {

deployContract(contract, callback) {
async.waterfall([
(next) => {
this.plugins.emitAndRunActionsForEvent('deployment:contract:determineArgs', { contract }, (err) => {
next(err);
});
},
(next) => {
this.plugins.emitAndRunActionsForEvent('deployment:contract:beforeDeploy', {contract: contract}, (err, _params) => {
// TODO: confirm this really works and shouldn't be next(err, params)
Expand Down
27 changes: 27 additions & 0 deletions site/source/docs/contracts_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,33 @@ development: {
...
```

### Calculating constructor parameters lazily

Another way to configure arguments for Smart Contract constructors is to calculate them lazily using an asynchronous function. This is especially useful if the Smart Contract's arguments depend on other runtime dependencies, such as already deployed Smart Contracts. To calculate arguments, all you have to do is define `args` as an (async) function and resolve with either a list of arguments or an object where each member maps to an individual constructor argument:

```
...
development: {
deploy: {
SimpleStorage: {
args: async ({ contracts, web3, logger}) => {
// do something with `contracts` and `web3` to determine
// arguments
let someValue = await ...;
return [someValue];
// or
return {
initialValue: someValue
};
}
}
}
}
...
```


### Configuring gas and gas price

Both, `gas` and `gasPrice` can be configured for each Smart Contract. If we don't want to configure that for every single contract, we can also specify `gas: auto` in the environment, like this:
Expand Down

0 comments on commit a4a0e9d

Please sign in to comment.