Skip to content

Commit

Permalink
draft implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
ascandone committed Dec 6, 2024
1 parent 4463674 commit ea1c5b9
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 5 deletions.
1 change: 1 addition & 0 deletions internal/analysis/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const FnSetTxMeta = "set_tx_meta"
const FnSetAccountMeta = "set_account_meta"
const FnVarOriginMeta = "meta"
const FnVarOriginBalance = "balance"
const FnVarOriginOverdraft = "overdraft_amount" // <- TODO integrate with lsp

var Builtins = map[string]FnCallResolution{
FnSetTxMeta: StatementFnCallResolution{
Expand Down
66 changes: 61 additions & 5 deletions internal/interpreter/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ func (s *programState) handleOrigin(type_ string, fnCall parser.FnCall) (Value,
}
return *monetary, nil

case analysis.FnVarOriginOverdraft:
monetary, err := overdraft(s, fnCall.Range, args)
if err != nil {
return nil, err
}
return *monetary, nil

default:
return nil, UnboundFunctionErr{Name: fnCall.Caller.Name}
}
Expand Down Expand Up @@ -778,6 +785,22 @@ func meta(
return value, nil
}

// Utility function to get the balance
func getBalance(
s *programState,
account string,
asset string,
) (*big.Int, InterpreterError) {
s.batchQuery(account, asset)
fetchBalanceErr := s.runBalancesQuery()
if fetchBalanceErr != nil {
return nil, QueryBalanceError{WrappedError: fetchBalanceErr}
}
balance := s.getCachedBalance(account, asset)
return balance, nil

}

func balance(
s *programState,
r parser.Range,
Expand All @@ -793,13 +816,12 @@ func balance(
}

// body
s.batchQuery(*account, *asset)
fetchBalanceErr := s.runBalancesQuery()
if fetchBalanceErr != nil {
return nil, QueryBalanceError{WrappedError: fetchBalanceErr}

balance, err := getBalance(s, *account, *asset)
if err != nil {
return nil, err
}

balance := s.getCachedBalance(*account, *asset)
if balance.Cmp(big.NewInt(0)) == -1 {
return nil, NegativeBalanceError{
Account: *account,
Expand All @@ -816,6 +838,40 @@ func balance(
return &m, nil
}

func overdraft(
s *programState,
r parser.Range,
args []Value,
) (*Monetary, InterpreterError) {
// TODO more precise args range location
p := NewArgsParser(args)
account := parseArg(p, r, expectAccount)
asset := parseArg(p, r, expectAsset)
err := p.parse()
if err != nil {
return nil, err
}

balance_, err := getBalance(s, *account, *asset)
if err != nil {
return nil, err
}

balanceIsPositive := balance_.Cmp(big.NewInt(0)) == 1
if balanceIsPositive {
return &Monetary{
Amount: NewMonetaryInt(0),
Asset: Asset(*asset),
}, nil
}

overdraft := new(big.Int).Neg(balance_)
return &Monetary{
Amount: MonetaryInt(*overdraft),
Asset: Asset(*asset),
}, nil
}

func setTxMeta(st *programState, r parser.Range, args []Value) InterpreterError {
p := NewArgsParser(args)
key := parseArg(p, r, expectString)
Expand Down
112 changes: 112 additions & 0 deletions internal/interpreter/interpreter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3266,6 +3266,118 @@ func TestSaveFromAccount(t *testing.T) {
})
}

// new semantics
func TestOverdraftFunctionWhenNegative(t *testing.T) {
script := `
vars { monetary $amt = overdraft_amount(@acc, EUR/2) }
send $amt (
source = @world
destination = @dest
)
`

tc := NewTestCase()
tc.compile(t, script)

tc.setBalance("acc", "EUR/2", -100)
tc.expected = CaseResult{
Postings: []Posting{
{
Asset: "EUR/2",
Amount: big.NewInt(100),
Source: "world",
Destination: "dest",
},
},
Error: nil,
}
test(t, tc)
}

func TestOverdraftFunctionWhenZero(t *testing.T) {
script := `
vars { monetary $amt = overdraft_amount(@acc, EUR/2) }
send $amt (
source = @world
destination = @dest
)
`

tc := NewTestCase()
tc.compile(t, script)

tc.expected = CaseResult{
Postings: []Posting{
// zero posting is omitted
},
Error: nil,
}
test(t, tc)
}

func TestOverdraftFunctionWhenPositive(t *testing.T) {
script := `
vars { monetary $amt = overdraft_amount(@acc, EUR/2) }
send $amt (
source = @world
destination = @dest
)
`

tc := NewTestCase()
tc.compile(t, script)

tc.setBalance("acc", "EUR/2", 100)

tc.expected = CaseResult{
Postings: []Posting{
// zero posting is omitted
},
Error: nil,
}
test(t, tc)
}

func TestOverdraftFunctionUseCaseRemoveDebt(t *testing.T) {
script := `
vars { monetary $amt = overdraft_amount(@user:001, USD/2) }
// we have at most 1000 USD/2 to remove user:001's debt
send [USD/2 1000] (
source = @world
destination = {
// but we send at most what we need to cancel the debt
max $amt to @user:001
remaining kept
}
)
`

tc := NewTestCase()
tc.compile(t, script)

tc.setBalance("user:001", "USD/2", -100)

tc.expected = CaseResult{
Postings: []Posting{
machine.Posting{
Asset: "USD/2",
Amount: big.NewInt(100),
Source: "world",
Destination: "user:001",
},
},
Error: nil,
}
test(t, tc)
}

func TestAddMonetariesSameCurrency(t *testing.T) {
script := `
send [COIN 1] + [COIN 2] (
Expand Down

0 comments on commit ea1c5b9

Please sign in to comment.