13
\$\begingroup\$

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.

\$\endgroup\$

closed as off-topic by cat, Blue, a spaghetto, Alex A. Apr 18 '16 at 18:51

  • This question does not appear to be about programming puzzles or code golf within the scope defined in the help center.
If this question can be reworded to fit the rules in the help center, please edit the question.

  • 1
    \$\begingroup\$ What do you mean by free money ? \$\endgroup\$ – Optimizer Sep 17 '14 at 16:50
  • 19
    \$\begingroup\$ I think the fact that you're using floating-point types to store the amount of money in each account is underhanded enough. \$\endgroup\$ – Martin Ender Sep 17 '14 at 16:59
  • 2
    \$\begingroup\$ I 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. \$\endgroup\$ – millinon Sep 17 '14 at 17:02
  • 2
    \$\begingroup\$ @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." \$\endgroup\$ – Xrylite Sep 17 '14 at 19:57
  • 3
    \$\begingroup\$ I'm voting to close this question as off-topic because underhanded challenges are no longer on-topic on this site. meta.codegolf.stackexchange.com/a/8326/20469 \$\endgroup\$ – cat Apr 18 '16 at 15:23
3
\$\begingroup\$

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 $
\$\endgroup\$
  • \$\begingroup\$ Without even looking at the rest of the code I'm guessing it has something to do with odb being an empty dictionary. \$\endgroup\$ – Joe Z. Nov 1 '14 at 22:37
  • \$\begingroup\$ 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. \$\endgroup\$ – Joe Z. Nov 1 '14 at 22:47
  • \$\begingroup\$ 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. \$\endgroup\$ – Joe Z. Nov 2 '14 at 0:45
  • \$\begingroup\$ @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). \$\endgroup\$ – Remy Nov 2 '14 at 1:17
  • \$\begingroup\$ It was fun trying to look through things, though, I'll give you that. \$\endgroup\$ – Joe Z. Nov 2 '14 at 1:28
1
\$\begingroup\$

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
\$\endgroup\$
  • \$\begingroup\$ Traceback (most recent call last): File "test.py", line 12, in <module> counting[-1]+=1 TypeError: cannot concatenate 'str' and 'int' objects \$\endgroup\$ – ErlVolton Oct 31 '14 at 3:47
  • 1
    \$\begingroup\$ I also get an error there. This relies on the ordering of a dictionary, which is (as far as I know) undefined behavior in Python. \$\endgroup\$ – Emil Oct 31 '14 at 10:42

Not the answer you're looking for? Browse other questions tagged or ask your own question.