mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-18 16:05:10 +00:00
Merge "FAB-11723 Developing Apps: Sample pt 1 -- contract"
This commit is contained in:
commit
4e7a174210
10 changed files with 523 additions and 0 deletions
16
commercial-paper/contract/.editorconfig
Executable file
16
commercial-paper/contract/.editorconfig
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
5
commercial-paper/contract/.eslintignore
Normal file
5
commercial-paper/contract/.eslintignore
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
coverage
|
||||
37
commercial-paper/contract/.eslintrc.js
Normal file
37
commercial-paper/contract/.eslintrc.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
env: {
|
||||
node: true,
|
||||
mocha: true
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 8,
|
||||
sourceType: 'script'
|
||||
},
|
||||
extends: "eslint:recommended",
|
||||
rules: {
|
||||
indent: ['error', 4],
|
||||
'linebreak-style': ['error', 'unix'],
|
||||
quotes: ['error', 'single'],
|
||||
semi: ['error', 'always'],
|
||||
'no-unused-vars': ['error', { args: 'none' }],
|
||||
'no-console': 'off',
|
||||
curly: 'error',
|
||||
eqeqeq: 'error',
|
||||
'no-throw-literal': 'error',
|
||||
strict: 'error',
|
||||
'no-var': 'error',
|
||||
'dot-notation': 'error',
|
||||
'no-tabs': 'error',
|
||||
'no-trailing-spaces': 'error',
|
||||
'no-use-before-define': 'error',
|
||||
'no-useless-call': 'error',
|
||||
'no-with': 'error',
|
||||
'operator-linebreak': 'error',
|
||||
yoda: 'error',
|
||||
'quote-props': ['error', 'as-needed']
|
||||
}
|
||||
};
|
||||
77
commercial-paper/contract/.npmignore
Normal file
77
commercial-paper/contract/.npmignore
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless
|
||||
7
commercial-paper/contract/index.js
Normal file
7
commercial-paper/contract/index.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports.contracts = require('./lib/cpcontract.js');
|
||||
113
commercial-paper/contract/lib/cpcontract.js
Normal file
113
commercial-paper/contract/lib/cpcontract.js
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Smart contract API brought into scope
|
||||
const {Contract} = require('fabric-contract-api');
|
||||
|
||||
// Commercial paper classes brought into scope
|
||||
const {CommercialPaper, CommercialPaperList} = require('./cpstate.js');
|
||||
|
||||
/**
|
||||
* Define the commercial paper smart contract extending Fabric Contract class
|
||||
*/
|
||||
class CommercialPaperContract extends Contract {
|
||||
|
||||
/**
|
||||
* Each smart contract can have a unique namespace; useful when multiple
|
||||
* smart contracts per file.
|
||||
* Use transaction context (ctx) to access list of all commercial papers.
|
||||
*/
|
||||
constructor() {
|
||||
super('org.papernet.commercialpaper');
|
||||
|
||||
this.setBeforeFn = (ctx)=>{
|
||||
ctx.cpList = new CommercialPaperList(ctx, 'COMMERCIALPAPER');
|
||||
return ctx;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue commercial paper
|
||||
* @param {TxContext} ctx the transaction context
|
||||
* @param {String} issuer commercial paper issuer
|
||||
* @param {Integer} paperNumber paper number for this issuer
|
||||
* @param {String} issueDateTime paper issue date
|
||||
* @param {String} maturityDateTime paper maturity date
|
||||
* @param {Integer} faceValue face value of paper
|
||||
*/
|
||||
async issue(ctx, issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) {
|
||||
|
||||
let cp = new CommercialPaper(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue);
|
||||
|
||||
// {issuer:"MagnetoCorp", paperNumber:"00001", "May31 2020", "Nov 30 2020", "5M USD"}
|
||||
|
||||
await ctx.cpList.addPaper(cp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Buy commercial paper
|
||||
* @param {TxContext} ctx the transaction context
|
||||
* @param {String} issuer commercial paper issuer
|
||||
* @param {Integer} paperNumber paper number for this issuer
|
||||
* @param {String} currentOwner current owner of paper
|
||||
* @param {String} newOwner new owner of paper
|
||||
* @param {Integer} price price paid for this paper
|
||||
* @param {String} purchaseDateTime time paper was purchased (i.e. traded)
|
||||
*/
|
||||
async buy(ctx, issuer, paperNumber, currentOwner, newOwner, price, purchaseDateTime) {
|
||||
|
||||
let cpKey = CommercialPaper.createKey(issuer, paperNumber);
|
||||
let cp = await ctx.cpList.getPaper(cpKey);
|
||||
|
||||
if (cp.getOwner() !== currentOwner) {
|
||||
throw new Error('Paper '+issuer+paperNumber+' is not owned by '+currentOwner);
|
||||
}
|
||||
// First buy moves state from ISSUED to TRADING
|
||||
if (cp.isIssued()) {
|
||||
cp.setTrading();
|
||||
}
|
||||
// Check paper is TRADING, not REDEEMED
|
||||
if (cp.IsTrading()) {
|
||||
cp.setOwner(newOwner);
|
||||
} else {
|
||||
throw new Error('Paper '+issuer+paperNumber+' is not trading. Current state = '+cp.getCurrentState());
|
||||
}
|
||||
|
||||
await ctx.cpList.updatePaper(cp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redeem commercial paper
|
||||
* @param {TxContext} ctx the transaction context
|
||||
* @param {String} issuer commercial paper issuer
|
||||
* @param {Integer} paperNumber paper number for this issuer
|
||||
* @param {String} redeemingOwner redeeming owner of paper
|
||||
* @param {String} redeemDateTime time paper was redeemed
|
||||
*/
|
||||
async redeem(ctx, issuer, paperNumber, redeemingOwner, redeemDateTime) {
|
||||
|
||||
let cpKey = CommercialPaper.createKey(issuer, paperNumber);
|
||||
let cp = await ctx.cpList.getPaper(cpKey);
|
||||
|
||||
// Check paper is TRADING, not REDEEMED
|
||||
if (cp.IsRedeemed()) {
|
||||
throw new Error('Paper '+issuer+paperNumber+' already redeemed');
|
||||
}
|
||||
|
||||
// Verify that the redeemer owns the commercial paper before redeeming it
|
||||
if (cp.getOwner() === redeemingOwner) {
|
||||
cp.setOwner(cp.getIssuer());
|
||||
cp.setRedeemed();
|
||||
} else {
|
||||
throw new Error('Redeeming owner does not own paper'+issuer+paperNumber);
|
||||
}
|
||||
|
||||
await ctx.cpList.updatePaper(cp);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = CommericalPaperContract;
|
||||
148
commercial-paper/contract/lib/cpstate.js
Normal file
148
commercial-paper/contract/lib/cpstate.js
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Helpful utilities class
|
||||
const Utils = require('./utils.js');
|
||||
|
||||
// Enumeration of commercial paper state values
|
||||
const cpState = {
|
||||
ISSUED: 1,
|
||||
TRADING: 2,
|
||||
REDEEMED: 3
|
||||
};
|
||||
|
||||
/**
|
||||
* CommercialPaper class defines a commercial paper state
|
||||
*/
|
||||
class CommercialPaper {
|
||||
|
||||
/**
|
||||
* Construct a commercial paper. Initial state is issued.
|
||||
*/
|
||||
constructor(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) {
|
||||
this.issuer = issuer;
|
||||
this.paperNumber = paperNumber;
|
||||
this.owner = issuer;
|
||||
this.issueDateTime = issueDateTime;
|
||||
this.maturityDateTime = maturityDateTime;
|
||||
this.faceValue = faceValue;
|
||||
this.currentState = cpState.ISSUED;
|
||||
this.key = CommercialPaper.createKey(issuer, paperNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* The commercial paper is uniquely identified by its key.
|
||||
* The key is a simple composite of issuer and paper number as strings.
|
||||
*/
|
||||
static createKey(issuer, paperNumber) {
|
||||
return JSON.stringify(issuer) + JSON.stringify(paperNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic getters and setters
|
||||
*/
|
||||
getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
getIssuer() {
|
||||
return this.issuer;
|
||||
}
|
||||
|
||||
setIssuer(newIssuer) {
|
||||
this.issuer = newIssuer;
|
||||
}
|
||||
|
||||
getOwner() {
|
||||
return this.owner;
|
||||
}
|
||||
|
||||
setOwner(newOwner) {
|
||||
this.owner = newOwner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Useful methods to encapsulate commercial paper states
|
||||
*/
|
||||
setTrading() {
|
||||
this.currentState = cpState.TRADING;
|
||||
}
|
||||
|
||||
setRedeemed() {
|
||||
this.currentState = cpState.REDEEMED;
|
||||
}
|
||||
|
||||
isTrading() {
|
||||
return this.currentState === cpState.TRADING;
|
||||
}
|
||||
|
||||
isRedeemed() {
|
||||
return this.currentState === cpState.REDEEMED;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* CommercialPaperList provides a virtual container to access all
|
||||
* commercial papers. Each paper has unique key which associates it
|
||||
* with the container, rather than the container containing a link to
|
||||
* the paper. This is important in Fabric becuase it minimizes
|
||||
* collisions for parallel transactions on different papers.
|
||||
*/
|
||||
class CommercialPaperList {
|
||||
|
||||
/**
|
||||
* For this sample, it is sufficient to create a commercial paper list
|
||||
* using a fixed container prefix. The transaction context is saved to
|
||||
* access Fabric APIs when required.
|
||||
*/
|
||||
constructor(ctx, prefix) {
|
||||
this.api = ctx.stub;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a paper to the list. Creates a new state in worldstate with
|
||||
* appropriate composite key. Note that paper defines its own key.
|
||||
* Paper object is serialized before writing.
|
||||
*/
|
||||
async addPaper(cp) {
|
||||
let key = this.api.createCompositeKey(this.prefix, [cp.getKey()]);
|
||||
let data = Utils.serialize(cp);
|
||||
await this.api.putState(key, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a paper from the list using issuer and paper number. Forms composite
|
||||
* keys to retrieve data from world state. State data is deserialized
|
||||
* into paper object before being returned.
|
||||
*/
|
||||
async getPaper(key) {
|
||||
let key = this.api.createCompositeKey(this.prefix, [key]);
|
||||
let data = await this.api.getState(key);
|
||||
let cp = Utils.deserialize(data);
|
||||
return cp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a paper in the list. Puts the new state in world state with
|
||||
* appropriate composite key. Note that paper defines its own key.
|
||||
* Paper object is serialized before writing. Logic is very similar to
|
||||
* addPaper() but kept separate becuase it is semantically distinct, and
|
||||
* may change.
|
||||
*/
|
||||
async updatePaper(cp) {
|
||||
let key = this.api.createCompositeKey(this.prefix, [cp.getKey()]);
|
||||
let data = Utils.serialize(cp);
|
||||
await this.api.putState(key, data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
CommercialPaper,
|
||||
CommercialPaperList
|
||||
};
|
||||
34
commercial-paper/contract/lib/utils.js
Normal file
34
commercial-paper/contract/lib/utils.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Utility class for data, object mapulation, e.g. serialization
|
||||
*/
|
||||
|
||||
class Utils {
|
||||
|
||||
/**
|
||||
* Convert object to buffer containing JSON data serialization
|
||||
* Typically used before putState() ledger API
|
||||
* @param {Object} object object to serialize
|
||||
* @return {buffer} buffer with the data to store
|
||||
*/
|
||||
static serialize(object){
|
||||
return Buffer.from(JSON.stringify(object));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize object, i.e. Covert serialized data to JSON object
|
||||
* Typically used after getState() ledger API
|
||||
* @param {Object} data object to deserialize
|
||||
* @return {json} json with the data to store
|
||||
*/
|
||||
static deserialize(data){
|
||||
return JSON.parse(data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Utils;
|
||||
45
commercial-paper/contract/package.json
Normal file
45
commercial-paper/contract/package.json
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"name": "smart-contract",
|
||||
"version": "0.0.1",
|
||||
"description": "Smart Contract",
|
||||
"engines": {
|
||||
"node": ">=8",
|
||||
"npm": ">=5"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"pretest": "npm run lint",
|
||||
"test": "nyc mocha --recursive",
|
||||
"start": "startChaincode"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"author": "Anthony ODowd",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"fabric-shim": "unstable",
|
||||
"fabric-contract-api": "unstable"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.1.2",
|
||||
"eslint": "^4.19.1",
|
||||
"mocha": "^5.2.0",
|
||||
"nyc": "^12.0.2",
|
||||
"sinon": "^6.0.0"
|
||||
},
|
||||
"nyc": {
|
||||
"exclude": [
|
||||
"coverage/**",
|
||||
"test/**"
|
||||
],
|
||||
"reporter": [
|
||||
"text-summary",
|
||||
"html"
|
||||
],
|
||||
"all": true,
|
||||
"check-coverage": true,
|
||||
"statements": 100,
|
||||
"branches": 100,
|
||||
"functions": 100,
|
||||
"lines": 100
|
||||
}
|
||||
}
|
||||
41
commercial-paper/contract/test/contract.js
Normal file
41
commercial-paper/contract/test/contract.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const Chaincode = require('../lib/chaincode');
|
||||
const { Stub } = require('fabric-shim');
|
||||
|
||||
require('chai').should();
|
||||
const sinon = require('sinon');
|
||||
|
||||
describe('Chaincode', () => {
|
||||
|
||||
describe('#Init', () => {
|
||||
|
||||
it('should work', async () => {
|
||||
const cc = new Chaincode();
|
||||
const stub = sinon.createStubInstance(Stub);
|
||||
stub.getFunctionAndParameters.returns({ fcn: 'initFunc', params: [] });
|
||||
const res = await cc.Init(stub);
|
||||
res.status.should.equal(Stub.RESPONSE_CODE.OK);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#Invoke', async () => {
|
||||
|
||||
it('should work', async () => {
|
||||
const cc = new Chaincode();
|
||||
const stub = sinon.createStubInstance(Stub);
|
||||
stub.getFunctionAndParameters.returns({ fcn: 'initFunc', params: [] });
|
||||
let res = await cc.Init(stub);
|
||||
res.status.should.equal(Stub.RESPONSE_CODE.OK);
|
||||
stub.getFunctionAndParameters.returns({ fcn: 'invokeFunc', params: [] });
|
||||
res = await cc.Invoke(stub);
|
||||
res.status.should.equal(Stub.RESPONSE_CODE.OK);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
Loading…
Reference in a new issue