mirror of
https://github.com/hanzo-fi/stack.git
synced 2026-10-30 20:34:43 +00:00
1713 lines
44 KiB
Go
1713 lines
44 KiB
Go
package compiler
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"math/big"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/formancehq/ledger/internal/machine"
|
|
|
|
program2 "github.com/formancehq/ledger/internal/machine/vm/program"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type TestCase struct {
|
|
Case string
|
|
Expected CaseResult
|
|
}
|
|
|
|
type CaseResult struct {
|
|
Instructions []byte
|
|
Resources []program2.Resource
|
|
Variables []string
|
|
Error string
|
|
}
|
|
|
|
func test(t *testing.T, c TestCase) {
|
|
p, err := Compile(c.Case)
|
|
if c.Expected.Error != "" {
|
|
require.Error(t, err)
|
|
require.NotEmpty(t, err.Error())
|
|
require.ErrorContains(t, err, c.Expected.Error)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
require.NotNil(t, p)
|
|
|
|
if len(c.Expected.Instructions) > 0 && !bytes.Equal(p.Instructions, c.Expected.Instructions) {
|
|
t.Error(fmt.Errorf(
|
|
"unexpected instructions:\n%v\nhas: %+v\nwant:%+v",
|
|
*p, p.Instructions, c.Expected.Instructions))
|
|
return
|
|
} else if len(p.Resources) != len(c.Expected.Resources) {
|
|
t.Error(fmt.Errorf(
|
|
"unexpected resources\n%v\nhas: \n%+v\nwant:\n%+v",
|
|
*p, p.Resources, c.Expected.Resources))
|
|
return
|
|
}
|
|
|
|
for i, expected := range c.Expected.Resources {
|
|
if !checkResourcesEqual(p.Resources[i], c.Expected.Resources[i]) {
|
|
t.Error(fmt.Errorf("%v: %v is not %v: %v",
|
|
p.Resources[i], reflect.TypeOf(p.Resources[i]).Name(),
|
|
expected, reflect.TypeOf(expected).Name(),
|
|
))
|
|
t.Error(fmt.Errorf(
|
|
"unexpected resources\n%v\nhas: \n%+v\nwant:\n%+v",
|
|
*p, p.Resources, c.Expected.Resources))
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func checkResourcesEqual(actual, expected program2.Resource) bool {
|
|
if reflect.TypeOf(actual) != reflect.TypeOf(expected) {
|
|
return false
|
|
}
|
|
switch res := actual.(type) {
|
|
case program2.Constant:
|
|
return machine.ValueEquals(res.Inner, expected.(program2.Constant).Inner)
|
|
case program2.Variable:
|
|
e := expected.(program2.Variable)
|
|
return res.Typ == e.Typ && res.Name == e.Name
|
|
case program2.VariableAccountMetadata:
|
|
e := expected.(program2.VariableAccountMetadata)
|
|
return res.Account == e.Account &&
|
|
res.Key == e.Key &&
|
|
res.Typ == e.Typ
|
|
case program2.VariableAccountBalance:
|
|
e := expected.(program2.VariableAccountBalance)
|
|
return res.Account == e.Account &&
|
|
res.Asset == e.Asset
|
|
case program2.Monetary:
|
|
e := expected.(program2.Monetary)
|
|
return res.Amount.Equal(e.Amount) && res.Asset == e.Asset
|
|
default:
|
|
panic(fmt.Errorf("invalid resource of type '%T'", res))
|
|
}
|
|
}
|
|
|
|
func TestSimplePrint(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: "print 1",
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 00, 00,
|
|
program2.OP_PRINT,
|
|
},
|
|
Resources: []program2.Resource{
|
|
program2.Constant{Inner: machine.NewMonetaryInt(1)},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestCompositeExpr(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: "print 29 + 15 - 2",
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 00, 00,
|
|
program2.OP_APUSH, 01, 00,
|
|
program2.OP_IADD,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_ISUB,
|
|
program2.OP_PRINT,
|
|
},
|
|
Resources: []program2.Resource{
|
|
program2.Constant{Inner: machine.NewMonetaryInt(29)},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(15)},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(2)},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestFail(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: "fail",
|
|
Expected: CaseResult{
|
|
Instructions: []byte{program2.OP_FAIL},
|
|
Resources: []program2.Resource{},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestCRLF(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: "print @a\r\nprint @b",
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 00, 00,
|
|
program2.OP_PRINT,
|
|
program2.OP_APUSH, 01, 00,
|
|
program2.OP_PRINT,
|
|
},
|
|
Resources: []program2.Resource{
|
|
program2.Constant{Inner: machine.AccountAddress("a")},
|
|
program2.Constant{Inner: machine.AccountAddress("b")},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestConstant(t *testing.T) {
|
|
user := machine.AccountAddress("user:U001")
|
|
test(t, TestCase{
|
|
Case: "print @user:U001",
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 00, 00,
|
|
program2.OP_PRINT,
|
|
},
|
|
Resources: []program2.Resource{program2.Constant{Inner: user}},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestSetTxMeta(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `
|
|
set_tx_meta("aaa", @platform)
|
|
set_tx_meta("bbb", GEM)
|
|
set_tx_meta("ccc", 42)
|
|
set_tx_meta("ddd", "test")
|
|
set_tx_meta("eee", [COIN 30])
|
|
set_tx_meta("fff", 15%)
|
|
`,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 00, 00,
|
|
program2.OP_APUSH, 01, 00,
|
|
program2.OP_TX_META,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_APUSH, 03, 00,
|
|
program2.OP_TX_META,
|
|
program2.OP_APUSH, 04, 00,
|
|
program2.OP_APUSH, 05, 00,
|
|
program2.OP_TX_META,
|
|
program2.OP_APUSH, 06, 00,
|
|
program2.OP_APUSH, 07, 00,
|
|
program2.OP_TX_META,
|
|
program2.OP_APUSH, 9, 00,
|
|
program2.OP_APUSH, 10, 00,
|
|
program2.OP_TX_META,
|
|
program2.OP_APUSH, 11, 00,
|
|
program2.OP_APUSH, 12, 00,
|
|
program2.OP_TX_META,
|
|
},
|
|
Resources: []program2.Resource{
|
|
program2.Constant{Inner: machine.AccountAddress("platform")},
|
|
program2.Constant{Inner: machine.String("aaa")},
|
|
program2.Constant{Inner: machine.Asset("GEM")},
|
|
program2.Constant{Inner: machine.String("bbb")},
|
|
program2.Constant{Inner: machine.NewNumber(42)},
|
|
program2.Constant{Inner: machine.String("ccc")},
|
|
program2.Constant{Inner: machine.String("test")},
|
|
program2.Constant{Inner: machine.String("ddd")},
|
|
program2.Constant{Inner: machine.Asset("COIN")},
|
|
program2.Monetary{Asset: 8, Amount: machine.NewMonetaryInt(30)},
|
|
program2.Constant{Inner: machine.String("eee")},
|
|
program2.Constant{Inner: machine.Portion{
|
|
Remaining: false,
|
|
Specific: big.NewRat(15, 100),
|
|
}},
|
|
program2.Constant{Inner: machine.String("fff")},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestSetTxMetaVars(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `
|
|
vars {
|
|
portion $commission
|
|
}
|
|
set_tx_meta("fee", $commission)
|
|
`,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 00, 00,
|
|
program2.OP_APUSH, 01, 00,
|
|
program2.OP_TX_META,
|
|
},
|
|
Resources: []program2.Resource{
|
|
program2.Variable{Typ: machine.TypePortion, Name: "commission"},
|
|
program2.Constant{Inner: machine.String("fee")},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestComments(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `
|
|
/* This is a multi-line comment, it spans multiple lines
|
|
and /* doesn't choke on nested comments */ ! */
|
|
vars {
|
|
account $a
|
|
}
|
|
// this is a single-line comment
|
|
print $a
|
|
`,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 00, 00,
|
|
program2.OP_PRINT,
|
|
},
|
|
Resources: []program2.Resource{
|
|
program2.Variable{Typ: machine.TypeAccount, Name: "a"},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestUndeclaredVariable(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: "print $nope",
|
|
Expected: CaseResult{
|
|
Error: "declared",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInvalidTypeInSendValue(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `
|
|
send @a (
|
|
source = {
|
|
@a
|
|
[GEM 2]
|
|
}
|
|
destination = @b
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "send monetary: the expression should be of type 'monetary' instead of 'account'",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInvalidTypeInSource(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `
|
|
send [USD/2 99] (
|
|
source = {
|
|
@a
|
|
[GEM 2]
|
|
}
|
|
destination = @b
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "wrong type",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestDestinationAllotment(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `send [EUR/2 43] (
|
|
source = @foo
|
|
destination = {
|
|
1/8 to @bar
|
|
7/8 to @baz
|
|
}
|
|
)`,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 02, 00, // @foo
|
|
program2.OP_APUSH, 01, 00, // @foo, [EUR/2 43]
|
|
program2.OP_ASSET, // @foo, EUR/2
|
|
program2.OP_APUSH, 03, 00, // @foo, EUR/2, 0
|
|
program2.OP_MONETARY_NEW, // @foo, [EUR/2 0]
|
|
program2.OP_TAKE_ALL, // [EUR/2 @foo <?>]
|
|
program2.OP_APUSH, 01, 00, // [EUR/2 @foo <?>], [EUR/2 43]
|
|
program2.OP_TAKE, // [EUR/2 @foo <?>], [EUR/2 @foo 43]
|
|
program2.OP_APUSH, 04, 00, // [EUR/2 @foo <?>], [EUR/2 @foo 43] 1
|
|
program2.OP_BUMP, // [EUR/2 @foo 43], [EUR/2 @foo <?>]
|
|
program2.OP_REPAY, // [EUR/2 @foo 43]
|
|
program2.OP_FUNDING_SUM, // [EUR/2 @foo 43], [EUR/2 43]
|
|
program2.OP_APUSH, 05, 00, // [EUR/2 @foo 43], [EUR/2 43], 7/8
|
|
program2.OP_APUSH, 06, 00, // [EUR/2 @foo 43], [EUR/2 43], 7/8, 1/8
|
|
program2.OP_APUSH, 07, 00, // [EUR/2 @foo 43], [EUR/2 43], 7/8, 1/8, 2
|
|
program2.OP_MAKE_ALLOTMENT, // [EUR/2 @foo 43], [EUR/2 43], {1/8 : 7/8}
|
|
program2.OP_ALLOC, // [EUR/2 @foo 43], [EUR/2 37], [EUR/2 6]
|
|
program2.OP_APUSH, 07, 00, // [EUR/2 @foo 43], [EUR/2 37] [EUR/2 6], 2
|
|
program2.OP_BUMP, // [EUR/2 37], [EUR/2 6], [EUR/2 @foo 43]
|
|
program2.OP_APUSH, 04, 00, // [EUR/2 37], [EUR/2 6], [EUR/2 @foo 43] 1
|
|
program2.OP_BUMP, // [EUR/2 37], [EUR/2 @foo 43], [EUR/2 6]
|
|
program2.OP_TAKE, // [EUR/2 37], [EUR/2 @foo 37], [EUR/2 @foo 6]
|
|
program2.OP_FUNDING_SUM, // [EUR/2 37], [EUR/2 @foo 37], [EUR/2 @foo 6] [EUR/2 6]
|
|
program2.OP_TAKE, // [EUR/2 37], [EUR/2 @foo 37], [EUR/2] [EUR/2 @foo 6]
|
|
program2.OP_APUSH, 8, 00, // [EUR/2 37], [EUR/2 @foo 37], [EUR/2] [EUR/2 @foo 6], @bar
|
|
program2.OP_SEND, // [EUR/2 37], [EUR/2 @foo 37], [EUR/2]
|
|
program2.OP_APUSH, 04, 00, // [EUR/2 37], [EUR/2 @foo 37], [EUR/2] 1
|
|
program2.OP_BUMP, // [EUR/2 37], [EUR/2], [EUR/2 @foo 37]
|
|
program2.OP_APUSH, 07, 00, // [EUR/2 37], [EUR/2], [EUR/2 @foo 37] 2
|
|
program2.OP_FUNDING_ASSEMBLE, // [EUR/2 37], [EUR/2 @foo 37]
|
|
program2.OP_APUSH, 04, 00, // [EUR/2 37], [EUR/2 @foo 37], 1
|
|
program2.OP_BUMP, // [EUR/2 @foo 37], [EUR/2 37]
|
|
program2.OP_TAKE, // [EUR/2], [EUR/2 @foo 37]
|
|
program2.OP_FUNDING_SUM, // [EUR/2], [EUR/2 @foo 37], [EUR/2 37]
|
|
program2.OP_TAKE, // [EUR/2], [EUR/2], [EUR/2 @foo 37]
|
|
program2.OP_APUSH, 9, 00, // [EUR/2], [EUR/2], [EUR/2 @foo 37], @baz
|
|
program2.OP_SEND, // [EUR/2], [EUR/2]
|
|
program2.OP_APUSH, 04, 00, // [EUR/2], [EUR/2], 1
|
|
program2.OP_BUMP, // [EUR/2], [EUR/2]
|
|
program2.OP_APUSH, 07, 00, // [EUR/2], [EUR/2], 2
|
|
program2.OP_FUNDING_ASSEMBLE, // [EUR/2]
|
|
program2.OP_REPAY, //
|
|
},
|
|
Resources: []program2.Resource{
|
|
program2.Constant{Inner: machine.Asset("EUR/2")},
|
|
program2.Monetary{
|
|
Asset: 0,
|
|
Amount: machine.NewMonetaryInt(43),
|
|
},
|
|
program2.Constant{Inner: machine.AccountAddress("foo")},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(0)},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(1)},
|
|
program2.Constant{Inner: machine.Portion{Specific: big.NewRat(7, 8)}},
|
|
program2.Constant{Inner: machine.Portion{Specific: big.NewRat(1, 8)}},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(2)},
|
|
program2.Constant{Inner: machine.AccountAddress("bar")},
|
|
program2.Constant{Inner: machine.AccountAddress("baz")},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestDestinationInOrder(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `send [COIN 50] (
|
|
source = @a
|
|
destination = {
|
|
max [COIN 10] to @b
|
|
remaining to @c
|
|
}
|
|
)`,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 02, 00, // @a
|
|
program2.OP_APUSH, 01, 00, // @a, [COIN 50]
|
|
program2.OP_ASSET, // @a, COIN
|
|
program2.OP_APUSH, 03, 00, // @a, COIN, 0
|
|
program2.OP_MONETARY_NEW, // @a, [COIN 0]
|
|
program2.OP_TAKE_ALL, // [COIN @a <?>]
|
|
program2.OP_APUSH, 01, 00, // [COIN @a <?>], [COIN 50]
|
|
program2.OP_TAKE, // [COIN @a <?>], [COIN @a 50]
|
|
program2.OP_APUSH, 04, 00, // [COIN @a <?>], [COIN @a 50], 1
|
|
program2.OP_BUMP, // [COIN @a 50], [COIN @a <?>]
|
|
program2.OP_REPAY, // [COIN @a 50]
|
|
program2.OP_FUNDING_SUM, // [COIN @a 50], [COIN 50] <- start of DestinationInOrder
|
|
program2.OP_ASSET, // [COIN @a 50], COIN
|
|
program2.OP_APUSH, 03, 00, // [COIN @a 50], COIN, 0
|
|
program2.OP_MONETARY_NEW, // [COIN @a 50], [COIN 0]
|
|
program2.OP_APUSH, 04, 00, // [COIN @a 50], [COIN 0], 1
|
|
program2.OP_BUMP, // [COIN 0], [COIN @a 50]
|
|
program2.OP_APUSH, 05, 00, // [COIN 0], [COIN @a 50], [COIN 10] <- start processing max subdestinations
|
|
program2.OP_TAKE_MAX, // [COIN 0], [COIN 0], [COIN @a 40], [COIN @a 10]
|
|
program2.OP_APUSH, 06, 00, // [COIN 0], [COIN 0], [COIN @a 40], [COIN @a 10], 2
|
|
program2.OP_BUMP, // [COIN 0], [COIN @a 40], [COIN @a 10], [COIN 0]
|
|
program2.OP_DELETE, // [COIN 0], [COIN @a 40], [COIN @a 10]
|
|
program2.OP_FUNDING_SUM, // [COIN 0], [COIN @a 40], [COIN @a 10], [COIN 10]
|
|
program2.OP_TAKE, // [COIN 0], [COIN @a 40], [COIN], [COIN @a 10]
|
|
program2.OP_APUSH, 07, 00, // [COIN 0], [COIN @a 40], [COIN], [COIN @a 10], @b
|
|
program2.OP_SEND, // [COIN 0], [COIN @a 40], [COIN]
|
|
program2.OP_FUNDING_SUM, // [COIN 0], [COIN @a 40], [COIN], [COIN 0]
|
|
program2.OP_APUSH, 8, 00, // [COIN 0], [COIN @a 40], [COIN], [COIN 0], 3
|
|
program2.OP_BUMP, // [COIN @a 40], [COIN], [COIN 0], [COIN 0]
|
|
program2.OP_MONETARY_ADD, // [COIN @a 40], [COIN], [COIN 0]
|
|
program2.OP_APUSH, 04, 00, // [COIN @a 40], [COIN], [COIN 0], 1
|
|
program2.OP_BUMP, // [COIN @a 40], [COIN 0], [COIN]
|
|
program2.OP_APUSH, 06, 00, // [COIN @a 40], [COIN 0], [COIN] 2
|
|
program2.OP_BUMP, // [COIN 0], [COIN], [COIN @a 40]
|
|
program2.OP_APUSH, 06, 00, // [COIN 0], [COIN], [COIN @a 40], 2
|
|
program2.OP_FUNDING_ASSEMBLE, // [COIN 0], [COIN @a 40]
|
|
program2.OP_FUNDING_REVERSE, // [COIN 0], [COIN @a 40] <- start processing remaining subdestination
|
|
program2.OP_APUSH, 04, 00, // [COIN 0], [COIN @a 40], 1
|
|
program2.OP_BUMP, // [COIN @a 40], [COIN 0]
|
|
program2.OP_TAKE, // [COIN @a 40], [COIN]
|
|
program2.OP_FUNDING_REVERSE, // [COIN @a 40], [COIN]
|
|
program2.OP_APUSH, 04, 00, // [COIN @a 40], [COIN], 1
|
|
program2.OP_BUMP, // [COIN], [COIN @a 40]
|
|
program2.OP_FUNDING_REVERSE, // [COIN], [COIN @a 40]
|
|
program2.OP_FUNDING_SUM, // [COIN], [COIN @a 40], [COIN 40]
|
|
program2.OP_TAKE, // [COIN], [COIN], [COIN @a 40]
|
|
program2.OP_APUSH, 9, 00, // [COIN], [COIN], [COIN @a 40], @c
|
|
program2.OP_SEND, // [COIN], [COIN]
|
|
program2.OP_APUSH, 04, 00, // [COIN], [COIN], 1
|
|
program2.OP_BUMP, // [COIN], [COIN]
|
|
program2.OP_APUSH, 06, 00, // [COIN], [COIN], 2
|
|
program2.OP_FUNDING_ASSEMBLE, // [COIN]
|
|
program2.OP_REPAY, //
|
|
},
|
|
Resources: []program2.Resource{
|
|
program2.Constant{Inner: machine.Asset("COIN")},
|
|
program2.Monetary{
|
|
Asset: 0,
|
|
Amount: machine.NewMonetaryInt(50),
|
|
},
|
|
program2.Constant{Inner: machine.AccountAddress("a")},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(0)},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(1)},
|
|
program2.Monetary{
|
|
Asset: 0,
|
|
Amount: machine.NewMonetaryInt(10),
|
|
},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(2)},
|
|
program2.Constant{Inner: machine.AccountAddress("b")},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(3)},
|
|
program2.Constant{Inner: machine.AccountAddress("c")},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestAllocationPercentages(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `send [EUR/2 43] (
|
|
source = @foo
|
|
destination = {
|
|
12.5% to @bar
|
|
37.5% to @baz
|
|
50% to @qux
|
|
}
|
|
)`,
|
|
Expected: CaseResult{
|
|
Resources: []program2.Resource{
|
|
program2.Constant{Inner: machine.Asset("EUR/2")},
|
|
program2.Monetary{
|
|
Asset: 0,
|
|
Amount: machine.NewMonetaryInt(43),
|
|
},
|
|
program2.Constant{Inner: machine.AccountAddress("foo")},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(0)},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(1)},
|
|
program2.Constant{Inner: machine.Portion{Specific: big.NewRat(1, 2)}},
|
|
program2.Constant{Inner: machine.Portion{Specific: big.NewRat(3, 8)}},
|
|
program2.Constant{Inner: machine.Portion{Specific: big.NewRat(1, 8)}},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(3)},
|
|
program2.Constant{Inner: machine.AccountAddress("bar")},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(2)},
|
|
program2.Constant{Inner: machine.AccountAddress("baz")},
|
|
program2.Constant{Inner: machine.AccountAddress("qux")},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestSend(t *testing.T) {
|
|
script := `
|
|
send [EUR/2 99] (
|
|
source = @alice
|
|
destination = @bob
|
|
)`
|
|
alice := machine.AccountAddress("alice")
|
|
bob := machine.AccountAddress("bob")
|
|
test(t, TestCase{
|
|
Case: script,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 02, 00, // @alice
|
|
program2.OP_APUSH, 01, 00, // @alice, [EUR/2 99]
|
|
program2.OP_ASSET, // @alice, EUR/2
|
|
program2.OP_APUSH, 03, 00, // @alice, EUR/2, 0
|
|
program2.OP_MONETARY_NEW, // @alice, [EUR/2 0]
|
|
program2.OP_TAKE_ALL, // [EUR/2 @alice <?>]
|
|
program2.OP_APUSH, 01, 00, // [EUR/2 @alice <?>], [EUR/2 99]
|
|
program2.OP_TAKE, // [EUR/2 @alice <?>], [EUR/2 @alice 99]
|
|
program2.OP_APUSH, 04, 00, // [EUR/2 @alice <?>], [EUR/2 @alice 99], 1
|
|
program2.OP_BUMP, // [EUR/2 @alice 99], [EUR/2 @alice <?>]
|
|
program2.OP_REPAY, // [EUR/2 @alice 99]
|
|
program2.OP_FUNDING_SUM, // [EUR/2 @alice 99], [EUR/2 99]
|
|
program2.OP_TAKE, // [EUR/2], [EUR/2 @alice 99]
|
|
program2.OP_APUSH, 05, 00, // [EUR/2], [EUR/2 @alice 99], @bob
|
|
program2.OP_SEND, // [EUR/2]
|
|
program2.OP_REPAY, //
|
|
}, Resources: []program2.Resource{
|
|
program2.Constant{Inner: machine.Asset("EUR/2")},
|
|
program2.Monetary{
|
|
Asset: 0,
|
|
Amount: machine.NewMonetaryInt(99),
|
|
},
|
|
program2.Constant{Inner: alice},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(0)},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(1)},
|
|
program2.Constant{Inner: bob}},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestSendAll(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `send [EUR/2 *] (
|
|
source = @alice
|
|
destination = @bob
|
|
)`,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 01, 00, // @alice
|
|
program2.OP_APUSH, 00, 00, // @alice, EUR/2
|
|
program2.OP_APUSH, 02, 00, // @alice, EUR/2, 0
|
|
program2.OP_MONETARY_NEW, // @alice, [EUR/2 0]
|
|
program2.OP_TAKE_ALL, // [EUR/2 @alice <?>]
|
|
program2.OP_FUNDING_SUM, // [EUR/2 @alice <?>], [EUR/2 <?>]
|
|
program2.OP_TAKE, // [EUR/2], [EUR/2 @alice <?>]
|
|
program2.OP_APUSH, 03, 00, // [EUR/2], [EUR/2 @alice <?>], @b
|
|
program2.OP_SEND, // [EUR/2]
|
|
program2.OP_REPAY, //
|
|
}, Resources: []program2.Resource{
|
|
program2.Constant{Inner: machine.Asset("EUR/2")},
|
|
program2.Constant{Inner: machine.AccountAddress("alice")},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(0)},
|
|
program2.Constant{Inner: machine.AccountAddress("bob")}},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestMetadata(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `
|
|
vars {
|
|
account $sale
|
|
account $seller = meta($sale, "seller")
|
|
portion $commission = meta($seller, "commission")
|
|
}
|
|
send [EUR/2 53] (
|
|
source = $sale
|
|
destination = {
|
|
$commission to @platform
|
|
remaining to $seller
|
|
}
|
|
)`,
|
|
Expected: CaseResult{
|
|
Resources: []program2.Resource{
|
|
program2.Variable{Typ: machine.TypeAccount, Name: "sale"},
|
|
program2.VariableAccountMetadata{
|
|
Typ: machine.TypeAccount,
|
|
Account: machine.NewAddress(0),
|
|
Key: "seller",
|
|
},
|
|
program2.VariableAccountMetadata{
|
|
Typ: machine.TypePortion,
|
|
Account: machine.NewAddress(1),
|
|
Key: "commission",
|
|
},
|
|
program2.Constant{Inner: machine.Asset("EUR/2")},
|
|
program2.Monetary{
|
|
Asset: 3,
|
|
Amount: machine.NewMonetaryInt(53),
|
|
},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(0)},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(1)},
|
|
program2.Constant{Inner: machine.NewPortionRemaining()},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(2)},
|
|
program2.Constant{Inner: machine.AccountAddress("platform")},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestSyntaxError(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: "print fail",
|
|
Expected: CaseResult{
|
|
Error: "mismatched input",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestLogicError(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `send [EUR/2 200] (
|
|
source = 200
|
|
destination = @bob
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "expected",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestPreventTakeAllFromWorld(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `send [GEM *] (
|
|
source = @world
|
|
destination = @foo
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "cannot",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestPreventAddToBottomlessSource(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `send [GEM 1000] (
|
|
source = {
|
|
@a
|
|
@world
|
|
@c
|
|
}
|
|
destination = @out
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "world",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestPreventAddToBottomlessSource2(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `send [GEM 1000] (
|
|
source = {
|
|
{
|
|
@a
|
|
@world
|
|
}
|
|
{
|
|
@b
|
|
@world
|
|
}
|
|
}
|
|
destination = @out
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "world",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestPreventSourceAlreadyEmptied(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `send [GEM 1000] (
|
|
source = {
|
|
{
|
|
@a
|
|
@b
|
|
}
|
|
@a
|
|
}
|
|
destination = @out
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "empt",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestPreventTakeAllFromAllocation(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `send [GEM *] (
|
|
source = {
|
|
50% from @a
|
|
50% from @b
|
|
}
|
|
destination = @out
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "all",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestWrongTypeSourceMax(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `send [GEM 15] (
|
|
source = {
|
|
max @foo from @bar
|
|
@world
|
|
}
|
|
destination = @baz
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "type",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestOverflowingAllocation(t *testing.T) {
|
|
t.Run(">100%", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `send [GEM 15] (
|
|
source = @world
|
|
destination = {
|
|
2/3 to @a
|
|
2/3 to @b
|
|
}
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "100%",
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("=100% + remaining", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `send [GEM 15] (
|
|
source = @world
|
|
destination = {
|
|
1/2 to @a
|
|
1/2 to @b
|
|
remaining to @c
|
|
}
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "100%",
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run(">100% + remaining", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `send [GEM 15] (
|
|
source = @world
|
|
destination = {
|
|
2/3 to @a
|
|
1/2 to @b
|
|
remaining to @c
|
|
}
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "100%",
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("const remaining + remaining", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `send [GEM 15] (
|
|
source = @world
|
|
destination = {
|
|
2/3 to @a
|
|
remaining to @b
|
|
remaining to @c
|
|
}
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "`remaining` in the same",
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("dyn remaining + remaining", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `vars {
|
|
portion $p
|
|
}
|
|
send [GEM 15] (
|
|
source = @world
|
|
destination = {
|
|
$p to @a
|
|
remaining to @b
|
|
remaining to @c
|
|
}
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "`remaining` in the same",
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run(">100% + remaining + variable", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `vars {
|
|
portion $prop
|
|
}
|
|
send [GEM 15] (
|
|
source = @world
|
|
destination = {
|
|
1/2 to @a
|
|
2/3 to @b
|
|
remaining to @c
|
|
$prop to @d
|
|
}
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "100%",
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("variable - remaining", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `vars {
|
|
portion $prop
|
|
}
|
|
send [GEM 15] (
|
|
source = @world
|
|
destination = {
|
|
2/3 to @a
|
|
$prop to @b
|
|
}
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "100%",
|
|
},
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestAllocationWrongDestination(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `send [GEM 15] (
|
|
source = @world
|
|
destination = [GEM 10]
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "account",
|
|
},
|
|
})
|
|
test(t, TestCase{
|
|
Case: `send [GEM 15] (
|
|
source = @world
|
|
destination = {
|
|
2/3 to @a
|
|
1/3 to [GEM 10]
|
|
}
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "account",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestAllocationInvalidPortion(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `vars {
|
|
account $p
|
|
}
|
|
send [GEM 15] (
|
|
source = @world
|
|
destination = {
|
|
10% to @a
|
|
$p to @b
|
|
}
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "type",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestOverdraftOnWorld(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `send [GEM 15] (
|
|
source = @world allowing overdraft up to [GEM 10]
|
|
destination = @foo
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "overdraft",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestOverdraftWrongType(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `send [GEM 15] (
|
|
source = @foo allowing overdraft up to @baz
|
|
destination = @bar
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "type",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestDestinationInOrderWrongType(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `send [GEM 15] (
|
|
source = @foo
|
|
destination = {
|
|
max @bar to @baz
|
|
remaining to @qux
|
|
}
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "type",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestSetAccountMeta(t *testing.T) {
|
|
t.Run("all types", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `
|
|
set_account_meta(@alice, "aaa", @platform)
|
|
set_account_meta(@alice, "bbb", GEM)
|
|
set_account_meta(@alice, "ccc", 42)
|
|
set_account_meta(@alice, "ddd", "test")
|
|
set_account_meta(@alice, "eee", [COIN 30])
|
|
set_account_meta(@alice, "fff", 15%)
|
|
`,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 00, 00,
|
|
program2.OP_APUSH, 01, 00,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_ACCOUNT_META,
|
|
program2.OP_APUSH, 03, 00,
|
|
program2.OP_APUSH, 04, 00,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_ACCOUNT_META,
|
|
program2.OP_APUSH, 05, 00,
|
|
program2.OP_APUSH, 06, 00,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_ACCOUNT_META,
|
|
program2.OP_APUSH, 7, 00,
|
|
program2.OP_APUSH, 8, 00,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_ACCOUNT_META,
|
|
program2.OP_APUSH, 10, 00,
|
|
program2.OP_APUSH, 11, 00,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_ACCOUNT_META,
|
|
program2.OP_APUSH, 12, 00,
|
|
program2.OP_APUSH, 13, 00,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_ACCOUNT_META,
|
|
},
|
|
Resources: []program2.Resource{
|
|
program2.Constant{Inner: machine.AccountAddress("platform")},
|
|
program2.Constant{Inner: machine.String("aaa")},
|
|
program2.Constant{Inner: machine.AccountAddress("alice")},
|
|
program2.Constant{Inner: machine.Asset("GEM")},
|
|
program2.Constant{Inner: machine.String("bbb")},
|
|
program2.Constant{Inner: machine.NewNumber(42)},
|
|
program2.Constant{Inner: machine.String("ccc")},
|
|
program2.Constant{Inner: machine.String("test")},
|
|
program2.Constant{Inner: machine.String("ddd")},
|
|
program2.Constant{Inner: machine.Asset("COIN")},
|
|
program2.Monetary{
|
|
Asset: 9,
|
|
Amount: machine.NewMonetaryInt(30),
|
|
},
|
|
program2.Constant{Inner: machine.String("eee")},
|
|
program2.Constant{Inner: machine.Portion{
|
|
Remaining: false,
|
|
Specific: big.NewRat(15, 100),
|
|
}},
|
|
program2.Constant{Inner: machine.String("fff")},
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("with vars", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `vars {
|
|
account $acc
|
|
}
|
|
send [EUR/2 100] (
|
|
source = @world
|
|
destination = $acc
|
|
)
|
|
set_account_meta($acc, "fees", 1%)`,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 03, 00,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_ASSET,
|
|
program2.OP_APUSH, 04, 00,
|
|
program2.OP_MONETARY_NEW,
|
|
program2.OP_TAKE_ALL,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_TAKE_MAX,
|
|
program2.OP_APUSH, 05, 00,
|
|
program2.OP_BUMP,
|
|
program2.OP_REPAY,
|
|
program2.OP_APUSH, 03, 00,
|
|
program2.OP_APUSH, 06, 00,
|
|
program2.OP_BUMP,
|
|
program2.OP_TAKE_ALWAYS,
|
|
program2.OP_APUSH, 06, 00,
|
|
program2.OP_FUNDING_ASSEMBLE,
|
|
program2.OP_FUNDING_SUM,
|
|
program2.OP_TAKE,
|
|
program2.OP_APUSH, 00, 00,
|
|
program2.OP_SEND,
|
|
program2.OP_REPAY,
|
|
program2.OP_APUSH, 07, 00,
|
|
program2.OP_APUSH, 8, 00,
|
|
program2.OP_APUSH, 00, 00,
|
|
program2.OP_ACCOUNT_META,
|
|
},
|
|
Resources: []program2.Resource{
|
|
program2.Variable{Typ: machine.TypeAccount, Name: "acc"},
|
|
program2.Constant{Inner: machine.Asset("EUR/2")},
|
|
program2.Monetary{
|
|
Asset: 1,
|
|
Amount: machine.NewMonetaryInt(100),
|
|
},
|
|
program2.Constant{Inner: machine.AccountAddress("world")},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(0)},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(1)},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(2)},
|
|
program2.Constant{Inner: machine.Portion{
|
|
Remaining: false,
|
|
Specific: big.NewRat(1, 100),
|
|
}},
|
|
program2.Constant{Inner: machine.String("fees")},
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("errors", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `set_account_meta(@alice, "fees")`,
|
|
Expected: CaseResult{
|
|
Error: "mismatched input",
|
|
},
|
|
})
|
|
test(t, TestCase{
|
|
Case: `set_account_meta("test")`,
|
|
Expected: CaseResult{
|
|
Error: "mismatched input",
|
|
},
|
|
})
|
|
test(t, TestCase{
|
|
Case: `set_account_meta(@alice, "t1", "t2", "t3")`,
|
|
Expected: CaseResult{
|
|
Error: "mismatched input",
|
|
},
|
|
})
|
|
test(t, TestCase{
|
|
Case: `vars {
|
|
portion $p
|
|
}
|
|
set_account_meta($p, "fees", 1%)`,
|
|
Expected: CaseResult{
|
|
Error: "should be of type account",
|
|
},
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestVariableBalance(t *testing.T) {
|
|
t.Run("simplest", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `vars {
|
|
monetary $bal = balance(@alice, COIN)
|
|
}
|
|
send $bal (
|
|
source = @alice
|
|
destination = @bob
|
|
)`,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 00, 00,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_ASSET,
|
|
program2.OP_APUSH, 03, 00,
|
|
program2.OP_MONETARY_NEW,
|
|
program2.OP_TAKE_ALL,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_TAKE,
|
|
program2.OP_APUSH, 04, 00,
|
|
program2.OP_BUMP,
|
|
program2.OP_REPAY,
|
|
program2.OP_FUNDING_SUM,
|
|
program2.OP_TAKE,
|
|
program2.OP_APUSH, 05, 00,
|
|
program2.OP_SEND,
|
|
program2.OP_REPAY,
|
|
},
|
|
Resources: []program2.Resource{
|
|
program2.Constant{Inner: machine.AccountAddress("alice")},
|
|
program2.Constant{Inner: machine.Asset("COIN")},
|
|
program2.VariableAccountBalance{Account: 0, Asset: 1},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(0)},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(1)},
|
|
program2.Constant{Inner: machine.AccountAddress("bob")},
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("with account variable", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `vars {
|
|
account $acc
|
|
monetary $bal = balance($acc, COIN)
|
|
}
|
|
send $bal (
|
|
source = @world
|
|
destination = @alice
|
|
)`,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 03, 00,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_ASSET,
|
|
program2.OP_APUSH, 04, 00,
|
|
program2.OP_MONETARY_NEW,
|
|
program2.OP_TAKE_ALL,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_TAKE_MAX,
|
|
program2.OP_APUSH, 05, 00,
|
|
program2.OP_BUMP,
|
|
program2.OP_REPAY,
|
|
program2.OP_APUSH, 03, 00,
|
|
program2.OP_APUSH, 06, 00,
|
|
program2.OP_BUMP,
|
|
program2.OP_TAKE_ALWAYS,
|
|
program2.OP_APUSH, 06, 00,
|
|
program2.OP_FUNDING_ASSEMBLE,
|
|
program2.OP_FUNDING_SUM,
|
|
program2.OP_TAKE,
|
|
program2.OP_APUSH, 07, 00,
|
|
program2.OP_SEND,
|
|
program2.OP_REPAY,
|
|
},
|
|
Resources: []program2.Resource{
|
|
program2.Variable{Typ: machine.TypeAccount, Name: "acc"},
|
|
program2.Constant{Inner: machine.Asset("COIN")},
|
|
program2.VariableAccountBalance{Account: 0, Asset: 1},
|
|
program2.Constant{Inner: machine.AccountAddress("world")},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(0)},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(1)},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(2)},
|
|
program2.Constant{Inner: machine.AccountAddress("alice")},
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("error variable type", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `vars {
|
|
account $bal = balance(@alice, COIN)
|
|
}
|
|
send $bal (
|
|
source = @alice
|
|
destination = @bob
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "variable $bal: type should be 'monetary' to pull account balance",
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("error no asset", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `vars {
|
|
monetary $bal = balance(@alice)
|
|
}
|
|
send $bal (
|
|
source = @alice
|
|
destination = @bob
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "mismatched input",
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("error too many arguments", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `vars {
|
|
monetary $bal = balance(@alice, USD, COIN)
|
|
}
|
|
send $bal (
|
|
source = @alice
|
|
destination = @bob
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "mismatched input ',' expecting ')'",
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("error wrong type for account", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `vars {
|
|
monetary $bal = balance(USD, COIN)
|
|
}
|
|
send $bal (
|
|
source = @alice
|
|
destination = @bob
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "variable $bal: the first argument to pull account balance should be of type 'account'",
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("error wrong type for asset", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `vars {
|
|
monetary $bal = balance(@alice, @bob)
|
|
}
|
|
send $bal (
|
|
source = @alice
|
|
destination = @bob
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "variable $bal: the second argument to pull account balance should be of type 'asset'",
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("error not in variables", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `send balance(@alice, COIN) (
|
|
source = @alice
|
|
destination = @bob
|
|
)`,
|
|
Expected: CaseResult{
|
|
Error: "mismatched input 'balance'",
|
|
},
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestVariableAsset(t *testing.T) {
|
|
script := `vars {
|
|
asset $ass
|
|
monetary $bal = balance(@alice, $ass)
|
|
}
|
|
|
|
send [$ass *] (
|
|
source = @alice
|
|
destination = @bob
|
|
)
|
|
|
|
send [$ass 1] (
|
|
source = @bob
|
|
destination = @alice
|
|
)
|
|
|
|
send $bal (
|
|
source = @alice
|
|
destination = @bob
|
|
)`
|
|
|
|
test(t, TestCase{
|
|
Case: script,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 01, 00,
|
|
program2.OP_APUSH, 00, 00,
|
|
program2.OP_APUSH, 03, 00,
|
|
program2.OP_MONETARY_NEW,
|
|
program2.OP_TAKE_ALL,
|
|
program2.OP_FUNDING_SUM,
|
|
program2.OP_TAKE,
|
|
program2.OP_APUSH, 04, 00,
|
|
program2.OP_SEND,
|
|
program2.OP_REPAY,
|
|
program2.OP_APUSH, 04, 00,
|
|
program2.OP_APUSH, 05, 00,
|
|
program2.OP_ASSET,
|
|
program2.OP_APUSH, 03, 00,
|
|
program2.OP_MONETARY_NEW,
|
|
program2.OP_TAKE_ALL,
|
|
program2.OP_APUSH, 05, 00,
|
|
program2.OP_TAKE,
|
|
program2.OP_APUSH, 06, 00,
|
|
program2.OP_BUMP,
|
|
program2.OP_REPAY,
|
|
program2.OP_FUNDING_SUM,
|
|
program2.OP_TAKE,
|
|
program2.OP_APUSH, 01, 00,
|
|
program2.OP_SEND,
|
|
program2.OP_REPAY,
|
|
program2.OP_APUSH, 01, 00,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_ASSET,
|
|
program2.OP_APUSH, 03, 00,
|
|
program2.OP_MONETARY_NEW,
|
|
program2.OP_TAKE_ALL,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_TAKE,
|
|
program2.OP_APUSH, 06, 00,
|
|
program2.OP_BUMP,
|
|
program2.OP_REPAY,
|
|
program2.OP_FUNDING_SUM,
|
|
program2.OP_TAKE,
|
|
program2.OP_APUSH, 04, 00,
|
|
program2.OP_SEND,
|
|
program2.OP_REPAY,
|
|
},
|
|
Resources: []program2.Resource{
|
|
program2.Variable{Typ: machine.TypeAsset, Name: "ass"},
|
|
program2.Constant{Inner: machine.AccountAddress("alice")},
|
|
program2.VariableAccountBalance{
|
|
Name: "bal",
|
|
Account: 1,
|
|
Asset: 0,
|
|
},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(0)},
|
|
program2.Constant{Inner: machine.AccountAddress("bob")},
|
|
program2.Monetary{
|
|
Asset: 0,
|
|
Amount: machine.NewMonetaryInt(1),
|
|
},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(1)},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestPrint(t *testing.T) {
|
|
script := `print 1 + 2 + 3`
|
|
test(t, TestCase{
|
|
Case: script,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 00, 00,
|
|
program2.OP_APUSH, 01, 00,
|
|
program2.OP_IADD,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_IADD,
|
|
program2.OP_PRINT,
|
|
},
|
|
Resources: []program2.Resource{
|
|
program2.Constant{Inner: machine.NewMonetaryInt(1)},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(2)},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(3)},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestSendWithArithmetic(t *testing.T) {
|
|
t.Run("nominal", func(t *testing.T) {
|
|
script := `
|
|
vars {
|
|
asset $ass
|
|
monetary $mon
|
|
}
|
|
send [EUR 1] + $mon + [$ass 3] - [EUR 4] (
|
|
source = @a
|
|
destination = @b
|
|
)`
|
|
|
|
test(t, TestCase{
|
|
Case: script,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 06, 00,
|
|
program2.OP_APUSH, 03, 00,
|
|
program2.OP_ASSET,
|
|
program2.OP_APUSH, 07, 00,
|
|
program2.OP_MONETARY_NEW,
|
|
program2.OP_TAKE_ALL,
|
|
program2.OP_APUSH, 03, 00,
|
|
program2.OP_APUSH, 01, 00,
|
|
program2.OP_MONETARY_ADD,
|
|
program2.OP_APUSH, 04, 00,
|
|
program2.OP_MONETARY_ADD,
|
|
program2.OP_APUSH, 05, 00,
|
|
program2.OP_MONETARY_SUB,
|
|
program2.OP_TAKE,
|
|
program2.OP_APUSH, 8, 00,
|
|
program2.OP_BUMP,
|
|
program2.OP_REPAY,
|
|
program2.OP_FUNDING_SUM,
|
|
program2.OP_TAKE,
|
|
program2.OP_APUSH, 9, 00,
|
|
program2.OP_SEND,
|
|
program2.OP_REPAY,
|
|
},
|
|
Resources: []program2.Resource{
|
|
program2.Variable{
|
|
Typ: machine.TypeAsset,
|
|
Name: "ass",
|
|
},
|
|
program2.Variable{
|
|
Typ: machine.TypeMonetary,
|
|
Name: "mon",
|
|
},
|
|
program2.Constant{Inner: machine.Asset("EUR")},
|
|
program2.Monetary{
|
|
Asset: 2,
|
|
Amount: machine.NewMonetaryInt(1),
|
|
},
|
|
program2.Monetary{
|
|
Asset: 0,
|
|
Amount: machine.NewMonetaryInt(3),
|
|
},
|
|
program2.Monetary{
|
|
Asset: 2,
|
|
Amount: machine.NewMonetaryInt(4),
|
|
},
|
|
program2.Constant{Inner: machine.AccountAddress("a")},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(0)},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(1)},
|
|
program2.Constant{Inner: machine.AccountAddress("b")},
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("error incompatible types", func(t *testing.T) {
|
|
script := `send [EUR 1] + 2 (
|
|
source = @world
|
|
destination = @bob
|
|
)`
|
|
|
|
test(t, TestCase{
|
|
Case: script,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{},
|
|
Resources: []program2.Resource{},
|
|
Error: "tried to do an arithmetic operation with incompatible left and right-hand side operand types: monetary and number",
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("error incompatible types var", func(t *testing.T) {
|
|
script := `
|
|
vars {
|
|
number $nb
|
|
}
|
|
send [EUR 1] - $nb (
|
|
source = @world
|
|
destination = @bob
|
|
)`
|
|
|
|
test(t, TestCase{
|
|
Case: script,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{},
|
|
Resources: []program2.Resource{},
|
|
Error: "tried to do an arithmetic operation with incompatible left and right-hand side operand types: monetary and number",
|
|
},
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestSaveFromAccount(t *testing.T) {
|
|
t.Run("simple", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `
|
|
save [EUR 10] from @alice
|
|
|
|
send [EUR 20] (
|
|
source = @alice
|
|
destination = @bob
|
|
)`,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 01, 00,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_SAVE,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_APUSH, 03, 00,
|
|
program2.OP_ASSET,
|
|
program2.OP_APUSH, 04, 00,
|
|
program2.OP_MONETARY_NEW,
|
|
program2.OP_TAKE_ALL,
|
|
program2.OP_APUSH, 03, 00,
|
|
program2.OP_TAKE,
|
|
program2.OP_APUSH, 05, 00,
|
|
program2.OP_BUMP,
|
|
program2.OP_REPAY,
|
|
program2.OP_FUNDING_SUM,
|
|
program2.OP_TAKE,
|
|
program2.OP_APUSH, 06, 00,
|
|
program2.OP_SEND,
|
|
program2.OP_REPAY,
|
|
},
|
|
Resources: []program2.Resource{
|
|
program2.Constant{Inner: machine.Asset("EUR")},
|
|
program2.Monetary{
|
|
Asset: 0,
|
|
Amount: machine.NewMonetaryInt(10),
|
|
},
|
|
program2.Constant{Inner: machine.AccountAddress("alice")},
|
|
program2.Monetary{
|
|
Asset: 0,
|
|
Amount: machine.NewMonetaryInt(20),
|
|
},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(0)},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(1)},
|
|
program2.Constant{Inner: machine.AccountAddress("bob")},
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("save all", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `
|
|
save [EUR *] from @alice
|
|
|
|
send [EUR 20] (
|
|
source = @alice
|
|
destination = @bob
|
|
)`,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 00, 00,
|
|
program2.OP_APUSH, 01, 00,
|
|
program2.OP_SAVE,
|
|
program2.OP_APUSH, 01, 00,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_ASSET,
|
|
program2.OP_APUSH, 03, 00,
|
|
program2.OP_MONETARY_NEW,
|
|
program2.OP_TAKE_ALL,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_TAKE,
|
|
program2.OP_APUSH, 04, 00,
|
|
program2.OP_BUMP,
|
|
program2.OP_REPAY,
|
|
program2.OP_FUNDING_SUM,
|
|
program2.OP_TAKE,
|
|
program2.OP_APUSH, 05, 00,
|
|
program2.OP_SEND,
|
|
program2.OP_REPAY,
|
|
},
|
|
Resources: []program2.Resource{
|
|
program2.Constant{Inner: machine.Asset("EUR")},
|
|
program2.Constant{Inner: machine.AccountAddress("alice")},
|
|
program2.Monetary{
|
|
Asset: 0,
|
|
Amount: machine.NewMonetaryInt(20),
|
|
},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(0)},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(1)},
|
|
program2.Constant{Inner: machine.AccountAddress("bob")},
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("with asset var", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `
|
|
vars {
|
|
asset $ass
|
|
}
|
|
|
|
save [$ass 10] from @alice
|
|
|
|
send [$ass 20] (
|
|
source = @alice
|
|
destination = @bob
|
|
)`,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 01, 00,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_SAVE,
|
|
program2.OP_APUSH, 02, 00,
|
|
program2.OP_APUSH, 03, 00,
|
|
program2.OP_ASSET,
|
|
program2.OP_APUSH, 04, 00,
|
|
program2.OP_MONETARY_NEW,
|
|
program2.OP_TAKE_ALL,
|
|
program2.OP_APUSH, 03, 00,
|
|
program2.OP_TAKE,
|
|
program2.OP_APUSH, 05, 00,
|
|
program2.OP_BUMP,
|
|
program2.OP_REPAY,
|
|
program2.OP_FUNDING_SUM,
|
|
program2.OP_TAKE,
|
|
program2.OP_APUSH, 06, 00,
|
|
program2.OP_SEND,
|
|
program2.OP_REPAY,
|
|
},
|
|
Resources: []program2.Resource{
|
|
program2.Variable{Typ: machine.TypeAsset, Name: "ass"},
|
|
program2.Monetary{
|
|
Asset: 0,
|
|
Amount: machine.NewMonetaryInt(10),
|
|
},
|
|
program2.Constant{Inner: machine.AccountAddress("alice")},
|
|
program2.Monetary{
|
|
Asset: 0,
|
|
Amount: machine.NewMonetaryInt(20),
|
|
},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(0)},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(1)},
|
|
program2.Constant{Inner: machine.AccountAddress("bob")},
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("with monetary var", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `
|
|
vars {
|
|
monetary $mon
|
|
}
|
|
|
|
save $mon from @alice
|
|
|
|
send [EUR 20] (
|
|
source = @alice
|
|
destination = @bob
|
|
)`,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{
|
|
program2.OP_APUSH, 00, 00,
|
|
program2.OP_APUSH, 01, 00,
|
|
program2.OP_SAVE,
|
|
program2.OP_APUSH, 01, 00,
|
|
program2.OP_APUSH, 03, 00,
|
|
program2.OP_ASSET,
|
|
program2.OP_APUSH, 04, 00,
|
|
program2.OP_MONETARY_NEW,
|
|
program2.OP_TAKE_ALL,
|
|
program2.OP_APUSH, 03, 00,
|
|
program2.OP_TAKE,
|
|
program2.OP_APUSH, 05, 00,
|
|
program2.OP_BUMP,
|
|
program2.OP_REPAY,
|
|
program2.OP_FUNDING_SUM,
|
|
program2.OP_TAKE,
|
|
program2.OP_APUSH, 06, 00,
|
|
program2.OP_SEND,
|
|
program2.OP_REPAY,
|
|
},
|
|
Resources: []program2.Resource{
|
|
program2.Variable{Typ: machine.TypeMonetary, Name: "mon"},
|
|
program2.Constant{Inner: machine.AccountAddress("alice")},
|
|
program2.Constant{Inner: machine.Asset("EUR")},
|
|
program2.Monetary{
|
|
Asset: 2,
|
|
Amount: machine.NewMonetaryInt(20),
|
|
},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(0)},
|
|
program2.Constant{Inner: machine.NewMonetaryInt(1)},
|
|
program2.Constant{Inner: machine.AccountAddress("bob")},
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("error wrong type monetary", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `
|
|
save 30 from @alice
|
|
`,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{},
|
|
Resources: []program2.Resource{},
|
|
Error: "save monetary from account: the first expression should be of type 'monetary' instead of 'number'",
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("error wrong type account", func(t *testing.T) {
|
|
test(t, TestCase{
|
|
Case: `
|
|
save [EUR 30] from ALICE
|
|
`,
|
|
Expected: CaseResult{
|
|
Instructions: []byte{},
|
|
Resources: []program2.Resource{},
|
|
Error: "save monetary from account: the second expression should be of type 'account' instead of 'asset'",
|
|
},
|
|
})
|
|
})
|
|
}
|