--- /dev/null
+package compiler
+
+import (
+ "encoding/hex"
+ "fmt"
+ "strconv"
+ "strings"
+
+ chainjson "github.com/bytom/encoding/json"
+)
+
+// Contract is a compiled Equity contract.
+type Contract struct {
+ // Name is the contract name.
+ Name string `json:"name"`
+
+ // Params is the list of contract parameters.
+ Params []*Param `json:"params,omitempty"`
+
+ // Clauses is the list of contract clauses.
+ Clauses []*Clause `json:"clauses"`
+
+ // Value is the name of the value locked by the contract.
+ Value string `json:"value"`
+
+ // Body is the optimized bytecode of the contract body. This is not
+ // a complete program! Use instantiate to turn this (plus some
+ // arguments) into a program.
+ Body chainjson.HexBytes `json:"body_bytecode"`
+
+ // Opcodes is the human-readable string of opcodes corresponding to
+ // Body.
+ Opcodes string `json:"body_opcodes,omitempty"`
+
+ // Recursive tells whether this contract calls itself. (This is
+ // used to select between two possible instantiation options.)
+ Recursive bool `json:"recursive"`
+
+ // Pre-optimized list of instruction steps, with stack snapshots.
+ Steps []Step `json:"-"`
+}
+
+// Param is a contract or clause parameter.
+type Param struct {
+ // Name is the parameter name.
+ Name string `json:"name"`
+
+ // Type is the declared parameter type.
+ Type typeDesc `json:"type"`
+
+ // InferredType, if available, is a more-specific type than Type,
+ // inferred from the logic of the contract.
+ InferredType typeDesc `json:"inferred_type,omitempty"`
+}
+
+// Clause is a compiled contract clause.
+type Clause struct {
+ // Name is the clause name.
+ Name string `json:"name"`
+
+ // Params is the list of clause parameters.
+ Params []*Param `json:"params,omitempty"`
+
+ // Reqs is the list of requirements (from the clause's "requires"
+ // section).
+ Reqs []*ClauseReq `json:"reqs,omitempty"`
+
+ statements []statement
+
+ // BlockHeight is the list of expressions passed to greater()/less() in this
+ // clause.
+ BlockHeight []string `json:"blockheight,omitempty"`
+
+ // HashCalls is the list of hash functions and their arguments used
+ // in this clause.
+ HashCalls []HashCall `json:"hash_calls,omitempty"`
+
+ // Values is the list of values unlocked or relocked in this clause.
+ Values []ValueInfo `json:"values"`
+
+ // Contracts is the list of contracts called by this clause.
+ Contracts []string `json:"contracts,omitempty"`
+}
+
+// HashCall describes a call to a hash function.
+type HashCall struct {
+ // HashType is "sha3" or "sha256".
+ HashType string `json:"hash_type"`
+
+ // Arg is the expression passed to the hash function.
+ Arg string `json:"arg"`
+
+ // ArgType is the type of Arg.
+ ArgType string `json:"arg_type"`
+}
+
+// ClauseReq describes a payment requirement of a clause (one of the
+// things after the "requires" keyword).
+type ClauseReq struct {
+ Name string `json:"name"`
+
+ assetExpr, amountExpr expression
+
+ // Asset is the expression describing the required asset.
+ Asset string `json:"asset"`
+
+ // Amount is the expression describing the required amount.
+ Amount string `json:"amount"`
+}
+
+type statement interface {
+ countVarRefs(map[string]int)
+}
+
+type verifyStatement struct {
+ expr expression
+}
+
+func (s verifyStatement) countVarRefs(counts map[string]int) {
+ s.expr.countVarRefs(counts)
+}
+
+type lockStatement struct {
+ locked expression
+ program expression
+
+ // Added as a decoration, used by CHECKOUTPUT
+ index int64
+}
+
+func (s lockStatement) countVarRefs(counts map[string]int) {
+ s.locked.countVarRefs(counts)
+ s.program.countVarRefs(counts)
+}
+
+type unlockStatement struct {
+ expr expression
+}
+
+func (s unlockStatement) countVarRefs(counts map[string]int) {
+ s.expr.countVarRefs(counts)
+}
+
+type expression interface {
+ String() string
+ typ(*environ) typeDesc
+ countVarRefs(map[string]int)
+}
+
+type binaryExpr struct {
+ left, right expression
+ op *binaryOp
+}
+
+func (e binaryExpr) String() string {
+ return fmt.Sprintf("(%s %s %s)", e.left, e.op.op, e.right)
+}
+
+func (e binaryExpr) typ(*environ) typeDesc {
+ return e.op.result
+}
+
+func (e binaryExpr) countVarRefs(counts map[string]int) {
+ e.left.countVarRefs(counts)
+ e.right.countVarRefs(counts)
+}
+
+type unaryExpr struct {
+ op *unaryOp
+ expr expression
+}
+
+func (e unaryExpr) String() string {
+ return fmt.Sprintf("%s%s", e.op.op, e.expr)
+}
+
+func (e unaryExpr) typ(*environ) typeDesc {
+ return e.op.result
+}
+
+func (e unaryExpr) countVarRefs(counts map[string]int) {
+ e.expr.countVarRefs(counts)
+}
+
+type callExpr struct {
+ fn expression
+ args []expression
+}
+
+func (e callExpr) String() string {
+ var argStrs []string
+ for _, a := range e.args {
+ argStrs = append(argStrs, a.String())
+ }
+ return fmt.Sprintf("%s(%s)", e.fn, strings.Join(argStrs, ", "))
+}
+
+func (e callExpr) typ(env *environ) typeDesc {
+ if b := referencedBuiltin(e.fn); b != nil {
+ switch b.name {
+ case "sha3":
+ if len(e.args) == 1 {
+ switch e.args[0].typ(env) {
+ case strType:
+ return sha3StrType
+ case pubkeyType:
+ return sha3PubkeyType
+ }
+ }
+
+ case "sha256":
+ if len(e.args) == 1 {
+ switch e.args[0].typ(env) {
+ case strType:
+ return sha256StrType
+ case pubkeyType:
+ return sha256PubkeyType
+ }
+ }
+ }
+
+ return b.result
+ }
+ if e.fn.typ(env) == predType {
+ return boolType
+ }
+ if e.fn.typ(env) == contractType {
+ return progType
+ }
+ return nilType
+}
+
+func (e callExpr) countVarRefs(counts map[string]int) {
+ e.fn.countVarRefs(counts)
+ for _, a := range e.args {
+ a.countVarRefs(counts)
+ }
+}
+
+type varRef string
+
+func (v varRef) String() string {
+ return string(v)
+}
+
+func (e varRef) typ(env *environ) typeDesc {
+ if entry := env.lookup(string(e)); entry != nil {
+ return entry.t
+ }
+ return nilType
+}
+
+func (e varRef) countVarRefs(counts map[string]int) {
+ counts[string(e)]++
+}
+
+type bytesLiteral []byte
+
+func (e bytesLiteral) String() string {
+ return "0x" + hex.EncodeToString([]byte(e))
+}
+
+func (bytesLiteral) typ(*environ) typeDesc {
+ return "String"
+}
+
+func (bytesLiteral) countVarRefs(map[string]int) {}
+
+type integerLiteral int64
+
+func (e integerLiteral) String() string {
+ return strconv.FormatInt(int64(e), 10)
+}
+
+func (integerLiteral) typ(*environ) typeDesc {
+ return "Integer"
+}
+
+func (integerLiteral) countVarRefs(map[string]int) {}
+
+type booleanLiteral bool
+
+func (e booleanLiteral) String() string {
+ if e {
+ return "true"
+ }
+ return "false"
+}
+
+func (booleanLiteral) typ(*environ) typeDesc {
+ return "Boolean"
+}
+
+func (booleanLiteral) countVarRefs(map[string]int) {}
+
+type listExpr []expression
+
+func (e listExpr) String() string {
+ var elts []string
+ for _, elt := range e {
+ elts = append(elts, elt.String())
+ }
+ return fmt.Sprintf("[%s]", strings.Join(elts, ", "))
+}
+
+func (listExpr) typ(*environ) typeDesc {
+ return "List"
+}
+
+func (e listExpr) countVarRefs(counts map[string]int) {
+ for _, elt := range e {
+ elt.countVarRefs(counts)
+ }
+}
--- /dev/null
+package compiler
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+type builder struct {
+ items []*builderItem
+ pendingVerify *builderItem
+}
+
+type builderItem struct {
+ opcodes string
+ stk stack
+}
+
+func (b *builder) add(opcodes string, newstack stack) stack {
+ if b.pendingVerify != nil {
+ b.items = append(b.items, b.pendingVerify)
+ b.pendingVerify = nil
+ }
+ item := &builderItem{opcodes: opcodes, stk: newstack}
+ if opcodes == "VERIFY" {
+ b.pendingVerify = item
+ } else {
+ b.items = append(b.items, item)
+ }
+ return newstack
+}
+
+func (b *builder) addRoll(stk stack, n int) stack {
+ b.addInt64(stk, int64(n))
+ return b.add("ROLL", stk.roll(n))
+}
+
+func (b *builder) addDup(stk stack) stack {
+ return b.add("DUP", stk.dup())
+}
+
+func (b *builder) addInt64(stk stack, n int64) stack {
+ s := strconv.FormatInt(n, 10)
+ return b.add(s, stk.add(s))
+}
+
+func (b *builder) addNumEqual(stk stack, desc string) stack {
+ return b.add("NUMEQUAL", stk.dropN(2).add(desc))
+}
+
+func (b *builder) addJumpIf(stk stack, label string) stack {
+ return b.add(fmt.Sprintf("JUMPIF:$%s", label), stk.drop())
+}
+
+func (b *builder) addJumpTarget(stk stack, label string) stack {
+ return b.add("$"+label, stk)
+}
+
+func (b *builder) addDrop(stk stack) stack {
+ return b.add("DROP", stk.drop())
+}
+
+func (b *builder) forgetPendingVerify() {
+ b.pendingVerify = nil
+}
+
+func (b *builder) addJump(stk stack, label string) stack {
+ return b.add(fmt.Sprintf("JUMP:$%s", label), stk)
+}
+
+func (b *builder) addVerify(stk stack) stack {
+ return b.add("VERIFY", stk.drop())
+}
+
+func (b *builder) addData(stk stack, data []byte) stack {
+ var s string
+ switch len(data) {
+ case 0:
+ s = "0"
+ case 1:
+ s = strconv.FormatInt(int64(data[0]), 10)
+ default:
+ s = fmt.Sprintf("0x%x", data)
+ }
+ return b.add(s, stk.add(s))
+}
+
+func (b *builder) addAmount(stk stack) stack {
+ return b.add("AMOUNT", stk.add("<amount>"))
+}
+
+func (b *builder) addAsset(stk stack) stack {
+ return b.add("ASSET", stk.add("<asset>"))
+}
+
+func (b *builder) addCheckOutput(stk stack, desc string) stack {
+ return b.add("CHECKOUTPUT", stk.dropN(5).add(desc))
+}
+
+func (b *builder) addBoolean(stk stack, val bool) stack {
+ if val {
+ return b.add("TRUE", stk.add("true"))
+ }
+ return b.add("FALSE", stk.add("false"))
+}
+
+func (b *builder) addOps(stk stack, ops string, desc string) stack {
+ return b.add(ops, stk.add(desc))
+}
+
+func (b *builder) addToAltStack(stk stack) (stack, string) {
+ t := stk.top()
+ return b.add("TOALTSTACK", stk.drop()), t
+}
+
+func (b *builder) addTxSigHash(stk stack) stack {
+ return b.add("TXSIGHASH", stk.add("<txsighash>"))
+}
+
+func (b *builder) addFromAltStack(stk stack, alt string) stack {
+ return b.add("FROMALTSTACK", stk.add(alt))
+}
+
+func (b *builder) addSwap(stk stack) stack {
+ return b.add("SWAP", stk.swap())
+}
+
+func (b *builder) addCheckMultisig(stk stack, n int, desc string) stack {
+ return b.add("CHECKMULTISIG", stk.dropN(n).add(desc))
+}
+
+func (b *builder) addOver(stk stack) stack {
+ return b.add("OVER", stk.over())
+}
+
+func (b *builder) addPick(stk stack, n int) stack {
+ b.addInt64(stk, int64(n))
+ return b.add("PICK", stk.pick(n))
+}
+
+func (b *builder) addCatPushdata(stk stack, desc string) stack {
+ return b.add("CATPUSHDATA", stk.dropN(2).add(desc))
+}
+
+func (b *builder) addCat(stk stack, desc string) stack {
+ return b.add("CAT", stk.dropN(2).add(desc))
+}
+
+func (b *builder) opcodes() string {
+ var ops []string
+ for _, item := range b.items {
+ ops = append(ops, item.opcodes)
+ }
+ return strings.Join(ops, " ")
+}
+
+// This is for producing listings like:
+// 5 | [... <clause selector> borrower lender deadline balanceAmount balanceAsset 5]
+// ROLL | [... borrower lender deadline balanceAmount balanceAsset <clause selector>]
+// JUMPIF:$default | [... borrower lender deadline balanceAmount balanceAsset]
+// $repay | [... borrower lender deadline balanceAmount balanceAsset]
+// 0 | [... borrower lender deadline balanceAmount balanceAsset 0]
+// 0 | [... borrower lender deadline balanceAmount balanceAsset 0 0]
+// 3 | [... borrower lender deadline balanceAmount balanceAsset 0 0 3]
+// ROLL | [... borrower lender deadline balanceAsset 0 0 balanceAmount]
+// 3 | [... borrower lender deadline balanceAsset 0 0 balanceAmount 3]
+// ROLL | [... borrower lender deadline 0 0 balanceAmount balanceAsset]
+// 1 | [... borrower lender deadline 0 0 balanceAmount balanceAsset 1]
+// 6 | [... borrower lender deadline 0 0 balanceAmount balanceAsset 1 6]
+// ROLL | [... borrower deadline 0 0 balanceAmount balanceAsset 1 lender]
+// CHECKOUTPUT | [... borrower deadline checkOutput(payment, lender)]
+// VERIFY | [... borrower deadline]
+// 1 | [... borrower deadline 1]
+// 0 | [... borrower deadline 1 0]
+// AMOUNT | [... borrower deadline 1 0 <amount>]
+// ASSET | [... borrower deadline 1 0 <amount> <asset>]
+// 1 | [... borrower deadline 1 0 <amount> <asset> 1]
+// 6 | [... borrower deadline 1 0 <amount> <asset> 1 6]
+// ROLL | [... deadline 1 0 <amount> <asset> 1 borrower]
+// CHECKOUTPUT | [... deadline checkOutput(collateral, borrower)]
+// JUMP:$_end | [... borrower lender deadline balanceAmount balanceAsset]
+// $default | [... borrower lender deadline balanceAmount balanceAsset]
+// 2 | [... borrower lender deadline balanceAmount balanceAsset 2]
+// ROLL | [... borrower lender balanceAmount balanceAsset deadline]
+// MINTIME LESSTHAN | [... borrower lender balanceAmount balanceAsset after(deadline)]
+// VERIFY | [... borrower lender balanceAmount balanceAsset]
+// 0 | [... borrower lender balanceAmount balanceAsset 0]
+// 0 | [... borrower lender balanceAmount balanceAsset 0 0]
+// AMOUNT | [... borrower lender balanceAmount balanceAsset 0 0 <amount>]
+// ASSET | [... borrower lender balanceAmount balanceAsset 0 0 <amount> <asset>]
+// 1 | [... borrower lender balanceAmount balanceAsset 0 0 <amount> <asset> 1]
+// 7 | [... borrower lender balanceAmount balanceAsset 0 0 <amount> <asset> 1 7]
+// ROLL | [... borrower balanceAmount balanceAsset 0 0 <amount> <asset> 1 lender]
+// CHECKOUTPUT | [... borrower balanceAmount balanceAsset checkOutput(collateral, lender)]
+// $_end | [... borrower lender deadline balanceAmount balanceAsset]
+
+type (
+ Step struct {
+ Opcodes string `json:"opcodes"`
+ Stack string `json:"stack"`
+ }
+)
+
+func (b *builder) steps() []Step {
+ var result []Step
+ for _, item := range b.items {
+ result = append(result, Step{item.opcodes, item.stk.String()})
+ }
+ return result
+}
--- /dev/null
+package compiler
+
+// func TestBuilder(t *testing.T) {
+// cases := []struct {
+// name string
+// f func(*builder)
+// wantHex string
+// }{
+// {
+// "single pushdata",
+// func(b *builder) {
+// b.addInt64(1)
+// },
+// "51",
+// },
+// {
+// "pushdata and verify",
+// func(b *builder) {
+// b.addInt64(1)
+// b.addOp(vm.OP_VERIFY)
+// },
+// "51",
+// },
+// {
+// "pushdata, verify, second pushdata",
+// func(b *builder) {
+// b.addInt64(1)
+// b.addOp(vm.OP_VERIFY)
+// b.addInt64(2)
+// },
+// "516952",
+// },
+// }
+// for _, c := range cases {
+// t.Run(c.name, func(t *testing.T) {
+// b := newBuilder()
+// c.f(b)
+// got, err := b.build()
+// if err != nil {
+// t.Fatal(err)
+// }
+// want, err := hex.DecodeString(c.wantHex)
+// if err != nil {
+// t.Fatal(err)
+// }
+// if !bytes.Equal(got, want) {
+// t.Errorf("got %x, want %x", got, want)
+// }
+// })
+// }
+// }
--- /dev/null
+package compiler
+
+type builtin struct {
+ name string
+ opcodes string
+ args []typeDesc
+ result typeDesc
+}
+
+var builtins = []builtin{
+ {"sha3", "SHA3", []typeDesc{nilType}, hashType},
+ {"sha256", "SHA256", []typeDesc{nilType}, hashType},
+ {"size", "SIZE SWAP DROP", []typeDesc{nilType}, intType},
+ {"abs", "ABS", []typeDesc{intType}, intType},
+ {"min", "MIN", []typeDesc{intType, intType}, intType},
+ {"max", "MAX", []typeDesc{intType, intType}, intType},
+ {"checkTxSig", "TXSIGHASH SWAP CHECKSIG", []typeDesc{pubkeyType, sigType}, boolType},
+ {"concat", "CAT", []typeDesc{nilType, nilType}, strType},
+ {"concatpush", "CATPUSHDATA", []typeDesc{nilType, nilType}, strType},
+ {"below", "BLOCKHEIGHT GREATERTHAN", []typeDesc{intType}, boolType},
+ {"above", "BLOCKHEIGHT LESSTHAN", []typeDesc{intType}, boolType},
+ {"checkTxMultiSig", "", []typeDesc{listType, listType}, boolType}, // WARNING WARNING WOOP WOOP special case
+}
+
+type binaryOp struct {
+ op string
+ precedence int
+ opcodes string
+
+ left, right, result typeDesc
+}
+
+var binaryOps = []binaryOp{
+ // disjunctions disallowed (for now?)
+ // {"||", 1, "BOOLOR", "Boolean", "Boolean", "Boolean"},
+
+ // and disallow this too
+ // {"&&", 2, "BOOLAND", "Boolean", "Boolean", "Boolean"},
+
+ {">", 3, "GREATERTHAN", "Integer", "Integer", "Boolean"},
+ {"<", 3, "LESSTHAN", "Integer", "Integer", "Boolean"},
+ {">=", 3, "GREATERTHANOREQUAL", "Integer", "Integer", "Boolean"},
+ {"<=", 3, "LESSTHANOREQUAL", "Integer", "Integer", "Boolean"},
+
+ {"==", 3, "EQUAL", "", "", "Boolean"},
+ {"!=", 3, "EQUAL NOT", "", "", "Boolean"},
+
+ {"^", 4, "XOR", "", "", ""},
+ {"|", 4, "OR", "", "", ""},
+
+ {"+", 4, "ADD", "Integer", "Integer", "Integer"},
+ {"-", 4, "SUB", "Integer", "Integer", "Integer"},
+
+ // {"&^", 5, "INVERT AND", "", "", ""},
+ {"&", 5, "AND", "", "", ""},
+
+ {"<<", 5, "LSHIFT", "Integer", "Integer", "Integer"},
+ {">>", 5, "RSHIFT", "Integer", "Integer", "Integer"},
+
+ {"%", 5, "MOD", "Integer", "Integer", "Integer"},
+ {"*", 5, "MUL", "Integer", "Integer", "Integer"},
+ {"/", 5, "DIV", "Integer", "Integer", "Integer"},
+}
+
+type unaryOp struct {
+ op string
+ opcodes string
+
+ operand, result typeDesc
+}
+
+var unaryOps = []unaryOp{
+ {"-", "NEGATE", "Integer", "Integer"},
+
+ // not not allowed (for now?)
+ // {"!", "NOT", "Boolean", "Boolean"},
+
+ {"~", "INVERT", "", ""},
+}
--- /dev/null
+package compiler
+
+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
+ }
+ }
+ }
+ }
+ }
+ return false
+}
+
+func prohibitSigParams(contract *Contract) error {
+ for _, p := range contract.Params {
+ if p.Type == sigType {
+ return fmt.Errorf("contract parameter \"%s\" has type Signature, but contract parameters cannot have type Signature", p.Name)
+ }
+ }
+ return nil
+}
+
+func prohibitValueParams(contract *Contract) error {
+ for _, p := range contract.Params {
+ if p.Type == valueType {
+ return fmt.Errorf("Value-typed contract parameter \"%s\" must appear in a \"locks\" clause", p.Name)
+ }
+ }
+ for _, c := range contract.Clauses {
+ for _, p := range c.Params {
+ if p.Type == valueType {
+ return fmt.Errorf("Value-typed parameter \"%s\" of clause \"%s\" must appear in a \"requires\" clause", p.Name, c.Name)
+ }
+ }
+ }
+ return nil
+}
+
+func requireAllParamsUsedInClauses(params []*Param, clauses []*Clause) error {
+ for _, p := range params {
+ used := false
+ for _, c := range clauses {
+ err := requireAllParamsUsedInClause([]*Param{p}, c)
+ if err == nil {
+ used = true
+ break
+ }
+ }
+ if !used {
+ return fmt.Errorf("parameter \"%s\" is unused", p.Name)
+ }
+ }
+ return nil
+}
+
+func requireAllParamsUsedInClause(params []*Param, clause *Clause) error {
+ for _, p := range params {
+ used := false
+ for _, stmt := range clause.statements {
+ switch s := stmt.(type) {
+ case *verifyStatement:
+ used = references(s.expr, p.Name)
+ case *lockStatement:
+ used = references(s.locked, p.Name) || references(s.program, p.Name)
+ case *unlockStatement:
+ used = references(s.expr, p.Name)
+ }
+ if used {
+ break
+ }
+ }
+ if !used {
+ for _, r := range clause.Reqs {
+ if references(r.amountExpr, p.Name) || references(r.assetExpr, p.Name) {
+ used = true
+ break
+ }
+ }
+ }
+ if !used {
+ return fmt.Errorf("parameter \"%s\" is unused in clause \"%s\"", p.Name, clause.Name)
+ }
+ }
+ return nil
+}
+
+func references(expr expression, name string) bool {
+ switch e := expr.(type) {
+ case *binaryExpr:
+ return references(e.left, name) || references(e.right, name)
+ case *unaryExpr:
+ return references(e.expr, name)
+ case *callExpr:
+ if references(e.fn, name) {
+ return true
+ }
+ for _, a := range e.args {
+ if references(a, name) {
+ return true
+ }
+ }
+ return false
+ case varRef:
+ return string(e) == name
+ case listExpr:
+ for _, elt := range []expression(e) {
+ if references(elt, name) {
+ return true
+ }
+ }
+ return false
+ }
+ return false
+}
+
+func requireAllValuesDisposedOnce(contract *Contract, clause *Clause) error {
+ err := valueDisposedOnce(contract.Value, clause)
+ if err != nil {
+ return err
+ }
+ for _, req := range clause.Reqs {
+ err = valueDisposedOnce(req.Name, clause)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func valueDisposedOnce(name string, clause *Clause) error {
+ var count int
+ for _, s := range clause.statements {
+ switch stmt := s.(type) {
+ case *unlockStatement:
+ if references(stmt.expr, name) {
+ count++
+ }
+ case *lockStatement:
+ if references(stmt.locked, name) {
+ count++
+ }
+ }
+ }
+ switch count {
+ case 0:
+ return fmt.Errorf("value \"%s\" not disposed in clause \"%s\"", name, clause.Name)
+ case 1:
+ return nil
+ default:
+ return fmt.Errorf("value \"%s\" disposed multiple times in clause \"%s\"", name, clause.Name)
+ }
+}
+
+func referencedBuiltin(expr expression) *builtin {
+ if v, ok := expr.(varRef); ok {
+ for _, b := range builtins {
+ if string(v) == b.name {
+ return &b
+ }
+ }
+ }
+ return nil
+}
+
+func assignIndexes(clause *Clause) {
+ var nextIndex int64
+ for _, s := range clause.statements {
+ switch stmt := s.(type) {
+ case *lockStatement:
+ stmt.index = nextIndex
+ nextIndex++
+
+ case *unlockStatement:
+ 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)
+ }
+
+ case *lockStatement:
+ if t := stmt.locked.typ(env); t != valueType {
+ return fmt.Errorf("expression in lock statement in clause \"%s\" has type \"%s\", must be Value", 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)
+ }
+
+ case *unlockStatement:
+ if t := stmt.expr.typ(env); t != valueType {
+ return fmt.Errorf("expression \"%s\" in unlock statement of clause \"%s\" has type \"%s\", must be Value", stmt.expr, clause.Name, t)
+ }
+ if stmt.expr.String() != contract.Value {
+ return fmt.Errorf("expression in unlock statement of clause \"%s\" must be the contract value", clause.Name)
+ }
+ }
+ }
+ return nil
+}
--- /dev/null
+package compiler
+
+import "testing"
+
+func TestRequireAllParamsUsedInClauses(t *testing.T) {
+ clauses := []*Clause{
+ &Clause{
+ statements: []statement{
+ &verifyStatement{expr: varRef("foo")},
+ &verifyStatement{
+ expr: &binaryExpr{
+ left: varRef("foo"),
+ right: varRef("bar"),
+ },
+ },
+ &lockStatement{
+ locked: varRef("baz"),
+ program: varRef("foo"),
+ },
+ },
+ },
+ &Clause{
+ statements: []statement{
+ &verifyStatement{expr: varRef("foo")},
+ &verifyStatement{
+ expr: &binaryExpr{
+ left: varRef("foo"),
+ right: varRef("plugh"),
+ },
+ },
+ &lockStatement{
+ locked: varRef("xyzzy"),
+ program: varRef("foo"),
+ },
+ },
+ },
+ }
+
+ cases := []struct {
+ name string
+ params []string
+ want string
+ }{
+ {
+ name: "contract param used in both clauses",
+ params: []string{"foo"},
+ },
+ {
+ name: "contract param used in one clause",
+ params: []string{"bar"},
+ },
+ {
+ name: "contract param used in no clauses",
+ params: []string{"y2"},
+ want: "parameter \"y2\" is unused",
+ },
+ }
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var params []*Param
+ for _, p := range c.params {
+ params = append(params, &Param{Name: p})
+ }
+ err := requireAllParamsUsedInClauses(params, clauses)
+ if err == nil && c.want == "" {
+ return
+ }
+ if err == nil {
+ t.Errorf("got err==nil, want %s", c.want)
+ return
+ }
+ if err.Error() != c.want {
+ t.Errorf("got %s, want %s", err, c.want)
+ }
+ })
+ }
+}
--- /dev/null
+package main
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "strings"
+
+ "github.com/equity/compiler"
+)
+
+func main() {
+ packageName := flag.String("package", "main", "Go package name for generated file")
+ flag.Parse()
+
+ contracts, err := compiler.Compile(os.Stdin)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Printf("package %s\n\n", *packageName)
+
+ imports := map[string]bool{
+ "bytes": true,
+ "encoding/hex": true,
+ "fmt": true,
+ "github.com/bytom/equity/compiler": true,
+ "github.com/bytom/protocol/vm": true,
+ }
+
+ buf := new(bytes.Buffer)
+
+ if len(contracts) == 1 {
+ fmt.Fprintf(buf, "var %s_body_bytes []byte\n\n", contracts[0].Name)
+ } else {
+ fmt.Fprintf(buf, "var (\n")
+ for _, contract := range contracts {
+ fmt.Fprintf(buf, "\t%s_body_bytes []byte\n", contract.Name)
+ }
+ fmt.Fprintf(buf, ")\n\n")
+ }
+
+ fmt.Fprintf(buf, "func init() {\n")
+ for _, contract := range contracts {
+ fmt.Fprintf(buf, "\t%s_body_bytes, _ = hex.DecodeString(\"%x\")\n", contract.Name, contract.Body)
+ }
+ fmt.Fprintf(buf, "}\n\n")
+
+ for _, contract := range contracts {
+ fmt.Fprintf(buf, "// contract %s(%s) locks %s\n", contract.Name, paramsStr(contract.Params), contract.Value)
+ fmt.Fprintf(buf, "//\n")
+ maxWidth := 0
+ for _, step := range contract.Steps {
+ if len(step.Opcodes) > maxWidth {
+ maxWidth = len(step.Opcodes)
+ }
+ }
+ format := fmt.Sprintf("// %%-%d.%ds %%s\n", maxWidth, maxWidth)
+ for _, step := range contract.Steps {
+ fmt.Fprintf(buf, format, step.Opcodes, step.Stack)
+ }
+ fmt.Fprintf(buf, "\n")
+
+ fmt.Fprintf(buf, "// PayTo%s instantiates contract %s as a program with specific arguments.\n", contract.Name, contract.Name)
+ goParams, newImports := asGoParams(contract.Params)
+ for _, imp := range newImports {
+ imports[imp] = true
+ }
+ fmt.Fprintf(buf, "func PayTo%s(%s) ([]byte, error) {\n", contract.Name, goParams)
+ fmt.Fprintf(buf, "\t_contractParams := []compiler.Param{\n")
+ for _, param := range contract.Params {
+ fmt.Fprintf(buf, "\t\t{Name: \"%s\", Type: \"%s\"},\n", param.Name, param.Type)
+ }
+ fmt.Fprintf(buf, "\t}\n")
+ fmt.Fprintf(buf, "\tvar _contractArgs []compiler.ContractArg\n")
+ for _, param := range contract.Params {
+ switch param.Type {
+ case "Amount":
+ fmt.Fprintf(buf, "\t_%s := int64(%s)\n", param.Name, param.Name)
+ fmt.Fprintf(buf, "\t_contractArgs = append(_contractArgs, compiler.ContractArg{I: &_%s})\n", param.Name)
+ case "Asset":
+ fmt.Fprintf(buf, "\t_%s := %s[:]\n", param.Name, param.Name)
+ fmt.Fprintf(buf, "\t_contractArgs = append(_contractArgs, compiler.ContractArg{S: &_%s})\n", param.Name)
+ case "Boolean", "Hash", "Program", "PublicKey", "Signature", "String":
+ fmt.Fprintf(buf, "\t_contractArgs = append(_contractArgs, compiler.ContractArg{S: &%s})\n", param.Name)
+ case "Integer":
+ fmt.Fprintf(buf, "\t_contractArgs = append(_contractArgs, compiler.ContractArg{I: &%s})\n", param.Name)
+ }
+ }
+ fmt.Fprintf(buf, "\treturn compiler.Instantiate(_contractParams, %s_body_bytes, %v, _contractArgs)\n", contract.Name, contract.Recursive)
+ fmt.Fprintf(buf, "}\n\n")
+
+ fmt.Fprintf(buf, "// ParsePayTo%s parses the arguments out of an instantiation of contract %s.\n", contract.Name, contract.Name)
+ fmt.Fprintf(buf, "// If the input is not an instantiation of %s, returns an error.\n", contract.Name)
+ fmt.Fprintf(buf, "func ParsePayTo%s(prog []byte) ([][]byte, error) {\n", contract.Name)
+ fmt.Fprintf(buf, "\tvar result [][]byte\n")
+ fmt.Fprintf(buf, "\tinsts, err := vm.ParseProgram(prog)\n")
+ fmt.Fprintf(buf, "\tif err != nil {\n")
+ fmt.Fprintf(buf, "\t\treturn nil, err\n")
+ fmt.Fprintf(buf, "\t}\n")
+ fmt.Fprintf(buf, "\tfor i := 0; i < %d; i++ {\n", len(contract.Params))
+ fmt.Fprintf(buf, "\t\tif len(insts) == 0 {\n")
+ fmt.Fprintf(buf, "\t\t\treturn nil, fmt.Errorf(\"program too short\")\n")
+ fmt.Fprintf(buf, "\t\t}\n")
+ fmt.Fprintf(buf, "\t\tif !insts[0].IsPushdata() {\n")
+ fmt.Fprintf(buf, "\t\t\treturn nil, fmt.Errorf(\"too few arguments\")\n")
+ fmt.Fprintf(buf, "\t\t}\n")
+ fmt.Fprintf(buf, "\t\tresult = append(result, insts[0].Data)\n")
+ fmt.Fprintf(buf, "\t\tinsts = insts[1:]\n")
+ fmt.Fprintf(buf, "\t}\n")
+ if contract.Recursive {
+ // args... body DEPTH OVER 0 CHECKPREDICATE
+ fmt.Fprintf(buf, "\tif len(insts) == 0 {\n")
+ fmt.Fprintf(buf, "\t\treturn nil, fmt.Errorf(\"program too short\")\n")
+ fmt.Fprintf(buf, "\t}\n")
+ fmt.Fprintf(buf, "\tif !insts[0].IsPushdata() {\n")
+ fmt.Fprintf(buf, "\t\treturn nil, fmt.Errorf(\"too few arguments\")\n")
+ fmt.Fprintf(buf, "\t}\n")
+ fmt.Fprintf(buf, "\tif !bytes.Equal(%s_body_bytes, insts[0].Data) {\n", contract.Name)
+ fmt.Fprintf(buf, "\t\treturn nil, fmt.Errorf(\"body bytes do not match %s\")\n", contract.Name)
+ fmt.Fprintf(buf, "\t}\n")
+ fmt.Fprintf(buf, "\tinsts = insts[1:]\n")
+ } // else args ... DEPTH body 0 CHECKPREDICATE
+ fmt.Fprintf(buf, "\tif len(insts) != 4 {\n")
+ fmt.Fprintf(buf, "\t\treturn nil, fmt.Errorf(\"program too short\")\n")
+ fmt.Fprintf(buf, "\t}\n")
+ fmt.Fprintf(buf, "\tif insts[0].Op != vm.OP_DEPTH {\n")
+ fmt.Fprintf(buf, "\t\treturn nil, fmt.Errorf(\"wrong program format\")\n")
+ fmt.Fprintf(buf, "\t}\n")
+ if contract.Recursive {
+ fmt.Fprintf(buf, "\tif insts[1].Op != vm.OP_OVER {\n")
+ fmt.Fprintf(buf, "\t\treturn nil, fmt.Errorf(\"wrong program format\")\n")
+ fmt.Fprintf(buf, "\t}\n")
+ } else {
+ fmt.Fprintf(buf, "\tif !insts[1].IsPushdata() {\n")
+ fmt.Fprintf(buf, "\t\treturn nil, fmt.Errorf(\"wrong program format\")\n")
+ fmt.Fprintf(buf, "\t}\n")
+ fmt.Fprintf(buf, "\tif !bytes.Equal(%s_body_bytes, insts[1].Data) {\n", contract.Name)
+ fmt.Fprintf(buf, "\t\treturn nil, fmt.Errorf(\"body bytes do not match %s\")\n", contract.Name)
+ fmt.Fprintf(buf, "\t}\n")
+ }
+ fmt.Fprintf(buf, "\tif !insts[2].IsPushdata() {\n")
+ fmt.Fprintf(buf, "\t\treturn nil, fmt.Errorf(\"wrong program format\")\n")
+ fmt.Fprintf(buf, "\t}\n")
+ fmt.Fprintf(buf, "\tv, err := vm.AsInt64(insts[2].Data)\n")
+ fmt.Fprintf(buf, "\tif err != nil {\n")
+ fmt.Fprintf(buf, "\t\treturn nil, err\n")
+ fmt.Fprintf(buf, "\t}\n")
+ fmt.Fprintf(buf, "\tif v != 0 {\n")
+ fmt.Fprintf(buf, "\t\treturn nil, fmt.Errorf(\"wrong program format\")\n")
+ fmt.Fprintf(buf, "\t}\n")
+ fmt.Fprintf(buf, "\tif insts[3].Op != vm.OP_CHECKPREDICATE {\n")
+ fmt.Fprintf(buf, "\t\treturn nil, fmt.Errorf(\"wrong program format\")\n")
+ fmt.Fprintf(buf, "\t}\n")
+ fmt.Fprintf(buf, "\treturn result, nil\n")
+ fmt.Fprintf(buf, "}\n\n")
+
+ // TODO(bobg): RedeemFoo_Bar functions for marshaling the args to
+ // the Bar clause of contract Foo.
+ }
+
+ fmt.Printf("import (\n")
+ for imp := range imports {
+ fmt.Printf("\t\"%s\"\n", imp)
+ }
+ fmt.Printf(")\n\n")
+
+ os.Stdout.Write(buf.Bytes())
+}
+
+func paramsStr(params []*compiler.Param) string {
+ var strs []string
+ for _, p := range params {
+ strs = append(strs, fmt.Sprintf("%s: %s", p.Name, p.Type))
+ }
+ return strings.Join(strs, ", ")
+}
+
+func asGoParams(params []*compiler.Param) (goParams string, imports []string) {
+ var strs []string
+ for _, p := range params {
+ var typ string
+ switch p.Type {
+ case "Amount":
+ typ = "uint64"
+ case "Asset":
+ typ = "bc.AssetId"
+ imports = append(imports, "github.com/bytom/protocol/bc")
+ case "Boolean":
+ typ = "bool"
+ case "Hash":
+ typ = "[]byte"
+ case "Integer":
+ typ = "int64"
+ case "Program":
+ typ = "[]byte"
+ case "PublicKey":
+ typ = "ed25519.PublicKey"
+ imports = append(imports, "github.com/bytom/crypto/ed25519")
+ case "Signature":
+ typ = "[]byte"
+ case "String":
+ typ = "[]byte"
+ }
+ strs = append(strs, fmt.Sprintf("%s %s", p.Name, typ))
+ }
+ return strings.Join(strs, ", "), imports
+}
--- /dev/null
+package compiler
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+
+ chainjson "github.com/bytom/encoding/json"
+ "github.com/bytom/errors"
+ "github.com/bytom/protocol/vm"
+ "github.com/bytom/protocol/vm/vmutil"
+)
+
+// ValueInfo describes how a blockchain value is used in a contract
+// clause.
+type ValueInfo struct {
+ // Name is the clause's name for this value.
+ Name string `json:"name"`
+
+ // Program is the program expression used to the lock the value, if
+ // the value is locked with "lock." If it's unlocked with "unlock"
+ // instead, this is empty.
+ Program string `json:"program,omitempty"`
+
+ // Asset is the expression describing the asset type the value must
+ // have, as it appears in a clause's "requires" section. If this is
+ // the contract value instead, this is empty.
+ Asset string `json:"asset,omitempty"`
+
+ // Amount is the expression describing the amount the value must
+ // have, as it appears in a clause's "requires" section. If this is
+ // the contract value instead, this is empty.
+ Amount string `json:"amount,omitempty"`
+}
+
+// ContractArg is an argument with which to instantiate a contract as
+// a program. Exactly one of B, I, and S should be supplied.
+type ContractArg struct {
+ B *bool `json:"boolean,omitempty"`
+ I *int64 `json:"integer,omitempty"`
+ S *chainjson.HexBytes `json:"string,omitempty"`
+}
+
+// Compile parses a sequence of Equity contracts from the supplied reader
+// and produces Contract objects containing the compiled bytecode and
+// other analysis. If argMap is non-nil, it maps contract names to
+// lists of arguments with which to instantiate them as programs, with
+// the results placed in the contract's Program field. A contract
+// named in argMap but not found in the input is silently ignored.
+func Compile(r io.Reader) ([]*Contract, error) {
+ inp, err := ioutil.ReadAll(r)
+ if err != nil {
+ return nil, errors.Wrap(err, "reading input")
+ }
+ contracts, err := parse(inp)
+ if err != nil {
+ return nil, errors.Wrap(err, "parse error")
+ }
+
+ globalEnv := newEnviron(nil)
+ for _, k := range keywords {
+ globalEnv.add(k, nilType, roleKeyword)
+ }
+ for _, b := range builtins {
+ globalEnv.add(b.name, nilType, roleBuiltin)
+ }
+
+ // All contracts must be checked for recursiveness before any are
+ // compiled.
+ for _, contract := range contracts {
+ contract.Recursive = checkRecursive(contract)
+ }
+
+ for _, contract := range contracts {
+ err = globalEnv.addContract(contract)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ for _, contract := range contracts {
+ err = compileContract(contract, globalEnv)
+ if err != nil {
+ return nil, errors.Wrap(err, "compiling contract")
+ }
+ for _, clause := range contract.Clauses {
+ for _, stmt := range clause.statements {
+ switch s := stmt.(type) {
+ case *lockStatement:
+ valueInfo := ValueInfo{
+ Name: s.locked.String(),
+ Program: s.program.String(),
+ }
+ if s.locked.String() != contract.Value {
+ for _, r := range clause.Reqs {
+ if s.locked.String() == r.Name {
+ valueInfo.Asset = r.assetExpr.String()
+ valueInfo.Amount = r.amountExpr.String()
+ break
+ }
+ }
+ }
+ clause.Values = append(clause.Values, valueInfo)
+ case *unlockStatement:
+ valueInfo := ValueInfo{Name: contract.Value}
+ clause.Values = append(clause.Values, valueInfo)
+ }
+ }
+ }
+ }
+
+ return contracts, nil
+}
+
+func Instantiate(body []byte, params []*Param, recursive bool, args []ContractArg) ([]byte, error) {
+ if len(args) != len(params) {
+ return nil, fmt.Errorf("got %d argument(s), want %d", len(args), len(params))
+ }
+
+ // typecheck args against param types
+ for i, param := range params {
+ arg := args[i]
+ switch param.Type {
+ case amountType, intType:
+ if arg.I == nil {
+ return nil, fmt.Errorf("type mismatch in arg %d (want integer)", i)
+ }
+ case assetType, hashType, progType, pubkeyType, sigType, strType:
+ if arg.S == nil {
+ return nil, fmt.Errorf("type mismatch in arg %d (want string)", i)
+ }
+ case boolType:
+ if arg.B == nil {
+ return nil, fmt.Errorf("type mismatch in arg %d (want boolean)", i)
+ }
+ }
+ }
+
+ b := vmutil.NewBuilder()
+
+ for i := len(args) - 1; i >= 0; i-- {
+ a := args[i]
+ switch {
+ case a.B != nil:
+ var n int64
+ if *a.B {
+ n = 1
+ }
+ b.AddInt64(n)
+ case a.I != nil:
+ b.AddInt64(*a.I)
+ case a.S != nil:
+ b.AddData(*a.S)
+ }
+ }
+
+ if recursive {
+ // <argN> <argN-1> ... <arg1> <body> DEPTH OVER 0 CHECKPREDICATE
+ b.AddData(body)
+ b.AddOp(vm.OP_DEPTH).AddOp(vm.OP_OVER)
+ } else {
+ // <argN> <argN-1> ... <arg1> DEPTH <body> 0 CHECKPREDICATE
+ b.AddOp(vm.OP_DEPTH)
+ b.AddData(body)
+ }
+ b.AddInt64(0)
+ b.AddOp(vm.OP_CHECKPREDICATE)
+ return b.Build()
+}
+
+func compileContract(contract *Contract, globalEnv *environ) error {
+ var err error
+
+ if len(contract.Clauses) == 0 {
+ return fmt.Errorf("empty contract")
+ }
+ env := newEnviron(globalEnv)
+ for _, p := range contract.Params {
+ err = env.add(p.Name, p.Type, roleContractParam)
+ if err != nil {
+ return err
+ }
+ }
+ err = env.add(contract.Value, valueType, roleContractValue)
+ if err != nil {
+ return err
+ }
+ for _, c := range contract.Clauses {
+ err = env.add(c.Name, nilType, roleClause)
+ if err != nil {
+ return err
+ }
+ }
+
+ err = prohibitValueParams(contract)
+ if err != nil {
+ return err
+ }
+ err = prohibitSigParams(contract)
+ if err != nil {
+ return err
+ }
+ err = requireAllParamsUsedInClauses(contract.Params, contract.Clauses)
+ if err != nil {
+ return err
+ }
+
+ var stk stack
+
+ if len(contract.Clauses) > 1 {
+ stk = stk.add("<clause selector>")
+ }
+
+ for i := len(contract.Params) - 1; i >= 0; i-- {
+ p := contract.Params[i]
+ stk = stk.add(p.Name)
+ }
+
+ if contract.Recursive {
+ stk = stk.add(contract.Name)
+ }
+
+ b := &builder{}
+
+ if len(contract.Clauses) == 1 {
+ err = compileClause(b, stk, contract, env, contract.Clauses[0])
+ if err != nil {
+ return err
+ }
+ } else {
+ if len(contract.Params) > 0 {
+ // A clause selector is at the bottom of the stack. Roll it to the
+ // top.
+ n := len(contract.Params)
+ if contract.Recursive {
+ n++
+ }
+ stk = b.addRoll(stk, n) // stack: [<clause params> <contract params> [<maybe contract body>] <clause selector>]
+ }
+
+ var stk2 stack
+
+ // clauses 2..N-1
+ for i := len(contract.Clauses) - 1; i >= 2; i-- {
+ stk = b.addDup(stk) // stack: [... <clause selector> <clause selector>]
+ stk = b.addInt64(stk, int64(i)) // stack: [... <clause selector> <clause selector> <i>]
+ stk = b.addNumEqual(stk, fmt.Sprintf("(<clause selector> == %d)", i)) // stack: [... <clause selector> <i == clause selector>]
+ stk = b.addJumpIf(stk, contract.Clauses[i].Name) // stack: [... <clause selector>]
+ stk2 = stk // stack starts here for clauses 2 through N-1
+ }
+
+ // clause 1
+ stk = b.addJumpIf(stk, contract.Clauses[1].Name) // consumes the clause selector
+
+ // no jump needed for clause 0
+
+ for i, clause := range contract.Clauses {
+ if i > 1 {
+ // Clauses 0 and 1 have no clause selector on top of the
+ // stack. Clauses 2 and later do.
+ stk = stk2
+ }
+
+ b.addJumpTarget(stk, clause.Name)
+
+ if i > 1 {
+ stk = b.addDrop(stk)
+ }
+
+ err = compileClause(b, stk, contract, env, clause)
+ if err != nil {
+ return errors.Wrapf(err, "compiling clause \"%s\"", clause.Name)
+ }
+ b.forgetPendingVerify()
+ if i < len(contract.Clauses)-1 {
+ b.addJump(stk, "_end")
+ }
+ }
+ b.addJumpTarget(stk, "_end")
+ }
+
+ opcodes := optimize(b.opcodes())
+ prog, err := vm.Assemble(opcodes)
+ if err != nil {
+ return err
+ }
+
+ contract.Body = prog
+ contract.Opcodes = opcodes
+
+ contract.Steps = b.steps()
+
+ return nil
+}
+
+func compileClause(b *builder, contractStk stack, contract *Contract, env *environ, clause *Clause) error {
+ var err error
+
+ // copy env to leave outerEnv unchanged
+ env = newEnviron(env)
+ for _, p := range clause.Params {
+ err = env.add(p.Name, p.Type, roleClauseParam)
+ if err != nil {
+ return err
+ }
+ }
+ for _, req := range clause.Reqs {
+ err = env.add(req.Name, valueType, roleClauseValue)
+ if err != nil {
+ return err
+ }
+ req.Asset = req.assetExpr.String()
+ req.Amount = req.amountExpr.String()
+ }
+
+ assignIndexes(clause)
+
+ var stk stack
+ for _, p := range clause.Params {
+ // NOTE: the order of clause params is not reversed, unlike
+ // contract params (and also unlike the arguments to Equity
+ // function-calls).
+ stk = stk.add(p.Name)
+ }
+ stk = stk.addFromStack(contractStk)
+
+ // a count of the number of times each variable is referenced
+ counts := make(map[string]int)
+ for _, req := range clause.Reqs {
+ req.assetExpr.countVarRefs(counts)
+ req.amountExpr.countVarRefs(counts)
+ }
+ for _, s := range clause.statements {
+ s.countVarRefs(counts)
+ }
+
+ for _, s := range clause.statements {
+ switch stmt := s.(type) {
+ case *verifyStatement:
+ stk, err = compileExpr(b, stk, contract, clause, env, counts, stmt.expr)
+ if err != nil {
+ return errors.Wrapf(err, "in verify statement in clause \"%s\"", clause.Name)
+ }
+ stk = b.addVerify(stk)
+
+ // special-case reporting of certain function calls
+ if c, ok := stmt.expr.(*callExpr); ok && len(c.args) == 1 {
+ if b := referencedBuiltin(c.fn); b != nil {
+ switch b.name {
+ case "below":
+ clause.BlockHeight = append(clause.BlockHeight, c.args[0].String())
+ case "above":
+ clause.BlockHeight = append(clause.BlockHeight, c.args[0].String())
+ }
+ }
+ }
+
+ case *lockStatement:
+ // index
+ stk = b.addInt64(stk, stmt.index)
+
+ // TODO: permit more complex expressions for locked,
+ // like "lock x+y with foo" (?)
+
+ if stmt.locked.String() == contract.Value {
+ stk = b.addAmount(stk)
+ stk = b.addAsset(stk)
+ } else {
+ var req *ClauseReq
+ for _, r := range clause.Reqs {
+ if stmt.locked.String() == r.Name {
+ req = r
+ break
+ }
+ }
+ if req == nil {
+ return fmt.Errorf("unknown value \"%s\" in lock statement in clause \"%s\"", stmt.locked, clause.Name)
+ }
+
+ // amount
+ stk, err = compileExpr(b, stk, contract, clause, env, counts, req.amountExpr)
+ if err != nil {
+ return errors.Wrapf(err, "in lock statement in clause \"%s\"", clause.Name)
+ }
+
+ // asset
+ stk, err = compileExpr(b, stk, contract, clause, env, counts, req.assetExpr)
+ if err != nil {
+ return errors.Wrapf(err, "in lock statement in clause \"%s\"", clause.Name)
+ }
+ }
+
+ // version
+ stk = b.addInt64(stk, 1)
+
+ // prog
+ stk, err = compileExpr(b, stk, contract, clause, env, counts, stmt.program)
+ if err != nil {
+ return errors.Wrapf(err, "in lock statement in clause \"%s\"", clause.Name)
+ }
+
+ stk = b.addCheckOutput(stk, fmt.Sprintf("checkOutput(%s, %s)", stmt.locked, stmt.program))
+ stk = b.addVerify(stk)
+
+ case *unlockStatement:
+ if len(clause.statements) == 1 {
+ // This is the only statement in the clause, make sure TRUE is
+ // on the stack.
+ stk = b.addBoolean(stk, true)
+ }
+ }
+ }
+
+ err = requireAllValuesDisposedOnce(contract, clause)
+ if err != nil {
+ return err
+ }
+ err = typeCheckClause(contract, clause, env)
+ if err != nil {
+ return err
+ }
+ err = requireAllParamsUsedInClause(clause.Params, clause)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func compileExpr(b *builder, stk stack, contract *Contract, clause *Clause, env *environ, counts map[string]int, expr expression) (stack, error) {
+ var err error
+
+ switch e := expr.(type) {
+ case *binaryExpr:
+ // Do typechecking after compiling subexpressions (because other
+ // compilation errors are more interesting than type mismatch
+ // errors).
+
+ stk, err = compileExpr(b, stk, contract, clause, env, counts, e.left)
+ if err != nil {
+ return stk, errors.Wrapf(err, "in left operand of \"%s\" expression", e.op.op)
+ }
+ stk, err = compileExpr(b, stk, contract, clause, env, counts, e.right)
+ if err != nil {
+ return stk, errors.Wrapf(err, "in right operand of \"%s\" expression", e.op.op)
+ }
+
+ lType := e.left.typ(env)
+ if e.op.left != "" && lType != e.op.left {
+ return stk, fmt.Errorf("in \"%s\", left operand has type \"%s\", must be \"%s\"", e, lType, e.op.left)
+ }
+
+ rType := e.right.typ(env)
+ if e.op.right != "" && rType != e.op.right {
+ return stk, fmt.Errorf("in \"%s\", right operand has type \"%s\", must be \"%s\"", e, rType, e.op.right)
+ }
+
+ switch e.op.op {
+ case "==", "!=":
+ if lType != rType {
+ // Maybe one is Hash and the other is (more-specific-Hash subtype).
+ // TODO(bobg): generalize this mechanism
+ if lType == hashType && isHashSubtype(rType) {
+ propagateType(contract, clause, env, rType, e.left)
+ } else if rType == hashType && isHashSubtype(lType) {
+ propagateType(contract, clause, env, lType, e.right)
+ } else {
+ return stk, fmt.Errorf("type mismatch in \"%s\": left operand has type \"%s\", right operand has type \"%s\"", e, lType, rType)
+ }
+ }
+ if lType == "Boolean" {
+ return stk, fmt.Errorf("in \"%s\": using \"%s\" on Boolean values not allowed", e, e.op.op)
+ }
+ }
+
+ stk = b.addOps(stk.dropN(2), e.op.opcodes, e.String())
+
+ case *unaryExpr:
+ // Do typechecking after compiling subexpression (because other
+ // compilation errors are more interesting than type mismatch
+ // errors).
+
+ var err error
+ stk, err = compileExpr(b, stk, contract, clause, env, counts, e.expr)
+ if err != nil {
+ return stk, errors.Wrapf(err, "in \"%s\" expression", e.op.op)
+ }
+
+ if e.op.operand != "" && e.expr.typ(env) != e.op.operand {
+ return stk, fmt.Errorf("in \"%s\", operand has type \"%s\", must be \"%s\"", e, e.expr.typ(env), e.op.operand)
+ }
+ b.addOps(stk.drop(), e.op.opcodes, e.String())
+
+ case *callExpr:
+ bi := referencedBuiltin(e.fn)
+ if bi == nil {
+ if v, ok := e.fn.(varRef); ok {
+ if entry := env.lookup(string(v)); entry != nil && entry.t == contractType {
+ clause.Contracts = append(clause.Contracts, entry.c.Name)
+
+ partialName := fmt.Sprintf("%s(...)", v)
+ stk = b.addData(stk, nil)
+
+ if len(e.args) != len(entry.c.Params) {
+ return stk, fmt.Errorf("contract \"%s\" expects %d argument(s), got %d", entry.c.Name, len(entry.c.Params), len(e.args))
+ }
+
+ for i := len(e.args) - 1; i >= 0; i-- {
+ arg := e.args[i]
+ if entry.c.Params[i].Type != "" && arg.typ(env) != entry.c.Params[i].Type {
+ return stk, fmt.Errorf("argument %d to contract \"%s\" has type \"%s\", must be \"%s\"", i, entry.c.Name, arg.typ(env), entry.c.Params[i].Type)
+ }
+ stk, err = compileExpr(b, stk, contract, clause, env, counts, arg)
+ if err != nil {
+ return stk, err
+ }
+ stk = b.addCatPushdata(stk, partialName)
+ }
+
+ switch {
+ case entry.c == contract:
+ // Recursive call - cannot use entry.c.Body
+ // <argN> <argN-1> ... <arg1> <body> DEPTH OVER 0 CHECKPREDICATE
+ stk, err = compileRef(b, stk, counts, varRef(contract.Name))
+ if err != nil {
+ return stk, errors.Wrap(err, "compiling contract call")
+ }
+ stk = b.addCatPushdata(stk, partialName)
+ stk = b.addData(stk, []byte{byte(vm.OP_DEPTH), byte(vm.OP_OVER)})
+ stk = b.addCat(stk, partialName)
+
+ case entry.c.Recursive:
+ // Non-recursive call to a (different) recursive contract
+ // <argN> <argN-1> ... <arg1> <body> DEPTH OVER 0 CHECKPREDICATE
+ if len(entry.c.Body) == 0 {
+ // TODO(bobg): sort input contracts topologically to permit forward calling
+ return stk, fmt.Errorf("contract \"%s\" not defined", entry.c.Name)
+ }
+ stk = b.addData(stk, entry.c.Body)
+ stk = b.addCatPushdata(stk, partialName)
+ stk = b.addData(stk, []byte{byte(vm.OP_DEPTH), byte(vm.OP_OVER)})
+ stk = b.addCat(stk, partialName)
+
+ default:
+ // Non-recursive call to non-recursive contract
+ // <argN> <argN-1> ... <arg1> DEPTH <body> 0 CHECKPREDICATE
+ stk = b.addData(stk, []byte{byte(vm.OP_DEPTH)})
+ stk = b.addCat(stk, partialName)
+ if len(entry.c.Body) == 0 {
+ // TODO(bobg): sort input contracts topologically to permit forward calling
+ return stk, fmt.Errorf("contract \"%s\" not defined", entry.c.Name)
+ }
+ stk = b.addData(stk, entry.c.Body)
+ stk = b.addCatPushdata(stk, partialName)
+ }
+ stk = b.addData(stk, vm.Int64Bytes(0))
+ stk = b.addCatPushdata(stk, partialName)
+ stk = b.addData(stk, []byte{byte(vm.OP_CHECKPREDICATE)})
+ stk = b.addCat(stk, e.String())
+
+ return stk, nil
+ }
+ }
+ return stk, fmt.Errorf("unknown function \"%s\"", e.fn)
+ }
+
+ if len(e.args) != len(bi.args) {
+ return stk, fmt.Errorf("wrong number of args for \"%s\": have %d, want %d", bi.name, len(e.args), len(bi.args))
+ }
+
+ // WARNING WARNING WOOP WOOP
+ // special-case hack
+ // WARNING WARNING WOOP WOOP
+ if bi.name == "checkTxMultiSig" {
+ if _, ok := e.args[0].(listExpr); !ok {
+ return stk, fmt.Errorf("checkTxMultiSig expects list literals, got %T for argument 0", e.args[0])
+ }
+ if _, ok := e.args[1].(listExpr); !ok {
+ return stk, fmt.Errorf("checkTxMultiSig expects list literals, got %T for argument 1", e.args[1])
+ }
+
+ var k1, k2 int
+
+ stk, k1, err = compileArg(b, stk, contract, clause, env, counts, e.args[1])
+ if err != nil {
+ return stk, err
+ }
+
+ // stack: [... sigM ... sig1 M]
+
+ var altEntry string
+ stk, altEntry = b.addToAltStack(stk) // stack: [... sigM ... sig1]
+ stk = b.addTxSigHash(stk) // stack: [... sigM ... sig1 txsighash]
+
+ stk, k2, err = compileArg(b, stk, contract, clause, env, counts, e.args[0])
+ if err != nil {
+ return stk, err
+ }
+
+ // stack: [... sigM ... sig1 txsighash pubkeyN ... pubkey1 N]
+
+ stk = b.addFromAltStack(stk, altEntry) // stack: [... sigM ... sig1 txsighash pubkeyN ... pubkey1 N M]
+ stk = b.addSwap(stk) // stack: [... sigM ... sig1 txsighash pubkeyN ... pubkey1 M N]
+ stk = b.addCheckMultisig(stk, k1+k2, e.String())
+
+ return stk, nil
+ }
+
+ var k int
+
+ for i := len(e.args) - 1; i >= 0; i-- {
+ a := e.args[i]
+ var k2 int
+ var err error
+ stk, k2, err = compileArg(b, stk, contract, clause, env, counts, a)
+ if err != nil {
+ return stk, errors.Wrapf(err, "compiling argument %d in call expression", i)
+ }
+ k += k2
+ }
+
+ // Do typechecking after compiling subexpressions (because other
+ // compilation errors are more interesting than type mismatch
+ // errors).
+ for i, actual := range e.args {
+ if bi.args[i] != "" && actual.typ(env) != bi.args[i] {
+ return stk, fmt.Errorf("argument %d to \"%s\" has type \"%s\", must be \"%s\"", i, bi.name, actual.typ(env), bi.args[i])
+ }
+ }
+
+ stk = b.addOps(stk.dropN(k), bi.opcodes, e.String())
+
+ // special-case reporting
+ switch bi.name {
+ case "sha3", "sha256":
+ clause.HashCalls = append(clause.HashCalls, HashCall{bi.name, e.args[0].String(), string(e.args[0].typ(env))})
+ }
+
+ case varRef:
+ return compileRef(b, stk, counts, e)
+
+ case integerLiteral:
+ stk = b.addInt64(stk, int64(e))
+
+ case bytesLiteral:
+ stk = b.addData(stk, []byte(e))
+
+ case booleanLiteral:
+ stk = b.addBoolean(stk, bool(e))
+
+ case listExpr:
+ // Lists are excluded here because they disobey the invariant of
+ // this function: namely, that it increases the stack size by
+ // exactly one. (A list pushes its items and its length on the
+ // stack.) But they're OK as function-call arguments because the
+ // function (presumably) consumes all the stack items added.
+ return stk, fmt.Errorf("encountered list outside of function-call context")
+ }
+ return stk, nil
+}
+
+func compileArg(b *builder, stk stack, contract *Contract, clause *Clause, env *environ, counts map[string]int, expr expression) (stack, int, error) {
+ var n int
+ if list, ok := expr.(listExpr); ok {
+ for i := 0; i < len(list); i++ {
+ elt := list[len(list)-i-1]
+ var err error
+ stk, err = compileExpr(b, stk, contract, clause, env, counts, elt)
+ if err != nil {
+ return stk, 0, err
+ }
+ n++
+ }
+ stk = b.addInt64(stk, int64(len(list)))
+ n++
+ return stk, n, nil
+ }
+ var err error
+ stk, err = compileExpr(b, stk, contract, clause, env, counts, expr)
+ return stk, 1, err
+}
+
+func compileRef(b *builder, stk stack, counts map[string]int, ref varRef) (stack, error) {
+ depth := stk.find(string(ref))
+ if depth < 0 {
+ return stk, fmt.Errorf("undefined reference: \"%s\"", ref)
+ }
+
+ var isFinal bool
+ if count, ok := counts[string(ref)]; ok && count > 0 {
+ count--
+ counts[string(ref)] = count
+ isFinal = count == 0
+ }
+
+ switch depth {
+ case 0:
+ if !isFinal {
+ stk = b.addDup(stk)
+ }
+ case 1:
+ if isFinal {
+ stk = b.addSwap(stk)
+ } else {
+ stk = b.addOver(stk)
+ }
+ default:
+ if isFinal {
+ stk = b.addRoll(stk, depth)
+ } else {
+ stk = b.addPick(stk, depth)
+ }
+ }
+ return stk, nil
+}
+
+func (a *ContractArg) UnmarshalJSON(b []byte) error {
+ var m map[string]json.RawMessage
+ err := json.Unmarshal(b, &m)
+ if err != nil {
+ return err
+ }
+ if r, ok := m["boolean"]; ok {
+ var bval bool
+ err = json.Unmarshal(r, &bval)
+ if err != nil {
+ return err
+ }
+ a.B = &bval
+ return nil
+ }
+ if r, ok := m["integer"]; ok {
+ var ival int64
+ err = json.Unmarshal(r, &ival)
+ if err != nil {
+ return err
+ }
+ a.I = &ival
+ return nil
+ }
+ r, ok := m["string"]
+ if !ok {
+ return fmt.Errorf("contract arg must define one of boolean, integer, string")
+ }
+ var sval chainjson.HexBytes
+ err = json.Unmarshal(r, &sval)
+ if err != nil {
+ return err
+ }
+ a.S = &sval
+ return nil
+}
--- /dev/null
+package compiler
+
+import (
+ "encoding/hex"
+ "encoding/json"
+ "strings"
+ "testing"
+
+ "github.com/bytom/equity/compiler/equitytest"
+)
+
+func TestCompile(t *testing.T) {
+ cases := []struct {
+ name string
+ contract string
+ wantJSON string
+ }{
+ {
+ "TrivialLock",
+ equitytest.TrivialLock,
+ `[{"name":"TrivialLock","clauses":[{"name":"trivialUnlock","values":[{"name":"locked"}]}],"value":"locked","body_bytecode":"51","body_opcodes":"TRUE","recursive":false}]`,
+ },
+ {
+ "LockWithPublicKey",
+ equitytest.LockWithPublicKey,
+ `[{"name":"LockWithPublicKey","params":[{"name":"publicKey","type":"PublicKey"}],"clauses":[{"name":"unlockWithSig","params":[{"name":"sig","type":"Signature"}],"values":[{"name":"locked"}]}],"value":"locked","body_bytecode":"ae7cac","body_opcodes":"TXSIGHASH SWAP CHECKSIG","recursive":false}]`,
+ },
+ {
+ "LockWithPublicKeyHash",
+ equitytest.LockWithPKHash,
+ `[{"name":"LockWithPublicKeyHash","params":[{"name":"pubKeyHash","type":"Hash","inferred_type":"Sha3(PublicKey)"}],"clauses":[{"name":"spend","params":[{"name":"pubKey","type":"PublicKey"},{"name":"sig","type":"Signature"}],"hash_calls":[{"hash_type":"sha3","arg":"pubKey","arg_type":"PublicKey"}],"values":[{"name":"value"}]}],"value":"value","body_bytecode":"5279aa887cae7cac","body_opcodes":"2 PICK SHA3 EQUALVERIFY SWAP TXSIGHASH SWAP CHECKSIG","recursive":false}]`,
+ },
+ {
+ "LockWith2of3Keys",
+ equitytest.LockWith2of3Keys,
+ `[{"name":"LockWith3Keys","params":[{"name":"pubkey1","type":"PublicKey"},{"name":"pubkey2","type":"PublicKey"},{"name":"pubkey3","type":"PublicKey"}],"clauses":[{"name":"unlockWith2Sigs","params":[{"name":"sig1","type":"Signature"},{"name":"sig2","type":"Signature"}],"values":[{"name":"locked"}]}],"value":"locked","body_bytecode":"537a547a526bae71557a536c7cad","body_opcodes":"3 ROLL 4 ROLL 2 TOALTSTACK TXSIGHASH 2ROT 5 ROLL 3 FROMALTSTACK SWAP CHECKMULTISIG","recursive":false}]`,
+ },
+ {
+ "LockToOutput",
+ equitytest.LockToOutput,
+ `[{"name":"LockToOutput","params":[{"name":"address","type":"Program"}],"clauses":[{"name":"relock","values":[{"name":"locked","program":"address"}]}],"value":"locked","body_bytecode":"00c3c251547ac1","body_opcodes":"0 AMOUNT ASSET 1 4 ROLL CHECKOUTPUT","recursive":false}]`,
+ },
+ {
+ "TradeOffer",
+ equitytest.TradeOffer,
+ `[{"name":"TradeOffer","params":[{"name":"requestedAsset","type":"Asset"},{"name":"requestedAmount","type":"Amount"},{"name":"sellerProgram","type":"Program"},{"name":"sellerKey","type":"PublicKey"}],"clauses":[{"name":"trade","reqs":[{"name":"payment","asset":"requestedAsset","amount":"requestedAmount"}],"values":[{"name":"payment","program":"sellerProgram","asset":"requestedAsset","amount":"requestedAmount"},{"name":"offered"}]},{"name":"cancel","params":[{"name":"sellerSig","type":"Signature"}],"values":[{"name":"offered","program":"sellerProgram"}]}],"value":"offered","body_bytecode":"547a6413000000007b7b51547ac16322000000547a547aae7cac6900c3c251567ac1","body_opcodes":"4 ROLL JUMPIF:$cancel $trade 0 ROT ROT 1 4 ROLL CHECKOUTPUT JUMP:$_end $cancel 4 ROLL 4 ROLL TXSIGHASH SWAP CHECKSIG VERIFY 0 AMOUNT ASSET 1 6 ROLL CHECKOUTPUT $_end","recursive":false}]`,
+ },
+ {
+ "EscrowedTransfer",
+ equitytest.EscrowedTransfer,
+ `[{"name":"EscrowedTransfer","params":[{"name":"agent","type":"PublicKey"},{"name":"sender","type":"Program"},{"name":"recipient","type":"Program"}],"clauses":[{"name":"approve","params":[{"name":"sig","type":"Signature"}],"values":[{"name":"value","program":"recipient"}]},{"name":"reject","params":[{"name":"sig","type":"Signature"}],"values":[{"name":"value","program":"sender"}]}],"value":"value","body_bytecode":"537a641a000000537a7cae7cac6900c3c251557ac16328000000537a7cae7cac6900c3c251547ac1","body_opcodes":"3 ROLL JUMPIF:$reject $approve 3 ROLL SWAP TXSIGHASH SWAP CHECKSIG VERIFY 0 AMOUNT ASSET 1 5 ROLL CHECKOUTPUT JUMP:$_end $reject 3 ROLL SWAP TXSIGHASH SWAP CHECKSIG VERIFY 0 AMOUNT ASSET 1 4 ROLL CHECKOUTPUT $_end","recursive":false}]`,
+ },
+ {
+ "CollateralizedLoan",
+ equitytest.CollateralizedLoan,
+ `[{"name":"CollateralizedLoan","params":[{"name":"balanceAsset","type":"Asset"},{"name":"balanceAmount","type":"Amount"},{"name":"finalHeight","type":"Integer"},{"name":"lender","type":"Program"},{"name":"borrower","type":"Program"}],"clauses":[{"name":"repay","reqs":[{"name":"payment","asset":"balanceAsset","amount":"balanceAmount"}],"values":[{"name":"payment","program":"lender","asset":"balanceAsset","amount":"balanceAmount"},{"name":"collateral","program":"borrower"}]},{"name":"default","blockheight":["finalHeight"],"values":[{"name":"collateral","program":"lender"}]}],"value":"collateral","body_bytecode":"557a641b000000007b7b51557ac16951c3c251557ac163260000007bcd9f6900c3c251567ac1","body_opcodes":"5 ROLL JUMPIF:$default $repay 0 ROT ROT 1 5 ROLL CHECKOUTPUT VERIFY 1 AMOUNT ASSET 1 5 ROLL CHECKOUTPUT JUMP:$_end $default ROT BLOCKHEIGHT LESSTHAN VERIFY 0 AMOUNT ASSET 1 6 ROLL CHECKOUTPUT $_end","recursive":false}]`,
+ },
+ {
+ "RevealPreimage",
+ equitytest.RevealPreimage,
+ `[{"name":"RevealPreimage","params":[{"name":"hash","type":"Hash","inferred_type":"Sha3(String)"}],"clauses":[{"name":"reveal","params":[{"name":"string","type":"String"}],"hash_calls":[{"hash_type":"sha3","arg":"string","arg_type":"String"}],"values":[{"name":"value"}]}],"value":"value","body_bytecode":"7caa87","body_opcodes":"SWAP SHA3 EQUAL","recursive":false}]`,
+ },
+ {
+ "CallOptionWithSettlement",
+ equitytest.CallOptionWithSettlement,
+ `[{"name":"CallOptionWithSettlement","params":[{"name":"strikePrice","type":"Amount"},{"name":"strikeCurrency","type":"Asset"},{"name":"sellerProgram","type":"Program"},{"name":"sellerKey","type":"PublicKey"},{"name":"buyerKey","type":"PublicKey"},{"name":"finalHeight","type":"Integer"}],"clauses":[{"name":"exercise","params":[{"name":"buyerSig","type":"Signature"}],"reqs":[{"name":"payment","asset":"strikeCurrency","amount":"strikePrice"}],"blockheight":["finalHeight"],"values":[{"name":"payment","program":"sellerProgram","asset":"strikeCurrency","amount":"strikePrice"},{"name":"underlying"}]},{"name":"expire","blockheight":["finalHeight"],"values":[{"name":"underlying","program":"sellerProgram"}]},{"name":"settle","params":[{"name":"sellerSig","type":"Signature"},{"name":"buyerSig","type":"Signature"}],"values":[{"name":"underlying"}]}],"value":"underlying","body_bytecode":"567a76529c64360000006425000000557acda06971ae7cac69007c7b51547ac16346000000557acd9f6900c3c251567ac1634600000075577a547aae7cac69557a547aae7cac","body_opcodes":"6 ROLL DUP 2 NUMEQUAL JUMPIF:$settle JUMPIF:$expire $exercise 5 ROLL BLOCKHEIGHT GREATERTHAN VERIFY 2ROT TXSIGHASH SWAP CHECKSIG VERIFY 0 SWAP ROT 1 4 ROLL CHECKOUTPUT JUMP:$_end $expire 5 ROLL BLOCKHEIGHT LESSTHAN VERIFY 0 AMOUNT ASSET 1 6 ROLL CHECKOUTPUT JUMP:$_end $settle DROP 7 ROLL 4 ROLL TXSIGHASH SWAP CHECKSIG VERIFY 5 ROLL 4 ROLL TXSIGHASH SWAP CHECKSIG $_end","recursive":false}]`,
+ },
+ {
+ "PriceChanger",
+ equitytest.PriceChanger,
+ `[{"name":"PriceChanger","params":[{"name":"askAmount","type":"Amount"},{"name":"askAsset","type":"Asset"},{"name":"sellerKey","type":"PublicKey"},{"name":"sellerProg","type":"Program"}],"clauses":[{"name":"changePrice","params":[{"name":"newAmount","type":"Amount"},{"name":"newAsset","type":"Asset"},{"name":"sig","type":"Signature"}],"values":[{"name":"offered","program":"PriceChanger(newAmount, newAsset, sellerKey, sellerProg)"}],"contracts":["PriceChanger"]},{"name":"redeem","reqs":[{"name":"payment","asset":"askAsset","amount":"askAmount"}],"values":[{"name":"payment","program":"sellerProg","asset":"askAsset","amount":"askAmount"},{"name":"offered"}]}],"value":"offered","body_bytecode":"557a6432000000557a5479ae7cac6900c3c25100597a89587a89587a89587a89557a890274787e008901c07ec1633a000000007b537a51567ac1","body_opcodes":"5 ROLL JUMPIF:$redeem $changePrice 5 ROLL 4 PICK TXSIGHASH SWAP CHECKSIG VERIFY 0 AMOUNT ASSET 1 0 9 ROLL CATPUSHDATA 8 ROLL CATPUSHDATA 8 ROLL CATPUSHDATA 8 ROLL CATPUSHDATA 5 ROLL CATPUSHDATA 0x7478 CAT 0 CATPUSHDATA 192 CAT CHECKOUTPUT JUMP:$_end $redeem 0 ROT 3 ROLL 1 6 ROLL CHECKOUTPUT $_end","recursive":true}]`,
+ },
+ {
+ "OneTwo",
+ equitytest.OneTwo,
+ `[{"name":"Two","params":[{"name":"b","type":"Program"},{"name":"c","type":"Program"},{"name":"expirationHeight","type":"Integer"}],"clauses":[{"name":"redeem","blockheight":["expirationHeight"],"values":[{"name":"value","program":"b"}]},{"name":"default","blockheight":["expirationHeight"],"values":[{"name":"value","program":"c"}]}],"value":"value","body_bytecode":"537a64170000007bcda06900c3c251547ac163220000007bcd9f6900c3c251557ac1","body_opcodes":"3 ROLL JUMPIF:$default $redeem ROT BLOCKHEIGHT GREATERTHAN VERIFY 0 AMOUNT ASSET 1 4 ROLL CHECKOUTPUT JUMP:$_end $default ROT BLOCKHEIGHT LESSTHAN VERIFY 0 AMOUNT ASSET 1 5 ROLL CHECKOUTPUT $_end","recursive":false},{"name":"One","params":[{"name":"a","type":"Program"},{"name":"b","type":"Program"},{"name":"c","type":"Program"},{"name":"switchHeight","type":"Integer"},{"name":"blockHeight","type":"Integer"}],"clauses":[{"name":"redeem","blockheight":["switchHeight"],"values":[{"name":"value","program":"a"}]},{"name":"switch","blockheight":["switchHeight"],"values":[{"name":"value","program":"Two(b, c, blockHeight)"}],"contracts":["Two"]}],"value":"value","body_bytecode":"557a6418000000537acda06900c3c251547ac16358000000537acd9f6900c3c25100587a89577a89567a8901747e22537a64170000007bcda06900c3c251547ac163220000007bcd9f6900c3c251557ac189008901c07ec1","body_opcodes":"5 ROLL JUMPIF:$switch $redeem 3 ROLL BLOCKHEIGHT GREATERTHAN VERIFY 0 AMOUNT ASSET 1 4 ROLL CHECKOUTPUT JUMP:$_end $switch 3 ROLL BLOCKHEIGHT LESSTHAN VERIFY 0 AMOUNT ASSET 1 0 8 ROLL CATPUSHDATA 7 ROLL CATPUSHDATA 6 ROLL CATPUSHDATA 116 CAT 0x537a64170000007bcda06900c3c251547ac163220000007bcd9f6900c3c251557ac1 CATPUSHDATA 0 CATPUSHDATA 192 CAT CHECKOUTPUT $_end","recursive":false}]`,
+ },
+ }
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ r := strings.NewReader(c.contract)
+ got, err := Compile(r)
+ if err != nil {
+ t.Fatal(err)
+ }
+ gotJSON, _ := json.Marshal(got)
+ if string(gotJSON) != c.wantJSON {
+ t.Errorf("\ngot %s\nwant %s", string(gotJSON), c.wantJSON)
+ } else {
+ for _, contract := range got {
+ t.Log(contract.Opcodes)
+ }
+ }
+ })
+ }
+}
+
+func mustDecodeHex(h string) []byte {
+ bits, err := hex.DecodeString(h)
+ if err != nil {
+ panic(err)
+ }
+ return bits
+}
--- /dev/null
+/*
+Package equity provides a compiler for Chain's Equity contract language.
+
+A contract is a means to lock some payment in the output of a
+transaction. It contains a number of clauses, each describing a way to
+unlock, or redeem, the payment in a subsequent transaction. By
+executing the statements in a clause, using contract arguments
+supplied by the payer and clause arguments supplied by the redeemer,
+nodes in a Chain network can determine whether a proposed spend is
+valid.
+
+The language definition is in flux, but here's what's implemented as
+of late May 2017.
+
+ program = contract*
+
+ contract = "contract" identifier "(" [params] ")" "locks" identifier "{" clause+ "}"
+
+ The identifier after "locks" is a name for the value locked by
+ the contract. It must be unlocked or re-locked (with "unlock"
+ or "lock") in every clause.
+
+ clause = "clause" identifier "(" [params] ")" ["requires" requirements] "{" statement+ "}"
+
+ The requirements are blockchain values that must be present in
+ the spending transaction in order to spend the value locked by
+ the earlier transaction. Each such value must be re-locked
+ (with "lock") in its clause.
+
+ statement = verify | unlock | lock
+
+ verify = "verify" expr
+
+ Verifies that boolean expression expr produces a true result.
+
+ unlock = "unlock" expr
+
+ Expr must evaluate to the contract value. This unlocks that
+ value for any use.
+
+ lock = "lock" expr "with" expr
+
+ The first expr must be a blockchain value (i.e., one named
+ with "locks" or "requires"). The second expr must be a
+ program. This unlocks expr and re-locks it with the new
+ program.
+
+ requirements = requirement | requirements "," requirement
+
+ requirement = identifier ":" expr "of" expr
+
+ The first expr must be an amount, the second must be an
+ asset. This denotes that the named value must have the given
+ quantity and asset type.
+
+ params = param | params "," param
+
+ param = idlist ":" identifier
+
+ The identifiers in idlist are individual parameter names. The
+ identifier after the colon is their type. Available types are:
+
+ Amount; Asset; Boolean; Hash; Integer; Program; PublicKey;
+ Signature; String; Time
+
+ idlist = identifier | idlist "," identifier
+
+ expr = unary_expr | binary_expr | call_expr | identifier | "(" expr ")" | literal
+
+ unary_expr = unary_op expr
+
+ binary_expr = expr binary_op expr
+
+ call_expr = expr "(" [args] ")"
+
+ If expr is the name of an Equity contract, then calling it (with
+ the appropriate arguments) produces a program suitable for use
+ in "lock" statements.
+
+ Otherwise, expr should be one of these builtin functions:
+
+ sha3(x)
+ SHA3-256 hash of x.
+ sha256(x)
+ SHA-256 hash of x.
+ size(x)
+ Size in bytes of x.
+ abs(x)
+ Absolute value of x.
+ min(x, y)
+ The lesser of x and y.
+ max(x, y)
+ The greater of x and y.
+ checkTxSig(pubkey, signature)
+ Whether signature matches both the spending
+ transaction and pubkey.
+ concat(x, y)
+ The concatenation of x and y.
+ concatpush(x, y)
+ The concatenation of x with the bytecode sequence
+ needed to push y on the ChainVM stack.
+ before(x)
+ Whether the spending transaction is happening before
+ time x.
+ after(x)
+ Whether the spending transaction is happening after
+ time x.
+ checkTxMultiSig([pubkey1, pubkey2, ...], [sig1, sig2, ...])
+ Like checkTxSig, but for M-of-N signature checks.
+ Every sig must match both the spending transaction and
+ one of the pubkeys. There may be more pubkeys than
+ sigs, but they are only checked left-to-right so must
+ be supplied in the same order as the sigs. The square
+ brackets here are literal and must appear as shown.
+
+ unary_op = "-" | "~"
+
+ binary_op = ">" | "<" | ">=" | "<=" | "==" | "!=" | "^" | "|" |
+ "+" | "-" | "&" | "<<" | ">>" | "%" | "*" | "/"
+
+ args = expr | args "," expr
+
+ literal = int_literal | str_literal | hex_literal
+
+*/
+package compiler
--- /dev/null
+package compiler
+
+import "fmt"
+
+// name-binding environment
+type environ struct {
+ entries map[string]*envEntry
+ parent *environ
+}
+
+type envEntry struct {
+ t typeDesc
+ r role
+ c *Contract // if t == contractType
+}
+
+type role int
+
+const (
+ roleKeyword role = 1 + iota
+ roleBuiltin
+ roleContract
+ roleContractParam
+ roleContractValue
+ roleClause
+ roleClauseParam
+ roleClauseValue
+)
+
+var roleDesc = map[role]string{
+ roleKeyword: "keyword",
+ roleBuiltin: "built-in function",
+ roleContract: "contract",
+ roleContractParam: "contract parameter",
+ roleContractValue: "contract value",
+ roleClause: "clause",
+ roleClauseParam: "clause parameter",
+ roleClauseValue: "clause value",
+}
+
+func newEnviron(parent *environ) *environ {
+ return &environ{
+ entries: make(map[string]*envEntry),
+ parent: parent,
+ }
+}
+
+func (e *environ) add(name string, t typeDesc, r role) error {
+ if entry := e.lookup(name); entry != nil {
+ return fmt.Errorf("%s \"%s\" conflicts with %s", roleDesc[r], name, roleDesc[entry.r])
+ }
+ e.entries[name] = &envEntry{t: t, r: r}
+ return nil
+}
+
+func (e *environ) addContract(contract *Contract) error {
+ if entry := e.lookup(contract.Name); entry != nil {
+ return fmt.Errorf("%s \"%s\" conflicts with %s", roleDesc[roleContract], contract.Name, roleDesc[entry.r])
+ }
+ e.entries[contract.Name] = &envEntry{t: contractType, r: roleContract, c: contract}
+ return nil
+}
+
+func (e environ) lookup(name string) *envEntry {
+ if res, ok := e.entries[name]; ok {
+ return res
+ }
+ if e.parent != nil {
+ return e.parent.lookup(name)
+ }
+ return nil
+}
--- /dev/null
+package equitytest
+
+const TrivialLock = `
+contract TrivialLock() locks locked {
+ clause trivialUnlock() {
+ unlock locked
+ }
+}
+`
+
+const LockWithPublicKey = `
+contract LockWithPublicKey(publicKey: PublicKey) locks locked {
+ clause unlockWithSig(sig: Signature) {
+ verify checkTxSig(publicKey, sig)
+ unlock locked
+ }
+}
+`
+
+const LockWithPKHash = `
+contract LockWithPublicKeyHash(pubKeyHash: Hash) locks value {
+ clause spend(pubKey: PublicKey, sig: Signature) {
+ verify sha3(pubKey) == pubKeyHash
+ verify checkTxSig(pubKey, sig)
+ unlock value
+ }
+}
+`
+
+const LockWith2of3Keys = `
+contract LockWith3Keys(pubkey1, pubkey2, pubkey3: PublicKey) locks locked {
+ clause unlockWith2Sigs(sig1, sig2: Signature) {
+ verify checkTxMultiSig([pubkey1, pubkey2, pubkey3], [sig1, sig2])
+ unlock locked
+ }
+}
+`
+
+const LockToOutput = `
+contract LockToOutput(address: Program) locks locked {
+ clause relock() {
+ lock locked with address
+ }
+}
+`
+
+const TradeOffer = `
+contract TradeOffer(requestedAsset: Asset, requestedAmount: Amount, sellerProgram: Program, sellerKey: PublicKey) locks offered {
+ clause trade() requires payment: requestedAmount of requestedAsset {
+ lock payment with sellerProgram
+ unlock offered
+ }
+ clause cancel(sellerSig: Signature) {
+ verify checkTxSig(sellerKey, sellerSig)
+ lock offered with sellerProgram
+ }
+}
+`
+
+const EscrowedTransfer = `
+contract EscrowedTransfer(agent: PublicKey, sender: Program, recipient: Program) locks value {
+ clause approve(sig: Signature) {
+ verify checkTxSig(agent, sig)
+ lock value with recipient
+ }
+ clause reject(sig: Signature) {
+ verify checkTxSig(agent, sig)
+ lock value with sender
+ }
+}
+`
+
+const CollateralizedLoan = `
+contract CollateralizedLoan(balanceAsset: Asset, balanceAmount: Amount, finalHeight: Integer, lender: Program, borrower: Program) locks collateral {
+ clause repay() requires payment: balanceAmount of balanceAsset {
+ lock payment with lender
+ lock collateral with borrower
+ }
+ clause default() {
+ verify above(finalHeight)
+ lock collateral with lender
+ }
+}
+`
+
+const RevealPreimage = `
+contract RevealPreimage(hash: Hash) locks value {
+ clause reveal(string: String) {
+ verify sha3(string) == hash
+ unlock value
+ }
+}
+`
+
+const PriceChanger = `
+contract PriceChanger(askAmount: Amount, askAsset: Asset, sellerKey: PublicKey, sellerProg: Program) locks offered {
+ clause changePrice(newAmount: Amount, newAsset: Asset, sig: Signature) {
+ verify checkTxSig(sellerKey, sig)
+ lock offered with PriceChanger(newAmount, newAsset, sellerKey, sellerProg)
+ }
+ clause redeem() requires payment: askAmount of askAsset {
+ lock payment with sellerProg
+ unlock offered
+ }
+}
+`
+
+const CallOptionWithSettlement = `
+contract CallOptionWithSettlement(strikePrice: Amount,
+ strikeCurrency: Asset,
+ sellerProgram: Program,
+ sellerKey: PublicKey,
+ buyerKey: PublicKey,
+ finalHeight: Integer) locks underlying {
+ clause exercise(buyerSig: Signature)
+ requires payment: strikePrice of strikeCurrency {
+ verify below(finalHeight)
+ verify checkTxSig(buyerKey, buyerSig)
+ lock payment with sellerProgram
+ unlock underlying
+ }
+ clause expire() {
+ verify above(finalHeight)
+ lock underlying with sellerProgram
+ }
+ clause settle(sellerSig: Signature, buyerSig: Signature) {
+ verify checkTxSig(sellerKey, sellerSig)
+ verify checkTxSig(buyerKey, buyerSig)
+ unlock underlying
+ }
+}
+`
+
+const OneTwo = `
+contract Two(b, c: Program, expirationHeight: Integer) locks value {
+ clause redeem() {
+ verify below(expirationHeight)
+ lock value with b
+ }
+ clause default() {
+ verify above(expirationHeight)
+ lock value with c
+ }
+}
+contract One(a, b, c: Program, switchHeight, blockHeight: Integer) locks value {
+ clause redeem() {
+ verify below(switchHeight)
+ lock value with a
+ }
+ clause switch() {
+ verify above(switchHeight)
+ lock value with Two(b, c, blockHeight)
+ }
+}
+`
--- /dev/null
+package compiler
+
+import "strings"
+
+var optimizations = []struct {
+ before, after string
+}{
+ {"0 ROLL", ""},
+ {"0 PICK", "DUP"},
+ {"1 ROLL", "SWAP"},
+ {"1 PICK", "OVER"},
+ {"2 ROLL", "ROT"},
+ {"TRUE VERIFY", ""},
+ {"SWAP SWAP", ""},
+ {"OVER OVER", "2DUP"},
+ {"SWAP OVER", "TUCK"},
+ {"DROP DROP", "2DROP"},
+ {"SWAP DROP", "NIP"},
+ {"5 ROLL 5 ROLL", "2ROT"},
+ {"3 PICK 3 PICK", "2OVER"},
+ {"3 ROLL 3 ROLL", "2SWAP"},
+ {"2 PICK 2 PICK 2 PICK", "3DUP"},
+ {"1 ADD", "1ADD"},
+ {"1 SUB", "1SUB"},
+ {"EQUAL VERIFY", "EQUALVERIFY"},
+ {"SWAP TXSIGHASH ROT", "TXSIGHASH SWAP"},
+ {"SWAP EQUAL", "EQUAL"},
+ {"SWAP EQUALVERIFY", "EQUALVERIFY"},
+ {"SWAP ADD", "ADD"},
+ {"SWAP BOOLAND", "BOOLAND"},
+ {"SWAP BOOLOR", "BOOLOR"},
+ {"SWAP MIN", "MIN"},
+ {"SWAP MAX", "MAX"},
+ {"DUP 2 PICK EQUAL", "2DUP EQUAL"},
+ {"DUP 2 PICK EQUALVERIFY", "2DUP EQUALVERIFY"},
+ {"DUP 2 PICK ADD", "2DUP ADD"},
+ {"DUP 2 PICK BOOLAND", "2DUP BOOLAND"},
+ {"DUP 2 PICK BOOLOR", "2DUP BOOLOR"},
+ {"DUP 2 PICK MIN", "2DUP MIN"},
+ {"DUP 2 PICK MAX", "2DUP MAX"},
+}
+
+func optimize(opcodes string) string {
+ opcodes = " " + opcodes + " "
+ looping := true
+ for looping {
+ looping = false
+ for _, o := range optimizations {
+ before := " " + o.before + " "
+ var after string
+ if o.after == "" {
+ after = " "
+ } else {
+ after = " " + o.after + " "
+ }
+ newOpcodes := strings.Replace(opcodes, before, after, -1)
+ if newOpcodes != opcodes {
+ looping = true
+ opcodes = newOpcodes
+ }
+ }
+ }
+ return strings.TrimSpace(opcodes)
+}
--- /dev/null
+package compiler
+
+import (
+ "bytes"
+ "encoding/hex"
+ "fmt"
+ "strconv"
+ "unicode"
+)
+
+// We have some function naming conventions.
+//
+// For terminals:
+// scanX takes buf and position, returns new position (and maybe a value)
+// peekX takes *parser, returns bool or string
+// consumeX takes *parser and maybe a required literal, maybe returns value
+// also updates the parser position
+//
+// For nonterminals:
+// parseX takes *parser, returns AST node, updates parser position
+
+type parser struct {
+ buf []byte
+ pos int
+}
+
+func (p *parser) errorf(format string, args ...interface{}) {
+ panic(parserErr{buf: p.buf, offset: p.pos, format: format, args: args})
+}
+
+// parse is the main entry point to the parser
+func parse(buf []byte) (contracts []*Contract, err error) {
+ defer func() {
+ if val := recover(); val != nil {
+ if e, ok := val.(parserErr); ok {
+ err = e
+ } else {
+ panic(val)
+ }
+ }
+ }()
+ p := &parser{buf: buf}
+ contracts = parseContracts(p)
+ return
+}
+
+// parse functions
+
+func parseContracts(p *parser) []*Contract {
+ var result []*Contract
+ for peekKeyword(p) == "contract" {
+ contract := parseContract(p)
+ result = append(result, contract)
+ }
+ return result
+}
+
+// contract name(p1, p2: t1, p3: t2) locks value { ... }
+func parseContract(p *parser) *Contract {
+ consumeKeyword(p, "contract")
+ name := consumeIdentifier(p)
+ params := parseParams(p)
+ consumeKeyword(p, "locks")
+ value := consumeIdentifier(p)
+ consumeTok(p, "{")
+ clauses := parseClauses(p)
+ consumeTok(p, "}")
+ return &Contract{Name: name, Params: params, Clauses: clauses, Value: value}
+}
+
+// (p1, p2: t1, p3: t2)
+func parseParams(p *parser) []*Param {
+ var params []*Param
+ consumeTok(p, "(")
+ first := true
+ for !peekTok(p, ")") {
+ if first {
+ first = false
+ } else {
+ consumeTok(p, ",")
+ }
+ pt := parseParamsType(p)
+ params = append(params, pt...)
+ }
+ consumeTok(p, ")")
+ return params
+}
+
+func parseClauses(p *parser) []*Clause {
+ var clauses []*Clause
+ for !peekTok(p, "}") {
+ c := parseClause(p)
+ clauses = append(clauses, c)
+ }
+ return clauses
+}
+
+func parseParamsType(p *parser) []*Param {
+ firstName := consumeIdentifier(p)
+ params := []*Param{&Param{Name: firstName}}
+ for peekTok(p, ",") {
+ consumeTok(p, ",")
+ name := consumeIdentifier(p)
+ params = append(params, &Param{Name: name})
+ }
+ consumeTok(p, ":")
+ typ := consumeIdentifier(p)
+ for _, parm := range params {
+ if tdesc, ok := types[typ]; ok {
+ parm.Type = tdesc
+ } else {
+ p.errorf("unknown type %s", typ)
+ }
+ }
+ return params
+}
+
+func parseClause(p *parser) *Clause {
+ var c Clause
+ consumeKeyword(p, "clause")
+ c.Name = consumeIdentifier(p)
+ c.Params = parseParams(p)
+ if peekKeyword(p) == "requires" {
+ consumeKeyword(p, "requires")
+ c.Reqs = parseClauseRequirements(p)
+ }
+ consumeTok(p, "{")
+ c.statements = parseStatements(p)
+ consumeTok(p, "}")
+ return &c
+}
+
+func parseClauseRequirements(p *parser) []*ClauseReq {
+ var result []*ClauseReq
+ first := true
+ for {
+ switch {
+ case first:
+ first = false
+ case peekTok(p, ","):
+ consumeTok(p, ",")
+ default:
+ return result
+ }
+ var req ClauseReq
+ req.Name = consumeIdentifier(p)
+ consumeTok(p, ":")
+ req.amountExpr = parseExpr(p)
+ consumeKeyword(p, "of")
+ req.assetExpr = parseExpr(p)
+ result = append(result, &req)
+ }
+}
+
+func parseStatements(p *parser) []statement {
+ var statements []statement
+ for !peekTok(p, "}") {
+ s := parseStatement(p)
+ statements = append(statements, s)
+ }
+ return statements
+}
+
+func parseStatement(p *parser) statement {
+ switch peekKeyword(p) {
+ case "verify":
+ return parseVerifyStmt(p)
+ case "lock":
+ return parseLockStmt(p)
+ case "unlock":
+ return parseUnlockStmt(p)
+ }
+ panic(parseErr(p.buf, p.pos, "unknown keyword \"%s\"", peekKeyword(p)))
+}
+
+func parseVerifyStmt(p *parser) *verifyStatement {
+ consumeKeyword(p, "verify")
+ expr := parseExpr(p)
+ return &verifyStatement{expr: expr}
+}
+
+func parseLockStmt(p *parser) *lockStatement {
+ consumeKeyword(p, "lock")
+ locked := parseExpr(p)
+ consumeKeyword(p, "with")
+ program := parseExpr(p)
+ return &lockStatement{locked: locked, program: program}
+}
+
+func parseUnlockStmt(p *parser) *unlockStatement {
+ consumeKeyword(p, "unlock")
+ expr := parseExpr(p)
+ return &unlockStatement{expr}
+}
+
+func parseExpr(p *parser) expression {
+ // Uses the precedence-climbing algorithm
+ // <https://en.wikipedia.org/wiki/Operator-precedence_parser#Precedence_climbing_method>
+ expr := parseUnaryExpr(p)
+ expr2, pos := parseExprCont(p, expr, 0)
+ if pos < 0 {
+ p.errorf("expected expression")
+ }
+ p.pos = pos
+ return expr2
+}
+
+func parseUnaryExpr(p *parser) expression {
+ op, pos := scanUnaryOp(p.buf, p.pos)
+ if pos < 0 {
+ return parseExpr2(p)
+ }
+ p.pos = pos
+ expr := parseUnaryExpr(p)
+ return &unaryExpr{op: op, expr: expr}
+}
+
+func parseExprCont(p *parser, lhs expression, minPrecedence int) (expression, int) {
+ for {
+ op, pos := scanBinaryOp(p.buf, p.pos)
+ if pos < 0 || op.precedence < minPrecedence {
+ break
+ }
+ p.pos = pos
+
+ rhs := parseUnaryExpr(p)
+
+ for {
+ op2, pos2 := scanBinaryOp(p.buf, p.pos)
+ if pos2 < 0 || op2.precedence <= op.precedence {
+ break
+ }
+ rhs, p.pos = parseExprCont(p, rhs, op2.precedence)
+ if p.pos < 0 {
+ return nil, -1 // or is this an error?
+ }
+ }
+ lhs = &binaryExpr{left: lhs, right: rhs, op: op}
+ }
+ return lhs, p.pos
+}
+
+func parseExpr2(p *parser) expression {
+ if expr, pos := scanLiteralExpr(p.buf, p.pos); pos >= 0 {
+ p.pos = pos
+ return expr
+ }
+ return parseExpr3(p)
+}
+
+func parseExpr3(p *parser) expression {
+ e := parseExpr4(p)
+ if peekTok(p, "(") {
+ args := parseArgs(p)
+ return &callExpr{fn: e, args: args}
+ }
+ return e
+}
+
+func parseExpr4(p *parser) expression {
+ if peekTok(p, "(") {
+ consumeTok(p, "(")
+ e := parseExpr(p)
+ consumeTok(p, ")")
+ return e
+ }
+ if peekTok(p, "[") {
+ var elts []expression
+ consumeTok(p, "[")
+ first := true
+ for !peekTok(p, "]") {
+ if first {
+ first = false
+ } else {
+ consumeTok(p, ",")
+ }
+ e := parseExpr(p)
+ elts = append(elts, e)
+ }
+ consumeTok(p, "]")
+ return listExpr(elts)
+ }
+ name := consumeIdentifier(p)
+ return varRef(name)
+}
+
+func parseArgs(p *parser) []expression {
+ var exprs []expression
+ consumeTok(p, "(")
+ first := true
+ for !peekTok(p, ")") {
+ if first {
+ first = false
+ } else {
+ consumeTok(p, ",")
+ }
+ e := parseExpr(p)
+ exprs = append(exprs, e)
+ }
+ consumeTok(p, ")")
+ return exprs
+}
+
+// peek functions
+
+func peekKeyword(p *parser) string {
+ name, _ := scanIdentifier(p.buf, p.pos)
+ return name
+}
+
+func peekTok(p *parser, token string) bool {
+ pos := scanTok(p.buf, p.pos, token)
+ return pos >= 0
+}
+
+// consume functions
+
+var keywords = []string{
+ "contract", "clause", "verify", "output", "return",
+ "locks", "requires", "of", "lock", "with", "unlock",
+}
+
+func consumeKeyword(p *parser, keyword string) {
+ pos := scanKeyword(p.buf, p.pos, keyword)
+ if pos < 0 {
+ p.errorf("expected keyword %s", keyword)
+ }
+ p.pos = pos
+}
+
+func consumeIdentifier(p *parser) string {
+ name, pos := scanIdentifier(p.buf, p.pos)
+ if pos < 0 {
+ p.errorf("expected identifier")
+ }
+ p.pos = pos
+ return name
+}
+
+func consumeTok(p *parser, token string) {
+ pos := scanTok(p.buf, p.pos, token)
+ if pos < 0 {
+ p.errorf("expected %s token", token)
+ }
+ p.pos = pos
+}
+
+// scan functions
+
+func scanUnaryOp(buf []byte, offset int) (*unaryOp, int) {
+ // Maximum munch. Make sure "-3" scans as ("-3"), not ("-", "3").
+ if _, pos := scanIntLiteral(buf, offset); pos >= 0 {
+ return nil, -1
+ }
+ for _, op := range unaryOps {
+ newOffset := scanTok(buf, offset, op.op)
+ if newOffset >= 0 {
+ return &op, newOffset
+ }
+ }
+ return nil, -1
+}
+
+func scanBinaryOp(buf []byte, offset int) (*binaryOp, int) {
+ offset = skipWsAndComments(buf, offset)
+ var (
+ found *binaryOp
+ newOffset = -1
+ )
+ for i, op := range binaryOps {
+ offset2 := scanTok(buf, offset, op.op)
+ if offset2 >= 0 {
+ if found == nil || len(op.op) > len(found.op) {
+ found = &binaryOps[i]
+ newOffset = offset2
+ }
+ }
+ }
+ return found, newOffset
+}
+
+// TODO(bobg): boolean literals?
+func scanLiteralExpr(buf []byte, offset int) (expression, int) {
+ offset = skipWsAndComments(buf, offset)
+ intliteral, newOffset := scanIntLiteral(buf, offset)
+ if newOffset >= 0 {
+ return intliteral, newOffset
+ }
+ strliteral, newOffset := scanStrLiteral(buf, offset)
+ if newOffset >= 0 {
+ return strliteral, newOffset
+ }
+ bytesliteral, newOffset := scanBytesLiteral(buf, offset) // 0x6c249a...
+ if newOffset >= 0 {
+ return bytesliteral, newOffset
+ }
+ return nil, -1
+}
+
+func scanIdentifier(buf []byte, offset int) (string, int) {
+ offset = skipWsAndComments(buf, offset)
+ i := offset
+ for ; i < len(buf) && isIDChar(buf[i], i == offset); i++ {
+ }
+ if i == offset {
+ return "", -1
+ }
+ return string(buf[offset:i]), i
+}
+
+func scanTok(buf []byte, offset int, s string) int {
+ offset = skipWsAndComments(buf, offset)
+ prefix := []byte(s)
+ if bytes.HasPrefix(buf[offset:], prefix) {
+ return offset + len(prefix)
+ }
+ return -1
+}
+
+func scanKeyword(buf []byte, offset int, keyword string) int {
+ id, newOffset := scanIdentifier(buf, offset)
+ if newOffset < 0 {
+ return -1
+ }
+ if id != keyword {
+ return -1
+ }
+ return newOffset
+}
+
+func scanIntLiteral(buf []byte, offset int) (integerLiteral, int) {
+ offset = skipWsAndComments(buf, offset)
+ start := offset
+ if offset < len(buf) && buf[offset] == '-' {
+ offset++
+ }
+ i := offset
+ for ; i < len(buf) && unicode.IsDigit(rune(buf[i])); i++ {
+ }
+ if i > offset {
+ n, err := strconv.ParseInt(string(buf[start:i]), 10, 64)
+ if err != nil {
+ return 0, -1
+ }
+ return integerLiteral(n), i
+ }
+ return 0, -1
+}
+
+func scanStrLiteral(buf []byte, offset int) (bytesLiteral, int) {
+ offset = skipWsAndComments(buf, offset)
+ if offset >= len(buf) || buf[offset] != '\'' {
+ return bytesLiteral{}, -1
+ }
+ for i := offset + 1; i < len(buf); i++ {
+ if buf[i] == '\'' {
+ return bytesLiteral(buf[offset : i+1]), i + 1
+ }
+ if buf[i] == '\\' {
+ i++
+ }
+ }
+ panic(parseErr(buf, offset, "unterminated string literal"))
+}
+
+func scanBytesLiteral(buf []byte, offset int) (bytesLiteral, int) {
+ offset = skipWsAndComments(buf, offset)
+ if offset+4 >= len(buf) {
+ return nil, -1
+ }
+ if buf[offset] != '0' || (buf[offset+1] != 'x' && buf[offset+1] != 'X') {
+ return nil, -1
+ }
+ if !isHexDigit(buf[offset+2]) || !isHexDigit(buf[offset+3]) {
+ return nil, -1
+ }
+ i := offset + 4
+ for ; i < len(buf); i += 2 {
+ if i == len(buf)-1 {
+ panic(parseErr(buf, offset, "odd number of digits in hex literal"))
+ }
+ if !isHexDigit(buf[i]) {
+ break
+ }
+ if !isHexDigit(buf[i+1]) {
+ panic(parseErr(buf, offset, "odd number of digits in hex literal"))
+ }
+ }
+ decoded := make([]byte, hex.DecodedLen(i-(offset+2)))
+ _, err := hex.Decode(decoded, buf[offset+2:i])
+ if err != nil {
+ return bytesLiteral{}, -1
+ }
+ return bytesLiteral(decoded), i
+}
+
+func skipWsAndComments(buf []byte, offset int) int {
+ var inComment bool
+ for ; offset < len(buf); offset++ {
+ c := buf[offset]
+ if inComment {
+ if c == '\n' {
+ inComment = false
+ }
+ } else {
+ if c == '/' && offset < len(buf)-1 && buf[offset+1] == '/' {
+ inComment = true
+ offset++ // skip two chars instead of one
+ } else if !unicode.IsSpace(rune(c)) {
+ break
+ }
+ }
+ }
+ return offset
+}
+
+func isHexDigit(b byte) bool {
+ return (b >= '0' && b <= '9') || (b >= 'a' && b <= 'f') || (b >= 'A' && b <= 'F')
+}
+
+func isIDChar(c byte, initial bool) bool {
+ if c >= 'a' && c <= 'z' {
+ return true
+ }
+ if c >= 'A' && c <= 'Z' {
+ return true
+ }
+ if c == '_' {
+ return true
+ }
+ if initial {
+ return false
+ }
+ return unicode.IsDigit(rune(c))
+}
+
+type parserErr struct {
+ buf []byte
+ offset int
+ format string
+ args []interface{}
+}
+
+func parseErr(buf []byte, offset int, format string, args ...interface{}) error {
+ return parserErr{buf: buf, offset: offset, format: format, args: args}
+}
+
+func (p parserErr) Error() string {
+ // Lines start at 1, columns start at 0, like nature intended.
+ line := 1
+ col := 0
+ for i := 0; i < p.offset; i++ {
+ if p.buf[i] == '\n' {
+ line++
+ col = 0
+ } else {
+ col++
+ }
+ }
+ args := []interface{}{line, col}
+ args = append(args, p.args...)
+ return fmt.Sprintf("line %d, col %d: "+p.format, args...)
+}
--- /dev/null
+package compiler
+
+type (
+ stack struct {
+ *stackEntry
+ }
+ stackEntry struct {
+ str string
+ prev *stackEntry
+ }
+)
+
+func (stk stack) isEmpty() bool {
+ return stk.stackEntry == nil
+}
+
+func (stk stack) top() string {
+ if stk.isEmpty() {
+ return ""
+ }
+ return stk.str
+}
+
+func (stk stack) add(str string) stack {
+ e := &stackEntry{
+ str: str,
+ prev: stk.stackEntry,
+ }
+ return stack{e}
+}
+
+func (stk stack) addFromStack(other stack) stack {
+ if other.isEmpty() {
+ return stk
+ }
+ res := stk.addFromStack(other.drop())
+ return res.add(other.top())
+}
+
+func (stk stack) drop() stack {
+ if !stk.isEmpty() {
+ stk = stack{stk.prev}
+ }
+ return stk
+}
+
+func (stk stack) dropN(n int) stack {
+ for n > 0 {
+ stk = stk.drop()
+ n--
+ }
+ return stk
+}
+
+func (stk stack) find(str string) int {
+ if stk.isEmpty() {
+ return -1
+ }
+ if stk.str == str {
+ return 0
+ }
+ res := stk.drop().find(str)
+ if res < 0 {
+ return res
+ }
+ return res + 1
+}
+
+func (stk stack) roll(n int) stack {
+ var x func(stack, int) (stack, string)
+ x = func(stk stack, n int) (stack, string) {
+ if n == 0 {
+ return stk.drop(), stk.top()
+ }
+ stk2, entry := x(stk.drop(), n-1)
+ return stk2.add(stk.top()), entry
+ }
+ stk, entry := x(stk, n)
+ return stk.add(entry)
+}
+
+func (stk stack) swap() stack {
+ a := stk.top()
+ stk = stk.drop()
+ b := stk.top()
+ stk = stk.drop()
+ return stk.add(a).add(b)
+}
+
+func (stk stack) dup() stack {
+ return stk.add(stk.top())
+}
+
+func (stk stack) over() stack {
+ t := stk.drop().top()
+ return stk.add(t)
+}
+
+func (stk stack) pick(n int) stack {
+ t := stk.dropN(n).top()
+ return stk.add(t)
+}
+
+func (stk stack) String() string {
+ if stk.stackEntry == nil {
+ return "[]"
+ }
+ var x func(stk stack) string
+ x = func(stk stack) string {
+ if stk.stackEntry == nil {
+ return ""
+ }
+ return x(stk.drop()) + " " + stk.stackEntry.str
+ }
+ return "[..." + x(stk) + "]"
+}
--- /dev/null
+package compiler
+
+type typeDesc string
+
+var (
+ amountType = typeDesc("Amount")
+ assetType = typeDesc("Asset")
+ boolType = typeDesc("Boolean")
+ contractType = typeDesc("Contract")
+ hashType = typeDesc("Hash")
+ intType = typeDesc("Integer")
+ listType = typeDesc("List")
+ nilType = typeDesc("")
+ predType = typeDesc("Predicate")
+ progType = typeDesc("Program")
+ pubkeyType = typeDesc("PublicKey")
+ sigType = typeDesc("Signature")
+ strType = typeDesc("String")
+ valueType = typeDesc("Value")
+
+ sha3StrType = typeDesc("Sha3(String)")
+ sha3PubkeyType = typeDesc("Sha3(PublicKey)")
+ sha256StrType = typeDesc("Sha256(String)")
+ sha256PubkeyType = typeDesc("Sha256(PublicKey)")
+)
+
+var types = map[string]typeDesc{
+ string(amountType): amountType,
+ string(assetType): assetType,
+ string(boolType): boolType,
+ string(hashType): hashType,
+ string(intType): intType,
+ string(listType): listType,
+ string(nilType): nilType,
+ string(predType): predType,
+ string(progType): progType,
+ string(pubkeyType): pubkeyType,
+ string(sigType): sigType,
+ string(strType): strType,
+ string(valueType): valueType,
+
+ string(sha3StrType): sha3StrType,
+ string(sha3PubkeyType): sha3PubkeyType,
+ string(sha256StrType): sha256StrType,
+ string(sha256PubkeyType): sha256PubkeyType,
+}
+
+func isHashSubtype(t typeDesc) bool {
+ switch t {
+ case sha3StrType, sha3PubkeyType, sha256StrType, sha256PubkeyType:
+ return true
+ }
+ return false
+}
+
+func propagateType(contract *Contract, clause *Clause, env *environ, t typeDesc, e expression) {
+ v, ok := e.(varRef)
+ if !ok {
+ return
+ }
+ if entry := env.lookup(string(v)); entry != nil {
+ entry.t = t
+ for _, p := range contract.Params {
+ if p.Name == string(v) {
+ p.InferredType = t
+ return
+ }
+ }
+ for _, p := range clause.Params {
+ if p.Name == string(v) {
+ p.InferredType = t
+ return
+ }
+ }
+ }
+}