+++ /dev/null
-Experimental tree
-=================
-
-Code in this tree may be committed to `main` with minimal review, but may not be imported by production code.
-
-A thorough code review is required to move code out of this tree and into Chain Core proper.
+++ /dev/null
-package compiler
-
-import (
- "encoding/hex"
- "fmt"
- "strconv"
- "strings"
-
- chainjson "github.com/bytom/encoding/json"
-)
-
-// Contract is a compiled Ivy 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:"declared_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
-
- // MinTimes is the list of expressions passed to after() in this
- // clause.
- MinTimes []string `json:"mintimes,omitempty"`
-
- // MaxTimes is the list of expressions passed to before() in this
- // clause.
- MaxTimes []string `json:"maxtimes,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(6).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},
- {"before", "MAXTIME GREATERTHAN", []typeDesc{timeType}, boolType},
- {"after", "MINTIME LESSTHAN", []typeDesc{timeType}, 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/bytom/exp/ivy/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,
- "chain/exp/ivy/compiler": true,
- "chain/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)
- case "Time":
- fmt.Fprintf(buf, "\t_%s := %s.UnixNano() / time.Millisecond\n", param.Name, param.Name)
- 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, "chain/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, "chain/crypto/ed25519")
- case "Signature":
- typ = "[]byte"
- case "String":
- typ = "[]byte"
- case "Time":
- typ = "time.Time"
- imports = append(imports, "time")
- }
- 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 Ivy 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, timeType:
- 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 Ivy
- // 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 "before":
- clause.MaxTimes = append(clause.MaxTimes, c.args[0].String())
- case "after":
- clause.MinTimes = append(clause.MinTimes, c.args[0].String())
- }
- }
- }
-
- case *lockStatement:
- // index
- stk = b.addInt64(stk, stmt.index)
-
- // refdatahash
- stk = b.addData(stk, nil)
-
- // 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/exp/ivy/compiler/ivytest"
-)
-
-func TestCompile(t *testing.T) {
- cases := []struct {
- name string
- contract string
- wantJSON string
- }{
- {
- "TrivialLock",
- ivytest.TrivialLock,
- `[{"name":"TrivialLock","clauses":[{"name":"trivialUnlock","values":[{"name":"locked"}]}],"value":"locked","body_bytecode":"51","body_opcodes":"TRUE","recursive":false}]`,
- },
- {
- "LockWithPublicKey",
- ivytest.LockWithPublicKey,
- `[{"name":"LockWithPublicKey","params":[{"name":"publicKey","declared_type":"PublicKey"}],"clauses":[{"name":"unlockWithSig","params":[{"name":"sig","declared_type":"Signature"}],"values":[{"name":"locked"}]}],"value":"locked","body_bytecode":"ae7cac","body_opcodes":"TXSIGHASH SWAP CHECKSIG","recursive":false}]`,
- },
- {
- "LockWithPublicKeyHash",
- ivytest.LockWithPKHash,
- `[{"name":"LockWithPublicKeyHash","params":[{"name":"pubKeyHash","declared_type":"Hash","inferred_type":"Sha3(PublicKey)"}],"clauses":[{"name":"spend","params":[{"name":"pubKey","declared_type":"PublicKey"},{"name":"sig","declared_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",
- ivytest.LockWith2of3Keys,
- `[{"name":"LockWith3Keys","params":[{"name":"pubkey1","declared_type":"PublicKey"},{"name":"pubkey2","declared_type":"PublicKey"},{"name":"pubkey3","declared_type":"PublicKey"}],"clauses":[{"name":"unlockWith2Sigs","params":[{"name":"sig1","declared_type":"Signature"},{"name":"sig2","declared_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",
- ivytest.LockToOutput,
- `[{"name":"LockToOutput","params":[{"name":"address","declared_type":"Program"}],"clauses":[{"name":"relock","values":[{"name":"locked","program":"address"}]}],"value":"locked","body_bytecode":"0000c3c251557ac1","body_opcodes":"0 0 AMOUNT ASSET 1 5 ROLL CHECKOUTPUT","recursive":false}]`,
- },
- {
- "TradeOffer",
- ivytest.TradeOffer,
- `[{"name":"TradeOffer","params":[{"name":"requestedAsset","declared_type":"Asset"},{"name":"requestedAmount","declared_type":"Amount"},{"name":"sellerProgram","declared_type":"Program"},{"name":"sellerKey","declared_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","declared_type":"Signature"}],"values":[{"name":"offered","program":"sellerProgram"}]}],"value":"offered","body_bytecode":"547a641300000000007251557ac16323000000547a547aae7cac690000c3c251577ac1","body_opcodes":"4 ROLL JUMPIF:$cancel $trade 0 0 2SWAP 1 5 ROLL CHECKOUTPUT JUMP:$_end $cancel 4 ROLL 4 ROLL TXSIGHASH SWAP CHECKSIG VERIFY 0 0 AMOUNT ASSET 1 7 ROLL CHECKOUTPUT $_end","recursive":false}]`,
- },
- {
- "EscrowedTransfer",
- ivytest.EscrowedTransfer,
- `[{"name":"EscrowedTransfer","params":[{"name":"agent","declared_type":"PublicKey"},{"name":"sender","declared_type":"Program"},{"name":"recipient","declared_type":"Program"}],"clauses":[{"name":"approve","params":[{"name":"sig","declared_type":"Signature"}],"values":[{"name":"value","program":"recipient"}]},{"name":"reject","params":[{"name":"sig","declared_type":"Signature"}],"values":[{"name":"value","program":"sender"}]}],"value":"value","body_bytecode":"537a641b000000537a7cae7cac690000c3c251567ac1632a000000537a7cae7cac690000c3c251557ac1","body_opcodes":"3 ROLL JUMPIF:$reject $approve 3 ROLL SWAP TXSIGHASH SWAP CHECKSIG VERIFY 0 0 AMOUNT ASSET 1 6 ROLL CHECKOUTPUT JUMP:$_end $reject 3 ROLL SWAP TXSIGHASH SWAP CHECKSIG VERIFY 0 0 AMOUNT ASSET 1 5 ROLL CHECKOUTPUT $_end","recursive":false}]`,
- },
- {
- "RevealPreimage",
- ivytest.RevealPreimage,
- `[{"name":"RevealPreimage","params":[{"name":"hash","declared_type":"Hash","inferred_type":"Sha3(String)"}],"clauses":[{"name":"reveal","params":[{"name":"string","declared_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}]`,
- },
- {
- "PriceChanger",
- ivytest.PriceChanger,
- `[{"name":"PriceChanger","params":[{"name":"askAmount","declared_type":"Amount"},{"name":"askAsset","declared_type":"Asset"},{"name":"sellerKey","declared_type":"PublicKey"},{"name":"sellerProg","declared_type":"Program"}],"clauses":[{"name":"changePrice","params":[{"name":"newAmount","declared_type":"Amount"},{"name":"newAsset","declared_type":"Asset"},{"name":"sig","declared_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":"557a6433000000557a5479ae7cac690000c3c251005a7a89597a89597a89597a89567a890274787e008901c07ec1633d0000000000537a547a51577ac1","body_opcodes":"5 ROLL JUMPIF:$redeem $changePrice 5 ROLL 4 PICK TXSIGHASH SWAP CHECKSIG VERIFY 0 0 AMOUNT ASSET 1 0 10 ROLL CATPUSHDATA 9 ROLL CATPUSHDATA 9 ROLL CATPUSHDATA 9 ROLL CATPUSHDATA 6 ROLL CATPUSHDATA 0x7478 CAT 0 CATPUSHDATA 192 CAT CHECKOUTPUT JUMP:$_end $redeem 0 0 3 ROLL 4 ROLL 1 7 ROLL CHECKOUTPUT $_end","recursive":true}]`,
- },
- }
- 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 ivy provides a compiler for Chain's Ivy 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 Ivy 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 ivytest
-
-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, deadline: Time, lender: Program, borrower: Program) locks collateral {
- clause repay() requires payment: balanceAmount of balanceAsset {
- lock payment with lender
- lock collateral with borrower
- }
- clause default() {
- verify after(deadline)
- 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,
- deadline: Time) locks underlying {
- clause exercise(buyerSig: Signature)
- requires payment: strikePrice of strikeCurrency {
- verify before(deadline)
- verify checkTxSig(buyerKey, buyerSig)
- lock payment with sellerProgram
- unlock underlying
- }
- clause expire() {
- verify after(deadline)
- 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, expirationTime: Time) locks value {
- clause redeem() {
- verify before(expirationTime)
- lock value with b
- }
- clause default() {
- verify after(expirationTime)
- lock value with c
- }
-}
-contract One(a, b, c: Program, switchTime, expirationTime: Time) locks value {
- clause redeem() {
- verify before(switchTime)
- lock value with a
- }
- clause switch() {
- verify after(switchTime)
- lock value with Two(b, c, expirationTime)
- }
-}
-`
+++ /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")
- timeType = typeDesc("Time")
- 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(timeType): timeType,
- 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
- }
- }
- }
-}