Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Payable Feature #2

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
41 changes: 34 additions & 7 deletions src/main/scala/edu/berkeley/cs/rise/quartz/Solidity.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ object Solidity {
case Bool => "bool"
case Timespan => "uint"
case HashValue(_) => "bytes32"
case Mapping(keyType, valueType) => s"mapping(${writeType(keyType, payable)} => ${writeType(valueType, payable)})"
case Mapping(keyType, valueType) => s"mapping(${writeType(keyType, false)} => ${writeType(valueType, payable)})"
case Sequence(elementType) => s"${writeType(elementType, payable)}[]"
case Struct(name) => name
}
Expand Down Expand Up @@ -200,13 +200,18 @@ object Solidity {
builder.toString()
}

private def writeTransition(transition: Transition, useCall: Boolean = false): String = {
private def writeTransition(transition: Transition, useCall: Boolean = false, payableFields: Set[String]): String = {
val builder = new StringBuilder()

val paramsRepr = transition.parameters.fold("") { params =>
// Remove parameters that are used in the original source but are built in to Solidity
val effectiveParams = params.filter(p => !BUILTIN_PARAMS.contains(p.name))
val payableParams = extractPayableVars(transition.body.getOrElse(Seq.empty[Statement]), effectiveParams.map(_.name).toSet)
var payableParams : Set[String] = Set.empty[String]
john-b-yang marked this conversation as resolved.
Show resolved Hide resolved
var setSize = payableParams.size
do {
john-b-yang marked this conversation as resolved.
Show resolved Hide resolved
setSize = payableParams.size
john-b-yang marked this conversation as resolved.
Show resolved Hide resolved
payableParams = extractPayableParams(transition.body.getOrElse(Seq.empty[Statement]), effectiveParams.map(_.name).toSet, payableFields, payableParams)
} while (payableParams.size != setSize);
writeParameters(effectiveParams.zip(effectiveParams.map(p => payableParams.contains(p.name))))
}

Expand Down Expand Up @@ -485,7 +490,12 @@ object Solidity {
appendLine(builder, s"contract $name {")
indentationLevel += 1

val payableFields = extractPayableVars(stateMachine.flattenStatements, stateMachine.fields.map(_.name).toSet)
var payableFields : Set[String] = Set.empty[String]
var fieldSize = 0
do {
john-b-yang marked this conversation as resolved.
Show resolved Hide resolved
fieldSize = payableFields.size
payableFields = extractPayableVars(stateMachine.flattenStatements, stateMachine.fields.map(_.name).toSet, payableFields)
} while (payableFields.size != fieldSize)

stateMachine.structs.foreach { case (name, fields) => builder.append(writeStructDefinition(name, fields)) }

Expand All @@ -502,7 +512,7 @@ object Solidity {
builder.append(writeAuthorizationFields(stateMachine))
builder.append("\n")

stateMachine.transitions foreach { t => builder.append(writeTransition(t, useCall)) }
stateMachine.transitions foreach { t => builder.append(writeTransition(t, useCall, payableFields)) }
extractAllMembershipTypes(stateMachine).foreach(ty => builder.append(writeSequenceContainsTest(ty) + "\n"))
builder.append("\n")

Expand Down Expand Up @@ -567,10 +577,27 @@ object Solidity {
case _ => Set.empty[String]
}

private def extractPayableVars(statements: Seq[Statement], scope: Set[String] = Set.empty[String]): Set[String] = {
val names = statements.foldLeft(Set.empty[String]) { (current, statement) =>
private def extractPayableVars(statements: Seq[Statement], scope: Set[String] = Set.empty[String], payableSet: Set[String]): Set[String] = {
val names = statements.foldLeft(payableSet) { (current, statement) =>
statement match {
case Send(destination, _, _) => current.union(extractVarNames(destination))
case Assignment(left, right) if current.contains(extractVarNames(left).toSeq(0)) => current.union(extractVarNames(right))
john-b-yang marked this conversation as resolved.
Show resolved Hide resolved
john-b-yang marked this conversation as resolved.
Show resolved Hide resolved
case _ => current
}
}

if (scope.nonEmpty) {
names.intersect(scope)
} else {
names
}
}

private def extractPayableParams(statements: Seq[Statement], scope: Set[String] = Set.empty[String], payableFields: Set[String], payableParams: Set[String]): Set[String] = {
val names = statements.foldLeft(payableParams) { (current, statement) =>
statement match {
case Send(destination, _, _) => current.union(extractVarNames(destination))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If destination includes field references, won't those be added in here as well, not just parameters?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify, is this if destination is of type struct? If so, yeah that's correctly, I'll be have to mindful of that when parsing and figuring out which variable within the struct's fields should be payable.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just pointing out that this method is called extractPayableParams but here, when you use extractVarNames on destination, I don't think it would properly distinguish between fields and parameters.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh gotcha, yes yes that makes sense. Before I think the resolution was that I'd just union it with the set of parameters of that transition, but admittedly that's not a very good solution. For the current version, I check for whether the variable is a parameter or field before adding it 👍

case Assignment(left, right) if payableFields.contains(extractVarNames(left).toSeq(0)) || current.contains(extractVarNames(left).toSeq(0)) => current.union(extractVarNames(right))
case _ => current
}
}
Expand Down
19 changes: 19 additions & 0 deletions src/test/resources/payable/field.qtz
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
contract Field {
data {
A: Identity
B: Identity
C: Identity
}

initialize: -> open {
B = A
}

test1: open -> open {
send 0 to A
}

test2: open -> open {
C = B
}
}
19 changes: 19 additions & 0 deletions src/test/resources/payable/field2.qtz
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
contract Field {
data {
A: Identity
B: Identity
C: Identity
}

initialize: -> open {
A = B
}

test1: open -> open {
send 0 to A
}

test2: open -> open {
B = C
}
}
14 changes: 14 additions & 0 deletions src/test/resources/payable/mapping.qtz
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
contract Mapping {
data {
Map1: Mapping[Uint, Identity]
Map2: Mapping[Identity, Identity]
}

initialize: ->(id: Uint) open {
send 0 to Map1[id]
}

test1: open ->(id: Identity) open {
send 0 to Map2[id]
}
}
11 changes: 11 additions & 0 deletions src/test/resources/payable/mapping2.qtz
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
contract Mapping {
data {
Map1: Mapping[Uint, Mapping[Identity, Identity]]
Map2: Mapping[Identity, Mapping[Uint, Identity]]
}

initialize: ->(id: Uint, id2: Identity) open {
send 0 to Map1[id][id2]
send 0 to Map2[id2][id]
}
}
19 changes: 19 additions & 0 deletions src/test/resources/payable/param.qtz
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
contract Param {
data {
A: Identity
B: Identity
}

initialize: ->(id: Identity) open {
A = id
A = B
}

test1: open -> open {
send 0 to A
}

test2: open ->(id: Identity) open {
B = id
}
}
15 changes: 15 additions & 0 deletions src/test/resources/payable/param2.qtz
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
contract Param {
data {
A: Identity
}

initialize: ->(id: Identity, id2: Identity, id3: Identity) open {
A = id
id = id2
id3 = id2
}

test1: open -> open {
send 0 to A
}
}