Building Basic Contract Design
The following contract defines three commands; Create
, Update
and Delete
. The verify
function delegates these command types to verifyCreate
, verifyUpdate
, and verifyDelete
functions respectively, for example:
public final class ExampleContract implements Contract {
private interface ExampleContractCommand extends Command { }
public static class Create implements ExampleContractCommand { }
public static class Update implements ExampleContractCommand { }
public static class Delete implements ExampleContractCommand { }
@Override
public void verify(UtxoLedgerTransaction transaction) {
List<? extends ExampleContractCommand> commands = transaction
.getCommands(ExampleContractCommand.class);
for (ExampleContractCommand command : commands) {
if (command instanceof Create) verifyCreate(transaction);
else if (command instanceof Update) verifyUpdate(transaction);
else if (command instanceof Delete) verifyDelete(transaction);
else throw new IllegalStateException("Unrecognised command type.");
}
}
private void verifyCreate(UtxoLedgerTransaction transaction) {
// Verify Create constraints
}
private void verifyUpdate(UtxoLedgerTransaction transaction) {
// Verify Update constraints
}
private void verifyDelete(UtxoLedgerTransaction transaction) {
// Verify Delete constraints
}
}
Designing a contract as shown in the above example will suffice in many cases. Assuming that the constraints have been implemented correctly, the contract functionality and design is perfectly acceptable.
Derivable Contract Design
The following contract refactors the above to support the ability to derive contracts, and provide additional constraints in a secure and controlled way.
The contract still provides the same three commands: Create
, Update
, and Delete
. The verify
function delegates these command types to verifyCreate
, verifyUpdate
, and verifyDelete
functions respectively, which in turn call onVerifyCreate
, onVerifyUpdate
, and onVerifyDelete
respectively.
verify
function is marked final
. This is necessary to prevent derived contract implementations from circumventing the base contract rules.public class ExampleContract implements Contract {
private interface ExampleContractCommand extends Command { }
public static class Create implements ExampleContractCommand { }
public static class Update implements ExampleContractCommand { }
public static class Delete implements ExampleContractCommand { }
@Override
public final void verify(UtxoLedgerTransaction transaction) {
List<? extends ExampleContractCommand> commands = transaction
.getCommands(ExampleContractCommand.class);
for (ExampleContractCommand command : commands) {
if (command instanceof Create) verifyCreate(transaction);
else if (command instanceof Update) verifyUpdate(transaction);
else if (command instanceof Delete) verifyDelete(transaction);
else throw new IllegalStateException("Unrecognised command type.");
}
}
protected void onVerifyCreate(UtxoLedgerTransaction transaction) { }
protected void onVerifyUpdate(UtxoLedgerTransaction transaction) { }
protected void onVerifyDelete(UtxoLedgerTransaction transaction) { }
private void verifyCreate(UtxoLedgerTransaction transaction) {
// Verify base Create constraints
// Then verify additional Create constraints implemented by derived contracts
onVerifyCreate(transaction);
}
private void verifyUpdate(UtxoLedgerTransaction transaction) {
// Verify base Update constraints
// Then verify additional Update constraints implemented by derived contracts
onVerifyUpdate(transaction);
}
private void verifyDelete(UtxoLedgerTransaction transaction) {
// Verify base Delete constraints
// Then verify additional Delete constraints implemented by derived contracts
onVerifyDelete(transaction);
}
}
Refactoring a contract as shown in the above example allows CorDapp Corda Distributed Application. A Java (or any JVM targeting language) application built using the Corda build toolchain and CorDapp API to solve some problem that is best solved in a decentralized manner. implementors to derive from the contract, allowing additional constraints which will be verified in addition to the constraints specified by the base contract.
There are still some outstanding issues with this design, where this design approach no longer fits the design goals of the system being implemented.
The problem really lies in the verify
function; for example:
public final void verify(UtxoLedgerTransaction transaction) {
List<? extends ExampleContractCommand> commands = transaction
.getCommands(ExampleContractCommand.class);
for (ExampleContractCommand command : commands) {
if (command instanceof Create) verifyCreate(transaction);
else if (command instanceof Update) verifyUpdate(transaction);
else if (command instanceof Delete) verifyDelete(transaction);
else throw new IllegalStateException("Unrecognised command type.");
}
}
The verify
function is marked final for security reasons, and therefore additional commands cannot be added to the contract. For example, the contract may wish to describe multiple ways to Update
a state
An immutable object representing a fact known by one or more participants at a specific point in time. You can use states to represent any type of data, and any kind of fact.
, or set of states. The contract only defines a single Update
command: there can only be one mechanism to perform updates.
The second problem lies in the commands themselves and their names. Create
, Update
, and Delete
are very ambiguous names, which may not make sense depending on the context of the contract being implemented.
Delegated Command Design
In the contracts above, the commands are nothing more than marker classes; effectively they are cases in a switch statement, which allows the contract’s verify
function to delegate responsibility of specific contract constraints to other functions, such as verifyCreate
, verifyUpdate
, and verifyDelete
.
Implement the verify
function on the command itself. Instead of being an empty marker class, this gives the command responsibility, as it becomes responsible for implementing its associated contract verification constraints.
In this case, define a VerifiableCommand
interface with a verify
function; for example:
public interface VerifiableCommand extends Command {
void verify(UtxoLedgerTransaction transaction);
}
Now that you have a command which itself can implement contract verification constraints, you can use this as the basis for the ExampleContractCommand
class. This needs to be a class rather than an interface, because you need to be in complete control of its implementations for security.
You can achieve this by making the default constructor package private, so that only commands within the same package can extend it; for example:
public class ExampleContractCommand implements VerifiableCommand {
ExampleContractCommand() { }
}
Next, implement this interface as Create
, Update
and Delete
commands; for example:
public class Create extends ExampleContractCommand {
@Override
public final void verify(UtxoLedgerTransaction transaction) {
// Verify base Create constraints
// Then verify additional Create constraints implemented in derived commands
onVerify(transaction);
}
protected void onVerify(UtxoLedgerTransaction transaction) { }
}
public class Update extends ExampleContractCommand {
@Override
public final void verify(UtxoLedgerTransaction transaction) {
// Verify base Update constraints
// Then verify additional Update constraints implemented in derived commands
onVerify(transaction);
}
protected void onVerify(UtxoLedgerTransaction transaction) { }
}
public class Delete extends ExampleContractCommand {
@Override
public final void verify(UtxoLedgerTransaction transaction) {
// Verify base Delete constraints
// Then verify additional Delete constraints implemented in derived commands
onVerify(transaction);
}
protected void onVerify(UtxoLedgerTransaction transaction) { }
}
Create
, Update
, and Delete
commands are not marked final
. Therefore, you can extend the contract verification constraints from these points, but you cannot extend them from ExampleContractCommand
.Delegated Contract Design
As you have now delegated contract verification constraint logic to the commands themselves, you must also refactor the contract to support this delegation. The contract implementation in this case becomes simpler, since it is no longer responsible for defining contract verification constraints. For example:
public final class ExampleContract implements Contract {
@Override
public void verify(UtxoLedgerTransaction transaction) {
List<? extends ExampleContractCommand> commands = transaction
.getCommands(ExampleContractCommand.class);
for (ExampleContractCommand command : commands) {
command.verify(transaction);
}
}
}
This design addresses the outstanding issues in regard to being able to extend a contract with multiple commands, and being able to assign names to commands that make sense in the context that they’re used. For example:
class Mint extends Create { ... }
class Issue extends Update { ... }
class Transfer extends Update { ... }
class Exchange extends Update { ... }
class Redeem extends Update { ... }
class Burn extends Delete { ... }
Create
, Update
, or Delete
.Was this page helpful?
Thanks for your feedback!
Chat with us
Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.
Propose documentation improvements directly
Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.
We're sorry this page wasn't helpful. Let us know how we can make it better!
Chat with us
Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.
Create an issue
Create a new GitHub issue in this repository - submit technical feedback, draw attention to a potential documentation bug, or share ideas for improvement and general feedback.
Propose documentation improvements directly
Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.