Skip to content

Commit

Permalink
adding support for integer literals (#311)
Browse files Browse the repository at this point in the history
* adding a test case that shows that #303 has indeed not been completed

* scraps and notes, but this is where the change will go

* initial progress

* this translates integer literals per #303. it does nothing with the return
statement, so the current output yul is not valid.

* removing a needless assertion

* #303 returning an integer constant now correctly fails. the previous implementation of return just dropped the return entirely. added a comment about how to fix this, per discussion with @mcoblenz

* #303 updating test program

* updating test program #303

* updating the mustache file per the comment, which lets the intconst file compile. see #310 for more discussion

* typo in example module name

* copying the empty contract shell file for intconst, just to demonstrate that we at least make yul that compiles and that ganache will accept
  • Loading branch information
ivoysey committed Apr 2, 2021
1 parent 80acacd commit 8fb5202
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 5 deletions.
7 changes: 7 additions & 0 deletions resources/tests/GanacheTests/IntConst.obs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
main contract IntConst{
int x;
transaction intconst() {
x = 4;
return;
}
}
150 changes: 150 additions & 0 deletions resources/tests/GanacheTests/IntConst.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#!/bin/bash

# todo these are constants that i think are likely to change per-test in
# the future; i've hoisted them up here so that they can get read in from a
# config file per test later. (issue #302)
NAME=IntConst
GAS=30000000 # this is a magic number (issue #302)
GAS_HEX=$(printf '%x' $GAS)
GAS_PRICE='0x9184e72a000' # this is a magic number from the YUL docs (issue #302)
START_ETH=5000000 # this is a magic number (issue #302)
NUM_ACCT=1

# check to make sure that both tools are installed, fail otherwise.
if ! hash ganache-cli
then
echo "ganache-cli is not installed, Install it with 'npm install -g ganache-cli'."
exit 1
fi

# compile the contract to yul, also creating the directory to work in
sbt "runMain edu.cmu.cs.obsidian.Main --yul resources/tests/GanacheTests/$NAME.obs"

# check to make sure that solc succeeded, failing otherwise
if [ $? -ne 0 ]; then
echo "$NAME test failed: sbt exited cannot compile obs to yul"
exit 1
fi

if [ ! -d "$NAME" ]; then
echo "$NAME directory failed to get created"
exit 1
fi

cd "$NAME"

# generate the evm from yul
echo "running solc to produce evm bytecode"
docker run -v "$( pwd -P )":/sources ethereum/solc:stable --abi --bin --strict-assembly /sources/"$NAME".yul > "$NAME".evm

# check to make sure that solc succeeded, failing otherwise
if [ $? -ne 0 ]; then
echo "$NAME test failed: solc cannot compile yul code"
exit 1
fi

# todo this is a bit of a hack. solc is supposed to output a json object
# and it just isn't. so this is grepping through to grab the right lines
# with the hex that represents the output. this likely fails if the binary
# is more than one line long. (issue #302)
TOP=`grep -n "Binary representation" $NAME.evm | cut -f1 -d:`
BOT=`grep -n "Text representation" $NAME.evm | cut -f1 -d:`
TOP=$((TOP+1)) # drop the line with the name
BOT=$((BOT-1)) # drop the empty line after the binary
EVM_BIN=`sed -n $TOP','$BOT'p' $NAME.evm`
echo "binary representation is: $EVM_BIN"

# start up ganache
echo "starting ganache-cli"
ganache-cli --gasLimit "$GAS" --accounts="$NUM_ACCT" --defaultBalanceEther="$START_ETH" &> /dev/null &

# form the JSON object to ask for the list of accounts
ACCT_DATA=$( jq -ncM \
--arg "jn" "2.0" \
--arg "mn" "eth_accounts" \
--argjson "pn" "[]" \
--arg "idn" "1" \
'{"jsonrpc":$jn,"method":$mn,"params":$pn,"id":$idn}'
)

# ganache-cli takes a little while to start up, and the first thing that we
# need from it is the list of accounts. so we poll on the account endpoint
# until we get a good result to avoid using sleep or something less precise.
echo "querying ganache-cli until accounts are available"
KEEPGOING=1
ACCTS=""
until [ "$KEEPGOING" -eq 0 ] ;
do
ACCTS=$(curl --silent -X POST --data "$ACCT_DATA" http://localhost:8545)
KEEPGOING=$?
sleep 1
done
echo

# we'll return this at the exit at the bottom of the file; TravisCI says a
# job passes or fails based on the last command run
RET=0

# todo: i'm not sure what account to mark as the "to" account. i think i
# can use that later to test the output of running more complicated
# contracts. i'll need to make more than one account when i start up
# ganache. (issue #302)
ACCT=`echo $ACCTS | jq '.result[0]' | tr -d '"'`
echo "ACCT is $ACCT"

# todo what's that 0x0 mean?
PARAMS=$( jq -ncM \
--arg "fn" "$ACCT" \
--arg "gn" "0x$GAS_HEX" \
--arg "gpn" "$GAS_PRICE" \
--arg "vn" "0x0" \
--arg "dn" "0x$EVM_BIN" \
'{"from":$fn,"gas":$gn,"gasPrice":$gpn,"value":$vn,"data":$dn}'
)

SEND_DATA=$( jq -ncM \
--arg "jn" "2.0" \
--arg "mn" "eth_sendTransaction" \
--argjson "pn" "$PARAMS" \
--arg "idn" "1" \
'{"jsonrpc":$jn,"method":$mn,"params":$pn,"id":$idn}'
)

echo "transaction being sent is given by"
echo "$SEND_DATA" # | jq -M #todo why doesn't this work on travis? also below. (issue #302)
echo

RESP=$(curl -s -X POST --data "$SEND_DATA" http://localhost:8545)
echo "response from ganache is: $RESP"
# ((echo "$RESP" | tr -d '\n') ; echo) # | jq -M # (issue #302)

# todo: this is not an exhaustive or principled way to check the output of
# curling a post. (issue #302)
if [ "$RESP" == "400 Bad Request" ]
then
echo "got a 400 bad response from ganache-cli"
exit 1
fi

ERROR=$(echo "$RESP" | tr -d '\n' | jq '.error.message')
if [ "$ERROR" != "null" ]
then
RET=1
echo "transaction produced an error: $ERROR"
fi

# todo check the result of test somehow to indicate failure or not (issue #302)

# clean up by killing ganache and the local files
# todo: make this a subroutine that can get called at any of the exits (issue #302)
echo "killing ganache-cli"
kill -9 $(lsof -t -i:8545)

# todo: for debugging it's nice to be able to look at these. maybe delete
# them by default but take a flag to keep them around. (issue #302)
rm "$NAME.yul"
rm "$NAME.evm"
cd "../"
rmdir "$NAME"

exit "$RET"
21 changes: 16 additions & 5 deletions src/main/scala/edu/cmu/cs/obsidian/codegen/CodeGenYul.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ package edu.cmu.cs.obsidian.codegen

import java.io.{File, FileWriter}
import java.nio.file.{Files, Path, Paths}

import edu.cmu.cs.obsidian.CompilerOptions
import edu.cmu.cs.obsidian.parser._
import edu.cmu.cs.obsidian.Main.{findMainContract, findMainContractName}

import edu.cmu.cs.obsidian.typecheck.ContractType

import scala.collection.immutable.Map
Expand Down Expand Up @@ -222,9 +220,17 @@ object CodeGenYul extends CodeGenerator {
def translateStatement(s: Statement): Seq[YulStatement] = {
s match {
case Return() =>
Seq()
Seq(Leave())
case ReturnExpr(e) =>
translateExpr(e)
assert(false, "TODO: returning a value not implemented")
Seq()
// former implementation is this:
//
// translateExpr(e)
//
// removing this may cause some tests to fail; i'm not sure. to implement this the
// right way, we need to allocate some space in some form of memory and put e there
// to follow the semantics for return in Yul.
case Assignment(assignTo, e) =>
assignTo match {
// TODO only support int/int256 now
Expand Down Expand Up @@ -258,12 +264,15 @@ object CodeGenYul extends CodeGenerator {
val idx = tempSymbolTable(x)
val expr = FunctionCall(Identifier("sload"), Seq(Literal(LiteralKind.number, idx.toString(), "int")))
Seq(ExpressionStatement(expr))
case NumLiteral(n) =>
// we compile to int, which is s256 in yul
Seq(ExpressionStatement(Literal(LiteralKind.number,n.toString(),"int")))
case TrueLiteral() =>
Seq(ExpressionStatement(Literal(LiteralKind.boolean, "true", "bool")))
case FalseLiteral() =>
Seq(ExpressionStatement(Literal(LiteralKind.boolean, "false", "bool")))
case _ =>
assert(false, "TODO: " + e.toString())
assert(false, "TODO: translation of " + e.toString() + " is not implemented")
Seq() // TODO unimplemented
}
}
Expand Down Expand Up @@ -373,6 +382,8 @@ class FuncScope(f: FunctionDefinition) {
e match {
case func: FunctionCall =>
codeBody = codeBody :+ new Body(func.yulFunctionCallString())
case litn : Literal =>
codeBody = codeBody :+ new Body(litn.value.toString())
}
case _ => ()
}
Expand Down

0 comments on commit 8fb5202

Please sign in to comment.