Reformat the crowdfund contract

This commit is contained in:
Mike Hearn 2015-11-30 16:36:12 +00:00
parent b8a50a65cf
commit b3f7e307c7
2 changed files with 63 additions and 59 deletions

View File

@ -29,7 +29,7 @@ class CrowdFund : Contract {
override fun toString() = "Crowdsourcing($fundingTarget sought by $owner by $closingTime)"
}
data class Pledge (
data class Pledge(
val owner: PublicKey,
val amount: Amount
) : SerializeableWithKryo
@ -37,22 +37,25 @@ class CrowdFund : Contract {
interface Commands : Command {
object Register : Commands
object Fund : Commands
object Funded : Commands
object Unfunded : Commands
}
override fun verify(tx: TransactionForVerification) {
// There are two possible things that can be done with Crowdsourcing.
// The first is creating it. The second is funding it with cash
// The third is closing it on or after the closing date, and returning funds to
// pledge-makers if the target is unmet, or passing to the recipient.
val command = tx.commands.requireSingleCommand<CrowdFund.Commands>()
// There are two possible things that can be done with Crowdsourcing.
// The first is creating it. The second is funding it with cash
// The third is closing it on or after the closing date, and returning funds to
// pledge-makers if the target is unmet, or passing to the recipient.
val command = tx.commands.requireSingleCommand<CrowdFund.Commands>()
val outputCrowdFund: CrowdFund.State = tx.outStates.filterIsInstance<CrowdFund.State>().single()
val outputCash: List<Cash.State> = tx.outStates.filterIsInstance<Cash.State>()
when (command.value) {
when (command.value) {
is Commands.Register -> {
requireThat {
"the transaction is signed by the owner of the crowdsourcing" by (command.signers.contains(outputCrowdFund.owner))
@ -93,56 +96,55 @@ class CrowdFund : Contract {
}
}
is Commands.Unfunded -> {
val inputCrowdFund: CrowdFund.State = tx.inStates.filterIsInstance<CrowdFund.State>().single()
// TODO how can this be made smarter? feels wrong as a separate function
fun checkReturns(inputCrowdFund: CrowdFund.State, outputCash: List<Cash.State>): Boolean {
for (pledge in inputCrowdFund.pledges) {
if (outputCash.filter { it.amount == pledge.amount && it.owner == pledge.owner}.isEmpty()) return false
is Commands.Unfunded -> {
val inputCrowdFund: CrowdFund.State = tx.inStates.filterIsInstance<CrowdFund.State>().single()
// TODO how can this be made smarter? feels wrong as a separate function
fun checkReturns(inputCrowdFund: CrowdFund.State, outputCash: List<Cash.State>): Boolean {
for (pledge in inputCrowdFund.pledges) {
if (outputCash.filter { it.amount == pledge.amount && it.owner == pledge.owner }.isEmpty()) return false
}
return true
}
val time = tx.time ?: throw IllegalStateException("Transaction must be timestamped")
requireThat {
"the closing date has past" by (time >= outputCrowdFund.closingTime)
"the pledges did not meet the target" by (inputCrowdFund.pledgeTotal < inputCrowdFund.fundingTarget)
"the output cash returns equal the pledge total, if the target is not reached" by (outputCash.sumCash() == inputCrowdFund.pledgeTotal)
"the output cash is distributed to the pledge-makers, if the target is not reached" by (checkReturns(inputCrowdFund, outputCash))
"the output cash is distributed to the pledge-makers, if the target is not reached" by (outputCash.map { it.amount }.containsAll(inputCrowdFund.pledges.map { it.amount }))
"the input has an open state" by (!inputCrowdFund.closed)
"the output registration has a closed state" by (outputCrowdFund.closed)
// TODO how to simplify the boilerplate associated with unchanged elements
"the owner hasn't changed" by (outputCrowdFund.owner == inputCrowdFund.owner)
"the funding name has not changed" by (outputCrowdFund.fundingName == inputCrowdFund.fundingName)
"the funding target has not changed" by (outputCrowdFund.fundingTarget == inputCrowdFund.fundingTarget)
"the closing time has not changed" by (outputCrowdFund.closingTime == inputCrowdFund.closingTime)
"the pledged total is unchanged" by (outputCrowdFund.pledgeTotal == inputCrowdFund.pledgeTotal)
"the pledged count is unchanged" by (outputCrowdFund.pledgeCount == inputCrowdFund.pledgeCount)
"the pledges are unchanged" by (outputCrowdFund.pledges == inputCrowdFund.pledges)
}
return true
}
val time = tx.time ?: throw IllegalStateException("Transaction must be timestamped")
requireThat {
"the closing date has past" by (time >= outputCrowdFund.closingTime)
"the pledges did not meet the target" by (inputCrowdFund.pledgeTotal < inputCrowdFund.fundingTarget)
"the output cash returns equal the pledge total, if the target is not reached" by (outputCash.sumCash() == inputCrowdFund.pledgeTotal)
"the output cash is distributed to the pledge-makers, if the target is not reached" by (checkReturns(inputCrowdFund, outputCash))
"the output cash is distributed to the pledge-makers, if the target is not reached" by (outputCash.map { it.amount }.containsAll(inputCrowdFund.pledges.map { it.amount }))
"the input has an open state" by (!inputCrowdFund.closed)
"the output registration has a closed state" by (outputCrowdFund.closed)
// TODO how to simplify the boilerplate associated with unchanged elements
"the owner hasn't changed" by (outputCrowdFund.owner == inputCrowdFund.owner)
"the funding name has not changed" by (outputCrowdFund.fundingName == inputCrowdFund.fundingName)
"the funding target has not changed" by (outputCrowdFund.fundingTarget == inputCrowdFund.fundingTarget)
"the closing time has not changed" by (outputCrowdFund.closingTime == inputCrowdFund.closingTime)
"the pledged total is unchanged" by (outputCrowdFund.pledgeTotal == inputCrowdFund.pledgeTotal)
"the pledged count is unchanged" by (outputCrowdFund.pledgeCount == inputCrowdFund.pledgeCount)
"the pledges are unchanged" by (outputCrowdFund.pledges == inputCrowdFund.pledges)
is Commands.Funded -> {
val inputCrowdFund: CrowdFund.State = tx.inStates.filterIsInstance<CrowdFund.State>().single()
val time = tx.time ?: throw IllegalStateException("Transaction must be timestamped")
requireThat {
"the closing date has past" by (time >= outputCrowdFund.closingTime)
"the input has an open state" by (!inputCrowdFund.closed)
"the output registration has a closed state" by (outputCrowdFund.closed)
// TODO how to simplify the boilerplate associated with unchanged elements
"the owner hasn't changed" by (outputCrowdFund.owner == inputCrowdFund.owner)
"the funding name has not changed" by (outputCrowdFund.fundingName == inputCrowdFund.fundingName)
"the funding target has not changed" by (outputCrowdFund.fundingTarget == inputCrowdFund.fundingTarget)
"the closing time has not changed" by (outputCrowdFund.closingTime == inputCrowdFund.closingTime)
"the pledged total is unchanged" by (outputCrowdFund.pledgeTotal == inputCrowdFund.pledgeTotal)
"the pledged count is unchanged" by (outputCrowdFund.pledgeCount == inputCrowdFund.pledgeCount)
"the pledges are unchanged" by (outputCrowdFund.pledges == inputCrowdFund.pledges)
}
}
}
is Commands.Funded -> {
val inputCrowdFund: CrowdFund.State = tx.inStates.filterIsInstance<CrowdFund.State>().single()
val time = tx.time ?: throw IllegalStateException("Transaction must be timestamped")
requireThat {
"the closing date has past" by (time >= outputCrowdFund.closingTime)
"the input has an open state" by (!inputCrowdFund.closed)
"the output registration has a closed state" by (outputCrowdFund.closed)
// TODO how to simplify the boilerplate associated with unchanged elements
"the owner hasn't changed" by (outputCrowdFund.owner == inputCrowdFund.owner)
"the funding name has not changed" by (outputCrowdFund.fundingName == inputCrowdFund.fundingName)
"the funding target has not changed" by (outputCrowdFund.fundingTarget == inputCrowdFund.fundingTarget)
"the closing time has not changed" by (outputCrowdFund.closingTime == inputCrowdFund.closingTime)
"the pledged total is unchanged" by (outputCrowdFund.pledgeTotal == inputCrowdFund.pledgeTotal)
"the pledged count is unchanged" by (outputCrowdFund.pledgeCount == inputCrowdFund.pledgeCount)
"the pledges are unchanged" by (outputCrowdFund.pledges == inputCrowdFund.pledges)
}
}
// TODO: Think about how to evolve contracts over time with new commands.
else -> throw IllegalArgumentException("Unrecognised command")
}
}

View File

@ -62,11 +62,13 @@ class CrowdFundTests {
transaction {
input ("funding opportunity")
input("alice's $1000")
output ("pledged opportunity") { CF_1.copy(
pledges = CF_1.pledges + CrowdFund.Pledge(ALICE, 1000.DOLLARS),
pledgeCount = CF_1.pledgeCount + 1,
pledgeTotal = CF_1.pledgeTotal + 1000.DOLLARS
) }
output ("pledged opportunity") {
CF_1.copy(
pledges = CF_1.pledges + CrowdFund.Pledge(ALICE, 1000.DOLLARS),
pledgeCount = CF_1.pledgeCount + 1,
pledgeTotal = CF_1.pledgeTotal + 1000.DOLLARS
)
}
output { 1000.DOLLARS.CASH `owned by` MINI_CORP_PUBKEY }
arg(ALICE) { Cash.Commands.Move() }
arg(ALICE) { CrowdFund.Commands.Fund }
@ -78,7 +80,7 @@ class CrowdFundTests {
output ("funded and closed") { "pledged opportunity".output.copy(closed = true) }
arg(MINI_CORP_PUBKEY) { CrowdFund.Commands.Funded }
}
}
}
}
fun cashOutputsToWallet(vararg states: Cash.State): Pair<LedgerTransaction, List<StateAndRef<Cash.State>>> {
@ -129,4 +131,4 @@ class CrowdFundTests {
}
}
}