OSDN Git Service

fix related check to support that if-else statement include lock/unlock statement...
authoroysheng <33340252+oysheng@users.noreply.github.com>
Thu, 13 Sep 2018 09:11:35 +0000 (17:11 +0800)
committerPaladz <yzhu101@uottawa.ca>
Thu, 13 Sep 2018 09:11:35 +0000 (17:11 +0800)
* fix Hash Type check

* optimise lock statement

* add if-else check

* optimise check value

* modify value used count

* add statement result check for if-else

* fix check index for if-else statement

* modify test name

* add recursive contract

compiler/checks.go
compiler/compile.go
compiler/compile_test.go
compiler/equitytest/equitytest.go

index a0c7992..b9720af 100644 (file)
@@ -5,18 +5,40 @@ import "fmt"
 func checkRecursive(contract *Contract) bool {
        for _, clause := range contract.Clauses {
                for _, stmt := range clause.statements {
-                       if l, ok := stmt.(*lockStatement); ok {
-                               if c, ok := l.program.(*callExpr); ok {
-                                       if references(c.fn, contract.Name) {
-                                               return true
-                                       }
-                               }
+                       if result := checkStatRecursive(stmt, contract.Name); result {
+                               return true
                        }
                }
        }
        return false
 }
 
+func checkStatRecursive(stmt statement, contractName string) bool {
+       switch s := stmt.(type) {
+       case *ifStatement:
+               for _, trueStmt := range s.body.trueBody {
+                       if result := checkStatRecursive(trueStmt, contractName); result {
+                               return true
+                       }
+               }
+
+               for _, falseStmt := range s.body.falseBody {
+                       if result := checkStatRecursive(falseStmt, contractName); result {
+                               return true
+                       }
+               }
+
+       case *lockStatement:
+               if c, ok := s.program.(*callExpr); ok {
+                       if references(c.fn, contractName) {
+                               return true
+                       }
+               }
+       }
+
+       return false
+}
+
 func prohibitSigParams(contract *Contract) error {
        for _, p := range contract.Params {
                if p.Type == sigType {
@@ -133,19 +155,13 @@ func requireAllValuesDisposedOnce(contract *Contract, clause *Clause) error {
 
 func valueDisposedOnce(value ValueInfo, clause *Clause) error {
        var count int
-       for _, s := range clause.statements {
-               switch stmt := s.(type) {
-               case *unlockStatement:
-                       if references(stmt.unlockedAmount, value.Amount) && references(stmt.unlockedAsset, value.Asset) {
-                               count++
-                       }
-               case *lockStatement:
-                       if references(stmt.lockedAmount, value.Amount) && references(stmt.lockedAsset, value.Asset) {
-                               count++
-                       }
-               }
+       for _, stmt := range clause.statements {
+               count = valueDisposedCount(value, stmt, count)
        }
        switch count {
+       case -1:
+               return fmt.Errorf("valueAmount \"%s\" and valueAsset \"%s\" used times is not equal between if and else statement in clause \"%s\"",
+                       value.Amount, value.Asset, clause.Name)
        case 0:
                return fmt.Errorf("valueAmount \"%s\" or valueAsset \"%s\" not disposed in clause \"%s\"", value.Amount, value.Asset, clause.Name)
        case 1:
@@ -155,6 +171,37 @@ func valueDisposedOnce(value ValueInfo, clause *Clause) error {
        }
 }
 
+func valueDisposedCount(value ValueInfo, stat statement, count int) int {
+       switch stmt := stat.(type) {
+       case *ifStatement:
+               var trueCount int
+               var falseCount int
+               for _, trueStmt := range stmt.body.trueBody {
+                       trueCount = valueDisposedCount(value, trueStmt, count)
+               }
+
+               for _, falseStmt := range stmt.body.falseBody {
+                       falseCount = valueDisposedCount(value, falseStmt, count)
+               }
+
+               if trueCount != falseCount {
+                       return -1
+               }
+               count = trueCount
+
+       case *unlockStatement:
+               if references(stmt.unlockedAmount, value.Amount) && references(stmt.unlockedAsset, value.Asset) {
+                       count++
+               }
+       case *lockStatement:
+               if references(stmt.lockedAmount, value.Amount) && references(stmt.lockedAsset, value.Asset) {
+                       count++
+               }
+       }
+
+       return count
+}
+
 func referencedBuiltin(expr expression) *builtin {
        if v, ok := expr.(varRef); ok {
                for _, b := range builtins {
@@ -166,51 +213,105 @@ func referencedBuiltin(expr expression) *builtin {
        return nil
 }
 
-func assignIndexes(clause *Clause) {
+func assignIndexes(clause *Clause) error {
        var nextIndex int64
-       for _, s := range clause.statements {
-               switch stmt := s.(type) {
-               case *lockStatement:
-                       stmt.index = nextIndex
-                       nextIndex++
+       for i, stmt := range clause.statements {
+               if nextIndex = assignStatIndexes(stmt, nextIndex, i != len(clause.statements)-1); nextIndex < 0 {
+                       return fmt.Errorf("Not support that the number of lock/unlock statement is not equal between ifbody and elsebody when the if-else is not the last statement in clause \"%s\"", clause.Name)
+               }
+       }
+
+       return nil
+}
 
-               case *unlockStatement:
-                       nextIndex++
+func assignStatIndexes(stat statement, nextIndex int64, nonFinalFlag bool) int64 {
+       switch stmt := stat.(type) {
+       case *ifStatement:
+               trueIndex := nextIndex
+               falseIndex := nextIndex
+               for _, trueStmt := range stmt.body.trueBody {
+                       trueIndex = assignStatIndexes(trueStmt, trueIndex, nonFinalFlag)
                }
+
+               for _, falseStmt := range stmt.body.falseBody {
+                       falseIndex = assignStatIndexes(falseStmt, falseIndex, nonFinalFlag)
+               }
+
+               if trueIndex != falseIndex && nonFinalFlag {
+                       return -1
+               } else if trueIndex == falseIndex {
+                       nextIndex = trueIndex
+               }
+
+       case *lockStatement:
+               stmt.index = nextIndex
+               nextIndex++
+
+       case *unlockStatement:
+               nextIndex++
        }
+
+       return nextIndex
 }
 
 func typeCheckClause(contract *Contract, clause *Clause, env *environ) error {
        for _, s := range clause.statements {
-               switch stmt := s.(type) {
-               case *verifyStatement:
-                       if t := stmt.expr.typ(env); t != boolType {
-                               return fmt.Errorf("expression in verify statement in clause \"%s\" has type \"%s\", must be Boolean", clause.Name, t)
-                       }
+               if err := typeCheckStatement(s, contract.Value, clause.Name, env); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
 
-               case *lockStatement:
-                       if t := stmt.lockedAmount.typ(env); !(t == intType || t == amountType) {
-                               return fmt.Errorf("lockedAmount expression \"%s\" in lock statement in clause \"%s\" has type \"%s\", must be Integer", stmt.lockedAmount, clause.Name, t)
-                       }
-                       if t := stmt.lockedAsset.typ(env); t != assetType {
-                               return fmt.Errorf("lockedAsset expression \"%s\" in lock statement in clause \"%s\" has type \"%s\", must be Asset", stmt.lockedAsset, clause.Name, t)
-                       }
-                       if t := stmt.program.typ(env); t != progType {
-                               return fmt.Errorf("program in lock statement in clause \"%s\" has type \"%s\", must be Program", clause.Name, t)
+func typeCheckStatement(stat statement, contractValue ValueInfo, clauseName string, env *environ) error {
+       switch stmt := stat.(type) {
+       case *ifStatement:
+               for _, trueStmt := range stmt.body.trueBody {
+                       if err := typeCheckStatement(trueStmt, contractValue, clauseName, env); err != nil {
+                               return err
                        }
+               }
 
-               case *unlockStatement:
-                       if t := stmt.unlockedAmount.typ(env); !(t == intType || t == amountType) {
-                               return fmt.Errorf("unlockedAmount expression \"%s\" in unlock statement of clause \"%s\" has type \"%s\", must be Integer", stmt.unlockedAmount, clause.Name, t)
-                       }
-                       if t := stmt.unlockedAsset.typ(env); t != assetType {
-                               return fmt.Errorf("unlockedAsset expression \"%s\" in unlock statement of clause \"%s\" has type \"%s\", must be Asset", stmt.unlockedAsset, clause.Name, t)
-                       }
-                       if stmt.unlockedAmount.String() != contract.Value.Amount || stmt.unlockedAsset.String() != contract.Value.Asset {
-                               return fmt.Errorf("amount \"%s\" of asset \"%s\" expression in unlock statement of clause \"%s\" must be the contract valueAmount \"%s\" of valueAsset \"%s\"",
-                                       stmt.unlockedAmount.String(), stmt.unlockedAsset.String(), clause.Name, contract.Value.Amount, contract.Value.Asset)
+               for _, falseStmt := range stmt.body.falseBody {
+                       if err := typeCheckStatement(falseStmt, contractValue, clauseName, env); err != nil {
+                               return err
                        }
                }
+
+       case *defineStatement:
+               if stmt.expr.typ(env) != stmt.varName.Type && !isHashSubtype(stmt.expr.typ(env)) {
+                       return fmt.Errorf("expression in define statement in clause \"%s\" has type \"%s\", must be \"%s\"",
+                               clauseName, stmt.expr.typ(env), stmt.varName.Type)
+               }
+
+       case *verifyStatement:
+               if t := stmt.expr.typ(env); t != boolType {
+                       return fmt.Errorf("expression in verify statement in clause \"%s\" has type \"%s\", must be Boolean", clauseName, t)
+               }
+
+       case *lockStatement:
+               if t := stmt.lockedAmount.typ(env); !(t == intType || t == amountType) {
+                       return fmt.Errorf("lockedAmount expression \"%s\" in lock statement in clause \"%s\" has type \"%s\", must be Integer", stmt.lockedAmount, clauseName, t)
+               }
+               if t := stmt.lockedAsset.typ(env); t != assetType {
+                       return fmt.Errorf("lockedAsset expression \"%s\" in lock statement in clause \"%s\" has type \"%s\", must be Asset", stmt.lockedAsset, clauseName, t)
+               }
+               if t := stmt.program.typ(env); t != progType {
+                       return fmt.Errorf("program in lock statement in clause \"%s\" has type \"%s\", must be Program", clauseName, t)
+               }
+
+       case *unlockStatement:
+               if t := stmt.unlockedAmount.typ(env); !(t == intType || t == amountType) {
+                       return fmt.Errorf("unlockedAmount expression \"%s\" in unlock statement of clause \"%s\" has type \"%s\", must be Integer", stmt.unlockedAmount, clauseName, t)
+               }
+               if t := stmt.unlockedAsset.typ(env); t != assetType {
+                       return fmt.Errorf("unlockedAsset expression \"%s\" in unlock statement of clause \"%s\" has type \"%s\", must be Asset", stmt.unlockedAsset, clauseName, t)
+               }
+               if stmt.unlockedAmount.String() != contractValue.Amount || stmt.unlockedAsset.String() != contractValue.Asset {
+                       return fmt.Errorf("amount \"%s\" of asset \"%s\" expression in unlock statement of clause \"%s\" must be the contract valueAmount \"%s\" of valueAsset \"%s\"",
+                               stmt.unlockedAmount.String(), stmt.unlockedAsset.String(), clauseName, contractValue.Amount, contractValue.Asset)
+               }
        }
+
        return nil
 }
index e86399d..b9199f2 100644 (file)
@@ -304,7 +304,10 @@ func compileClause(b *builder, contractStk stack, contract *Contract, env *envir
                        return err
                }
        }
-       assignIndexes(clause)
+
+       if err = assignIndexes(clause); err != nil {
+               return err
+       }
 
        var stk stack
        for _, p := range clause.Params {
@@ -395,6 +398,16 @@ func compileStatement(b *builder, stk stack, contract *Contract, env *environ, c
                                st.countVarRefs(counts)
                        }
 
+                       // modify value amount because of using only once
+                       if counts[contract.Value.Amount] > 1 {
+                               counts[contract.Value.Amount] = 1
+                       }
+
+                       // modify value asset because of using only once
+                       if counts[contract.Value.Asset] > 1 {
+                               counts[contract.Value.Asset] = 1
+                       }
+
                        for _, st := range stmt.body.trueBody {
                                if stk, err = compileStatement(b, stk, contract, env, clause, counts, st, sequence); err != nil {
                                        return stk, err
@@ -413,6 +426,16 @@ func compileStatement(b *builder, stk stack, contract *Contract, env *environ, c
                                st.countVarRefs(counts)
                        }
 
+                       // modify value amount because of using only once
+                       if counts[contract.Value.Amount] > 1 {
+                               counts[contract.Value.Amount] = 1
+                       }
+
+                       // modify value asset because of using only once
+                       if counts[contract.Value.Asset] > 1 {
+                               counts[contract.Value.Asset] = 1
+                       }
+
                        stk = condStk
                        b.addJump(stk, "endif_"+strSequence)
                        b.addJumpTarget(stk, "else_"+strSequence)
@@ -432,12 +455,6 @@ func compileStatement(b *builder, stk stack, contract *Contract, env *environ, c
                        return stk, errors.Wrapf(err, "in define statement in clause \"%s\"", clause.Name)
                }
 
-               // check variable type
-               if stmt.expr.typ(env) != stmt.varName.Type {
-                       return stk, fmt.Errorf("expression in define statement in clause \"%s\" has type \"%s\", must be \"%s\"",
-                               clause.Name, stmt.expr.typ(env), stmt.varName.Type)
-               }
-
                // modify stack name
                stk.str = stmt.varName.Name
 
@@ -476,24 +493,36 @@ func compileStatement(b *builder, stk stack, contract *Contract, env *environ, c
                        stk = b.addAmount(stk, contract.Value.Amount)
                        stk = b.addAsset(stk, contract.Value.Asset)
                } else {
-                       if strings.Contains(stmt.lockedAmount.String(), contract.Value.Amount) {
-                               stk = b.addAmount(stk, contract.Value.Amount)
-                       }
-
-                       if strings.Contains(stmt.lockedAsset.String(), contract.Value.Asset) {
-                               stk = b.addAsset(stk, contract.Value.Asset)
-                       }
-
                        // amount
-                       stk, err = compileExpr(b, stk, contract, clause, env, counts, stmt.lockedAmount)
-                       if err != nil {
-                               return stk, errors.Wrapf(err, "in lock statement in clause \"%s\"", clause.Name)
+                       if stmt.lockedAmount.String() == contract.Value.Amount {
+                               stk = b.addAmount(stk, contract.Value.Amount)
+                       } else if strings.Contains(stmt.lockedAmount.String(), contract.Value.Amount) {
+                               stk = b.addAmount(stk, contract.Value.Amount)
+                               stk, err = compileExpr(b, stk, contract, clause, env, counts, stmt.lockedAmount)
+                               if err != nil {
+                                       return stk, errors.Wrapf(err, "in lock statement in clause \"%s\"", clause.Name)
+                               }
+                       } else {
+                               stk, err = compileExpr(b, stk, contract, clause, env, counts, stmt.lockedAmount)
+                               if err != nil {
+                                       return stk, errors.Wrapf(err, "in lock statement in clause \"%s\"", clause.Name)
+                               }
                        }
 
                        // asset
-                       stk, err = compileExpr(b, stk, contract, clause, env, counts, stmt.lockedAsset)
-                       if err != nil {
-                               return stk, errors.Wrapf(err, "in lock statement in clause \"%s\"", clause.Name)
+                       if stmt.lockedAsset.String() == contract.Value.Asset {
+                               stk = b.addAsset(stk, contract.Value.Asset)
+                       } else if strings.Contains(stmt.lockedAsset.String(), contract.Value.Asset) {
+                               stk = b.addAsset(stk, contract.Value.Asset)
+                               stk, err = compileExpr(b, stk, contract, clause, env, counts, stmt.lockedAsset)
+                               if err != nil {
+                                       return stk, errors.Wrapf(err, "in lock statement in clause \"%s\"", clause.Name)
+                               }
+                       } else {
+                               stk, err = compileExpr(b, stk, contract, clause, env, counts, stmt.lockedAsset)
+                               if err != nil {
+                                       return stk, errors.Wrapf(err, "in lock statement in clause \"%s\"", clause.Name)
+                               }
                        }
                }
 
index aea9644..f1ff092 100644 (file)
@@ -57,6 +57,11 @@ func TestCompile(t *testing.T) {
                        "7caa87",
                },
                {
+                       "PriceChanger",
+                       equitytest.PriceChanger,
+                       "557a6432000000557a5479ae7cac6900c3c25100597a89587a89587a89587a89557a890274787e008901c07ec1633a000000007b537a51567ac1",
+               },
+               {
                        "TestDefineVar",
                        equitytest.TestDefineVar,
                        "52797b937b7887916987",
@@ -72,8 +77,8 @@ func TestCompile(t *testing.T) {
                        "7b641f0000007087916976547aa00087641a000000765379a06963240000007b7bae7cac",
                },
                {
-                       "TestIfRecursive",
-                       equitytest.TestIfRecursive,
+                       "TestIfNesting",
+                       equitytest.TestIfNesting,
                        "7b644400000054795279879169765579a00087643500000052795479a000876429000000765379a06952795579879169633a000000765479a06953797b8791635c0000007654798791695279a000876459000000527978a0697d8791",
                },
        }
index b08374a..701662f 100644 (file)
@@ -78,6 +78,18 @@ contract RevealPreimage(hash: Hash) locks amount of asset {
   }
 }
 `
+const PriceChanger = `
+contract PriceChanger(askAmount: Amount, askAsset: Asset, sellerKey: PublicKey, sellerProg: Program) locks valueAmount of valueAsset {
+  clause changePrice(newAmount: Amount, newAsset: Asset, sig: Signature) {
+    verify checkTxSig(sellerKey, sig)
+    lock valueAmount of valueAsset with PriceChanger(newAmount, newAsset, sellerKey, sellerProg)
+  }
+  clause redeem() {
+    lock askAmount of askAsset with sellerProg
+    unlock valueAmount of valueAsset
+  }
+}
+`
 
 const TestDefineVar = `
 contract TestDefineVar(result: Integer) locks valueAmount of valueAsset {
@@ -119,8 +131,8 @@ contract TestIfAndMultiClause(a: Integer, cancelKey: PublicKey) locks valueAmoun
 }
 `
 
-const TestIfRecursive = `
-contract TestIfRecursive(a: Integer, count:Integer) locks valueAmount of valueAsset {
+const TestIfNesting = `
+contract TestIfNesting(a: Integer, count:Integer) locks valueAmount of valueAsset {
   clause check(b: Integer, c: Integer, d: Integer) {
     verify b != count
     if a > b {