Skip to content
This repository has been archived by the owner on Sep 7, 2023. It is now read-only.

Commit

Permalink
(refactor) Cleaner API and CLI for invoking a clause
Browse files Browse the repository at this point in the history
Signed-off-by: Jerome Simeon <jeromesimeon@me.com>
  • Loading branch information
jeromesimeon committed Mar 5, 2019
1 parent f6f8947 commit a0a93fe
Show file tree
Hide file tree
Showing 11 changed files with 259 additions and 197 deletions.
51 changes: 47 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,55 @@ the compiled JavaScript code in `./examples/volumediscount/logic.js`

### Invoke a contract

To compile and invoke that contract:
To compile and execute a contract by sending a request:

```text
$ ergorun ./examples/volumediscount/model.cto ./examples/volumediscount/logic.ergo --contractName org.accordproject.volumediscount.VolumeDiscount --contract ./examples/volumediscount/contract.json --request ./examples/volumediscount/request.json --state ./examples/volumediscount/state.json
13:40:03 - info: Logging initialized. 2018-06-17T17:40:03.587Z
13:40:03 - info: {"response":{"discountRate":2.8,"$class":"org.accordproject.volumediscount.VolumeDiscountResponse"},"state":{"$class":"org.accordproject.cicero.contract.AccordContractState","stateId":"1"},"emit":[]}
$ ergorun execute ./examples/volumediscount/model.cto ./examples/volumediscount/logic.ergo --contractName org.accordproject.volumediscount.VolumeDiscount --contract ./examples/volumediscount/contract.json --request ./examples/volumediscount/request.json --state ./examples/volumediscount/state.json
06:40:01 - info:
{
"response": {
"discountRate": 2.8,
"$class": "org.accordproject.volumediscount.VolumeDiscountResponse"
},
"state": {
"$class": "org.accordproject.cicero.contract.AccordContractState",
"stateId": "1"
},
"emit": []
}
```

To compile and invoke a specific contract clause:

```text
$ ergorun invoke ./examples/volumediscount/model.cto ./examples/volumediscount/logic.ergo --contractName org.accordproject.volumediscount.VolumeDiscount --clauseName volumediscount --contract ./examples/volumediscount/contract.json --params ./examples/volumediscount/params.json --state ./examples/volumediscount/state.json
06:40:29 - info:
{
"response": {
"discountRate": 2.8,
"$class": "org.accordproject.volumediscount.VolumeDiscountResponse"
},
"state": {
"$class": "org.accordproject.cicero.contract.AccordContractState",
"stateId": "1"
},
"emit": []
}
```

To compile and obtain the initial state for the contract:

```text
$ ergorun init ./examples/volumediscount/model.cto ./examples/volumediscount/logic.ergo --contractName org.accordproject.volumediscount.VolumeDiscount --contract ./examples/volumediscount/contract.json
06:40:29 - info:
{
"response": null,
"state": {
"stateId": "org.accordproject.cicero.contract.AccordContractState#1",
"$class": "org.accordproject.cicero.contract.AccordContractState"
},
"emit": []
}
```

## For developers
Expand Down
4 changes: 3 additions & 1 deletion backends/javascript/ergo-runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -695,8 +695,10 @@ function dateTimeDiff(date1, date2) {
function mustBeDate(date) {
if (typeof date == "string") {
return moment.parseZone(date).utcOffset(utcOffset, false);
} else if (date instanceof Date) {
return moment(date).utcOffset(utcOffset, false);
} else {
return date.clone();
return date.clone().utcOffset(utcOffset, false);;
}
}

Expand Down
74 changes: 29 additions & 45 deletions packages/ergo-cli/bin/ergorun.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,28 @@ const Moment = require('moment');
const Logger = require('@accordproject/ergo-compiler/lib/logger');

require('yargs')
.command('send', 'send a request to an Ergo contract', (yargs) => {
yargs.demandOption(['contract', 'request', 'contractName', 'currentTime'], 'Please provide at least contract, request and contractName');
.command('execute', 'execute an Ergo contract with a request', (yargs) => {
yargs.demandOption(['contractName', 'contract', 'request'], 'Please provide at least the contractName, with contract data and request');
yargs.usage('Usage: $0 --contract [file] --state [file] --request [file] [ctos] [ergos]');
yargs.option('contractName', {
describe: 'the name of the contract'
});
yargs.option('contract', {
describe: 'path to the contract data'
});
yargs.option('request', {
describe: 'path to the request data'
}).array('request');
yargs.option('state', {
describe: 'path to the state data',
type: 'string',
default: null
});
yargs.option('contractName', {
describe: 'the name of the contract'
});
yargs.option('currentTime', {
describe: 'the current time',
type: 'string',
default: Moment().format() // Defaults to now
});
yargs.option('state', {
describe: 'path to the state data',
type: 'string',
default: null
});
yargs.option('request', {
describe: 'path to the request data'
}).array('request');
}, (argv) => {
let ctoPaths = [];
let ergoPaths = [];
Expand All @@ -64,11 +59,11 @@ require('yargs')
}

if (argv.verbose) {
Logger.info(`send request to Ergo ${ergoPaths} over data ${argv.contract} with request ${argv.request}, state ${argv.state} and CTOs ${ctoPaths}`);
Logger.info(`execute request to Ergo ${ergoPaths} over data ${argv.contract} with request ${argv.request}, state ${argv.state} and CTOs ${ctoPaths}`);
}

// Run contract
Commands.send(ergoPaths, ctoPaths, argv.contract, argv.request, argv.state, argv.contractName, argv.currentTime)
Commands.execute(ergoPaths, ctoPaths, argv.contractName, { file: argv.contract }, argv.state ? { file: argv.state } : null, argv.currentTime, argv.request.map(r => { return { file: r }; }))
.then((result) => {
Logger.info(JSON.stringify(result));
})
Expand All @@ -77,36 +72,30 @@ require('yargs')
});
})
.command('invoke', 'invoke a clause for an Ergo contract', (yargs) => {
yargs.demandOption(['contract', 'params', 'state', 'contractName', 'clauseName', 'currentTime'], 'Please provide at least contract, params, state, contractName and clauseName');
yargs.demandOption(['contractName', 'clauseName', 'contract', 'state', 'params'], 'Please provide at least the contractName and clauseName, with contract data, state, and params');
yargs.usage('Usage: $0 --contract [file] --state [file] --params [file] [ctos] [ergos]');
yargs.option('contract', {
describe: 'path to the contract data'
});
yargs.option('params', {
describe: 'path to the parameters',
type: 'string',
default: {}
});
yargs.option('state', {
describe: 'path to the state data',
type: 'string',
default: null
});
yargs.option('contractName', {
describe: 'the name of the contract'
});
yargs.option('clauseName', {
describe: 'the name of the clause to invoke'
});
yargs.option('contract', {
describe: 'path to the contract data'
});
yargs.option('state', {
describe: 'path to the state data',
type: 'string'
});
yargs.option('currentTime', {
describe: 'the current time',
type: 'string',
default: Moment().format() // Defaults to now
});
yargs.option('state', {
describe: 'path to the state data',
yargs.option('params', {
describe: 'path to the parameters',
type: 'string',
default: null
default: {}
});
}, (argv) => {
let ctoPaths = [];
Expand All @@ -129,7 +118,7 @@ require('yargs')
}

// Run contract
Commands.invoke(ergoPaths, ctoPaths, argv.contract, argv.params, argv.state, argv.contractName, argv.clauseName, argv.currentTime)
Commands.invoke(ergoPaths, ctoPaths, argv.contractName, argv.clauseName, { file: argv.contract }, { file: argv.state }, argv.currentTime, { file: argv.params })
.then((result) => {
Logger.info(JSON.stringify(result));
})
Expand All @@ -138,26 +127,21 @@ require('yargs')
});
})
.command('init', 'invoke init for an Ergo contract', (yargs) => {
yargs.demandOption(['contract', 'params', 'contractName', 'clauseName', 'currentTime'], 'Please provide at least contract, params and contractName');
yargs.demandOption(['contractName', 'contract'], 'Please provide at least contract, params and contractName');
yargs.usage('Usage: $0 --contract [file] --params [file] [ctos] [ergos]');
yargs.option('contract', {
describe: 'path to the contract data'
});
yargs.option('params', {
describe: 'path to the parameters',
type: 'string',
default: {}
});
yargs.option('contractName', {
describe: 'the name of the contract'
});
yargs.option('contract', {
describe: 'path to the contract data'
});
yargs.option('currentTime', {
describe: 'the current time',
type: 'string',
default: Moment().format() // Defaults to now
});
yargs.option('state', {
describe: 'path to the state data',
yargs.option('params', {
describe: 'path to the parameters',
type: 'string',
default: null
});
Expand All @@ -182,7 +166,7 @@ require('yargs')
}

// Run contract
Commands.init(ergoPaths, ctoPaths, argv.contract, argv.params, argv.contractName, argv.currentTime)
Commands.init(ergoPaths, ctoPaths, argv.contractName, { file: argv.contract }, argv.currentTime, argv.params ? { file: argv.params } : { content: '{}' })
.then((result) => {
Logger.info(JSON.stringify(result));
})
Expand Down
78 changes: 47 additions & 31 deletions packages/ergo-cli/lib/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,40 @@ const Fs = require('fs');
const Ergo = require('@accordproject/ergo-compiler/lib/ergo');
const ErgoEngine = require('@accordproject/ergo-engine/lib/ergo-engine');

/**
* Load a file or JSON string
*
* @param {object} input either a file name or a json string
* @return {object} JSON object
*/
function getJson(input) {
let jsonString;
if (input.file) {
jsonString = Fs.readFileSync(input.file, 'utf8');
} else {
jsonString = input.content;
}
return JSON.parse(jsonString);
}

/**
* Utility class that implements the commands exposed by the Ergo CLI.
* @class
*/
class Commands {
/**
* Send a request to an Ergo contract
* Execute an Ergo contract with a request
*
* @param {string[]} ergoPaths paths to the Ergo modules
* @param {string[]} ctoPaths paths to CTO models
* @param {string} contractPath path to the contract data in JSON
* @param {string[]} requestsPath path to the request transaction in JSON
* @param {string} statePath path to the state in JSON
* @param {string} contractName of the contract to execute
* @param {string} contractName of the contract
* @param {string} contractInput the contract data
* @param {string} stateInput the contract state
* @param {string} currentTime the definition of 'now'
* @param {string[]} requestsInput the requests
* @returns {object} Promise to the result of execution
*/
static send(ergoPaths,ctoPaths,contractPath,requestsPath,statePath,contractName,currentTime) {
static execute(ergoPaths,ctoPaths,contractName,contractInput,stateInput,currentTime,requestsInput) {
if (typeof ergoPaths === 'undefined') { ergoPaths = []; }
const ergoSources = [];
for (let i = 0; i < ergoPaths.length; i++) {
Expand All @@ -50,22 +66,22 @@ class Commands {
const ctoContent = Fs.readFileSync(ctoFile, 'utf8');
ctoSources.push({ 'name': ctoFile, 'content': ctoContent });
}
const contractJson = JSON.parse(Fs.readFileSync(contractPath, 'utf8'));
const contractJson = getJson(contractInput);
let requestsJson = [];
for (let i = 0; i < requestsPath.length; i++) {
requestsJson.push(JSON.parse(Fs.readFileSync(requestsPath[i], 'utf8')));
for (let i = 0; i < requestsInput.length; i++) {
requestsJson.push(getJson(requestsInput[i]));
}
let initResponse;
if (statePath === null) {
initResponse = ErgoEngine.init(ergoSources,ctoSources,'es6',contractJson,{},contractName,currentTime);
if (stateInput === null) {
initResponse = ErgoEngine.init(ergoSources,ctoSources,'es6',contractName,contractJson,currentTime,{});
} else {
const stateJson = JSON.parse(Fs.readFileSync(statePath, 'utf8'));
const stateJson = getJson(stateInput);
initResponse = Promise.resolve({ state: stateJson });
}
// Get all the other requests and chain execution through Promise.reduce()
return requestsJson.reduce((promise,requestJson) => {
return promise.then((result) => {
return ErgoEngine.send(ergoSources,ctoSources,'es6',contractJson,requestJson,result.state,contractName,currentTime);
return ErgoEngine.execute(ergoSources,ctoSources,'es6',contractName,contractJson,result.state,currentTime,requestJson);
});
}, initResponse);
}
Expand All @@ -75,15 +91,15 @@ class Commands {
*
* @param {string[]} ergoPaths paths to the Ergo modules
* @param {string[]} ctoPaths paths to CTO models
* @param {string} contractPath path to the contract data in JSON
* @param {object} paramsPath path to the parameters for the clause
* @param {string} statePath path to the state in JSON
* @param {string} contractName the contract to execute
* @param {string} clauseName the name of the clause to execute
* @param {string} contractName the contract
* @param {string} clauseName the name of the clause to invoke
* @param {string} contractInput the contract data
* @param {string} stateInput the contract state
* @param {string} currentTime the definition of 'now'
* @returns {object} Promise to the result of execution
* @param {object} paramsInput the parameters for the clause
* @returns {object} Promise to the result of invocation
*/
static invoke(ergoPaths,ctoPaths,contractPath,paramsPath,statePath,contractName,clauseName,currentTime) {
static invoke(ergoPaths,ctoPaths,contractName,clauseName,contractInput,stateInput,currentTime,paramsInput) {
if (typeof ergoPaths === 'undefined') { ergoPaths = []; }
const ergoSources = [];
for (let i = 0; i < ergoPaths.length; i++) {
Expand All @@ -98,24 +114,24 @@ class Commands {
const ctoContent = Fs.readFileSync(ctoFile, 'utf8');
ctoSources.push({ 'name': ctoFile, 'content': ctoContent });
}
const contractJson = JSON.parse(Fs.readFileSync(contractPath, 'utf8'));
const clauseParams = JSON.parse(Fs.readFileSync(paramsPath, 'utf8'));
const stateJson = JSON.parse(Fs.readFileSync(statePath, 'utf8'));
return ErgoEngine.invoke(ergoSources,ctoSources,'es6',contractJson,clauseParams,stateJson,contractName,clauseName,currentTime);
const contractJson = getJson(contractInput);
const clauseParams = getJson(paramsInput);
const stateJson = getJson(stateInput);
return ErgoEngine.invoke(ergoSources,ctoSources,'es6',contractName,clauseName,contractJson,stateJson,currentTime,clauseParams);
}

/**
* Invoke init for an Ergo contract
*
* @param {string[]} ergoPaths paths to the Ergo modules
* @param {string[]} ctoPaths paths to CTO models
* @param {string} contractPath path to the contract data in JSON
* @param {object} paramsPath path to the parameters for the clause
* @param {string} contractName the contract to execute
* @param {string} contractName the contract name
* @param {string} contractInput the contract data
* @param {string} currentTime the definition of 'now'
* @param {object} paramsInput the parameters for the clause
* @returns {object} Promise to the result of execution
*/
static init(ergoPaths,ctoPaths,contractPath,paramsPath,contractName,currentTime) {
static init(ergoPaths,ctoPaths,contractName,contractInput,currentTime,paramsInput) {
if (typeof ergoPaths === 'undefined') { ergoPaths = []; }
const ergoSources = [];
for (let i = 0; i < ergoPaths.length; i++) {
Expand All @@ -130,9 +146,9 @@ class Commands {
const ctoContent = Fs.readFileSync(ctoFile, 'utf8');
ctoSources.push({ 'name': ctoFile, 'content': ctoContent });
}
const contractJson = JSON.parse(Fs.readFileSync(contractPath, 'utf8'));
const clauseParams = JSON.parse(Fs.readFileSync(paramsPath, 'utf8'));
return ErgoEngine.init(ergoSources,ctoSources,'es6',contractJson,clauseParams,contractName,currentTime);
const contractJson = getJson(contractInput);
const clauseParams = getJson(paramsInput);
return ErgoEngine.init(ergoSources,ctoSources,'es6',contractName,contractJson,currentTime,clauseParams);
}

/**
Expand Down
Loading

0 comments on commit a0a93fe

Please sign in to comment.