Skip to content

Commit

Permalink
copy to addresses plus an offset, check pointer reads (#420)
Browse files Browse the repository at this point in the history
* changing the tracer to copy to addresses plus an offset, adding helper code to compute those

* swapping out nasty hex constant for a more concise representation

* whitesapce

* adding storage check on pointer writes

* only call trace after the constructor for main

* updating the test definition to reflect the changes in the tracer calls
  • Loading branch information
ivoysey committed Feb 22, 2022
1 parent 56bec1f commit 618ec3b
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 34 deletions.
5 changes: 2 additions & 3 deletions resources/tests/GanacheTests/tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -260,14 +260,13 @@
"file": "SetGetLogs.obs",
"expected": "15",
"trans" : "main_sgl",
"logged" : [0, 224, 224],
"shows_that_we_support": "simple set-get with tracers that emit values to the logs. this shows that the harness can read logs; the values logged are immaterial"
"shows_that_we_support": "simple set-get with tracers that emit values to the logs. this shows that the harness can read logs; at the moment the tracers here do not emit any"
},
{
"file": "SetGetConstructorField.obs",
"expected": "12",
"trans" : "main",
"logged" : [0,0,0],
"logged" : [0,0],
"shows_that_we_support": "set-get with a field so that the emitted tracer is not trivial, and that uses the constructor for that field "
}
]
Expand Down
65 changes: 42 additions & 23 deletions src/main/scala/edu/cmu/cs/obsidian/codegen/CodeGenYul.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package edu.cmu.cs.obsidian.codegen

import edu.cmu.cs.obsidian.CompilerOptions
import edu.cmu.cs.obsidian.{CompilerOptions, codegen}
import edu.cmu.cs.obsidian.typecheck._

import java.io.{File, FileWriter}
Expand Down Expand Up @@ -128,25 +128,28 @@ object CodeGenYul extends CodeGenerator {
for (d <- c.declarations) {
d match {
case Field(_, typ, fname, _) =>
val loc = fieldFromThis(ct.contractLookup(name), fname)
val log_temp = nextTemp()
val log =
val mem_loc: codegen.Expression = fieldFromThis(ct.contractLookup(name), fname)
val sto_loc: codegen.Expression = mapToStorageAddress(mem_loc)
val log_temp: Identifier = nextTemp()

val load = Seq(
//sstore(add(this,offset), mload(add(this,offset)))
LineComment("loading"),
ExpressionStatement(apply("sstore", sto_loc, apply("mload", mem_loc))))

val log : Seq[YulStatement] =
if (emit_logs) {
Seq(LineComment("logging"),
// allocate memory to log from
decl_1exp(log_temp, apply("allocate_memory", intlit(32))),
// load what we just wrote to storage to that location
ExpressionStatement(apply("mstore", log_temp, apply("sload", loc))),
ExpressionStatement(apply("mstore", log_temp, apply("sload", sto_loc))),
// emit the log
ExpressionStatement(apply("log0", log_temp, intlit(32)))
)
} else {
Seq()
}
val load = Seq(
//sstore(add(this,offset), mload(add(this,offset)))
LineComment("loading"),
ExpressionStatement(apply("sstore", loc, apply("mload", loc))))


typ match {
Expand All @@ -155,7 +158,7 @@ object CodeGenYul extends CodeGenerator {
body = body ++ load ++ log ++
Seq(
LineComment("traversal"),
ExpressionStatement(apply(nameTracer(contractType.contractName), loc))
ExpressionStatement(apply(nameTracer(contractType.contractName), mem_loc))
)
// todo: this recursive call may not be needed if we generate tracers
// for every contract in the program
Expand Down Expand Up @@ -301,10 +304,14 @@ object CodeGenYul extends CodeGenerator {
val id = nextTemp()
val e_yul = translateExpr(id, e, contractName, checkedTable, inMain)
val ct = checkedTable.contractLookup(contractName)
val address_of_field = Util.fieldFromThis(ct, x)
decl_0exp(id) +:
e_yul :+
(if (ct.allFields.exists(f => f.name.equals(x))) {
ExpressionStatement(apply("mstore", Util.fieldFromThis(ct, x), id))
ifInStorge(addr_to_check = address_of_field,
true_case = Seq(ExpressionStatement(apply("sstore", address_of_field, id))),
false_case = Seq(ExpressionStatement(apply("mstore", address_of_field, id)))
)
} else {
assign1(Identifier(x), id)
})
Expand Down Expand Up @@ -577,9 +584,12 @@ object CodeGenYul extends CodeGenerator {
case _ => false
}

// does this constructor belong to the main contract?
val isMainContract = checkedTable.contractLookup(contractType.contractName).contract.isMain

// the constructor(s) for the main contract are not prefixed with the name of the contract.
val invoke_name =
if (checkedTable.contractLookup(contractType.contractName).contract.isMain) {
if (isMainContract) {
contractType.contractName + hashOfFunctionName(contractType.contractName, typeNames)
} else {
transactionNameMapping(contractType.contractName, contractType.contractName) + hashOfFunctionName(contractType.contractName, typeNames)
Expand All @@ -589,21 +599,30 @@ object CodeGenYul extends CodeGenerator {
// arguments and invoke the constructor as normal transaction with the hash appended
// to the name to call the right one
val conCall =
if (checkedTable.contract(contractType.contractName).get.contract.declarations.exists(d => isMatchingConstructor(d))) {
translateInvocation(name = invoke_name,
args = args,
obstype = Some(UnitType()),
thisID = id_memaddr,
retvar = retvar, contractName = contractName, checkedTable = checkedTable, inMain = inMain)
} else {
Seq()
}
if (checkedTable.contract(contractType.contractName).get.contract.declarations.exists(d => isMatchingConstructor(d))) {
translateInvocation(name = invoke_name,
args = args,
obstype = Some(UnitType()),
thisID = id_memaddr,
retvar = retvar, contractName = contractName, checkedTable = checkedTable, inMain = inMain)
} else {
Seq()
}

// we only call the tracer after the constructor for the main contract
val traceCall =
if(isMainContract){
Seq(ExpressionStatement(apply(nameTracer(contractType.contractName), Identifier("this"))))
} else {
Seq()
}


Seq( // grab the appropriate amount of space of memory sequentially, off the free memory pointer
decl_1exp(id_memaddr, apply("allocate_memory", intlit(sizeOfContractST(contractType.contractName, checkedTable)))),

// return the address that the space starts at
assign1(retvar, id_memaddr)) ++ conCall :+ ExpressionStatement(apply(nameTracer(contractType.contractName), Identifier("this")))
// return the address that the space starts at, call the constructor and the tracer as above
assign1(retvar, id_memaddr)) ++ conCall ++ traceCall


case StateInitializer(stateName, fieldName, obstype) =>
Expand Down
43 changes: 35 additions & 8 deletions src/main/scala/edu/cmu/cs/obsidian/codegen/Util.scala
Original file line number Diff line number Diff line change
Expand Up @@ -523,15 +523,15 @@ object Util {
val ret = TypedName("ret", t)

val bod = t match {
case YATAddress() => throw new RuntimeException(s"abi decoding not implemented for ${t.toString}")
case YATAddress() => throw new RuntimeException(s"abi decoding not implemented for ${t.toString}")
case YATUInt32() =>
Seq(
//value := calldataload(offset)
assign1(Identifier(ret.name), apply("calldataload", Identifier(offset.name)))
)
case YATBool() => throw new RuntimeException(s"abi decoding not implemented for ${t.toString}")
case YATString() => throw new RuntimeException(s"abi decoding not implemented for ${t.toString}")
case YATContractName(name) => throw new RuntimeException(s"abi decoding not implemented for ${t.toString}")
Seq(
//value := calldataload(offset)
assign1(Identifier(ret.name), apply("calldataload", Identifier(offset.name)))
)
case YATBool() => throw new RuntimeException(s"abi decoding not implemented for ${t.toString}")
case YATString() => throw new RuntimeException(s"abi decoding not implemented for ${t.toString}")
case YATContractName(_) => throw new RuntimeException(s"abi decoding not implemented for ${t.toString}")
}

FunctionDefinition(name = abi_decode_name(t),
Expand Down Expand Up @@ -568,4 +568,31 @@ object Util {
returnVariables = retVars,
body = Block(bod))
}

// storage is 2**256 big; this is (2**256)/2, which is the same as 1 << 255 or shl(255,1) in yul
val storage_threshold: Expression = apply("shl", intlit(255), intlit(1))

/** given an expression representing a memory address its corresponding place in storage
*
* @param x the expression representing the memory address
* @return an expression that computes to the corresponding address in storage
*/
def mapToStorageAddress(x: Expression): Expression = {
apply("add", x, storage_threshold)
}

/** given an expression that represents an address, compute the yul statement that checks if it's
* a storage address or not and execute a sequence of statements in either case. if it does not
* represent an address, the behaviour is undefined.
*
* @param addr_to_check the expression to check for being in storage
* @param true_case what to do if the address is a storage address
* @param false_case what to to if the address is not a storage address
* @return the expression performing the check
*/
def ifInStorge(addr_to_check: Expression, true_case: Seq[YulStatement], false_case: Seq[YulStatement]): YulStatement = {
edu.cmu.cs.obsidian.codegen.Switch(apply("gt", addr_to_check, storage_threshold),
Seq(Case(boollit(true), Block(true_case)),
Case(boollit(false), Block(false_case))))
}
}

0 comments on commit 618ec3b

Please sign in to comment.