Corda Spaceship Auction-Part 2 (Contracts)

Mabel Oza
InsatiableMinds
Published in
6 min readSep 2, 2020

--

Photo by Jack O’Rourke on Unsplash

Contracts

Now for the brain of the CorDapp, our contracts, over here, we define the different elements involved and the business logic. Feel free to get the completed code from Github (https://github.com/moza88/SpaceshipAuction) and follow along. The contracts module is split out into two pieces, the contracts (business logic) and the states (data models). Delete the packages under the Kotlin folder and create our packages, com.force.auction.contracts and com.force.auction.states.

Before we get started let’s configure our module by going into the build.gradle file for contracts and copying in the following:

States

States are data models that describe the different facts in the DApp, in this dapp we have two facts we’re dealing with, Spaceships and Auctions.

We are going to define what is a spaceship in our Spaceship state (Spaceship.kt), the properties of our spaceships are name, description, image URL, the battery life in hours, speed miles per hour, size, and luxury.

Go to your states folder in your contract module and create a Kotlin class called Spaceship, in the Spaceship class copy in the code below. You will get an error at the very beginning with @BelongsToContract because we haven’t created the SpaceshipContract yet.

@BelongsToContract ties the states back to the contract that will be using the state, which is SpaceshipContract. When you reference the SpaceshipContract it will come off as an error because you never created it and if you do create a Kotlin class called SpaceshipContract you will still get errors unless you specify that it is in a Corda Contract.

After we specify all the values that make up a spaceship, we determine that the spaceship has a current owner (override val owner : AbstractParty) then we specify that all the participants in this transaction (override val participants: List<AbstractParty> = listOf(owner) are also a property of the spaceship.

We are implementing the interfaces OwnableState and LinearState, OwnableState states that there is an owner for the spaceship and LinearState will help us keep track of the states. Because we are using OwnableState we need to implement the withNewOwner member, the withNewOwner member deals with, as the name says, replacing the current owner with the new owner.

We are returning a CommandAndState with two things:

  • Command (an action we’re taking, referenced from the corresponding contract, SpaceshipContract interface)
SpaceshipContract.Commands.TransferSpaceship()
  • Spaceship State modified with the new owner.
Spaceship(this.linearId, this.name, this.description, this.imageUrl, this.batteryLifeHours, this.speedMPH, this.size, this.luxury, newOwner)

Now that let’s repeat the steps above for the AuctionState, describing the properties of what is an auction. In the AuctionState.kt state file add in the code below.

The first property we use in this state is a LinearPointer that’s taking in the LinearState datatype, this is helpful when one state depends on data in another state.

LinearPointer allows a ContractState to “point” to another LinearState creating a “many-to-one” relationship between all the states containing the pointer to a particular LinearState and the LinearState being pointed to.

Notice that some values have a ? after it’s data type like the lines val highestBid: Amount<Currency>? or val highestBidder: Party?, this is because these are values not fixed until the Dapp runs, we can have any amount that’s the highest bid and anyone can be the highest bidder.

In this state we are using the SchedulableState interface, this will allow us to schedule out an event to end an action.

Learn more about Event Scheduling in the documentation here: https://docs.corda.net/docs/corda-os/4.4/event-scheduling.html

Since we’re using ScheduableState we need to implement the member nextScheduledActivity. The state has no reference to its own StateRef so we supply that as an input in nextScheduledActivity, along with FlowLogicRefFactory because we’re going to refer to a flow (com.force.auction.flows.EndAuctionFlow, will be created soon). In this member we are saying that if the auction for an item is still active then let’s use our EndAuctionFlow along with our value, bidEndTime.

override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? {
if(!active){
return null
}
val flowLogicRef = flowLogicRefFactory.create("com.force.auction.flows.EndAuctionFlow",auctionId)
return ScheduledActivity(flowLogicRef,bidEndTime)
}

Now that we’ve completed our states let’s move over to creating contracts.

Contracts

Now for the business logic of the project, the contracts. In this DApp we’ll create 2 contracts, SpaceshipContract.kt and AuctionContract.kt.

Go to your Contract module and your contract folder and create the file SpaceshipContract.kt. Copy in the following code into the SpaceshipContract:

To create a contract in Corda we start by implementing the Contract interface (class SpacehipContract : Contract), when we are implementing the contract interface we need to add in a verify transaction method, override fun verify(tx: LedgerTransaction) {…}. In this contract, we don’t have any verification logic so we’ll keep it blank.

We are going to specify what type of actions this contract supports using the interface Commands, that’s implementing the CommandData interface. The actions in this contract are Transfering Spaceships and Creating Spaceships.

Next up is the AuctionContract.kt, similar concepts are the one above but this time we are going to add logic into our verify method. First, add in the following code to the AuctionContract.kt:

package com.force.auction.contractsimport net.corda.core.contracts.CommandData
import net.corda.core.contracts.Contract
import net.corda.core.transactions.LedgerTransaction
class AuctionContract : Contract {//Used to identify our contract with building a transaction
companion object{
const val ID = "com.force.auction.contracts"
}
//Transaction is only valid if it's not empty
override fun verify(tx: LedgerTransaction) {
}
interface Commands : CommandData {
class CreateAuction : Commands
class Bid : Commands
class EndAuction : Commands
class Settlement : Commands
class Exit : Commands
}
}

Now that we added in the actions this contract will support, creating an auction, bidding, ending an auction, settlement, and exiting the auction, we will write specific business logic for each. Before we get into that we’ll do a check to see if there is a transaction and then pull the command in the transaction. Copy the code below into your verify method:

...
override fun verify(tx: LedgerTransaction) {
//Transaction is only valid if it's not empty
if(tx.commands.isEmpty()) {
throw IllegalArgumentException("One command Expected")
}
val command = tx.commands[0]when (command.value) {is Commands.Bid -> requireThat {}is Commands.EndAuction -> requireThat {}is Commands.Settlement -> requireThat {}is Commands.Exit -> requireThat {}
}
}
...

Let’s go into each command and add in our rules:

We can answer a few questions from AuctionContract:

  • What are we receiving from this transaction?
val input = tx.inputsOfType<AuctionState>()[0]

The input type we’re receiving here is an auction state, refer back to your AuctionState you created earlier. You can pull the output, what you are getting out of the transaction similarly.

val output = tx.outputsOfType<AuctionState>()[0]
  • Is the Auction active?
"Auction is Active" using (!input.active)

Active ties back to our AuctionState, active is a boolean that we use in our nextScheduableActivity method in our state. If this boolean is false then the nextSchedulableActivity will return null otherwise it would fire the End Auction Flow and give us a bid end time (signifying the end of the auction).

override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? {
if(!active){
return null
}
val flowLogicRef = flowLogicRefFactory.create("com.force.auction.flows.EndAuctionFlow",auctionId)
return ScheduledActivity(flowLogicRef,bidEndTime)
}
  • What is an instance of the end auction command?
val commandEnd = tx.commandsOfType<Commands.EndAuction>()[0]
  • Did the auctioneer sign the transaction?
"Auctioneer Signature Required" using (commandEnd.signers.contains(output.auctioneer.owningKey))

We can check if other parties signed the transaction just by changing out the party. Here we are checking for the auctioneer’s id (owning key) is in our list of the command’s signers.

Now that we have stated our rules and what we’re dealing with in our Contract Module we’re going to get things moving with our Workflows. Check out Part 3.

--

--

Mabel Oza
InsatiableMinds

Making the financial world more secure, accessible, and transparent.