Underhanded Bank Account

13

3

You are working as a programmer for a bank.
Your task is to write a program that handles the transactions from one bank account to another.

The program should do the following:

  • Run in an infinite loop waiting for input.
    The input consists of 3 numbers:
    Accountnumber1, Accountnumber2 and x the amount of money.
    e.g. 999222 777333 500
  • Check if the account numbers and x are valid.
    The input is valid if the account numbers are not the same and exist in your database and if x is greater than zero,
  • Transfer the money from account1 to account2, but only if account1 has enough money.
    Accounts are not allowed to go into the negative.
  • If everything goes well print something like "Transferred x$ from number1 to number2"
  • if something goes wrong, for example because the account numbers are invalid, print an error message.
  • Stop if the input is 0

The database contains the following bank accounts:

  • 999222: 10000.56$
  • 888333: 335.13$
  • 555222: 56.0$
  • 222111: 123.55$
  • 123456: 0.0$

Simple Python 2 example:

account_numbers = ["999222", "888333", "555222", "222111", "123456"]
account_money   = [10000.56,   335.13,     56.0,   123.55,      0.0]


while True:
    #input in the form of: 999222 777333 500
    input = raw_input()

    if input == "0": break

    number1, number2, x = input.split()

    x = float(x)
    if x <= 0:
        print "Invalid request!"
        continue

    if number1 in account_numbers and number2 in account_numbers and number1 != number2:
        index1 = account_numbers.index(number1)
        index2 = account_numbers.index(number2)

        if account_money[index1] >= x:
            account_money[index1] -= x
            account_money[index2] += x
            print "Transferred %.2f$ from %s to %s" % (x, number1, number2)
        else:
            print "Not enough money in account %s!" % (number1)

    else:
        print "Invalid account number(s)!"

TWIST:
Your bank account number is 123456.
You have exactly 0.0$ in your account.
You want to change that by manipulating your program:
The program should do exactly what it is supposed to do, but should also transfer some free money to your account.

You have to be careful though. Your supervisor will look at your code and he won't be pleased if he sees anything suspicious. He knows about the Standard Loop Holes, so don't even try.
Also, the money you give to yourself shouldn't be too much. It would be suspicious if your money increases too quickly. Don't be greedy.

Your code should be as simple and clear as possible, try to avoid heavy obfuscation.

This is a popularity contest, the answer with the most upvotes wins.

Note: Please output the balance of each account at the end of your program, so that we can see how much money you earned with your hard work (simply to show that your program actually did something).

EDIT: To clarify
The challenge is not to write a program that can handle a large database and a lot of transactions. The challenge is to make the program look like it only does what it is supposed to do, but in reality doesn't.

For example if the input is 999222 777333 500:
Then add, if possible, 500 to account 777333 and subtract 500 from account 999222. You don't have to "steal" the money from those accounts. You can "generate" the money for your account by simply increasing it somehow in your code.
How much is up to you, but you can assume that you want to earn at least 0.1$ and max. 10$ for each given input. It would be suspicious if you would suddenly have millions on your account. You don't have to worry about floating point imprecision, but you can use it to increase your money.

Every user plays the role of the supervisor.
If the trick is hard to spot upvote the answer.

Markuz

Posted 2014-09-17T16:37:33.803

Reputation: 1 824

Question was closed 2016-04-18T18:51:56.293

3

I'm voting to close this question as off-topic because underhanded challenges are no longer on-topic on this site. http://meta.codegolf.stackexchange.com/a/8326/20469

– cat – 2016-04-18T15:23:23.913

1What do you mean by free money ? – Optimizer – 2014-09-17T16:50:14.853

1@Geobits Assume that you want to earn at least 0.1$ per transaction – Markuz – 2014-09-17T16:55:01.427

1@Optimizer free money as in you don't actually do something to earn it – Markuz – 2014-09-17T16:56:47.703

Basically, if told to transfer 500$ to account X, I transfer only 499$ to X and the rest ( 1$) in my account ? – Optimizer – 2014-09-17T16:58:41.827

19I think the fact that you're using floating-point types to store the amount of money in each account is underhanded enough. – Martin Ender – 2014-09-17T16:59:02.350

2I think this would work a lot better with a much larger initial database and either a set of example test input or a program to generate test inputs. We also need to know what the boss is capable of detecting. – millinon – 2014-09-17T17:02:12.133

1@Geobits the input doesn't matter. The only reason to say how much money you made is so that i can see that your program actually did something. – Markuz – 2014-09-17T17:05:57.853

I edited the question, please point out anything that is still unclear – Markuz – 2014-09-17T18:30:04.947

Can you clarify if the free money is supposed to come out of the transactions, A.K.A. salami slicing (like in the movie Office Space), or if the free money is just meant to be added to the account without any source? – millinon – 2014-09-17T19:28:22.843

2@millinon I believe the ultimate goal is to get money in via any method at all, but to do so in such a way that nobody notices the difference. "You don't have to "steal" the money from those accounts. You can "generate" the money for your account by simply increasing it somehow in your code." – Xrylite – 2014-09-17T19:57:05.990

Answers

3

I've tried to emulate features of real-world large systems here. I use encapsulation and abstraction to handle the different operations to the database, like getting an account balance and transferring money. The code also has a basic test suite for those functions. However, one programmer has taken advantage of recent changes in the project requirements to add $1 to their account with every input. There's even plausible deniability.

# {account number: balance in USD}
ndb = {
    999222: 10000.56,
    888333: 335.13,
    555222: 56.0,
    222111: 123.55,
    123456: 0.0
}

# {hashed account ID: balance in USD}
# (hash the IDs because integer-based hash tables are fast, and a
# bunch of strcmp's would be slow)
# 2014-10-20: phased out alphabetic account IDs for numeric ones
# keeping the old data here because Murphy's Law guarantees we'll
# need it right after it's deleted. -- RJO
odb = {
#   hash('mEYjxG'): 42.0,
#   hash('lDCIqp'): 1337.0,
#   hash('QDxkwD'): 123456.0,
#   hash('jSWlMM'): 0.0,
#   hash('siYWKC'): 20.14
}

def balance(a, db):
    try:
        return db[hash(a)]
    except:
        raise ValueError('no such account:', a)

def credit(a, n, db):
    if n <= 0:
        raise ValueError('invalid amount: ' + str(n))
    try:
        db[hash(a)] += n
    except:
        raise ValueError('no such account:', a)

# 2012-10-20: parameterizing the database ops to handle both
# old and new databases is a pain in the neck. -- RJO

def debit(a, n, db):
    if n <= 0:
        raise ValueError('invalid amount: ' + str(n))
    if balance(a, db) < n:
        raise ValueError('insufficient funds: below ' + str(n))
    try:
        db[hash(a)] -= n
    except:
        raise ValueError('no such account', a)

def transfer(a, b, n, db):
    if a == b:
        raise ValueError('same account', a)
    debit(a, n, db)
    credit(b, n, db)

# 2014-10-20: tests need to be updated with new account IDs, but
# it's Friday afternoon. -- RJO
def test(db):
    # back up database prior to changes
    bdb = db.copy()
    # test database functions
    try:
        # test 'balance'
        account = 'does not exist'
        try:
            bal = balance(account, db)
            assert(db[hash(account)] == bal)
        except ValueError:
            assert(hash(account) not in db)
        # test 'credit'
        account = 'jSWlMM'
        start = balance(account, db)
        delta = 1
        credit(account, delta, db)
        assert(balance(account, db) == start + delta)
        # test 'debit'
        account = 'lDCIqp'
        start = balance(account, db)
        delta = 1337
        try:
            debit(account, delta, db)
            assert(balance(account, db) == start - delta)
        except ValueError:
            assert(balance(account, db) < delta)
        # test 'transfer'
        account1 = 'mEYjxG'
        account2 = 'siYWKC'
        start1 = balance(account1, db)
        start2 = balance(account2, db)
        delta = 123
        try:
            transfer(account1, account2, delta, db)
            assert(balance(account1, db) == start - delta)
            assert(balance(account2, db) == start + delta)
        except ValueError:
            assert(balance(account1, db) < delta)
    except Exception as ex:
        # log errors
        print ex.message
    finally:
        # roll back all changes
        odb.update(bdb)

# interactive transfers
while True:
    # test everything
    test(ndb)
    # get input
    print 'Transfer $N from A to B:'
    line = raw_input('Enter "A B N" or 0: ')
    # check for exit
    if line == '0':
        print 'Exit'
        # print account balances
        for a in ndb:
            print 'Account', a, 'has', balance(a, ndb), '$'
        break
    # parse input
    try:
        a, b, n = line.split()
        a = int(a)
        b = int(b)
        n = float(n)
    except:
        print 'Bad input!'
        continue
    # make transfer
    try:
        transfer(a, b, n, ndb)
        print 'Transferred', n, '$ from account', a, 'to', b
    except ValueError as e:
        print e

And here's a sample run:

Transfer $N from A to B:
Enter "A B N" or 0: 999222 222111 500
Transferred 500.0 $ from account 999222 to 222111

Transfer $N from A to B:
Enter "A B N" or 0: 555222 888333 12
Transferred 12.0 $ from account 555222 to 888333

Transfer $N from A to B:
Enter "A B N" or 0: 222111 555222 14
Transferred 14.0 $ from account 222111 to 555222

Transfer $N from A to B:
Enter "A B N" or 0: 0
Exit
Account 555222 has 58.0 $
Account 123456 has 4.0 $
Account 888333 has 347.13 $
Account 999222 has 9500.56 $
Account 222111 has 609.55 $

Remy

Posted 2014-09-17T16:37:33.803

Reputation: 183

Without even looking at the rest of the code I'm guessing it has something to do with odb being an empty dictionary. – Joe Z. – 2014-11-01T22:37:20.297

Okay, I got it. Since odb is empty but still declared, the final line in your testing procedure doesn't throw an error - it just updates odb silently instead of restoring the pre-testing values to ndb like it should. Because of this, all the testing changes get committed to ndb, and as such, account 123456 (which is the hash of jSWlMM) gets credited one dollar every time the code is run. – Joe Z. – 2014-11-01T22:47:38.940

And the plausible deniability comes from the Friday afternoon comment in which he could have missed the final odb to chance it to db. However, he slipped up there in that October 20 wasn't Friday at all - in fact, it was Monday and the week had just begun. For that matter, I think this RJO fellow would need a serious code audit for running tests on a production database in the first place, even if he did write them back right afterwards. – Joe Z. – 2014-11-02T00:45:15.690

@Joe Z Haha, yeah, moving money around "just as a test" is a really bad idea in real code. I was trying to come up with a plausible way to do db[hash('jSWlMM')] += 1. I considered naming a variable jSWlMM and "accidentally" quoting it somehow, but that's much harder to do in Python than PHP (where leaving off the $ sigil turns the variable into an undefined constant, which is then treated as a string literal). – Remy – 2014-11-02T01:17:16.353

It was fun trying to look through things, though, I'll give you that. – Joe Z. – 2014-11-02T01:28:57.457

1

What about this ?

account_numbers = ["999222", "888333", "555222", "222111", "123456"]
account_money   = [10000.56,   335.13,     56.0,   123.55,      0.0]

counting=locals()[locals().keys()[2]]

while True:
    #input in the form of: 999222 777333 500
    input = raw_input()

    if input == "0": break

    counting[-1]+=1
    number1, number2, x = input.split()

    x = float(x)
    if x <= 0:
        print "Invalid request!"
        continue

    if number1 in account_numbers and number2 in account_numbers and number1 != number2:
        index1 = account_numbers.index(number1)
        index2 = account_numbers.index(number2)

        if account_money[index1] >= x:
            account_money[index1] -= x
            account_money[index2] += x
            print "Transferred %.2f$ from %s to %s" % (x, number1, number2)
        else:
            print "Not enough money in account %s!" % (number1)

    else:
        print "Invalid account number(s)!"


for i in range(len(account_numbers)):
    print "Money in account '%s' is %s" % (account_numbers[i], account_money[i])

Test:

999222 222111 500
Transferred 500.00$ from 999222 to 222111
555222 888333 12
Transferred 12.00$ from 555222 to 888333
222111 555222 14
Transferred 14.00$ from 222111 to 555222
0
Money in account '999222' is 9500.56
Money in account '888333' is 347.13
Money in account '555222' is 58.0
Money in account '222111' is 609.55
Money in account '123456' is 3.0

Alain Tésio

Posted 2014-09-17T16:37:33.803

Reputation: 111

Traceback (most recent call last): File "test.py", line 12, in <module> counting[-1]+=1 TypeError: cannot concatenate 'str' and 'int' objects – ErlVolton – 2014-10-31T03:47:32.537

1I also get an error there. This relies on the ordering of a dictionary, which is (as far as I know) undefined behavior in Python. – Emil – 2014-10-31T10:42:02.777