LedgerSwarm's RLN Smart Contract Framework
Smart Contracts - Technolgoy and Design
Introduction
LedgerSwarm Smart Contracts
A LedgerSwarm smart contract is a single Javascript module which exports a name, a description, a collection of JSON schemas and a collection of functions. Those functions include an initialization function and a set of transition functions.
The LedgerSwarmVM
The LedgerSwarm smart contract is executed on a LedgerSwarmVM. The LedgerSwamVM is implemented in NodeJS and can load and execute LedgerSwarm smart contracts. The LedgerSwarmVM is an NPM library which is has three helper functions –
Function | Description |
---|---|
loadContracts | takes a path as input and loads one or more LedgerSwarm RLN smart contracts |
init | Takes a contract name, genesis data and roles and returns the first state of a contract |
callfunction | Takes an identity, an input contract state, a transition function name and a parameter set and returns a new contract state |
The LedgerSwarmVM does not persist contract states.
Both the init and callfunction functions will enforce the smart contract schemas for the genesis data and for the parameters for each transition function. It will also enforce the flow control constraints specified in the smart contract.
The LedgerSwarm Smart Contract Host
A LedgerSwarm smart contract host is a server that runs a LedgerSwarmVM but only allows connection by Mutual TLS. Each smartcontract host can specify one or more root certificates that it will trust for identity assertions.
The Ledgerswarm smartcontract host is also responsible for persisting states of individual instances of smart contracts and making those states available to contract participants on a permissioned basis. Contract states are immutable and contain all the code and data required to independently prove a transition from one state to another.
Roadmap
Currently the LedgerSwarm smart contract runs on a host. This can be thought of as a notary. Any participant can run a LedgerSwarm smart contract host. The host is responsible for;
- identifying a contract participant using mutual TLS
- consuming the most recent state and creating a new state
- persisting each state in an immutable provable manner
- distributing states and state components on a permissioned basis to contract participants
Future developments
Allow any contract participant to propose a new state. This will require an ordering mechanism to resolve conflicts where two or more participants attempt to consume the same state.
The LedgerSwarm Smart Contract Wallet
Each participant communicates with the LedgerSwarm smart contract host via LedgerSwarm smart contract wallet. The wallet maintains private key and certificates to allow it to establish a mutual TLS connection to the smart contract host. Once connected to the host, it can (subject to permissions) initiate new instances of the smart contracts maintained by that host, call functions on smart contracts that it is party to and retrieve states and state components from the smart contract host.
The LedgerSwarm smart contract wallet can also interact with LedgerSwarm's RLN. Any RLN interactions that are required under a smartcontract are delegated to a contract participant to execute. Other contract participants can be delegated the function of verifying that the RLN action has successfully executed.
Writing Your First RLN Smart Contract
This exercise will take you through the development, testing and deployment of a LedgerSwarm smart contract. If you do not have access to a LedgerSwarm network, you will still be able to develop and test a smart contract by running it against a local LedgerSwarmVM.
To complete this exercise you need to have the following installed on your computer
Visual Studio
NodeJS
Mocha (for testing)
Structure of a Smart Contract
The following shows the different components for a smart contract. An example of each component is detailed for the Payment Mandate smart contract below.
The Payment Mandate Contract
A Payment Mandate Contract is a simple smart contract that has three parties, a payer, a receiver and an instructor. The contract executes a single payment between the payer and the receiver on the instruction of the instructor.
Downloading the Code
The Payment Mandate Contract is maintained on github at
https://github.com/setl/rln-smartcontract-paymentMandate.git
Create a clone of the repo as follows
Change to the new directory and install the required npm modules
Understanding the Source Code
ContractName and Description
The first section of the contract code defines two constancts contracName and description. The contractName is used to refer to the contract when creating an instance and needs to be unique on a contract host.
/*-----------------------------------------------------------------
Payment Mandate Contract
------------------------------------------------------------------*/
const contractName = "Payment Mandate";
const description =
"The Payment Mandate contract is an agreement for an instructor to trigger a single payment between a payer and a receiver";
Schemas for the genesis
JSON schemas are used to structure certain data objects in a LedgerSwarm smart contract. A JSON Schema is a way of describing a JSON objec. Details of JSON schema can be found here. An example of a simple JSON Schema is shown below.
const mySchema = {
type: "object",
properties: {
foo: { type: "string", enum[ "yes", "no"]},
bar: { type: "number"},
},
required: ["foo", "bar"],
additionalProperties: false
}
This schema describes an object which has two properties. The first is a string and can only take the values 'yes' or 'no'. The second is a number. Both properties are required and there can be no additional properties. Standard libraries exist to compare an object against a schema and return a validation resonse.
The second section of the code creates a partition schema, a party schema and a roleSchema. Partitions are indentifiers for a ledger participating in the RLN and are necessary if your contract is going to create RLN transactions. A contract can be restricted so that it only allows a set list of partitions to be used in an associated RLN transaction.
/*--------------------------------------------------------------------------------
Schema components used to buld the genesis schemas and function schemas
--------------------------------------------------------------------------------*/
const partitions = [
"DEMOUS01XXX - bank1",
"DEMOUS01BR1 - bank1branch",
"DEMOUS02XXX - bank2",
"DEMOUS02BR1 - bank2branch",
"CENTRLBK - central"
];
const partitionSchema = {
type: "string",
enum: partitions,
};
const partyMembers = [
"bank1",
"bank1branch",
"bank2",
"bank2branch",
"central"
];
const partySchema = {
type: "string",
enum: partyMembers,
};
const roleSchema = {
type: "object",
properties: {
party: partySchema,
confirmed: { type: "string", enum: ["waiting"] },
},
required: ["party", "confirmed"],
additionalProperties: false,
};
GenesisRoles and GenesisData
/*--------------------------------------------------------------------------------
Genesis Roles Schema and Genesis Data Schema required to initialize a contract
--------------------------------------------------------------------------------*/
const genesisRolesSchema = {
type: "object",
properties: {
payer: roleSchema,
receiver: roleSchema,
instructor: roleSchema,
},
required: ["payer", "receiver", "instructor"],
additionalProperties: false,
};
const genesisDataSchema = {
type: "object",
properties: {
comment: { type: "string" },
payerPartition: partitionSchema,
payerAddress: { type: "string" },
receiverPartition: partitionSchema,
receiverAddress: { type: "string" },
currency: { type: "string" , enum: ["USD"]},
amount: { type: "number" },
status: { type: "string", enum: ["initialized"] },
},
required: [
"comment",
"payerPartition",
"payerAddress",
"receiverPartition",
"receiverAddress",
"currency",
"amount",
"status"
],
additionalProperties: false,
};
The code snippet above defines schemas and data structures related to roles, partitions, and the initialization of a contract. Let's break it down:
-
partitions
: This array represents different partitions involved in the contract. Each partition is identified by a string value, such as "DEMOUS01XXX - bank1" or "CENTRLBK - central". -
partitionSchema
: This schema defines the validation rules for a partition. It specifies that the partition should be a string and must be one of the values present in thepartitions
array. -
partyMembers
: This array represents different party members involved in the contract. Each party member is identified by a string value, such as "bank1" or "central". -
partySchema
: This schema defines the validation rules for a party. It specifies that the party should be a string and must be one of the values present in thepartyMembers
array. -
roleSchema
: This schema defines the structure and validation rules for a role. It consists of two properties: "party" (which should be a valid party member) and "confirmed" (which should be a string with the value "waiting"). It also specifies that no additional properties are allowed in the role object. -
genesisRolesSchema
: This schema defines the structure and validation rules for the genesis roles required to initialize a contract. It consists of three properties: "payer", "receiver", and "instructor" (each following the structure defined by theroleSchema
). It specifies that these properties are required and no additional properties are allowed. -
genesisDataSchema
: This schema defines the structure and validation rules for the genesis data required to initialize a contract. It consists of several properties such as "comment", "payerPartition", "payerAddress", "receiverPartition", "receiverAddress", "currency", "amount", and "status". Each property has its own data type and validation rules defined. It specifies that these properties are required and no additional properties are allowed.
These schemas and data structures provide a way to define and validate the roles, partitions, and initial data for a contract. They ensure that the provided data adheres to the specified structure and constraints, enabling proper initialization and validation of contract-related information.
Schemas for Transition Functions
The code snippet below defines schemas for the inputs required by different functions to transition the state of a contract.
/*--------------------------------------------------------------------------------
Schema for Inputs to Functions to transition contract state.
One schema for each function
--------------------------------------------------------------------------------*/
const registerSchema = {
type: "object",
properties: {
register: { type: "string", enum: ["agreed", "rejected"] },
},
required: ["register"],
additionalProperties: false,
};
const makePaymentSchema = {
type: "object",
properties: {
paymentReference: { type: "string" },
execution: { type: "string", enum: ["manual","automatic"] },
},
required: ["paymentReference","execution"],
additionalProperties: false,
};
const confirmActionedSchema = {
type: "object",
properties: {
paymentReference: { type: "string" },
},
required: ["paymentReference"],
additionalProperties: false,
};
const confirmPaymentSchema = {
type: "object",
properties: {
paymentReference: { type: "string" },
},
required: ["paymentReference"],
additionalProperties: false,
};
Let's break it down:
-
registerSchema
: This schema defines the structure and validation rules for the inputs required by the "register" function. It consists of a single property "register" which should be a string with the value "agreed" or "rejected". It specifies that the "register" property is required and no additional properties are allowed. -
makePaymentSchema
: This schema defines the structure and validation rules for the inputs required by the "makePayment" function. It consists of two properties: "paymentReference" (which should be a string) and "execution" (which should be a string with the value "manual" or "automatic"). It specifies that both properties are required and no additional properties are allowed. -
confirmActionedSchema
: This schema defines the structure and validation rules for the inputs required by the "confirmActioned" function. It consists of a single property "paymentReference" which should be a string. It specifies that the "paymentReference" property is required and no additional properties are allowed. -
confirmPaymentSchema
: This schema defines the structure and validation rules for the inputs required by the "confirmPayment" function. It consists of a single property "paymentReference" which should be a string. It specifies that the "paymentReference" property is required and no additional properties are allowed.
These schemas ensure that the inputs provided to each function adhere to the specified structure and constraints. By validating the inputs against these schemas, you can ensure that the required parameters are provided and that they are of the correct data types and values expected by each function.
Flow Constraints
The flowConstraints
object represents a set of flow constraints in a state machine. It describes the possible actions and outcomes for different roles in each state.
These flow constraints define the allowed actions and possible outcomes for each role in each state of the state machine. The constraints determine the valid transitions between states based on the actions performed by different roles.
/*------------------------------------------------------------------
Smart Contract Flow Constraints
------------------------------------------------------------------*/
const flowConstraints = {
initialized: {
payer: {actions:["register"], outcomes:["agreed","rejected","initialized"]},
receiver: {actions:["register"], outcomes:["agreed","rejected","initialized"]},
instructor: {actions:["register"], outcomes:["agreed","rejected","initialized"]},
},
agreed: {
payer: {actions:[],outcomes:[]},
receiver: {actions:[],outcomes:[]},
instructor: {actions:["makePayment"], outcomes:["instructed"]},
},
instructed: {
payer: {actions:["confirmActioned"], outcomes:["instructed"]},
receiver: {actions:["confirmPayment"], outcomes:["complete"]},
instructor: {actions:[],outcomes:[]},
},
complete: {
payer: {actions:[],outcomes:[]},
receiver: {actions:[],outcomes:[]},
instructor: {actions:[],outcomes:[]},
},
rejected: {
payer: {actions:[],outcomes:[]},
receiver: {actions:[],outcomes:[]},
instructor: {actions:[],outcomes:[]}
}
}
Here's a breakdown of the defined states and their corresponding constraints:
-
State: "initialized"
- Constraints for "payer": The payer role can perform the "register" action, leading to outcomes of "agreed," "rejected," or staying in the "initialized" state.
- Constraints for "receiver": The receiver role can perform the "register" action, leading to outcomes of "agreed," "rejected," or staying in the "initialized" state.
- Constraints for "instructor": The instructor role can perform the "register" action, leading to outcomes of "agreed," "rejected," or staying in the "initialized" state.
-
State: "agreed"
- Constraints for "payer": No actions or outcomes are defined.
- Constraints for "receiver": No actions or outcomes are defined.
- Constraints for "instructor": The instructor role can perform the "makePayment" action, leading to the outcome of "instructed."
-
State: "instructed"
- Constraints for "payer": The payer role can perform the "confirmActioned" action, leading to the outcome of "instructed."
- Constraints for "receiver": The receiver role can perform the "confirmPayment" action, leading to the outcome of "complete."
- Constraints for "instructor": No actions or outcomes are defined.
-
State: "complete"
- Constraints for "payer": No actions or outcomes are defined.
- Constraints for "receiver": No actions or outcomes are defined.
- Constraints for "instructor": No actions or outcomes are defined.
-
State: "rejected"
- Constraints for "payer": No actions or outcomes are defined.
- Constraints for "receiver": No actions or outcomes are defined.
- Constraints for "instructor": No actions or outcomes are defined.
Initialisation Function
The init function is the function called to instantiate a new contract. The function shown below is general and can be used in any smart contract.
/*--------------------------------------------------------------------------------
Smart Contract Init Function
--------------------------------------------------------------------------------*/
function init(
caller,
genesisContractId,
genesisRoles,
genesisData,
genesisFunctions
) {
try {
// set the parties who can instantiate this contract
let admins = partyMembers;
if (!admins.includes(caller)) {
throw "You are not authorized to initialize this contract";
}
let contractState = {};
contractState.id = genesisContractId;
contractState.roles = {};
contractState.roles = genesisRoles;
contractState.data = genesisData;
contractState.functions = genesisFunctions;
contractState.priorHash = "";
return contractState;
} catch (err) {
return { failed: err };
}
}
- The function is called init, and it takes five arguments: the party that is calling the function, the ID of the contract being initialized, the roles that the contract will contain, the data that the contract will contain, and the functions that the contract will contain.
- The function first checks whether the party that is calling the function is authorized to initialize the contract. If it is, then the function continues to the next step.
- The function creates a new object called contractState. This object will hold all of the information about the contract. It is empty at first.
- The function adds two properties to the contractState object: id and roles. The id property is assigned the value of the genesisContractId argument. The roles property is assigned the value of the genesisRoles argument.
- The function adds two more properties to the contractState object: data and functions. The data property is assigned the value of the genesisData argument. The functions property is assigned the value of the genesisFunctions argument.
- The function adds one more property to the contractState object: priorHash. The priorHash property is assigned the value of an empty string.
- The function returns the contractState object.
Transition Functions
register function
The register
function is executed by each party in the contract. Until all parties have agreed the contract, it will remain in the initialized
state. The function shown below is general and can be used in any smart contract.
function register(caller, partyRoles, contractState, inputs) {
try {
// register the caller on each of their roles
for (var i=0; i<partyRoles.length; i++) {
let partyRole = partyRoles[i];
contractState.roles[partyRole].confirmed = inputs.register;
if (inputs.register == "rejected") {
contractState.data.status = "rejected";
}
}
// if rejected, update state and return
if (contractState.data.status == "rejected") {
contractState.priorHash = contractState.stateHash;
return contractState;
}
// check if all parties have agreed
let agreed = true;
for (var key in contractState.roles) {
if (contractState.roles[key].confirmed == "waiting") {
agreed = false;
}
}
// update contract state to agreed
if (agreed) {
contractState.data.status = "agreed";
}
contractState.priorHash = contractState.stateHash;
return contractState;
} catch (err) {
return { failed: err };
}
}
- The register function is executed by each party in the contract.
- Each party sets the register value for each of their roles to either "accepted" or "rejected".
- If any party sets the register value to "rejected", the contract is rejected.
- If all parties have set the register value to "accepted", the contract is agreed.
- The parties can also set the register value to "waiting" to indicate that they are not yet ready to accept or reject the contract.
- The register function returns the contract state with the updated register values.
makePayment function
The makePayment
function can only be called by the instructor. This is specified in the flowConstraints object and is hence enforced the the LedgerSwarmVM. The purpose of the function is to create an action event. This is an object within state that is an instruction to carry out an RLN transaction. The action specifies the submitter
who is responsible for actually executing the RLN action. It also specifies a consensus
object which contains a list of parties who must verify that the RLN transaction has successfully executed.
function makePayment(caller, partyRoles, contractState, inputs) {
try {
// if allowed by flow constraints, update state
let payer = "";
let receiver = "";
for (var key in contractState.roles) {
if (key == "payer") {
payer = contractState.roles[key].party;
}
if (key == "receiver") {
receiver = contractState.roles[key].party;
}
}
// check the reference is unique
isUnique = true;
if (contractState.data.events) {
for (var i=0; i<contractState.data.events.actions.length; i++) {
if (contractState.data.events.actions[i].paymentReference == inputs.paymentReference) {
isUnique = false;
}
}
}
if (!isUnique) {
throw "Payment reference is not unique";
}
// create the payment event
contractState.data.events = {};
contractState.data.events.actions = [];
let action = {};
// set the action
action.type="RLNCREATE"
action.atomicGroup = [];
transaction={}
transaction.action = "create";
transaction.debtor = payer;
transaction.debtorAccount = contractState.data.payerAddress;
transaction.debtorAgent = contractState.data.payerPartition;
transaction.creditor = receiver;
transaction.creditorAccount = contractState.data.receiverAddress;
transaction.creditorAgent = contractState.data.receiverPartition;
transaction.amount = contractState.data.amount;
transaction.currency = contractState.data.currency;
action.atomicGroup.push(transaction);
action.consensus = [];
let consensusObj = {};
// set the parties who must confirm action has happened
consensusObj.participant = receiver;
consensusObj.status = "unconfirmed";
action.consensus.push(consensusObj);
// set the party who must submit the actions
action.submitter = payer;
action.status = "proposed";
action.reference = inputs.paymentReference;
action.execution = inputs.execution;
// add the action to the event list
contractState.data.events.actions.push(action);
// set the contract status
contractState.data.status = "instructed";
contractState.priorHash = contractState.stateHash;
return contractState;
} catch (err) {
console.log(err);
return { failed: err };
}
}
The code above does the following;
- Check that the payment reference is unique (i.e. has not been used before)
- Create a new event, containing a single action
- Create the action, specifying the following:
- Type of action (RLNCREATE)
- Atomic group containing a single transaction
- Transaction type (create)
- Debtor, Debtor Account, Debtor Agent (payer)
- Creditor, Creditor Account, Creditor Agent (receiver)
- Amount, Currency (amount and currency from the contract state)
- Consensus list containing a single participant (receiver), with status "unconfirmed"
- Submitter (payer)
- Status (proposed)
- Reference (payment reference from inputs)
- Execution (execution from inputs)
- Set the contract status to "instructed"
- Return the contract state
confirmActioned function
The purpose of the confirmActioned
function is to allow the submitter to confirm that they have sent the transaction to the RLN. The action is then available for the parties defined in the consensus object to confirm that the RLN transaction has sucessfully completed.
function confirmActioned(caller, partyRoles, contractState, inputs) {
try {
// find the action with input.paymentReference and mark the status as actioned
let foundReference = false;
for (var i = 0; i < contractState.data.events.actions.length; i++) {
if (contractState.data.events.actions[i].reference == inputs.paymentReference
&& contractState.data.events.actions[i].submitter == caller
&& contractState.data.events.actions[i].status == "proposed") {
foundReference = true;
contractState.data.events.actions[i].status = "actioned";
}
}
if (!foundReference) {
throw "Payment reference not found or already actioned";
}
contractState.priorHash = contractState.stateHash;
return contractState;
} catch (err) {
return { failed: err };
}
}
- Check if the caller is in the partyRoles
- Check if the paymentReference is already in the contractState
- Check if the paymentReference is in the contractState.events.actions and that it is in the proposed status
- If the paymentReference is not found or already actioned, throw an error
- If the paymentReference is found and proposed, change the status to actioned
- Set the priorHash to the stateHash
- Return the updated contractState
confirmPayment function
The purpose of the ConfirmPayment
function is to allow parties who are specified in the consensus object to confirm that payment has completed. Only when all the parties listed in the consensus object have confirmed, will the smart contract status be updated to complete.
function confirmPayment(caller, partyRoles, contractState, inputs) {
try {
// if allowed by flow constraints, update state
let complete = true;
let referencefound = false;
for (var i = 0; i < contractState.data.events.actions.length; i++) {
let consensus = true;
if (contractState.data.events.actions[i].reference == inputs.paymentReference) {
referencefound = true;
}
for (var j=0; j < contractState.data.events.actions[i].consensus.length; j++) {
if (contractState.data.events.actions[i].consensus[j].participant == caller) {
contractState.data.events.actions[i].consensus[j].status = "confirmed";
} else {
if (contractState.data.events.actions[i].consensus[j].status == "unconfirmed") {
consensus = false;
}
}
}
if (consensus){
contractState.data.events.actions[i].status = "confirmed";
} else {
complete = false;
}
}
if (!referencefound) {
throw "Payment reference does not match";
}
if (complete) {
contractState.data.status = "complete";
}
contractState.priorHash = contractState.stateHash;
return contractState;
} catch (err) {
return { failed: err };
}
}
The code above does the following;
- Check that the caller is one of the parties to the contract.
- Check that the contract is in progress.
- Check that the reference matches one of the actions.
- Check that the caller has not already confirmed this action.
- Add the caller to the list of confirmations for this action.
- If all parties have confirmed, add the caller to the list of confirmations for the contract.
- If all actions have been confirmed, set the status of the contract to complete.
- Set the prior state hash to the current state hash.
- Return the updated contract state.
Exporting from the module
Finally, the module must export the following
contractName
: The name of the contractdescription
: A string describing the contractschemas
: an object with the following propertiesgenesisData
: the schema for the genesis datagenesisRoles
: the schema for the genesis roles- one schema for each of the transition functions
flowConstraints
: the flowConstraints object as described abovefunctions
: an object that comprises the init function and the transition functions
/*---------------------------------------------------
Export the contract details, functions and schemas
----------------------------------------------------*/
module.exports = {
contractName: contractName,
description: description,
schemas: {
genesisData: genesisDataSchema,
genesisRoles: genesisRolesSchema,
register: registerSchema,
makePayment: makePaymentSchema,
confirmActioned: confirmActionedSchema,
confirmPayment: confirmPaymentSchema,
},
flowConstraints: flowConstraints,
functions: {
init,
register,
makePayment,
confirmActioned,
confirmPayment,
},
};
Testing Your Contract
LedgerSwarm smart contracts can be tested by running them against a local LedgerSwarmVM. The Mocha test framework is used to create a range to tests to ensure the smart contract is behaving as expected.
Mocha is a feature-rich JavaScript test framework used for running tests on Node.js and in the browser. It provides a flexible and easy-to-use interface for writing and organizing test suites and test cases. Mocha supports asynchronous testing, supports various assertion libraries, and generates detailed test reports. It offers a wide range of functionalities, including test coverage reporting, before/after hooks, and test-specific timeouts, making it a popular choice for testing JavaScript applications and libraries.
To run the Mocha tests enter
npm test
The code below demonstrates a series of tests using the Mocha test framework for the smart contract "Payment Mandate". The LedgerSwarmVM is instantiated with the following line of code
Here's a summary of what the code does:
-
The first set of tests under the 'Init' describe block verifies that the contracts are loaded successfully from the contracts directory and checks if the 'Payment Mandate' contract is available.
-
The second set of tests under the 'Participants Register' describe block initializes a new contract by providing the necessary genesis roles and data. It then checks if the initialization process was successful by verifying the absence of the 'failed' property in the resulting state.
-
The third set of tests under the 'Exercise Smartcontract Functions' describe block demonstrates the execution of different functions on the smart contract. It includes tests for the instructor making a payment, the payer marking the payment as actioned, and the receiver confirming the payment. Each test validates the resulting state of the contract to ensure that the expected changes have occurred.
The 'assert' library is used to perform assertions and verify that the expected conditions are met during the execution of each test. If an assertion fails, it indicates that the test did not produce the expected results.
Overall, the code uses Mocha to structure and execute tests for the 'Payment Mandate' smart contract, allowing developers to ensure the correctness of its functionality and behavior.
var assert = require('assert');
const RLNSmartContract = require("@ledgerswarm/ls_smartcontract_vm");
const path = require('path');
let state0={}
let state1={}
let state2={}
let state3={}
let state4={}
describe('Init', function () {
describe('Contract Loads', function () {
it('should load contracts from the contracts directory', async function () {
await RLNSmartContract.loadContracts(path.join(__dirname,'../contracts'))
let hasFunction = (RLNSmartContract.pureFunctions.hasOwnProperty('Payment Mandate'))
assert.equal(hasFunction, true);
});
});
describe('Contract Initializes', function () {
it('should initialise a new contract', async function () {
let genesisRoles ={
payer:{party:'bank1',confirmed:"waiting"},
receiver:{party:'bank2',confirmed:"waiting"},
instructor:{party:'central',confirmed:"waiting"}
}
let payer ="DEMOUS01XXX - bank1"
let receiver ="DEMOUS02XXX - bank2"
let genesisData={
comment: "this is a comment",
payerPartition: payer,
payerAddress: "pa",
receiverPartition: receiver,
receiverAddress:"ra",
currency: "USD",
amount: 1000,
status: "initialized"
}
state0 = await RLNSmartContract.init('bank1', 'Payment Mandate', genesisRoles, genesisData)
let hasFailed = (state0.hasOwnProperty('failed'))
assert.equal(hasFailed, false);
});
});
});
describe('Participants Register', function () {
describe('Participants Register with Contract', function () {
it('should show that all participant can agree to a contract', async function () {
let stateA = await RLNSmartContract.callfunction('bank1', 'Payment Mandate', 'register', state0, {register:"agreed"})
let stateB = await RLNSmartContract.callfunction('bank2', 'Payment Mandate', 'register', stateA, {register:"agreed"})
state1 = await RLNSmartContract.callfunction('central', 'Payment Mandate', 'register', stateB, {register:"agreed"})
let hasRegistered = true
if (!state1.hasOwnProperty('roles')){hasRegistered=false}
if (hasRegistered){
if (state1.roles.payer.confirmed != "agreed"){hasRegistered=false}
if (state1.roles.receiver.confirmed != "agreed"){hasRegistered=false}
if (state1.roles.instructor.confirmed != "agreed"){hasRegistered=false}
}
assert.equal(hasRegistered, true);
});
});
});
describe('Exercise Smartcontract Functions', function () {
describe('Instructor makes a payment', function () {
it('should show that the instructor can make a payment', async function () {
state2 = await RLNSmartContract.callfunction('central', 'Payment Mandate', 'makePayment', state1, {paymentReference:"paymentref", execution:"manual"})
let hasPaymentEvent = true
if (!state2.hasOwnProperty('data')){hasPaymentEvent=false}
if (hasPaymentEvent){
if (!state2.data.hasOwnProperty('events')){hasPaymentEvent=false}
}
assert.equal(hasPaymentEvent, true);
});
});
describe('Payer marks payment as actioned', function () {
it('should show that the payer can mark a payment as actioned', async function () {
state3 = await RLNSmartContract.callfunction('bank1', 'Payment Mandate', 'confirmActioned', state2, {paymentReference:"paymentref"})
let hasPaymentEvent = true
if (!state3.hasOwnProperty('data')){hasPaymentEvent=false}
if (hasPaymentEvent){
if (!state3.data.hasOwnProperty('events')){hasPaymentEvent=false}
if (!state3.data.events.hasOwnProperty('actions')){hasPaymentEvent=false}
if (!state3.data.events.actions[0].hasOwnProperty('status')){
hasPaymentEvent=false
}
if (state3.data.events.actions[0].status!=='actioned'){hasPaymentEvent=false}
}
assert.equal(hasPaymentEvent, true);
});
});
describe('Receiver confirms a payment', function () {
it('should show that the receiver can confirm a payment', async function () {
state4 = await RLNSmartContract.callfunction('bank2', 'Payment Mandate', 'confirmPayment', state3, {paymentReference:"paymentref"})
let hasConfirmed = true
if (!state4.hasOwnProperty('data')){hasConfirmed=false}
if (hasConfirmed){
if (state4.data.status!=='complete'){hasConfirmed=false}
}
assert.equal(hasConfirmed, true);
});
});
});
Deploying To the Contract Host
LedgerSwarm smart contracts are made available to participants by a contract host. Deploying to a contract host is coming soon but in the meatime, we have deployed a number of contracts on the demo host for you to interact with.
Roadmap
Currently the LedgerSwarm smart contract runs on a host. This can be thought of as a notary. Any participant can run a LedgerSwarm smart contract host. The host is responsible for;
Loading onto a contract host will be available soon
Creating an Instance
Info
Busy writing this section - be with you shortly!
Calling Your Smart Contract Functions
Info
Busy writing this section - be with you shortly!
Terminating Your Smart Contract
Info
Busy writing this section - be with you shortly!