mirror of
https://github.com/hanzo-fi/stack.git
synced 2026-10-30 20:34:43 +00:00
239 lines
7.1 KiB
Go
239 lines
7.1 KiB
Go
package compiler
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/formancehq/ledger/internal/machine"
|
|
|
|
"github.com/formancehq/ledger/internal/machine/script/parser"
|
|
"github.com/formancehq/ledger/internal/machine/vm/program"
|
|
)
|
|
|
|
type FallbackAccount machine.Address
|
|
|
|
// VisitValueAwareSource returns the resource addresses of all the accounts
|
|
func (p *parseVisitor) VisitValueAwareSource(c parser.IValueAwareSourceContext, pushAsset func(), monAddr *machine.Address) (map[machine.Address]struct{}, *CompileError) {
|
|
neededAccounts := map[machine.Address]struct{}{}
|
|
isAll := monAddr == nil
|
|
switch c := c.(type) {
|
|
case *parser.SrcContext:
|
|
accounts, _, unbounded, compErr := p.VisitSource(c.Source(), pushAsset, isAll)
|
|
if compErr != nil {
|
|
return nil, compErr
|
|
}
|
|
for k, v := range accounts {
|
|
neededAccounts[k] = v
|
|
}
|
|
if !isAll {
|
|
p.PushAddress(*monAddr)
|
|
err := p.TakeFromSource(unbounded)
|
|
if err != nil {
|
|
return nil, LogicError(c, err)
|
|
}
|
|
}
|
|
case *parser.SrcAllotmentContext:
|
|
if isAll {
|
|
return nil, LogicError(c, errors.New("cannot take all balance of an allotment source"))
|
|
}
|
|
p.PushAddress(*monAddr)
|
|
p.VisitAllotment(c.SourceAllotment(), c.SourceAllotment().GetPortions())
|
|
p.AppendInstruction(program.OP_ALLOC)
|
|
|
|
sources := c.SourceAllotment().GetSources()
|
|
n := len(sources)
|
|
for i := 0; i < n; i++ {
|
|
accounts, _, fallback, compErr := p.VisitSource(sources[i], pushAsset, isAll)
|
|
if compErr != nil {
|
|
return nil, compErr
|
|
}
|
|
for k, v := range accounts {
|
|
neededAccounts[k] = v
|
|
}
|
|
err := p.Bump(int64(i + 1))
|
|
if err != nil {
|
|
return nil, LogicError(c, err)
|
|
}
|
|
err = p.TakeFromSource(fallback)
|
|
if err != nil {
|
|
return nil, LogicError(c, err)
|
|
}
|
|
}
|
|
err := p.PushInteger(machine.NewNumber(int64(n)))
|
|
if err != nil {
|
|
return nil, LogicError(c, err)
|
|
}
|
|
p.AppendInstruction(program.OP_FUNDING_ASSEMBLE)
|
|
}
|
|
return neededAccounts, nil
|
|
}
|
|
|
|
func (p *parseVisitor) TakeFromSource(fallback *FallbackAccount) error {
|
|
if fallback == nil {
|
|
p.AppendInstruction(program.OP_TAKE)
|
|
err := p.Bump(1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.AppendInstruction(program.OP_REPAY)
|
|
return nil
|
|
}
|
|
|
|
p.AppendInstruction(program.OP_TAKE_MAX)
|
|
err := p.Bump(1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.AppendInstruction(program.OP_REPAY)
|
|
p.PushAddress(machine.Address(*fallback))
|
|
err = p.Bump(2)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.AppendInstruction(program.OP_TAKE_ALWAYS)
|
|
err = p.PushInteger(machine.NewNumber(2))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.AppendInstruction(program.OP_FUNDING_ASSEMBLE)
|
|
return nil
|
|
}
|
|
|
|
// VisitSource returns the resource addresses of all the accounts,
|
|
// the addresses of accounts already emptied,
|
|
// and possibly a fallback account if the source has an unbounded overdraft allowance or contains @world
|
|
func (p *parseVisitor) VisitSource(c parser.ISourceContext, pushAsset func(), isAll bool) (map[machine.Address]struct{}, map[machine.Address]struct{}, *FallbackAccount, *CompileError) {
|
|
neededAccounts := map[machine.Address]struct{}{}
|
|
emptiedAccounts := map[machine.Address]struct{}{}
|
|
var fallback *FallbackAccount
|
|
switch c := c.(type) {
|
|
case *parser.SrcAccountContext:
|
|
ty, accAddr, compErr := p.VisitExpr(c.SourceAccount().GetAccount(), true)
|
|
if compErr != nil {
|
|
return nil, nil, nil, compErr
|
|
}
|
|
if ty != machine.TypeAccount {
|
|
return nil, nil, nil, LogicError(c, errors.New("wrong type: expected account or allocation as destination"))
|
|
}
|
|
if p.isWorld(*accAddr) {
|
|
f := FallbackAccount(*accAddr)
|
|
fallback = &f
|
|
}
|
|
|
|
overdraft := c.SourceAccount().GetOverdraft()
|
|
if overdraft == nil {
|
|
// no overdraft: use zero monetary
|
|
pushAsset()
|
|
err := p.PushInteger(machine.NewNumber(0))
|
|
if err != nil {
|
|
return nil, nil, nil, LogicError(c, err)
|
|
}
|
|
p.AppendInstruction(program.OP_MONETARY_NEW)
|
|
p.AppendInstruction(program.OP_TAKE_ALL)
|
|
} else {
|
|
if p.isWorld(*accAddr) {
|
|
return nil, nil, nil, LogicError(c, errors.New("@world is already set to an unbounded overdraft"))
|
|
}
|
|
switch c := overdraft.(type) {
|
|
case *parser.SrcAccountOverdraftSpecificContext:
|
|
ty, _, compErr := p.VisitExpr(c.GetSpecific(), true)
|
|
if compErr != nil {
|
|
return nil, nil, nil, compErr
|
|
}
|
|
if ty != machine.TypeMonetary {
|
|
return nil, nil, nil, LogicError(c, errors.New("wrong type: expected monetary"))
|
|
}
|
|
p.AppendInstruction(program.OP_TAKE_ALL)
|
|
case *parser.SrcAccountOverdraftUnboundedContext:
|
|
pushAsset()
|
|
err := p.PushInteger(machine.NewNumber(0))
|
|
if err != nil {
|
|
return nil, nil, nil, LogicError(c, err)
|
|
}
|
|
p.AppendInstruction(program.OP_MONETARY_NEW)
|
|
p.AppendInstruction(program.OP_TAKE_ALL)
|
|
f := FallbackAccount(*accAddr)
|
|
fallback = &f
|
|
}
|
|
}
|
|
neededAccounts[*accAddr] = struct{}{}
|
|
emptiedAccounts[*accAddr] = struct{}{}
|
|
|
|
if fallback != nil && isAll {
|
|
return nil, nil, nil, LogicError(c, errors.New("cannot take all balance of an unbounded source"))
|
|
}
|
|
|
|
case *parser.SrcMaxedContext:
|
|
accounts, _, subsourceFallback, compErr := p.VisitSource(c.SourceMaxed().GetSrc(), pushAsset, false)
|
|
if compErr != nil {
|
|
return nil, nil, nil, compErr
|
|
}
|
|
ty, _, compErr := p.VisitExpr(c.SourceMaxed().GetMax(), true)
|
|
if compErr != nil {
|
|
return nil, nil, nil, compErr
|
|
}
|
|
if ty != machine.TypeMonetary {
|
|
return nil, nil, nil, LogicError(c, errors.New("wrong type: expected monetary as max"))
|
|
}
|
|
for k, v := range accounts {
|
|
neededAccounts[k] = v
|
|
}
|
|
p.AppendInstruction(program.OP_TAKE_MAX)
|
|
err := p.Bump(1)
|
|
if err != nil {
|
|
return nil, nil, nil, LogicError(c, err)
|
|
}
|
|
p.AppendInstruction(program.OP_REPAY)
|
|
if subsourceFallback != nil {
|
|
p.PushAddress(machine.Address(*subsourceFallback))
|
|
err := p.Bump(2)
|
|
if err != nil {
|
|
return nil, nil, nil, LogicError(c, err)
|
|
}
|
|
p.AppendInstruction(program.OP_TAKE_ALL)
|
|
err = p.PushInteger(machine.NewNumber(2))
|
|
if err != nil {
|
|
return nil, nil, nil, LogicError(c, err)
|
|
}
|
|
p.AppendInstruction(program.OP_FUNDING_ASSEMBLE)
|
|
} else {
|
|
err := p.Bump(1)
|
|
if err != nil {
|
|
return nil, nil, nil, LogicError(c, err)
|
|
}
|
|
p.AppendInstruction(program.OP_DELETE)
|
|
}
|
|
case *parser.SrcInOrderContext:
|
|
sources := c.SourceInOrder().GetSources()
|
|
n := len(sources)
|
|
for i := 0; i < n; i++ {
|
|
accounts, emptied, subsourceFallback, compErr := p.VisitSource(sources[i], pushAsset, isAll)
|
|
if compErr != nil {
|
|
return nil, nil, nil, compErr
|
|
}
|
|
fallback = subsourceFallback
|
|
if subsourceFallback != nil && i != n-1 {
|
|
return nil, nil, nil, LogicError(c, errors.New("an unbounded subsource can only be in last position"))
|
|
}
|
|
for k, v := range accounts {
|
|
neededAccounts[k] = v
|
|
}
|
|
for k, v := range emptied {
|
|
if _, ok := emptiedAccounts[k]; ok {
|
|
return nil, nil, nil, LogicError(sources[i], fmt.Errorf("%v is already empty at this stage", p.resources[k]))
|
|
}
|
|
emptiedAccounts[k] = v
|
|
}
|
|
}
|
|
err := p.PushInteger(machine.NewNumber(int64(n)))
|
|
if err != nil {
|
|
return nil, nil, nil, LogicError(c, err)
|
|
}
|
|
p.AppendInstruction(program.OP_FUNDING_ASSEMBLE)
|
|
}
|
|
for address := range neededAccounts {
|
|
p.sources[address] = struct{}{}
|
|
}
|
|
return neededAccounts, emptiedAccounts, fallback, nil
|
|
}
|