mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-20 11:05:54 +00:00
New integration tests:
* New tests for autobridging and freeze * Discrepancy detection tests * Don't let Mocha suppress load time errors
This commit is contained in:
committed by
Nik Bougalis
parent
0848e348bb
commit
e14c700c60
177
test/autobridge-test.coffee
Normal file
177
test/autobridge-test.coffee
Normal file
@@ -0,0 +1,177 @@
|
||||
################################### REQUIRES ###################################
|
||||
|
||||
extend = require 'extend'
|
||||
fs = require 'fs'
|
||||
assert = require 'assert'
|
||||
{
|
||||
Amount
|
||||
UInt160
|
||||
Transaction
|
||||
} = require 'ripple-lib'
|
||||
|
||||
|
||||
testutils = require './testutils'
|
||||
{
|
||||
LedgerState
|
||||
LedgerVerifier
|
||||
TestAccount
|
||||
} = require './ledger-state'
|
||||
{
|
||||
beast_configured
|
||||
is_focused_test
|
||||
pretty_json
|
||||
server_setup_teardown
|
||||
skip_or_only
|
||||
str_ends_with
|
||||
submit_for_final
|
||||
} = require './batmans-belt'
|
||||
|
||||
#################################### CONFIG ####################################
|
||||
|
||||
config = testutils.init_config()
|
||||
|
||||
#################################### HELPERS ###################################
|
||||
|
||||
make_offer = (remote, account, pays, gets, flag_or_flags) ->
|
||||
tx = remote.transaction()
|
||||
tx.offer_create(account, pays, gets)
|
||||
tx.set_flags(flag_or_flags) if flag_or_flags?
|
||||
tx
|
||||
|
||||
dump_rpc_script = (ledger_state, test_decl) ->
|
||||
lines = ledger_state.compile_to_rpc_commands()
|
||||
|
||||
# Realias etc ;)
|
||||
# TODO
|
||||
account = test_decl.offer[0]
|
||||
[pays, gets, flags] = test_decl.offer[1..]
|
||||
tx = new Transaction({secrets: {}})
|
||||
tx.offer_create(account, pays, gets)
|
||||
tx.set_flags(flags)
|
||||
|
||||
tx_json = tx.tx_json
|
||||
# Account: account
|
||||
# TransactionType: "OfferCreate"
|
||||
# TakerPays: pays
|
||||
# TakerGets: gets
|
||||
|
||||
lines += "\nbuild/rippled submit #{account} '#{JSON.stringify tx_json}'"
|
||||
lines += "\nbuild/rippled ledger_accept\n"
|
||||
fs.writeFileSync(__dirname + '/../manual-offer-test.sh', lines)
|
||||
|
||||
dump_aliased_ledger = (pre_or_post, ledger_state, done) ->
|
||||
# TODO: generify to post/pre
|
||||
ledger_state.remote.request_ledger 'validated', {full: true}, (e, m) ->
|
||||
ledger_dump = ledger_state.pretty_json m.ledger.accountState
|
||||
fn = __dirname + "/../manual-offer-test-#{pre_or_post}-ledger.json"
|
||||
fs.writeFileSync(fn, ledger_dump)
|
||||
done()
|
||||
|
||||
################################# TEST FACTORY #################################
|
||||
|
||||
make_offer_create_test = (get_context, test_name, test_decl) ->
|
||||
'''
|
||||
|
||||
@get_context {Function}
|
||||
|
||||
a getter function, which gets the current context with the ripple-lib remote
|
||||
etc attached
|
||||
|
||||
@test_name {String}
|
||||
|
||||
This function will create a `test` using @test_name based on @test_decl
|
||||
|
||||
@test_decl {Object}
|
||||
|
||||
@pre_ledger
|
||||
@post_ledger
|
||||
@offer
|
||||
|
||||
'''
|
||||
test_func = skip_or_only test_name, test
|
||||
focused_test = is_focused_test test_name
|
||||
|
||||
test_func test_name, (done) ->
|
||||
context = get_context()
|
||||
|
||||
remote = context.remote
|
||||
ledger_state = context.ledger
|
||||
tx = make_offer(remote, test_decl.offer...)
|
||||
|
||||
submit_for_final tx, (m) ->
|
||||
'assert transaction was successful'
|
||||
assert.equal m.metadata.TransactionResult, 'tesSUCCESS'
|
||||
|
||||
context.ledger.verifier(test_decl.post_ledger).do_verify (errors) ->
|
||||
this_done = ->
|
||||
assert Object.keys(errors).length == 0,
|
||||
"post_ledger errors:\n"+ pretty_json errors
|
||||
done()
|
||||
|
||||
if focused_test
|
||||
dump_aliased_ledger('post', ledger_state, this_done)
|
||||
else
|
||||
this_done()
|
||||
test_func
|
||||
|
||||
ledger_state_setup = (get_context, decls) ->
|
||||
setup (done) ->
|
||||
[test_name, test_decl] = decls.shift()
|
||||
|
||||
context = get_context()
|
||||
focused_test = is_focused_test test_name
|
||||
|
||||
context.ledger =
|
||||
new LedgerState(test_decl.pre_ledger, assert, context.remote, config)
|
||||
|
||||
if focused_test
|
||||
dump_rpc_script(context.ledger, test_decl)
|
||||
|
||||
context.ledger.setup(
|
||||
# console.log
|
||||
->, # noop logging function
|
||||
->
|
||||
context.ledger.verifier().do_verify (errors) ->
|
||||
assert Object.keys(errors).length == 0,
|
||||
"pre_ledger errors:\n"+ pretty_json errors
|
||||
|
||||
if focused_test
|
||||
dump_aliased_ledger('pre', context.ledger, done)
|
||||
else
|
||||
done()
|
||||
)
|
||||
|
||||
############################### TEST DECLARATIONS ##############################
|
||||
|
||||
try
|
||||
offer_create_tests = require("./offer-tests-json")
|
||||
# console.log offer_create_tests
|
||||
# offer_create_tests = JSON.parse offer_tests_string
|
||||
extend offer_create_tests, {}
|
||||
catch e
|
||||
console.log e
|
||||
|
||||
if beast_configured('RIPPLE_ENABLE_AUTOBRIDGING', '1')
|
||||
suite_func = suite
|
||||
else
|
||||
suite_func = suite.skip
|
||||
|
||||
suite_func 'Offer Create Tests', ->
|
||||
try
|
||||
get_context = server_setup_teardown()
|
||||
# tests = ([k,v] for k,v of offer_create_tests)
|
||||
|
||||
tests = []
|
||||
only = false
|
||||
for k,v of offer_create_tests
|
||||
f = make_offer_create_test(get_context, k, v)
|
||||
if not only and f == test.only
|
||||
only = [[k, v]]
|
||||
if not str_ends_with k, '_skip'
|
||||
tests.push [k,v]
|
||||
|
||||
# f = make_offer_create_test(get_context, k, v) for [k,v] in tests
|
||||
ledger_state_setup(get_context, if only then only else tests)
|
||||
catch e
|
||||
console.log e
|
||||
|
||||
106
test/batmans-belt.coffee
Normal file
106
test/batmans-belt.coffee
Normal file
@@ -0,0 +1,106 @@
|
||||
################################### REQUIRES ###################################
|
||||
|
||||
fs = require 'fs'
|
||||
testutils = require './testutils'
|
||||
|
||||
################################### REQUIRES ###################################
|
||||
|
||||
exports.pretty_json = (v) -> JSON.stringify(v, undefined, 2)
|
||||
|
||||
exports.beast_configured = beast_configured = (k, v) ->
|
||||
'''
|
||||
|
||||
A very naive regex search in $repo_root/src/BeastConfig.h
|
||||
|
||||
@k the name of the macro
|
||||
@v the value as a string expected
|
||||
|
||||
@return {Boolean} k's configured value, trimmed, is v
|
||||
|
||||
'''
|
||||
beast_configured.buf ?= fs.readFileSync("#{__dirname}/../src/BeastConfig.h")
|
||||
pattern = "^#define\\s+#{k}\\s+(.*?)$"
|
||||
res = (new RegExp(pattern, 'm').exec(beast_configured.buf))
|
||||
return false if res == null
|
||||
actual = res[1].trim()
|
||||
return v == actual
|
||||
|
||||
exports.server_setup_teardown = (options) ->
|
||||
{setup_func, teardown_func, post_setup, server_opts} = options ? {}
|
||||
|
||||
context = null
|
||||
setup_func ?= setup
|
||||
teardown_func ?= teardown
|
||||
|
||||
setup_func (done) ->
|
||||
context = @
|
||||
testutils.build_setup(server_opts).call @, ->
|
||||
if post_setup?
|
||||
post_setup(context, done)
|
||||
else
|
||||
done()
|
||||
|
||||
teardown_func (done) ->
|
||||
testutils.build_teardown().call context, done
|
||||
|
||||
# We turn a function to access the `context`, if we returned it now, it
|
||||
# would be null (DUH ;)
|
||||
-> context
|
||||
|
||||
exports.str_ends_with = ends_with = (s, suffix) ->
|
||||
~s.indexOf(suffix, s.length - suffix.length)
|
||||
|
||||
exports.skip_or_only = (title, test_or_suite) ->
|
||||
if ends_with title, '_only'
|
||||
test_or_suite.only
|
||||
else if ends_with title, '_skip'
|
||||
test_or_suite.skip
|
||||
else
|
||||
test_or_suite
|
||||
|
||||
exports.is_focused_test = (test_name) -> ends_with test_name, '_only'
|
||||
|
||||
class BailError extends Error
|
||||
constructor: (@message) ->
|
||||
@message ?= "Failed test due to relying on prior failed tests"
|
||||
|
||||
exports.suite_test_bailer = () ->
|
||||
bailed = false
|
||||
bail = (e) -> bailed = true
|
||||
|
||||
suiteSetup 'suite_test_bailer', ->
|
||||
process.on 'uncaughtException', bail
|
||||
|
||||
suiteTeardown 'suite_test_bailer', ->
|
||||
process.removeListener 'uncaughtException', bail
|
||||
|
||||
wrapper = (test_func) ->
|
||||
test = (title, fn) ->
|
||||
wrapped = (done) ->
|
||||
if not bailed
|
||||
fn(done)
|
||||
else
|
||||
# We could do this, but it's just noisy
|
||||
if process.env.BAIL_PASSES
|
||||
done()
|
||||
else
|
||||
done(new BailError)
|
||||
test_func title, wrapped
|
||||
|
||||
test.only = test_func.only
|
||||
test.skip = test_func.skip
|
||||
|
||||
return test
|
||||
|
||||
wrapper(global.test)
|
||||
|
||||
exports.submit_for_final = (tx, done) ->
|
||||
'''
|
||||
|
||||
This helper submits a transaction, and once it's proposed sends a ledger
|
||||
accept so the transaction will finalize.
|
||||
|
||||
'''
|
||||
tx.on 'proposed', -> tx.remote.ledger_accept()
|
||||
tx.on 'final', (m) -> done(m)
|
||||
tx.submit()
|
||||
@@ -32,6 +32,8 @@ exports.servers = {
|
||||
'rpc_ip' : "0.0.0.0",
|
||||
'rpc_port' : 5005,
|
||||
'local_sequence' : true,
|
||||
'trace' : false,
|
||||
// 'trace' : true,
|
||||
'local_fee' : true,
|
||||
// 'validation_seed' : "shhDFVsmS2GSu5vUyZSPXYfj1r79h",
|
||||
// 'validators' : "n9L8LZZCwsdXzKUN9zoVxs4YznYXZ9hEhsQZY7aVpxtFaSceiyDZ beta",
|
||||
|
||||
@@ -68,7 +68,7 @@ The tests are written in a declarative style:
|
||||
$ signifies an order book rather than account
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
Tests can be written in the 'path-tests.json' file in same directory # <--
|
||||
Tests can be written in the 'path-tests-json.js' file in same directory # <--
|
||||
------------------------------------------------------------------------------
|
||||
"""
|
||||
#################################### HELPERS ###################################
|
||||
@@ -455,8 +455,10 @@ define_suites = (path_finding_cases) ->
|
||||
A0 = (new TestAccount('A0')).address
|
||||
assert A0 == 'rBmhuVAvi372AerwzwERGjhLjqkMmAwxX'
|
||||
|
||||
path_finding_cases_string = fs.readFileSync(__dirname + "/path-tests.json")
|
||||
path_finding_cases = JSON.parse path_finding_cases_string
|
||||
try
|
||||
path_finding_cases = require('./path-tests-json')
|
||||
catch e
|
||||
console.log e
|
||||
|
||||
# You need two gateways, same currency. A market maker. A source that trusts one
|
||||
# gateway and holds its currency, and a destination that trusts the other.
|
||||
135
test/discrepancy-test.coffee
Normal file
135
test/discrepancy-test.coffee
Normal file
@@ -0,0 +1,135 @@
|
||||
################################### REQUIRES ###################################
|
||||
|
||||
assert = require 'assert'
|
||||
async = require 'async'
|
||||
|
||||
{
|
||||
Amount
|
||||
Meta
|
||||
} = require 'ripple-lib'
|
||||
|
||||
testutils = require './testutils'
|
||||
|
||||
{
|
||||
pretty_json
|
||||
server_setup_teardown
|
||||
} = require './batmans-belt'
|
||||
|
||||
#################################### CONFIG ####################################
|
||||
|
||||
config = testutils.init_config()
|
||||
|
||||
#################################### HELPERS ###################################
|
||||
|
||||
tx_blob_submit_factory = (remote, txn) ->
|
||||
(next) ->
|
||||
req = remote.request_submit()
|
||||
req.message.tx_blob = txn
|
||||
req.once 'error', -> assert false, "unexpected error submitting txns"
|
||||
req.once 'success', (m) ->
|
||||
remote.once 'ledger_closed', -> next()
|
||||
remote.ledger_accept()
|
||||
req.request()
|
||||
|
||||
offer_amounts = (fields) ->
|
||||
[Amount.from_json(fields.TakerPays),
|
||||
Amount.from_json(fields.TakerGets)]
|
||||
|
||||
executed_offers = (meta) ->
|
||||
offers = {}
|
||||
|
||||
meta.nodes.forEach (n, i) ->
|
||||
if n.entryType == 'Offer'
|
||||
[prev_pays, prev_gets] = offer_amounts(n.fieldsPrev)
|
||||
[final_pays, final_gets] = offer_amounts(n.fields)
|
||||
|
||||
summary=
|
||||
pays_executed: prev_pays.subtract(final_pays).to_text_full()
|
||||
gets_executed: prev_gets.subtract(final_gets).to_text_full()
|
||||
pays_final: final_pays.to_text_full()
|
||||
gets_final: final_gets.to_text_full()
|
||||
owner: n.fields.Account
|
||||
|
||||
offers[n.ledgerIndex] = summary
|
||||
offers
|
||||
|
||||
build_tx_blob_submission_series = (remote, txns_to_submit) ->
|
||||
series = []
|
||||
while (txn = txns_to_submit.shift())
|
||||
series.push tx_blob_submit_factory remote, txn
|
||||
series
|
||||
|
||||
compute_xrp_discrepancy = (fee, meta) ->
|
||||
before = Amount.from_json(0)
|
||||
after = Amount.from_json(fee)
|
||||
|
||||
meta.nodes.forEach (n, i) ->
|
||||
if n.entryType == 'AccountRoot'
|
||||
prev = n.fieldsPrev?.Balance || n.fields.Balance
|
||||
final = n.fieldsFinal?.Balance || n.fields.Balance
|
||||
|
||||
before = before.add(Amount.from_json(prev))
|
||||
after = after.add(Amount.from_json(final))
|
||||
|
||||
before.subtract(after)
|
||||
|
||||
suite 'Discrepancy test', ->
|
||||
suite 'XRP Discrepancy', ->
|
||||
get_context = server_setup_teardown({server_opts: {ledger_file: 'ledger-6226713.json'}})
|
||||
test '01030E435C2EEBE2689FED7494BC159A4C9B98D0DF0B23F7DFCE223822237E8C', (done) ->
|
||||
{remote} = get_context()
|
||||
txns_to_submit = [
|
||||
# This is the nasty one ;)
|
||||
'1200002200020000240000124E61D5438D7EA4C680000000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD468400000000000000A69D4D3E7809B4814C8000000000000000000000000434E59000000000041C8BE2C0A6AA17471B9F6D0AF92AAB1C94D5A25732103FC5F96EA61889691EC7A56FB2B859B600DE68C0255BF580D5C22D02EB97AFCE474473045022100D14B60BC6E01E5C19471F87EB00A4BFCA16D039BB91AEE12DA1142E8C4CAE7C2022020E2809CF24DE2BC0C3DCF1A07C469DB415F880485B2B323E5B5AA1D9F6F22D48114AFD96601692A6C6416DBA294F0DA684675A824B28314AFD96601692A6C6416DBA294F0DA684675A824B20112300000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD401E5C92828261DBAAC933B6309C6F5C72AF020AFD4FF100000000000000000000000000000000000000000300000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD401E5C92828261DBAAC933B6309C6F5C72AF020AFD4FF01A034782E2DBAC4FB82B601CD50421E8EF24F3A00100000000000000000000000000000000000000000300000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD401E5C92828261DBAAC933B6309C6F5C72AF020AFD400'
|
||||
]
|
||||
series = build_tx_blob_submission_series remote, txns_to_submit
|
||||
async.series series, ->
|
||||
hash = '01030E435C2EEBE2689FED7494BC159A4C9B98D0DF0B23F7DFCE223822237E8C'
|
||||
remote.request_tx hash, (e, m) ->
|
||||
meta = new Meta(m.meta)
|
||||
zero = Amount.from_json(0)
|
||||
discrepancy = compute_xrp_discrepancy(m.Fee, meta)
|
||||
assert discrepancy.equals(zero), discrepancy.to_text_full()
|
||||
done()
|
||||
|
||||
suite 'RIPD 304', ->
|
||||
get_context = server_setup_teardown({server_opts: {ledger_file: 'ledger-7145315.json'}})
|
||||
test 'B1A305038D43BCDF3EA1D096E6A0ACC5FB0ECAE0C8F5D3A54AD76A2AA1E20EC4', (done) ->
|
||||
{remote} = get_context()
|
||||
|
||||
txns_to_submit = [
|
||||
'120008228000000024000030FD2019000030F0201B006D076B68400000000000000F732103325EB29A014DDE22289D0EA989861D481D54D54C727578AB6C2F18BC342D3829744630440220668D06C44144C284E0346EE7785EB41B72EDBB244FE6EE02F317011A07023C63022067A52367AC01941A3FE19477D7F588C862704A44A8A771BCAD6B7A9119B71E9E8114A7C1C74DADB3693C199888A901FC2B7FD0884EE1'
|
||||
'1200002200020000240000163161D551C37937E080000000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD468400000000000000A69D4D0BEC6A319514D00000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D173210342D49ADE3DBC5E8E25F02B4FBB169448157B016BA203A268C3E8CCC9DF1AE39F74463044022069A2B0F79A042CC65C7CCFDF610DEAD8FDA12F53E43061F9F75FAD5B398E657A02200A4A45BB4F31E922A52F843D5CE96D83446992A13393871C31FCD8A52AE4329F81148C4BE4DBAA81F7BC66720E5874EBD2D90C9563EA83148C4BE4DBAA81F7BC66720E5874EBD2D90C9563EA0112100000000000000000000000000000000000000000300000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD401E5C92828261DBAAC933B6309C6F5C72AF020AFD4FF300000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD401E5C92828261DBAAC933B6309C6F5C72AF020AFD4FF01585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C101DD39C650A96EDA48334E70CC4A85B8B2E8502CD3300000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD401E5C92828261DBAAC933B6309C6F5C72AF020AFD400'
|
||||
'1200082200000000240019B1A520190019B1A46840000000000000327321025718736160FA6632F48EA4354A35AB0340F8D7DC7083799B9C57C3E937D7185174463044022048B3669EDCA795A1897DA3C7328C8526940708DBB3FFAD88CA5DC22D0398A67502206B37796A743105DE05EE1A11BE017404B4F41FA17E6449E390C36F69D8907C078114AFFDCC86D33C153DA185156EB32608ACCF0BC713'
|
||||
'1200072200000000240019B1A664D550AF2D90A009D80000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD46540000002540BE4006840000000000000327321025718736160FA6632F48EA4354A35AB0340F8D7DC7083799B9C57C3E937D71851744630440220509F09B609573BC8ADDD55449DBD5201A40F6C1C3AA2D5D984ACB54E0F651F2E022019E6AF2937A5E76D8C9A2B5B0C4704D6BE637AAC17F2EE135DA449B0892B728B8114AFFDCC86D33C153DA185156EB32608ACCF0BC713'
|
||||
# This is the nasty one ;)
|
||||
'1200002200020000240000163261D551C37937E080000000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD468400000000000000A69D4D0BEC6A319514D00000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D173210342D49ADE3DBC5E8E25F02B4FBB169448157B016BA203A268C3E8CCC9DF1AE39F74473045022100C79C86BD18BBBC0343F0EB268A7770FDAEC30748ECCB9A6483E2B11488749DC00220544A5FF2D085FA5DD2A003AA9C3F031B8FE3FD4A443B659B9EE84E165795BC0581148C4BE4DBAA81F7BC66720E5874EBD2D90C9563EA83148C4BE4DBAA81F7BC66720E5874EBD2D90C9563EA0112100000000000000000000000000000000000000000300000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD401E5C92828261DBAAC933B6309C6F5C72AF020AFD4FF300000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD401E5C92828261DBAAC933B6309C6F5C72AF020AFD4FF01585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C101DD39C650A96EDA48334E70CC4A85B8B2E8502CD3300000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD401E5C92828261DBAAC933B6309C6F5C72AF020AFD400'
|
||||
]
|
||||
|
||||
series = build_tx_blob_submission_series remote, txns_to_submit
|
||||
|
||||
async.series series, ->
|
||||
hash = 'B1A305038D43BCDF3EA1D096E6A0ACC5FB0ECAE0C8F5D3A54AD76A2AA1E20EC4'
|
||||
remote.request_tx hash, (e, m) ->
|
||||
meta = new Meta(m.meta)
|
||||
|
||||
expected = {
|
||||
"003313896DA56CFA0996B36AF066589EF0E689230E67DA01D13320289C834A93": {
|
||||
"pays_executed": "955.967853/XRP",
|
||||
"gets_executed": "445.6722130686/JPY/rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"pays_final": "245,418.978522/XRP",
|
||||
"gets_final": "114414.3277869564/JPY/rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"owner": "rQLEvSnbgpcUXkTU8VvSzUPX4scukCjuzb"
|
||||
|
||||
},
|
||||
"9847793D6B936812907ED58455FBA4195205ABCACBE28DF9584C3A195A221E59": {
|
||||
"pays_executed": "4.19284145965/USD/rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"gets_executed": "955.967853/XRP",
|
||||
"pays_final": "13630.84998220238/USD/rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"gets_final": "3,107,833.795934/XRP",
|
||||
"owner": "rEhKZcz5Ndjm9BzZmmKrtvhXPnSWByssDv"
|
||||
}
|
||||
}
|
||||
## rhsxr2aAddyCKx5iZctebT4Padxv6iWDxb
|
||||
assert.deepEqual executed_offers(meta), expected
|
||||
done()
|
||||
3866
test/fixtures/ledger-6226713.json
vendored
Normal file
3866
test/fixtures/ledger-6226713.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
644
test/fixtures/ledger-7145315.json
vendored
Normal file
644
test/fixtures/ledger-7145315.json
vendored
Normal file
@@ -0,0 +1,644 @@
|
||||
{
|
||||
"accepted": true,
|
||||
"accountState": [
|
||||
{
|
||||
"Account": "rQLEvSnbgpcUXkTU8VvSzUPX4scukCjuzb",
|
||||
"BookDirectory": "92466F5377C34C5EA957034339321E217A23FA4E27A31D475B079EDE3AE532A1",
|
||||
"BookNode": "0000000000000000",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "Offer",
|
||||
"OwnerNode": "0000000000000000",
|
||||
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
|
||||
"PreviousTxnLgrSeq": 7145315,
|
||||
"Sequence": 710,
|
||||
"TakerGets": {
|
||||
"currency": "JPY",
|
||||
"issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"value": "119860.00000002"
|
||||
},
|
||||
"TakerPays": "257099957100",
|
||||
"index": "003313896DA56CFA0996B36AF066589EF0E689230E67DA01D13320289C834A93"
|
||||
},
|
||||
{
|
||||
"Account": "rEhKZcz5Ndjm9BzZmmKrtvhXPnSWByssDv",
|
||||
"Balance": "93495113081301",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"OwnerCount": 7,
|
||||
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
|
||||
"PreviousTxnLgrSeq": 7145315,
|
||||
"Sequence": 422,
|
||||
"index": "0522B5ABDFEBEF28C47AC64AEBCB6B5ABE0A00388141E32E177052B9742D47CF"
|
||||
},
|
||||
{
|
||||
"Flags": 0,
|
||||
"Indexes": [
|
||||
"89B369C951B15067BD52EF3F5FEC540DE773C4B6718FE1342222C1C935ED42DF",
|
||||
"9847793D6B936812907ED58455FBA4195205ABCACBE28DF9584C3A195A221E59"
|
||||
],
|
||||
"LedgerEntryType": "DirectoryNode",
|
||||
"Owner": "rEhKZcz5Ndjm9BzZmmKrtvhXPnSWByssDv",
|
||||
"RootIndex": "07E25CB74E530EF7B1D6718EE48BAC672B31F7E1908EC77D5C0DA82633A7919C",
|
||||
"index": "07E25CB74E530EF7B1D6718EE48BAC672B31F7E1908EC77D5C0DA82633A7919C"
|
||||
},
|
||||
{
|
||||
"Flags": 0,
|
||||
"Indexes": [
|
||||
"707CEC6ABA7BDA95DF712DABD6BC3BB1DD69CEED25977AB643EA94A471DD842C",
|
||||
"003313896DA56CFA0996B36AF066589EF0E689230E67DA01D13320289C834A93"
|
||||
],
|
||||
"LedgerEntryType": "DirectoryNode",
|
||||
"Owner": "rQLEvSnbgpcUXkTU8VvSzUPX4scukCjuzb",
|
||||
"RootIndex": "19477D184C2E34D30057C81581E4F04DDE2B64542D2CE7F1145B3D4A207E9301",
|
||||
"index": "19477D184C2E34D30057C81581E4F04DDE2B64542D2CE7F1145B3D4A207E9301"
|
||||
},
|
||||
{
|
||||
"Account": "rHsZHqa5oMQNL5hFm4kfLd47aEMYjPstpg",
|
||||
"BookDirectory": "BCF012C63E83DAF510C7B6B27FE1045CF913B0CF94049AB04E10B058B79947A8",
|
||||
"BookNode": "0000000000000000",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "Offer",
|
||||
"OwnerNode": "0000000000000000",
|
||||
"PreviousTxnID": "E5EC82E300777C4C0AE24499E2DA175AE00065BE8AC7C91518C37876377EC473",
|
||||
"PreviousTxnLgrSeq": 7145315,
|
||||
"Sequence": 1683876,
|
||||
"TakerGets": "10000000000",
|
||||
"TakerPays": {
|
||||
"currency": "JPY",
|
||||
"issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"value": "4697.494711257"
|
||||
},
|
||||
"index": "19D60E66D4CAB32F0823C5EAA8A58284AD48BAB63CACCBECB76166A990EECECF"
|
||||
},
|
||||
{
|
||||
"Balance": {
|
||||
"currency": "USD",
|
||||
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
|
||||
"value": "-0.0001000001918501896"
|
||||
},
|
||||
"Flags": 131072,
|
||||
"HighLimit": {
|
||||
"currency": "USD",
|
||||
"issuer": "rhS6Pb8oBMKshN6EznMeWCHJNHJuoom63r",
|
||||
"value": "0"
|
||||
},
|
||||
"HighNode": "0000000000000000",
|
||||
"LedgerEntryType": "RippleState",
|
||||
"LowLimit": {
|
||||
"currency": "USD",
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"value": "0"
|
||||
},
|
||||
"LowNode": "0000000000000000",
|
||||
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
|
||||
"PreviousTxnLgrSeq": 7145315,
|
||||
"index": "19DA8116FACDDB48DBFAEEB7036394883D42C67CA23349E3582AC63A61C0C08F"
|
||||
},
|
||||
{
|
||||
"ExchangeRate": "52221700304BE774",
|
||||
"Flags": 0,
|
||||
"Indexes": ["E986149D0F53C5F05FCF13751CA959F9BA31FABC0766AE2A4E906C02F10A5F28"],
|
||||
"LedgerEntryType": "DirectoryNode",
|
||||
"RootIndex": "1ACB79E7B8B4C59269CEAC5CA907D5E8C3BF3B294A33D3D752221700304BE774",
|
||||
"TakerGetsCurrency": "0000000000000000000000004A50590000000000",
|
||||
"TakerGetsIssuer": "E5C92828261DBAAC933B6309C6F5C72AF020AFD4",
|
||||
"TakerPaysCurrency": "0000000000000000000000005553440000000000",
|
||||
"TakerPaysIssuer": "0A20B3C85F482532A9578DBB3950B85CA06594D1",
|
||||
"index": "1ACB79E7B8B4C59269CEAC5CA907D5E8C3BF3B294A33D3D752221700304BE774"
|
||||
},
|
||||
{
|
||||
"Balance": {
|
||||
"currency": "JPY",
|
||||
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
|
||||
"value": "28771.88711726011"
|
||||
},
|
||||
"Flags": 1114112,
|
||||
"HighLimit": {
|
||||
"currency": "JPY",
|
||||
"issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"value": "0"
|
||||
},
|
||||
"HighNode": "0000000000000000",
|
||||
"LedgerEntryType": "RippleState",
|
||||
"LowLimit": {
|
||||
"currency": "JPY",
|
||||
"issuer": "rhS6Pb8oBMKshN6EznMeWCHJNHJuoom63r",
|
||||
"value": "10000000"
|
||||
},
|
||||
"LowNode": "0000000000000000",
|
||||
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
|
||||
"PreviousTxnLgrSeq": 7145315,
|
||||
"index": "47E2DF7CEC8979AFEB877402F1A8A4A514CE6F0159F5BDCA3538BD4A244EE81E"
|
||||
},
|
||||
{
|
||||
"Flags": 0,
|
||||
"Indexes": [
|
||||
"8782F28AC73A79162357EB1FB38E0AA5F55C066F0F2ACC774BBF095B21E07E64",
|
||||
"E232591F55AA7B82F584A5DBE414CA67C15869B0936C875095B5D08810A99EA5",
|
||||
"7D1F4F82D74930ED8F0CD4D75CD9B41E0A1ED7CAA6508AEF6C13BE57FCE01220",
|
||||
"B8F0B4940547D30B9706F32D3EB3A8EC60FD5F09499A157BB05549514DB335BC"
|
||||
],
|
||||
"LedgerEntryType": "DirectoryNode",
|
||||
"Owner": "rhsxr2aAddyCKx5iZctebT4Padxv6iWDxb",
|
||||
"RootIndex": "49DA34D0CCDB7AF9A1B5751ECDC647D6379033B0126D645CD16395E302239BAE",
|
||||
"index": "49DA34D0CCDB7AF9A1B5751ECDC647D6379033B0126D645CD16395E302239BAE"
|
||||
},
|
||||
{
|
||||
"Balance": {
|
||||
"currency": "JPY",
|
||||
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
|
||||
"value": "692595.1141695322"
|
||||
},
|
||||
"Flags": 1114112,
|
||||
"HighLimit": {
|
||||
"currency": "JPY",
|
||||
"issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"value": "0"
|
||||
},
|
||||
"HighNode": "0000000000000000",
|
||||
"LedgerEntryType": "RippleState",
|
||||
"LowLimit": {
|
||||
"currency": "JPY",
|
||||
"issuer": "rD8FXhcHtG8m1iHTJogoahPx3LJRjT7tpa",
|
||||
"value": "10000000"
|
||||
},
|
||||
"LowNode": "0000000000000000",
|
||||
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
|
||||
"PreviousTxnLgrSeq": 7145315,
|
||||
"index": "4D8AD84A38E3B4EE756E7B64CA539DF51FDEF036D96E8427FB461266B55CE97E"
|
||||
},
|
||||
{
|
||||
"Account": "rD8FXhcHtG8m1iHTJogoahPx3LJRjT7tpa",
|
||||
"Balance": "4049943048",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"OwnerCount": 5,
|
||||
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
|
||||
"PreviousTxnLgrSeq": 7145315,
|
||||
"Sequence": 5681,
|
||||
"index": "582F150193E8572D608ACED9E978EF85139834CDB24882550F74560D49F15040"
|
||||
},
|
||||
{
|
||||
"Flags": 0,
|
||||
"Indexes": ["E232591F55AA7B82F584A5DBE414CA67C15869B0936C875095B5D08810A99EA5"],
|
||||
"LedgerEntryType": "DirectoryNode",
|
||||
"Owner": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"RootIndex": "6319526CE8F9A8A44D7A870A89DC1B4AD848AA4F066FCB5390A9A268F6E16AEA",
|
||||
"index": "6319526CE8F9A8A44D7A870A89DC1B4AD848AA4F066FCB5390A9A268F6E16AEA"
|
||||
},
|
||||
{
|
||||
"Account": "rQLEvSnbgpcUXkTU8VvSzUPX4scukCjuzb",
|
||||
"Balance": "1244946044233",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"OwnerCount": 10,
|
||||
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
|
||||
"PreviousTxnLgrSeq": 7145315,
|
||||
"Sequence": 711,
|
||||
"index": "6A240B2366203BFB4CA5A14D78E43DA9EF5E6F1AF66B186842FF80876AEA9055"
|
||||
},
|
||||
{
|
||||
"Balance": {
|
||||
"currency": "JPY",
|
||||
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
|
||||
"value": "0"
|
||||
},
|
||||
"Flags": 1114112,
|
||||
"HighLimit": {
|
||||
"currency": "JPY",
|
||||
"issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"value": "0"
|
||||
},
|
||||
"HighNode": "0000000000000000",
|
||||
"LedgerEntryType": "RippleState",
|
||||
"LowLimit": {
|
||||
"currency": "JPY",
|
||||
"issuer": "rHsZHqa5oMQNL5hFm4kfLd47aEMYjPstpg",
|
||||
"value": "1"
|
||||
},
|
||||
"LowNode": "0000000000000000",
|
||||
"PreviousTxnID": "7A2C8EADCC58C5E7927FBD9C98C010292757282994E751BE1D162C65E6219658",
|
||||
"PreviousTxnLgrSeq": 7145307,
|
||||
"index": "6CB0EB288A835747D5FAE5FD036BAE11DB4A4787DE283199BF60C5B69291F418"
|
||||
},
|
||||
{
|
||||
"Balance": {
|
||||
"currency": "JPY",
|
||||
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
|
||||
"value": "-160129.7932997147"
|
||||
},
|
||||
"Flags": 131072,
|
||||
"HighLimit": {
|
||||
"currency": "JPY",
|
||||
"issuer": "rQLEvSnbgpcUXkTU8VvSzUPX4scukCjuzb",
|
||||
"value": "100000000"
|
||||
},
|
||||
"HighNode": "0000000000000000",
|
||||
"LedgerEntryType": "RippleState",
|
||||
"LowLimit": {
|
||||
"currency": "JPY",
|
||||
"issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"value": "0"
|
||||
},
|
||||
"LowNode": "0000000000000000",
|
||||
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
|
||||
"PreviousTxnLgrSeq": 7145315,
|
||||
"index": "707CEC6ABA7BDA95DF712DABD6BC3BB1DD69CEED25977AB643EA94A471DD842C"
|
||||
},
|
||||
{
|
||||
"Account": "rhsxr2aAddyCKx5iZctebT4Padxv6iWDxb",
|
||||
"BookDirectory": "FF304540EB391AD26231FC5FC98ECF6E85A41DE173C1FCE052227E0BAF9B1166",
|
||||
"BookNode": "0000000000000000",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "Offer",
|
||||
"OwnerNode": "0000000000000000",
|
||||
"PreviousTxnID": "4C1104ADBE498DED6F146B82C57E5DFC062C265354E86C0DF7155515F636DCF9",
|
||||
"PreviousTxnLgrSeq": 7141869,
|
||||
"Sequence": 573,
|
||||
"TakerGets": {
|
||||
"currency": "JPY",
|
||||
"issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"value": "3089.919693423499"
|
||||
},
|
||||
"TakerPays": {
|
||||
"currency": "USD",
|
||||
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"value": "29.99922032449999"
|
||||
},
|
||||
"index": "7D1F4F82D74930ED8F0CD4D75CD9B41E0A1ED7CAA6508AEF6C13BE57FCE01220"
|
||||
},
|
||||
{
|
||||
"Flags": 0,
|
||||
"Indexes": [
|
||||
"19DA8116FACDDB48DBFAEEB7036394883D42C67CA23349E3582AC63A61C0C08F",
|
||||
"E5186A5ED55BFE053D7F7553693A3AE3288933241CA32AEC8C136BF1B2B3238B",
|
||||
"89B369C951B15067BD52EF3F5FEC540DE773C4B6718FE1342222C1C935ED42DF"
|
||||
],
|
||||
"LedgerEntryType": "DirectoryNode",
|
||||
"Owner": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"RootIndex": "7E1247F78EFC74FA9C0AE39F37AF433966615EB9B757D8397C068C2849A8F4A5",
|
||||
"index": "7E1247F78EFC74FA9C0AE39F37AF433966615EB9B757D8397C068C2849A8F4A5"
|
||||
},
|
||||
{
|
||||
"Account": "rhsxr2aAddyCKx5iZctebT4Padxv6iWDxb",
|
||||
"Balance": "141999116",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"OwnerCount": 14,
|
||||
"PreviousTxnID": "52C20FF8946459677CC0B30E2F2D5E9452D22221ADFBD35E9C3192DA7AAD6F82",
|
||||
"PreviousTxnLgrSeq": 7145047,
|
||||
"Sequence": 582,
|
||||
"index": "7E7EBE111CB117C19F55CB87A1166D3235D32605AD29F5EFF795D84962FE4D5A"
|
||||
},
|
||||
{
|
||||
"Flags": 0,
|
||||
"Indexes": [
|
||||
"E5186A5ED55BFE053D7F7553693A3AE3288933241CA32AEC8C136BF1B2B3238B",
|
||||
"4D8AD84A38E3B4EE756E7B64CA539DF51FDEF036D96E8427FB461266B55CE97E"
|
||||
],
|
||||
"LedgerEntryType": "DirectoryNode",
|
||||
"Owner": "rD8FXhcHtG8m1iHTJogoahPx3LJRjT7tpa",
|
||||
"RootIndex": "803DB604B91165DFB9BEAE165415AE68D9E2D1C7C4BDC3AB569F6F3D4FB7D2EB",
|
||||
"index": "803DB604B91165DFB9BEAE165415AE68D9E2D1C7C4BDC3AB569F6F3D4FB7D2EB"
|
||||
},
|
||||
{
|
||||
"Balance": {
|
||||
"currency": "JPY",
|
||||
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
|
||||
"value": "9975.85778757913"
|
||||
},
|
||||
"Flags": 1114112,
|
||||
"HighLimit": {
|
||||
"currency": "JPY",
|
||||
"issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"value": "0"
|
||||
},
|
||||
"HighNode": "0000000000000000",
|
||||
"LedgerEntryType": "RippleState",
|
||||
"LowLimit": {
|
||||
"currency": "JPY",
|
||||
"issuer": "rhsxr2aAddyCKx5iZctebT4Padxv6iWDxb",
|
||||
"value": "1000000"
|
||||
},
|
||||
"LowNode": "0000000000000000",
|
||||
"PreviousTxnID": "52C20FF8946459677CC0B30E2F2D5E9452D22221ADFBD35E9C3192DA7AAD6F82",
|
||||
"PreviousTxnLgrSeq": 7145047,
|
||||
"index": "8782F28AC73A79162357EB1FB38E0AA5F55C066F0F2ACC774BBF095B21E07E64"
|
||||
},
|
||||
{
|
||||
"Account": "rhS6Pb8oBMKshN6EznMeWCHJNHJuoom63r",
|
||||
"Balance": "277438248",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"OwnerCount": 10,
|
||||
"PreviousTxnID": "9E793DBDA4D9C663C3D3FC57EBF99C3DA14FE092FCEB585E06FA0DF08123CD88",
|
||||
"PreviousTxnLgrSeq": 7145267,
|
||||
"Sequence": 12441,
|
||||
"index": "87F612640CBC200BAFC0D02A8F484F2E7EA5A06836A443BF1BFF9DE686659FB3"
|
||||
},
|
||||
{
|
||||
"Balance": {
|
||||
"currency": "USD",
|
||||
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
|
||||
"value": "-37608.51473797789"
|
||||
},
|
||||
"Flags": 2228224,
|
||||
"HighLimit": {
|
||||
"currency": "USD",
|
||||
"issuer": "rEhKZcz5Ndjm9BzZmmKrtvhXPnSWByssDv",
|
||||
"value": "50000"
|
||||
},
|
||||
"HighNode": "0000000000000000",
|
||||
"LedgerEntryType": "RippleState",
|
||||
"LowLimit": {
|
||||
"currency": "USD",
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"value": "0"
|
||||
},
|
||||
"LowNode": "0000000000000000",
|
||||
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
|
||||
"PreviousTxnLgrSeq": 7145315,
|
||||
"index": "89B369C951B15067BD52EF3F5FEC540DE773C4B6718FE1342222C1C935ED42DF"
|
||||
},
|
||||
{
|
||||
"ExchangeRate": "5B079EDE3AE532A1",
|
||||
"Flags": 0,
|
||||
"Indexes": ["003313896DA56CFA0996B36AF066589EF0E689230E67DA01D13320289C834A93"],
|
||||
"LedgerEntryType": "DirectoryNode",
|
||||
"RootIndex": "92466F5377C34C5EA957034339321E217A23FA4E27A31D475B079EDE3AE532A1",
|
||||
"TakerGetsCurrency": "0000000000000000000000004A50590000000000",
|
||||
"TakerGetsIssuer": "E5C92828261DBAAC933B6309C6F5C72AF020AFD4",
|
||||
"TakerPaysCurrency": "0000000000000000000000000000000000000000",
|
||||
"TakerPaysIssuer": "0000000000000000000000000000000000000000",
|
||||
"index": "92466F5377C34C5EA957034339321E217A23FA4E27A31D475B079EDE3AE532A1"
|
||||
},
|
||||
{
|
||||
"Account": "rEhKZcz5Ndjm9BzZmmKrtvhXPnSWByssDv",
|
||||
"BookDirectory": "DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4C0F95030898047E",
|
||||
"BookNode": "0000000000000000",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "Offer",
|
||||
"OwnerNode": "0000000000000000",
|
||||
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
|
||||
"PreviousTxnLgrSeq": 7145315,
|
||||
"Sequence": 413,
|
||||
"TakerGets": "3119514774512",
|
||||
"TakerPays": {
|
||||
"currency": "USD",
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"value": "13682.08234438571"
|
||||
},
|
||||
"index": "9847793D6B936812907ED58455FBA4195205ABCACBE28DF9584C3A195A221E59"
|
||||
},
|
||||
{
|
||||
"Account": "rGJrzrNBfv6ndJmzt1hTUJVx7z8o2bg3of",
|
||||
"Balance": "20136213182",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"OwnerCount": 14,
|
||||
"PreviousTxnID": "9C83F32CEE14164F5E74B92FB18B5F401E0707D1D0B89E3B0F76EE9B22205465",
|
||||
"PreviousTxnLgrSeq": 7145315,
|
||||
"Sequence": 12541,
|
||||
"index": "9A3D8BCEE8B1A6812356F2D15767A72F4AB2F4117A5316F17BFDE6AFF3EDAD14"
|
||||
},
|
||||
{
|
||||
"Flags": 0,
|
||||
"Indexes": [
|
||||
"47E2DF7CEC8979AFEB877402F1A8A4A514CE6F0159F5BDCA3538BD4A244EE81E",
|
||||
"19DA8116FACDDB48DBFAEEB7036394883D42C67CA23349E3582AC63A61C0C08F",
|
||||
"E986149D0F53C5F05FCF13751CA959F9BA31FABC0766AE2A4E906C02F10A5F28"
|
||||
],
|
||||
"LedgerEntryType": "DirectoryNode",
|
||||
"Owner": "rhS6Pb8oBMKshN6EznMeWCHJNHJuoom63r",
|
||||
"RootIndex": "9D0414606FD28AB1AED055D29F79DFF44F5B1AC33D2A0CBD6A236BEAB0B7F0D6",
|
||||
"index": "9D0414606FD28AB1AED055D29F79DFF44F5B1AC33D2A0CBD6A236BEAB0B7F0D6"
|
||||
},
|
||||
{
|
||||
"Account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"Balance": "14474614581",
|
||||
"Domain": "736E6170737761702E7573",
|
||||
"Flags": 655360,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"OwnerCount": 0,
|
||||
"PreviousTxnID": "85F810B93488EE16BAFDB7FD590E447DA9ECC4BF3F4DAD11B52A820D7B65A952",
|
||||
"PreviousTxnLgrSeq": 7145095,
|
||||
"Sequence": 50,
|
||||
"index": "A3C1529122C3DBD6C96B9DF009FF4896023FE6B4E05A508B1E81F3DCD9A6274B"
|
||||
},
|
||||
{
|
||||
"Account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"Balance": "276876380",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"OwnerCount": 1,
|
||||
"PreviousTxnID": "2B46B3B775C536E77B15B82086FA42904C01414E4C78164F82038ADBCF352621",
|
||||
"PreviousTxnLgrSeq": 7131717,
|
||||
"Sequence": 503,
|
||||
"index": "A5F37C05FBED611F326E48E6F0D14C6BBAC664CE14ACF4FCC0E959FD60330716"
|
||||
},
|
||||
{
|
||||
"Flags": 0,
|
||||
"Indexes": [
|
||||
"6CB0EB288A835747D5FAE5FD036BAE11DB4A4787DE283199BF60C5B69291F418",
|
||||
"19D60E66D4CAB32F0823C5EAA8A58284AD48BAB63CACCBECB76166A990EECECF"
|
||||
],
|
||||
"LedgerEntryType": "DirectoryNode",
|
||||
"Owner": "rHsZHqa5oMQNL5hFm4kfLd47aEMYjPstpg",
|
||||
"RootIndex": "B65B458CE90B410C20AC46F87480569F661F3AB426FF773B2EEB4E75947A5FD6",
|
||||
"index": "B65B458CE90B410C20AC46F87480569F661F3AB426FF773B2EEB4E75947A5FD6"
|
||||
},
|
||||
{
|
||||
"Account": "rHsZHqa5oMQNL5hFm4kfLd47aEMYjPstpg",
|
||||
"Balance": "100108450682",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"OwnerCount": 32,
|
||||
"PreviousTxnID": "E5EC82E300777C4C0AE24499E2DA175AE00065BE8AC7C91518C37876377EC473",
|
||||
"PreviousTxnLgrSeq": 7145315,
|
||||
"Sequence": 1683877,
|
||||
"index": "B79D156918390AC41C4DDE5F181417D55B01D0D183ACBB1B892FF163C5BC8344"
|
||||
},
|
||||
{
|
||||
"Account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"Balance": "4382136375728",
|
||||
"Domain": "6269747374616D702E6E6574",
|
||||
"EmailHash": "5B33B93C7FFE384D53450FC666BB11FB",
|
||||
"Flags": 131072,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"OwnerCount": 0,
|
||||
"PreviousTxnID": "D7EDB85038C0EAEDB2FA174074FBB741A002B87735DDD2133971A9D3E67101CC",
|
||||
"PreviousTxnLgrSeq": 7145290,
|
||||
"Sequence": 542,
|
||||
"TransferRate": 1002000000,
|
||||
"index": "B7D526FDDF9E3B3F95C3DC97C353065B0482302500BBB8051A5C090B596C6133"
|
||||
},
|
||||
{
|
||||
"Account": "rhsxr2aAddyCKx5iZctebT4Padxv6iWDxb",
|
||||
"BookDirectory": "FF304540EB391AD26231FC5FC98ECF6E85A41DE173C1FCE05222D49D5E80FAFB",
|
||||
"BookNode": "0000000000000000",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "Offer",
|
||||
"OwnerNode": "0000000000000000",
|
||||
"PreviousTxnID": "7ADF6C4A72AF9E0DE7C11B2A5A336F47E91EFC5A201EE635D56E79D1E58A3C44",
|
||||
"PreviousTxnLgrSeq": 7133996,
|
||||
"Sequence": 572,
|
||||
"TakerGets": {
|
||||
"currency": "JPY",
|
||||
"issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"value": "3060"
|
||||
},
|
||||
"TakerPays": {
|
||||
"currency": "USD",
|
||||
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"value": "30"
|
||||
},
|
||||
"index": "B8F0B4940547D30B9706F32D3EB3A8EC60FD5F09499A157BB05549514DB335BC"
|
||||
},
|
||||
{
|
||||
"ExchangeRate": "4E10B058B79947A8",
|
||||
"Flags": 0,
|
||||
"Indexes": ["19D60E66D4CAB32F0823C5EAA8A58284AD48BAB63CACCBECB76166A990EECECF"],
|
||||
"LedgerEntryType": "DirectoryNode",
|
||||
"RootIndex": "BCF012C63E83DAF510C7B6B27FE1045CF913B0CF94049AB04E10B058B79947A8",
|
||||
"TakerGetsCurrency": "0000000000000000000000000000000000000000",
|
||||
"TakerGetsIssuer": "0000000000000000000000000000000000000000",
|
||||
"TakerPaysCurrency": "0000000000000000000000004A50590000000000",
|
||||
"TakerPaysIssuer": "E5C92828261DBAAC933B6309C6F5C72AF020AFD4",
|
||||
"index": "BCF012C63E83DAF510C7B6B27FE1045CF913B0CF94049AB04E10B058B79947A8"
|
||||
},
|
||||
{
|
||||
"Flags": 0,
|
||||
"Indexes": [
|
||||
"6CB0EB288A835747D5FAE5FD036BAE11DB4A4787DE283199BF60C5B69291F418",
|
||||
"4D8AD84A38E3B4EE756E7B64CA539DF51FDEF036D96E8427FB461266B55CE97E",
|
||||
"47E2DF7CEC8979AFEB877402F1A8A4A514CE6F0159F5BDCA3538BD4A244EE81E",
|
||||
"8782F28AC73A79162357EB1FB38E0AA5F55C066F0F2ACC774BBF095B21E07E64",
|
||||
"707CEC6ABA7BDA95DF712DABD6BC3BB1DD69CEED25977AB643EA94A471DD842C"
|
||||
],
|
||||
"LedgerEntryType": "DirectoryNode",
|
||||
"Owner": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"RootIndex": "DD8763F37822A3129919DA194DC31D9A9FA5BEA547E233B32E4573F0E60D46D3",
|
||||
"index": "DD8763F37822A3129919DA194DC31D9A9FA5BEA547E233B32E4573F0E60D46D3"
|
||||
},
|
||||
{
|
||||
"ExchangeRate": "4C0F95030898047E",
|
||||
"Flags": 0,
|
||||
"Indexes": ["9847793D6B936812907ED58455FBA4195205ABCACBE28DF9584C3A195A221E59"],
|
||||
"LedgerEntryType": "DirectoryNode",
|
||||
"RootIndex": "DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4C0F95030898047E",
|
||||
"TakerGetsCurrency": "0000000000000000000000000000000000000000",
|
||||
"TakerGetsIssuer": "0000000000000000000000000000000000000000",
|
||||
"TakerPaysCurrency": "0000000000000000000000005553440000000000",
|
||||
"TakerPaysIssuer": "0A20B3C85F482532A9578DBB3950B85CA06594D1",
|
||||
"index": "DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4C0F95030898047E"
|
||||
},
|
||||
{
|
||||
"Balance": {
|
||||
"currency": "USD",
|
||||
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
|
||||
"value": "66.41236909484335"
|
||||
},
|
||||
"Flags": 65536,
|
||||
"HighLimit": {
|
||||
"currency": "USD",
|
||||
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"value": "0"
|
||||
},
|
||||
"HighNode": "0000000000000000",
|
||||
"LedgerEntryType": "RippleState",
|
||||
"LowLimit": {
|
||||
"currency": "USD",
|
||||
"issuer": "rhsxr2aAddyCKx5iZctebT4Padxv6iWDxb",
|
||||
"value": "0"
|
||||
},
|
||||
"LowNode": "0000000000000000",
|
||||
"PreviousTxnID": "9BDD9E9FCB197AE5AB5C77F57C18C89F777AB499EF87125C8233011D227181D8",
|
||||
"PreviousTxnLgrSeq": 7143372,
|
||||
"index": "E232591F55AA7B82F584A5DBE414CA67C15869B0936C875095B5D08810A99EA5"
|
||||
},
|
||||
{
|
||||
"Balance": {
|
||||
"currency": "USD",
|
||||
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
|
||||
"value": "-51.33482690775639"
|
||||
},
|
||||
"Flags": 2228224,
|
||||
"HighLimit": {
|
||||
"currency": "USD",
|
||||
"issuer": "rD8FXhcHtG8m1iHTJogoahPx3LJRjT7tpa",
|
||||
"value": "100000"
|
||||
},
|
||||
"HighNode": "0000000000000000",
|
||||
"LedgerEntryType": "RippleState",
|
||||
"LowLimit": {
|
||||
"currency": "USD",
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"value": "0"
|
||||
},
|
||||
"LowNode": "0000000000000000",
|
||||
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
|
||||
"PreviousTxnLgrSeq": 7145315,
|
||||
"index": "E5186A5ED55BFE053D7F7553693A3AE3288933241CA32AEC8C136BF1B2B3238B"
|
||||
},
|
||||
{
|
||||
"Account": "rhS6Pb8oBMKshN6EznMeWCHJNHJuoom63r",
|
||||
"BookDirectory": "1ACB79E7B8B4C59269CEAC5CA907D5E8C3BF3B294A33D3D752221700304BE774",
|
||||
"BookNode": "0000000000000000",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "Offer",
|
||||
"OwnerNode": "0000000000000000",
|
||||
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
|
||||
"PreviousTxnLgrSeq": 7145315,
|
||||
"Sequence": 12437,
|
||||
"TakerGets": {
|
||||
"currency": "JPY",
|
||||
"issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"value": "3965.070032365735"
|
||||
},
|
||||
"TakerPays": {
|
||||
"currency": "USD",
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"value": "38.04658677730045"
|
||||
},
|
||||
"index": "E986149D0F53C5F05FCF13751CA959F9BA31FABC0766AE2A4E906C02F10A5F28"
|
||||
},
|
||||
{
|
||||
"ExchangeRate": "52227E0BAF9B1166",
|
||||
"Flags": 0,
|
||||
"Indexes": ["7D1F4F82D74930ED8F0CD4D75CD9B41E0A1ED7CAA6508AEF6C13BE57FCE01220"],
|
||||
"LedgerEntryType": "DirectoryNode",
|
||||
"RootIndex": "FF304540EB391AD26231FC5FC98ECF6E85A41DE173C1FCE052227E0BAF9B1166",
|
||||
"TakerGetsCurrency": "0000000000000000000000004A50590000000000",
|
||||
"TakerGetsIssuer": "E5C92828261DBAAC933B6309C6F5C72AF020AFD4",
|
||||
"TakerPaysCurrency": "0000000000000000000000005553440000000000",
|
||||
"TakerPaysIssuer": "DD39C650A96EDA48334E70CC4A85B8B2E8502CD3",
|
||||
"index": "FF304540EB391AD26231FC5FC98ECF6E85A41DE173C1FCE052227E0BAF9B1166"
|
||||
},
|
||||
{
|
||||
"ExchangeRate": "5222D49D5E80FAFB",
|
||||
"Flags": 0,
|
||||
"Indexes": ["B8F0B4940547D30B9706F32D3EB3A8EC60FD5F09499A157BB05549514DB335BC"],
|
||||
"LedgerEntryType": "DirectoryNode",
|
||||
"RootIndex": "FF304540EB391AD26231FC5FC98ECF6E85A41DE173C1FCE05222D49D5E80FAFB",
|
||||
"TakerGetsCurrency": "0000000000000000000000004A50590000000000",
|
||||
"TakerGetsIssuer": "E5C92828261DBAAC933B6309C6F5C72AF020AFD4",
|
||||
"TakerPaysCurrency": "0000000000000000000000005553440000000000",
|
||||
"TakerPaysIssuer": "DD39C650A96EDA48334E70CC4A85B8B2E8502CD3",
|
||||
"index": "FF304540EB391AD26231FC5FC98ECF6E85A41DE173C1FCE05222D49D5E80FAFB"
|
||||
}
|
||||
],
|
||||
"account_hash": "21520663FE5BBAF001849ECB598D8784A73081F7CBDD4A96614FAB5D438DB0AA",
|
||||
"close_time": 455849230,
|
||||
"close_time_human": "2014-Jun-12 00:47:10",
|
||||
"close_time_resolution": 10,
|
||||
"closed": true,
|
||||
"hash": "9CB34D86A7C18EE551B9961C771BA94785F57B5CE88676365B9CFD4F36787CC2",
|
||||
"ledger_hash": "9CB34D86A7C18EE551B9961C771BA94785F57B5CE88676365B9CFD4F36787CC2",
|
||||
"ledger_index": "7145315",
|
||||
"parent_hash": "22B5D2A2264612E7AD3D9A58D1CDD9442EFC28ABFB92FE2363B2F1B623909526",
|
||||
"seqNum": "7145315",
|
||||
"totalCoins": "99999989148483436",
|
||||
"total_coins": "99999989148483436",
|
||||
"transaction_hash": "EAAD60D178E8D2CC428C147EF39407AF2D073D518A7E64788EC29F321C875C5A",
|
||||
"transactions": []
|
||||
}
|
||||
813
test/freeze-test.coffee
Normal file
813
test/freeze-test.coffee
Normal file
@@ -0,0 +1,813 @@
|
||||
################################### REQUIRES ###################################
|
||||
|
||||
extend = require 'extend'
|
||||
fs = require 'fs'
|
||||
assert = require 'assert'
|
||||
async = require 'async'
|
||||
|
||||
{
|
||||
Remote
|
||||
UInt160
|
||||
Transaction
|
||||
Amount
|
||||
} = require 'ripple-lib'
|
||||
|
||||
testutils = require './testutils'
|
||||
|
||||
{
|
||||
LedgerState
|
||||
LedgerVerifier
|
||||
TestAccount
|
||||
} = require './ledger-state'
|
||||
|
||||
{
|
||||
pretty_json
|
||||
server_setup_teardown
|
||||
skip_or_only
|
||||
submit_for_final
|
||||
suite_test_bailer
|
||||
} = require './batmans-belt'
|
||||
|
||||
|
||||
################################ FREEZE OVERVIEW ###############################
|
||||
'''
|
||||
|
||||
Freeze Feature Overview
|
||||
=======================
|
||||
|
||||
A frozen line prevents funds from being transferred to anyone except back to the
|
||||
issuer, yet does not prohibit acquisition of more of the issuer's assets, via
|
||||
receipt of a Payment or placing offers to buy them.
|
||||
|
||||
A trust line's Flags now assigns two bits, for toggling the freeze status of
|
||||
each side of a trustline.
|
||||
|
||||
GlobalFreeze
|
||||
------------
|
||||
|
||||
There is also, a global freeze, toggled by a bit in the AccountRoot Flags, which
|
||||
freezes ALL trust lines for an account.
|
||||
|
||||
Offers can not be created to buy or sell assets issued by an account with
|
||||
GlobalFreeze set.
|
||||
|
||||
Use cases (via (David (JoelKatz) Schwartz)):
|
||||
|
||||
There are two basic cases. One is a case where some kind of bug or flaw causes
|
||||
a large amount of an asset to somehow be created and the gateway hasn't yet
|
||||
decided how it's going to handle it.
|
||||
|
||||
The other is a reissue where one asset is replaced by another. In a community
|
||||
credit case, say someone tricks you into issuing a huge amount of an asset,
|
||||
but you set the no freeze flag. You can still set global freeze to protect
|
||||
others from trading valuable assets for assets you issued that are now,
|
||||
unfortunately, worthless. And if you're an honest guy, you can set up a new
|
||||
account and re-issue to people who are legitimate holders
|
||||
|
||||
NoFreeze
|
||||
--------
|
||||
|
||||
NoFreeze, is a set only flag bit in the account root.
|
||||
|
||||
When this bit is set:
|
||||
An account may not freeze it's side of a trustline
|
||||
|
||||
The NoFreeze bit can not be cleared
|
||||
|
||||
The GlobalFreeze flag bit can not cleared
|
||||
GlobalFreeze can be used as a matter of last resort
|
||||
|
||||
Flag Definitions
|
||||
================
|
||||
|
||||
LedgerEntry flags
|
||||
-----------------
|
||||
|
||||
RippleState
|
||||
|
||||
LowFreeze 0x00400000
|
||||
HighFreeze 0x00800000
|
||||
|
||||
AccountRoot
|
||||
|
||||
NoFreeze 0x00200000
|
||||
GlobalFeeze 0x00400000
|
||||
|
||||
Transaction flags
|
||||
-----------------
|
||||
|
||||
TrustSet (used with Flags)
|
||||
|
||||
SetFreeze 0x00100000
|
||||
ClearFreeze 0x00200000
|
||||
|
||||
AccountSet (used with SetFlag/ClearFlag)
|
||||
|
||||
NoFreeze 6
|
||||
GlobalFreeze 7
|
||||
|
||||
API Implications
|
||||
================
|
||||
|
||||
transaction.Payment
|
||||
-------------------
|
||||
|
||||
Any offers containing frozen funds found in the process of a tesSUCCESS will be
|
||||
removed from the books.
|
||||
|
||||
transaction.OfferCreate
|
||||
-----------------------
|
||||
|
||||
Selling an asset from a globally frozen issuer fails with tecFROZEN
|
||||
Selling an asset from a frozen line fails with tecUNFUNDED_OFFER
|
||||
|
||||
Any offers containing frozen funds found in the process of a tesSUCCESS will be
|
||||
removed from the books.
|
||||
|
||||
request.book_offers
|
||||
-------------------
|
||||
|
||||
All offers selling assets from a frozen line/acount (offers created before a
|
||||
freeze) will be filtered, except where in a global freeze situation where:
|
||||
|
||||
TakerGets.issuer == Account ($frozen_account)
|
||||
|
||||
request.path_find & transaction.Payment
|
||||
---------------------------------------
|
||||
|
||||
No Path may contain frozen trustlines, or offers (placed, prior to freezing) of
|
||||
assets from frozen lines.
|
||||
|
||||
request.account_offers
|
||||
-----------------------
|
||||
|
||||
These offers are unfiltered, merely walking the owner directory and reporting
|
||||
all offers.
|
||||
|
||||
'''
|
||||
|
||||
################################################################################
|
||||
|
||||
Flags =
|
||||
sle:
|
||||
AccountRoot:
|
||||
PasswordSpent: 0x00010000
|
||||
RequireDestTag: 0x00020000
|
||||
RequireAuth: 0x00040000
|
||||
DisallowXRP: 0x00080000
|
||||
NoFreeze: 0x00200000
|
||||
GlobalFreeze: 0x00400000
|
||||
|
||||
RippleState:
|
||||
LowFreeze: 0x00400000
|
||||
HighFreeze: 0x00800000
|
||||
tx:
|
||||
SetFlag:
|
||||
AccountRoot:
|
||||
NoFreeze: 6
|
||||
GlobalFreeze: 7
|
||||
|
||||
TrustSet:
|
||||
# New Flags
|
||||
SetFreeze: 0x00100000
|
||||
ClearFreeze: 0x00200000
|
||||
|
||||
|
||||
Transaction.flags.TrustSet ||= {};
|
||||
# Monkey Patch SetFreeze and ClearFreeze into old version of ripple-lib
|
||||
Transaction.flags.TrustSet.SetFreeze = Flags.tx.TrustSet.SetFreeze
|
||||
Transaction.flags.TrustSet.ClearFreeze = Flags.tx.TrustSet.ClearFreeze
|
||||
|
||||
GlobalFreeze = Flags.tx.SetFlag.AccountRoot.GlobalFreeze
|
||||
NoFreeze = Flags.tx.SetFlag.AccountRoot.NoFreeze
|
||||
|
||||
#################################### CONFIG ####################################
|
||||
|
||||
config = testutils.init_config()
|
||||
|
||||
#################################### HELPERS ###################################
|
||||
|
||||
get_lines = (remote, acc, done) ->
|
||||
remote.request_account_lines acc, null, 'validated', (err, lines) ->
|
||||
done(lines)
|
||||
|
||||
account_set_factory = (remote, ledger, alias_for) ->
|
||||
(acc, fields, done) ->
|
||||
tx = remote.transaction()
|
||||
tx.account_set(acc)
|
||||
extend tx.tx_json, fields
|
||||
|
||||
tx.on 'error', (err) ->
|
||||
assert !err, ("Unexpected error #{ledger.pretty_json err}\n" +
|
||||
"don't use this helper if expecting an error")
|
||||
|
||||
submit_for_final tx, (m) ->
|
||||
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
|
||||
affected_root = m.metadata.AffectedNodes[0].ModifiedNode
|
||||
assert.equal alias_for(affected_root.FinalFields.Account), acc
|
||||
done(affected_root)
|
||||
|
||||
make_payment_factory = (remote, ledger) ->
|
||||
(src, dst, amount, path, on_final) ->
|
||||
|
||||
if typeof path == 'function'
|
||||
on_final = path
|
||||
path = undefined
|
||||
|
||||
src_account = UInt160.json_rewrite src
|
||||
dst_account = UInt160.json_rewrite dst
|
||||
dst_amount = Amount.from_json amount
|
||||
|
||||
tx = remote.transaction().payment(src_account, dst_account, dst_amount)
|
||||
if not path?
|
||||
tx.build_path(true)
|
||||
else
|
||||
tx.path_add path.path
|
||||
tx.send_max path.send_max
|
||||
|
||||
tx.on 'error', (err) ->
|
||||
if err.engine_result?.slice(0,3) == 'tec'
|
||||
# We can handle this in the `final`
|
||||
return
|
||||
assert !err, ("Unexpected error #{ledger.pretty_json err}\n" +
|
||||
"don't use this helper if expecting an error")
|
||||
|
||||
submit_for_final tx, (m) ->
|
||||
on_final m
|
||||
|
||||
create_offer_factory = (remote, ledger) ->
|
||||
(acc, pays, gets, on_final) ->
|
||||
tx = remote.transaction().offer_create(acc, pays, gets)
|
||||
|
||||
tx.on 'error', (err) ->
|
||||
if err.engine_result?.slice(0,3) == 'tec'
|
||||
# We can handle this in the `final`
|
||||
return
|
||||
assert !err, ("Unexpected error #{ledger.pretty_json err}\n" +
|
||||
"don't use this helper if expecting an error")
|
||||
submit_for_final tx, (m) ->
|
||||
on_final m
|
||||
|
||||
ledger_state_setup = (pre_ledger) ->
|
||||
post_setup = (context, done) ->
|
||||
context.ledger = new LedgerState(pre_ledger,
|
||||
assert,
|
||||
context.remote,
|
||||
config)
|
||||
|
||||
context.ledger.setup(
|
||||
#-> # noop logging function
|
||||
->
|
||||
->
|
||||
context.ledger.verifier().do_verify (errors) ->
|
||||
assert Object.keys(errors).length == 0,
|
||||
"pre_ledger errors:\n"+ pretty_json errors
|
||||
done()
|
||||
)
|
||||
|
||||
verify_ledger_state = (ledger, remote, pre_state, done) ->
|
||||
{config, assert, am} = ledger
|
||||
verifier = new LedgerVerifier(pre_state, remote, config, assert, am)
|
||||
|
||||
verifier.do_verify (errors) ->
|
||||
assert Object.keys(errors).length == 0,
|
||||
"ledger_state errors:\n"+ pretty_json errors
|
||||
done()
|
||||
|
||||
|
||||
book_offers_factory = (remote) ->
|
||||
(pays, gets, on_book) ->
|
||||
asset = (a) ->
|
||||
if typeof a == 'string'
|
||||
ret = {}
|
||||
[ret['currency'], ret['issuer']] = a.split('/')
|
||||
ret
|
||||
else
|
||||
a
|
||||
|
||||
book=
|
||||
pays: asset(pays)
|
||||
gets: asset(gets)
|
||||
|
||||
remote.request_book_offers book, null, null, (err, book) ->
|
||||
if err
|
||||
assert !err, "error with request_book_offers #{err}"
|
||||
|
||||
on_book(book)
|
||||
|
||||
suite_setup = (state) ->
|
||||
'''
|
||||
|
||||
@state
|
||||
|
||||
The ledger state to setup, after starting the server
|
||||
|
||||
'''
|
||||
opts =
|
||||
setup_func: suiteSetup
|
||||
teardown_func: suiteTeardown
|
||||
post_setup: ledger_state_setup(state)
|
||||
|
||||
get_context = server_setup_teardown(opts)
|
||||
|
||||
helpers = null
|
||||
helpers_factory = ->
|
||||
context = {ledger, remote} = get_context()
|
||||
|
||||
alog = (obj) -> console.log ledger.pretty_json obj
|
||||
lines_for = (acc) -> get_lines(remote, arguments...)
|
||||
alias_for = (acc) -> ledger.am.lookup_alias(acc)
|
||||
|
||||
verify_ledger_state_before_suite = (pre) ->
|
||||
suiteSetup (done) -> verify_ledger_state(ledger, remote, pre, done)
|
||||
|
||||
{
|
||||
context: context
|
||||
remote: remote
|
||||
ledger: ledger
|
||||
lines_for: lines_for
|
||||
alog: alog
|
||||
alias_for: alias_for
|
||||
book_offers: book_offers_factory(remote)
|
||||
create_offer: create_offer_factory(remote, ledger, alias_for)
|
||||
account_set: account_set_factory(remote, ledger, alias_for)
|
||||
make_payment: make_payment_factory(remote, ledger, alias_for)
|
||||
verify_ledger_state_before_suite: verify_ledger_state_before_suite
|
||||
}
|
||||
|
||||
get_helpers = -> (helpers = helpers ? helpers_factory())
|
||||
|
||||
{
|
||||
get_context: get_context
|
||||
get_helpers: get_helpers
|
||||
}
|
||||
|
||||
##################################### TESTS ####################################
|
||||
|
||||
execute_if_enabled = (fn) ->
|
||||
path = "#{__dirname}/../src/ripple/module/data/protocol/TxFlags.h"
|
||||
if /asfGlobalFreeze/.exec(fs.readFileSync(path)) == null
|
||||
suite = global.suite.skip
|
||||
fn(suite)
|
||||
|
||||
execute_if_enabled (suite) ->
|
||||
suite 'Freeze Feature', ->
|
||||
suite 'RippleState Freeze', ->
|
||||
test = suite_test_bailer()
|
||||
h = null
|
||||
|
||||
{get_helpers} = suite_setup
|
||||
accounts:
|
||||
G1: balance: ['1000.0']
|
||||
|
||||
bob:
|
||||
balance: ['1000.0', '10-100/USD/G1']
|
||||
|
||||
alice:
|
||||
balance: ['1000.0', '100/USD/G1']
|
||||
offers: [['500.0', '100/USD/G1']]
|
||||
|
||||
suite 'Account with line unfrozen (proving operations normally work)', ->
|
||||
test 'can make Payment on that line', (done) ->
|
||||
{remote} = h = get_helpers()
|
||||
|
||||
h.make_payment 'alice', 'bob', '1/USD/G1', (m) ->
|
||||
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
|
||||
done()
|
||||
|
||||
test 'can receive Payment on that line', (done) ->
|
||||
h.make_payment 'bob', 'alice', '1/USD/G1', (m) ->
|
||||
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
|
||||
done()
|
||||
|
||||
suite 'Is created via a TrustSet with SetFreeze flag', ->
|
||||
test 'sets LowFreeze | HighFreeze flags', (done) ->
|
||||
{remote} = h
|
||||
|
||||
tx = remote.transaction()
|
||||
tx.ripple_line_set('G1', '0/USD/bob')
|
||||
tx.set_flags('SetFreeze')
|
||||
|
||||
submit_for_final tx, (m) ->
|
||||
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
|
||||
affected = m.metadata.AffectedNodes
|
||||
ripple_state = affected[1].ModifiedNode
|
||||
final = ripple_state.FinalFields
|
||||
assert.equal h.alias_for(final.LowLimit.issuer), 'G1'
|
||||
assert final.Flags & Flags.sle.RippleState.LowFreeze
|
||||
assert !(final.Flags & Flags.sle.RippleState.HighFreeze)
|
||||
|
||||
done()
|
||||
|
||||
suite 'Account with line frozen by issuer', ->
|
||||
test 'can buy more assets on that line', (done) ->
|
||||
h.create_offer 'bob', '5/USD/G1', '25.0', (m) ->
|
||||
meta = m.metadata
|
||||
assert.equal meta.TransactionResult, 'tesSUCCESS'
|
||||
line = meta.AffectedNodes[3]['ModifiedNode'].FinalFields
|
||||
assert.equal h.alias_for(line.HighLimit.issuer), 'bob'
|
||||
assert.equal line.Balance.value, '-15' # HighLimit means balance inverted
|
||||
done()
|
||||
|
||||
test 'can not sell assets from that line', (done) ->
|
||||
h.create_offer 'bob', '1.0', '5/USD/G1', (m) ->
|
||||
assert.equal m.engine_result, 'tecUNFUNDED_OFFER'
|
||||
done()
|
||||
|
||||
test 'can receive Payment on that line', (done) ->
|
||||
h.make_payment 'alice', 'bob', '1/USD/G1', (m) ->
|
||||
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
|
||||
done()
|
||||
|
||||
test 'can not make Payment from that line', (done) ->
|
||||
h.make_payment 'bob', 'alice', '1/USD/G1', (m) ->
|
||||
assert.equal m.engine_result, 'tecPATH_DRY'
|
||||
done()
|
||||
|
||||
suite 'request_account_lines', ->
|
||||
test 'shows `freeze_peer` and `freeze` respectively', (done) ->
|
||||
async.parallel [
|
||||
(next) ->
|
||||
h.lines_for 'G1', (lines) ->
|
||||
for line in lines.lines
|
||||
if h.alias_for(line.account) == 'bob'
|
||||
assert.equal line.freeze, true
|
||||
assert.equal line.balance, '-16'
|
||||
# unless we get here, the test will hang alerting us to
|
||||
# something amiss
|
||||
next() # setImmediate ;)
|
||||
break
|
||||
|
||||
(next) ->
|
||||
h.lines_for 'bob', (lines) ->
|
||||
for line in lines.lines
|
||||
if h.alias_for(line.account) == 'G1'
|
||||
assert.equal line.freeze_peer, true
|
||||
assert.equal line.balance, '16'
|
||||
next()
|
||||
break
|
||||
], ->
|
||||
done()
|
||||
|
||||
suite 'Is cleared via a TrustSet with ClearFreeze flag', ->
|
||||
test 'sets LowFreeze | HighFreeze flags', (done) ->
|
||||
{remote} = h
|
||||
|
||||
tx = remote.transaction()
|
||||
tx.ripple_line_set('G1', '0/USD/bob')
|
||||
tx.set_flags('ClearFreeze')
|
||||
|
||||
submit_for_final tx, (m) ->
|
||||
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
|
||||
affected = m.metadata.AffectedNodes
|
||||
ripple_state = affected[1].ModifiedNode
|
||||
final = ripple_state.FinalFields
|
||||
assert.equal h.alias_for(final.LowLimit.issuer), 'G1'
|
||||
assert !(final.Flags & Flags.sle.RippleState.LowFreeze)
|
||||
assert !(final.Flags & Flags.sle.RippleState.HighFreeze)
|
||||
|
||||
done()
|
||||
|
||||
suite 'Global (AccountRoot) Freeze', ->
|
||||
# NoFreeze: 0x00200000
|
||||
# GlobalFreeze: 0x00400000
|
||||
|
||||
test = suite_test_bailer()
|
||||
h = null
|
||||
|
||||
{get_helpers} = suite_setup
|
||||
accounts:
|
||||
G1:
|
||||
balance: ['12000.0']
|
||||
offers: [['10000.0', '100/USD/G1'], ['100/USD/G1', '10000.0']]
|
||||
|
||||
A1:
|
||||
balance: ['1000.0', '1000/USD/G1']
|
||||
offers: [['10000.0', '100/USD/G1']]
|
||||
trusts: ['1200/USD/G1']
|
||||
|
||||
A2:
|
||||
balance: ['20000.0', '100/USD/G1']
|
||||
trusts: ['200/USD/G1']
|
||||
offers: [['100/USD/G1', '10000.0']]
|
||||
|
||||
A3:
|
||||
balance: ['20000.0', '100/BTC/G1']
|
||||
|
||||
A4:
|
||||
balance: ['20000.0', '100/BTC/G1']
|
||||
|
||||
suite 'Is toggled via AccountSet using SetFlag and ClearFlag', ->
|
||||
test 'SetFlag GlobalFreeze should set 0x00400000 in Flags', (done) ->
|
||||
{remote} = h = get_helpers()
|
||||
|
||||
h.account_set 'G1', SetFlag: GlobalFreeze, (root) ->
|
||||
new_flags = root.FinalFields.Flags
|
||||
|
||||
assert !(new_flags & Flags.sle.AccountRoot.NoFreeze)
|
||||
assert (new_flags & Flags.sle.AccountRoot.GlobalFreeze)
|
||||
|
||||
done()
|
||||
|
||||
test 'ClearFlag GlobalFreeze should clear 0x00400000 in Flags', (done) ->
|
||||
{remote} = h = get_helpers()
|
||||
|
||||
h.account_set 'G1', ClearFlag: GlobalFreeze, (root) ->
|
||||
new_flags = root.FinalFields.Flags
|
||||
|
||||
assert !(new_flags & Flags.sle.AccountRoot.NoFreeze)
|
||||
assert !(new_flags & Flags.sle.AccountRoot.GlobalFreeze)
|
||||
|
||||
done()
|
||||
|
||||
suite 'Account without GlobalFreeze (proving operations normally work)', ->
|
||||
suite 'have visible offers', ->
|
||||
test 'where taker_gets is $unfrozen_issuer', (done) ->
|
||||
{remote} = h = get_helpers()
|
||||
|
||||
h.book_offers 'XRP', 'USD/G1', (book) ->
|
||||
assert.equal book.offers.length, 2
|
||||
|
||||
aliases = (h.alias_for(o.Account) for o in book.offers).sort()
|
||||
|
||||
assert.equal aliases[0], 'A1'
|
||||
assert.equal aliases[1], 'G1'
|
||||
|
||||
done()
|
||||
|
||||
test 'where taker_pays is $unfrozen_issuer', (done) ->
|
||||
h.book_offers 'USD/G1', 'XRP', (book) ->
|
||||
|
||||
assert.equal book.offers.length, 2
|
||||
aliases = (h.alias_for(o.Account) for o in book.offers).sort()
|
||||
|
||||
assert.equal aliases[0], 'A2'
|
||||
assert.equal aliases[1], 'G1'
|
||||
|
||||
done()
|
||||
|
||||
suite 'it\'s assets can be', ->
|
||||
|
||||
test 'bought on the market', (next) ->
|
||||
h.create_offer 'A3', '1/BTC/G1', '1.0', (m) ->
|
||||
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
|
||||
next()
|
||||
|
||||
test 'sold on the market', (next) ->
|
||||
h.create_offer 'A4', '1.0', '1/BTC/G1', (m) ->
|
||||
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
|
||||
next()
|
||||
|
||||
suite 'Payments', ->
|
||||
test 'direct issues can be sent', (done) ->
|
||||
{remote} = h = get_helpers()
|
||||
|
||||
h.make_payment 'G1', 'A2', '1/USD/G1', (m) ->
|
||||
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
|
||||
done()
|
||||
|
||||
test 'direct redemptions can be sent', (done) ->
|
||||
h.make_payment 'A2', 'G1', '1/USD/G1', (m) ->
|
||||
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
|
||||
done()
|
||||
|
||||
test 'via rippling can be sent', (done) ->
|
||||
h.make_payment 'A2', 'A1', '1/USD/G1', (m) ->
|
||||
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
|
||||
done()
|
||||
|
||||
test 'via rippling can be sent back', (done) ->
|
||||
h.make_payment 'A2', 'A1', '1/USD/G1', (m) ->
|
||||
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
|
||||
done()
|
||||
|
||||
suite 'Account with GlobalFreeze', ->
|
||||
suite 'Needs to set GlobalFreeze first', ->
|
||||
test 'SetFlag GlobalFreeze will toggle back to freeze', (done) ->
|
||||
h.account_set 'G1', SetFlag: GlobalFreeze, (root) ->
|
||||
new_flags = root.FinalFields.Flags
|
||||
|
||||
assert !(new_flags & Flags.sle.AccountRoot.NoFreeze)
|
||||
assert (new_flags & Flags.sle.AccountRoot.GlobalFreeze)
|
||||
|
||||
done()
|
||||
|
||||
suite 'it\'s assets can\'t be', ->
|
||||
test 'bought on the market', (next) ->
|
||||
h.create_offer 'A3', '1/BTC/G1', '1.0', (m) ->
|
||||
assert.equal m.engine_result, 'tecFROZEN'
|
||||
next()
|
||||
|
||||
test 'sold on the market', (next) ->
|
||||
h.create_offer 'A4', '1.0', '1/BTC/G1', (m) ->
|
||||
assert.equal m.engine_result, 'tecFROZEN'
|
||||
next()
|
||||
|
||||
suite 'it\'s offers are filtered', ->
|
||||
test ':TODO:verify: books_offers(*, $frozen_account/*) shows offers '+
|
||||
'owned by $frozen_account ', (done) ->
|
||||
|
||||
h.book_offers 'XRP', 'USD/G1', (book) ->
|
||||
assert.equal book.offers.length, 1
|
||||
done()
|
||||
|
||||
test ':TODO:verify: books_offers($frozen_account/*, *) shows no offers', (done) ->
|
||||
|
||||
h.book_offers 'USD/G1', 'XRP', (book) ->
|
||||
assert.equal book.offers.length, 0
|
||||
done()
|
||||
|
||||
test 'account_offers always shows their own offers', (done) ->
|
||||
{remote} = h = get_helpers()
|
||||
|
||||
remote.request_account_offers 'G1', null, 'validated', (err, res) ->
|
||||
assert.equal res.offers.length, 2
|
||||
done()
|
||||
|
||||
suite 'Payments', ->
|
||||
test 'direct issues can be sent', (done) ->
|
||||
{remote} = h = get_helpers()
|
||||
|
||||
h.make_payment 'G1', 'A2', '1/USD/G1', (m) ->
|
||||
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
|
||||
done()
|
||||
|
||||
test 'direct redemptions can be sent', (done) ->
|
||||
h.make_payment 'A2', 'G1', '1/USD/G1', (m) ->
|
||||
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
|
||||
done()
|
||||
|
||||
test 'via rippling cant be sent', (done) ->
|
||||
h.make_payment 'A2', 'A1', '1/USD/G1', (m) ->
|
||||
assert.equal m.engine_result, 'tecPATH_DRY'
|
||||
done()
|
||||
|
||||
suite 'Accounts with NoFreeze', ->
|
||||
test = suite_test_bailer()
|
||||
h = null
|
||||
|
||||
{get_helpers} = suite_setup
|
||||
accounts:
|
||||
G1: balance: ['12000.0']
|
||||
A1: balance: ['1000.0', '1000/USD/G1']
|
||||
|
||||
suite 'TrustSet NoFreeze', ->
|
||||
test 'should set 0x00200000 in Flags', (done) ->
|
||||
h = get_helpers()
|
||||
|
||||
h.account_set 'G1', SetFlag: NoFreeze, (root) ->
|
||||
new_flags = root.FinalFields.Flags
|
||||
|
||||
assert (new_flags & Flags.sle.AccountRoot.NoFreeze)
|
||||
assert !(new_flags & Flags.sle.AccountRoot.GlobalFreeze)
|
||||
|
||||
done()
|
||||
|
||||
test 'can not be cleared', (done) ->
|
||||
h.account_set 'G1', ClearFlag: NoFreeze, (root) ->
|
||||
new_flags = root.FinalFields.Flags
|
||||
|
||||
assert (new_flags & Flags.sle.AccountRoot.NoFreeze)
|
||||
assert !(new_flags & Flags.sle.AccountRoot.GlobalFreeze)
|
||||
|
||||
done()
|
||||
|
||||
suite 'GlobalFreeze', ->
|
||||
test 'can set GlobalFreeze', (done) ->
|
||||
h.account_set 'G1', SetFlag: GlobalFreeze, (root) ->
|
||||
new_flags = root.FinalFields.Flags
|
||||
|
||||
assert (new_flags & Flags.sle.AccountRoot.NoFreeze)
|
||||
assert (new_flags & Flags.sle.AccountRoot.GlobalFreeze)
|
||||
|
||||
done()
|
||||
|
||||
test 'can not unset GlobalFreeze', (done) ->
|
||||
h.account_set 'G1', ClearFlag: GlobalFreeze, (root) ->
|
||||
new_flags = root.FinalFields.Flags
|
||||
|
||||
assert (new_flags & Flags.sle.AccountRoot.NoFreeze)
|
||||
assert (new_flags & Flags.sle.AccountRoot.GlobalFreeze)
|
||||
|
||||
done()
|
||||
|
||||
suite 'their trustlines', ->
|
||||
test 'can\'t be frozen', (done) ->
|
||||
{remote} = h = get_helpers()
|
||||
|
||||
tx = remote.transaction()
|
||||
tx.ripple_line_set('G1', '0/USD/A1')
|
||||
tx.set_flags('SetFreeze')
|
||||
|
||||
submit_for_final tx, (m) ->
|
||||
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
|
||||
affected = m.metadata.AffectedNodes
|
||||
assert.equal affected.length, 1
|
||||
affected_type = affected[0]['ModifiedNode'].LedgerEntryType
|
||||
assert.equal affected_type, 'AccountRoot'
|
||||
|
||||
done()
|
||||
|
||||
suite 'Offers for frozen trustlines (not GlobalFreeze)', ->
|
||||
test = suite_test_bailer()
|
||||
remote = h = null
|
||||
|
||||
{get_helpers} = suite_setup
|
||||
accounts:
|
||||
G1:
|
||||
balance: ['1000.0']
|
||||
A2:
|
||||
balance: ['2000.0']
|
||||
trusts: ['1000/USD/G1']
|
||||
A3:
|
||||
balance: ['1000.0', '2000/USD/G1']
|
||||
offers: [['1000.0', '1000/USD/G1']]
|
||||
|
||||
A4:
|
||||
balance: ['1000.0', '2000/USD/G1']
|
||||
|
||||
suite 'will be removed by Payment with tesSUCCESS', ->
|
||||
test 'can normally make a payment partially consuming offer', (done) ->
|
||||
{remote} = h = get_helpers()
|
||||
|
||||
path =
|
||||
path: [{"currency": "USD", "issuer": "G1"}]
|
||||
send_max: '1.0'
|
||||
|
||||
h.make_payment 'A2', 'G1', '1/USD/G1', path, (m) ->
|
||||
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
|
||||
done()
|
||||
|
||||
test 'offer was only partially consumed', (done) ->
|
||||
remote.request_account_offers 'A3', null, 'validated', (err, res) ->
|
||||
assert res.offers.length == 1
|
||||
assert res.offers[0].taker_gets.value, '999'
|
||||
done()
|
||||
|
||||
test 'someone else creates an offer providing liquidity', (done) ->
|
||||
h.create_offer 'A4', '999.0', '999/USD/G1', (m) ->
|
||||
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
|
||||
affected = m.metadata.AffectedNodes
|
||||
done()
|
||||
|
||||
test 'owner of partially consumed offer\'s line is frozen', (done) ->
|
||||
tx = remote.transaction()
|
||||
tx.ripple_line_set('G1', '0/USD/A3')
|
||||
tx.set_flags('SetFreeze')
|
||||
|
||||
submit_for_final tx, (m) ->
|
||||
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
|
||||
affected = m.metadata.AffectedNodes
|
||||
ripple_state = affected[1].ModifiedNode
|
||||
final = ripple_state.FinalFields
|
||||
assert.equal h.alias_for(final.HighLimit.issuer), 'G1'
|
||||
assert !(final.Flags & Flags.sle.RippleState.LowFreeze)
|
||||
assert (final.Flags & Flags.sle.RippleState.HighFreeze)
|
||||
|
||||
done()
|
||||
|
||||
test 'Can make a payment via the new offer', (done) ->
|
||||
path =
|
||||
path: [{"currency": "USD", "issuer": "G1"}]
|
||||
send_max: '1.0'
|
||||
|
||||
h.make_payment 'A2', 'G1', '1/USD/G1', path, (m) ->
|
||||
# assert.equal m.engine_result, 'tecPATH_PARTIAL' # tecPATH_DRY
|
||||
assert.equal m.metadata.TransactionResult, 'tesSUCCESS' # tecPATH_DRY
|
||||
done()
|
||||
|
||||
test 'Partially consumed offer was removed by tes* payment', (done) ->
|
||||
remote.request_account_offers 'A3', null, 'validated', (err, res) ->
|
||||
assert res.offers.length == 0
|
||||
done()
|
||||
|
||||
suite 'will be removed by OfferCreate with tesSUCCESS', ->
|
||||
test 'freeze the new offer', (done) ->
|
||||
tx = remote.transaction()
|
||||
tx.ripple_line_set('G1', '0/USD/A4')
|
||||
tx.set_flags('SetFreeze')
|
||||
|
||||
submit_for_final tx, (m) ->
|
||||
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
|
||||
affected = m.metadata.AffectedNodes
|
||||
ripple_state = affected[0].ModifiedNode
|
||||
final = ripple_state.FinalFields
|
||||
assert.equal h.alias_for(final.LowLimit.issuer), 'G1'
|
||||
assert (final.Flags & Flags.sle.RippleState.LowFreeze)
|
||||
assert !(final.Flags & Flags.sle.RippleState.HighFreeze)
|
||||
|
||||
done()
|
||||
|
||||
test 'can no longer create a crossing offer', (done) ->
|
||||
h.create_offer 'A2', '999/USD/G1', '999.0', (m) ->
|
||||
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
|
||||
affected = m.metadata.AffectedNodes
|
||||
created = affected[5].CreatedNode
|
||||
new_fields = created.NewFields
|
||||
assert.equal h.alias_for(new_fields.Account), 'A2'
|
||||
done()
|
||||
|
||||
test 'offer was removed by offer_create', (done) ->
|
||||
remote.request_account_offers 'A4', null, 'validated', (err, res) ->
|
||||
assert res.offers.length == 0
|
||||
done()
|
||||
152
test/ledger-state-test.coffee
Normal file
152
test/ledger-state-test.coffee
Normal file
@@ -0,0 +1,152 @@
|
||||
################################################################################
|
||||
|
||||
async = require 'async'
|
||||
simple_assert = require 'assert'
|
||||
deep_eq = require 'deep-equal'
|
||||
testutils = require './testutils'
|
||||
|
||||
{
|
||||
LedgerVerifier
|
||||
Balance
|
||||
} = require './ledger-state'
|
||||
|
||||
#################################### CONFIG ####################################
|
||||
|
||||
config = testutils.init_config()
|
||||
|
||||
#################################### HELPERS ###################################
|
||||
|
||||
assert = simple_assert
|
||||
prettyj = pretty_json = (v) -> JSON.stringify(v, undefined, 2)
|
||||
|
||||
describe 'Balance', ->
|
||||
it 'parses native balances', ->
|
||||
bal = new Balance("1.000")
|
||||
assert.equal bal.is_native, true
|
||||
assert.equal bal.limit, null
|
||||
|
||||
it 'parses iou balances', ->
|
||||
bal = new Balance("1.000/USD/bob")
|
||||
assert.equal bal.is_native, false
|
||||
assert.equal bal.limit, null
|
||||
assert.equal bal.amount.currency().to_json(), 'USD'
|
||||
|
||||
it 'parses iou balances with limits', ->
|
||||
bal = new Balance("1-500/USD/bob")
|
||||
assert.equal bal.is_native, false
|
||||
assert.equal bal.amount.currency().to_json(), 'USD'
|
||||
assert.equal bal.limit.to_json().value, '500'
|
||||
assert.equal bal.amount.to_json().value, '1'
|
||||
|
||||
describe 'LedgerVerifier', ->
|
||||
lv = null
|
||||
|
||||
declaration=
|
||||
accounts:
|
||||
bob:
|
||||
balance: ['100.0', '200-500/USD/alice']
|
||||
offers: [['89.0', '100/USD/alice'], ['89.0', '100/USD/alice']]
|
||||
|
||||
# We are using this because mocha and coffee-script is a retarded combination
|
||||
# unfortunately, which terminates the program silently upon any require time
|
||||
# exceptions. TODO: investigate obviously, but for the moment this is an
|
||||
# acceptable workaround.
|
||||
suiteSetup ->
|
||||
remote_dummy = {set_secret: (->)}
|
||||
lv = new LedgerVerifier(declaration, remote_dummy, config, assert)
|
||||
|
||||
it 'tracks xrp balances', ->
|
||||
assert.equal lv.xrp_balances['bob'].to_json(), '100000000'
|
||||
|
||||
it 'tracks iou balances', ->
|
||||
assert.equal lv.iou_balances['bob']['USD/alice'].to_json().value, '200'
|
||||
|
||||
it 'tracks iou trust limits', ->
|
||||
assert.equal lv.trusts['bob']['USD/alice'].to_json().value, '500'
|
||||
|
||||
it 'can verify', ->
|
||||
account_offers = [
|
||||
{
|
||||
"account": "bob",
|
||||
"offers": [
|
||||
{
|
||||
"flags": 65536,
|
||||
"seq": 2,
|
||||
"taker_gets": {
|
||||
"currency": "USD",
|
||||
"issuer": "alice",
|
||||
"value": "100"
|
||||
},
|
||||
"taker_pays": "88000000"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
account_lines = [{
|
||||
"account": "bob",
|
||||
"lines": [
|
||||
{
|
||||
"account": "alice",
|
||||
"balance": "201",
|
||||
"currency": "USD",
|
||||
"limit": "500",
|
||||
"limit_peer": "0",
|
||||
"quality_in": 0,
|
||||
"quality_out": 0
|
||||
},
|
||||
]
|
||||
}]
|
||||
|
||||
account_infos = [{
|
||||
"account_data": {
|
||||
"Account": "bob",
|
||||
"Balance": "999"+ "999"+ "970",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"OwnerCount": 0,
|
||||
"PreviousTxnID": "3D7823B577A5AF5860273B3DD13CA82D072B63B3B095DE1784604A5B41D7DD1D",
|
||||
"PreviousTxnLgrSeq": 5,
|
||||
"Sequence": 3,
|
||||
"index": "59BEA57D1A27B6A560ECA226ABD10DE80C3ADC6961039908087ACDFA92F71489"
|
||||
},
|
||||
"ledger_current_index": 8
|
||||
}]
|
||||
|
||||
errors = lv.verify account_infos, account_lines, account_offers
|
||||
|
||||
assert.equal errors.bob.balance['USD/alice'].expected, '200'
|
||||
assert.equal errors.bob.balance['USD/alice'].actual, '201'
|
||||
|
||||
assert.equal errors.bob.balance['XRP'].expected, '100'
|
||||
assert.equal errors.bob.balance['XRP'].actual, '999.99997'
|
||||
|
||||
assert.equal errors.bob.offers[0].taker_pays.actual, '88/XRP'
|
||||
assert.equal errors.bob.offers[0].taker_pays.expected, '89/XRP'
|
||||
|
||||
# {"expected":["89.0","100/USD/alice"],"actual":"missing"}
|
||||
assert.equal errors.bob.offers[1].actual, 'missing'
|
||||
|
||||
expected = {
|
||||
"bob": {
|
||||
"balance": {
|
||||
"XRP": {
|
||||
"actual": "999.99997",
|
||||
"expected": "100"
|
||||
},
|
||||
"USD/alice": {
|
||||
"actual": "201",
|
||||
"expected": "200"
|
||||
}
|
||||
},
|
||||
"offers": [
|
||||
{
|
||||
"taker_pays": {
|
||||
"expected": "89/XRP",
|
||||
"actual": "88/XRP"
|
||||
}
|
||||
},
|
||||
"missing"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,10 @@ assert = require 'assert'
|
||||
Remote
|
||||
Seed
|
||||
Base
|
||||
UInt160
|
||||
Transaction
|
||||
sjcl
|
||||
} = require 'ripple-lib'
|
||||
{Server} = require './server'
|
||||
testutils = require './testutils'
|
||||
|
||||
#################################### HELPERS ###################################
|
||||
@@ -36,10 +36,167 @@ exports.TestAccount = class TestAccount
|
||||
@passphrase = passphrase
|
||||
[@address, @master_seed, @key_pair] = @derive_pair(passphrase)
|
||||
|
||||
############################# LEDGER STATE COMPILER ############################
|
||||
parse_balance_and_trust = (val) ->
|
||||
reg = new RegExp("([0-9.]+)-([0-9.]+)(/[^/]+/[^/]+)")
|
||||
m = reg.exec val
|
||||
if m != null
|
||||
[m[1] + m[3], m[2] + m[3]]
|
||||
else
|
||||
undefined
|
||||
|
||||
exports.LedgerState = class LedgerState
|
||||
setup_issuer_realiaser: ->
|
||||
exports.parse_amount = parse_amount = (amt_val) ->
|
||||
amt = Amount.from_json(amt_val)
|
||||
if not amt.is_valid()
|
||||
amt = Amount.from_human(amt_val)
|
||||
if not amt.is_valid()
|
||||
amt = null
|
||||
amt
|
||||
|
||||
exports.Balance = class Balance
|
||||
'''
|
||||
|
||||
Represents a parsed balance declaration, which could represent an xrp balance
|
||||
or an iou balance and optional limit.
|
||||
|
||||
@amount
|
||||
@limit
|
||||
@balance
|
||||
|
||||
'''
|
||||
constructor: (value) ->
|
||||
limited = parse_balance_and_trust value
|
||||
if limited?
|
||||
[amount, limit] = limited
|
||||
@amount = parse_amount amount
|
||||
@limit = parse_amount limit
|
||||
@is_native = false
|
||||
else
|
||||
@amount = parse_amount value
|
||||
@is_native = @amount.is_native()
|
||||
@limit = null
|
||||
|
||||
################################################################################
|
||||
|
||||
class BulkRequests
|
||||
constructor: (@remote, @assert, @pretty_json) ->
|
||||
|
||||
transactor: (fn, args_list, on_each, callback) ->
|
||||
if args_list.length == 0
|
||||
return callback()
|
||||
|
||||
if not callback?
|
||||
callback = on_each
|
||||
on_each = null
|
||||
|
||||
@assert callback?, "Must supply a callback"
|
||||
finalized = {
|
||||
n: args_list.length
|
||||
one: ->
|
||||
if --finalized.n <= 0
|
||||
callback()
|
||||
}
|
||||
|
||||
#remote = @remote
|
||||
async.concatSeries(args_list, ((args, callback) =>
|
||||
tx = @remote.transaction()
|
||||
fn.apply(tx, args)
|
||||
on_each?(args..., tx) # after payment() offer_create() etc so set_flags works
|
||||
|
||||
tx.on("proposed", (m) =>
|
||||
@assert m.engine_result is "tesSUCCESS", "Transactor failure: #{@pretty_json m}"
|
||||
callback()
|
||||
# testutils.ledger_close remote, ->
|
||||
).on('final', (m) =>
|
||||
finalized.one()
|
||||
# callback()
|
||||
)
|
||||
.on("error", (m) =>
|
||||
@assert false, @pretty_json m
|
||||
).submit()
|
||||
),
|
||||
=> testutils.ledger_close @remote, ->
|
||||
)
|
||||
|
||||
requester: (fn, args_list, on_each, callback, on_results) ->
|
||||
if not callback?
|
||||
callback = on_each
|
||||
on_each = null
|
||||
|
||||
@assert callback?, "Must supply a callback"
|
||||
|
||||
async.concatSeries(args_list, ((args, callback) =>
|
||||
req = fn.apply @remote, args
|
||||
on_each?(args..., req)
|
||||
req.on("success", (m) =>
|
||||
if m.status?
|
||||
@assert m.status is "success", "requester failure: #{@pretty_json m}"
|
||||
callback(null, m)
|
||||
).on("error", (m) =>
|
||||
@assert false, @pretty_json m
|
||||
).request()
|
||||
),
|
||||
(error, results_list) ->
|
||||
on_results?(results_list)
|
||||
callback(error, results_list)
|
||||
)
|
||||
|
||||
|
||||
################################# ALIAS MANAGER ################################
|
||||
|
||||
class AliasManager
|
||||
constructor: (@config, remote, aliases) ->
|
||||
'''
|
||||
|
||||
@config
|
||||
includes `accounts` property, with structure same as that exported
|
||||
in testconfig.js
|
||||
|
||||
@remote
|
||||
a Remote object
|
||||
|
||||
@aliases
|
||||
A list of aliases
|
||||
|
||||
'''
|
||||
@add_accounts_to_config(@config, aliases)
|
||||
@set_test_account_secrets(remote, @config)
|
||||
@realias_issuer = @create_issuer_realiaser()
|
||||
@alias_lookup = @create_alias_lookup()
|
||||
|
||||
create_alias_lookup: ->
|
||||
lookup = {}
|
||||
for nick,acc of @config.accounts
|
||||
lookup[acc.account] = nick
|
||||
lookup
|
||||
|
||||
lookup_alias: (address) ->
|
||||
@alias_lookup[UInt160.json_rewrite address]
|
||||
|
||||
pretty_json: (v) =>
|
||||
@realias_issuer pretty_json(v)
|
||||
|
||||
add_accounts_to_config: (config, accounts) ->
|
||||
for account in accounts
|
||||
if not config.accounts[account]?
|
||||
acc = config.accounts[account] = {}
|
||||
user = new TestAccount(account)
|
||||
acc.account = user.address
|
||||
acc.secret = user.master_seed
|
||||
|
||||
set_test_account_secrets: (remote, config) ->
|
||||
# TODO: config.accounts
|
||||
for nick,acc of config.accounts
|
||||
# # Index by nickname ...
|
||||
remote.set_secret nick, acc.secret
|
||||
# # ... and by account ID
|
||||
remote.set_secret acc.account, acc.secret
|
||||
|
||||
amount_key: (amt) ->
|
||||
currency = amt.currency().to_json()
|
||||
issuer = @realias_issuer amt.issuer().to_json()
|
||||
"#{currency}/#{issuer}"
|
||||
|
||||
create_issuer_realiaser: ->
|
||||
users = @config.accounts
|
||||
lookup = {}
|
||||
accounts = []
|
||||
@@ -49,25 +206,16 @@ exports.LedgerState = class LedgerState
|
||||
lookup[user.account] = name
|
||||
|
||||
realias = new RegExp(accounts.join("|"), "g")
|
||||
@realias_issuer = (str) -> str.replace(realias, (match) ->lookup[match])
|
||||
(str) -> str.replace(realias, (match) ->lookup[match])
|
||||
|
||||
############################# LEDGER STATE COMPILER ############################
|
||||
|
||||
exports.LedgerState = class LedgerState
|
||||
parse_amount: (amt_val) ->
|
||||
amt = Amount.from_json(amt_val)
|
||||
if not amt.is_valid()
|
||||
amt = Amount.from_human(amt_val)
|
||||
if not amt.is_valid()
|
||||
amt = null
|
||||
amt
|
||||
parse_amount(amt_val)
|
||||
|
||||
amount_key: (amt) ->
|
||||
currency = amt.currency().to_json()
|
||||
issuer = @realias_issuer amt.issuer().to_json()
|
||||
"#{currency}/#{issuer}"
|
||||
|
||||
apply: (context)->
|
||||
@create_accounts_by_issuing_xrp_from_root(context)
|
||||
@create_trust_limits(context)
|
||||
@deliver_ious(context)
|
||||
@am.amount_key amt
|
||||
|
||||
record_iou: (account_id, amt)->
|
||||
key = @amount_key amt
|
||||
@@ -120,15 +268,26 @@ exports.LedgerState = class LedgerState
|
||||
"No balance declared for #{account_id}"
|
||||
|
||||
for amt_val in account.balance
|
||||
trust = null
|
||||
balance_trust = parse_balance_and_trust(amt_val)
|
||||
|
||||
if balance_trust?
|
||||
[amt_val, trust_val] = balance_trust
|
||||
trust = @parse_amount trust_val
|
||||
@assert trust != null,
|
||||
"Trust amount #{trust_val} specified for #{account_id} "
|
||||
"is not valid"
|
||||
|
||||
amt = @parse_amount amt_val
|
||||
@assert amt != null,
|
||||
"Balance amount #{amt_val} specified for #{account_id} is not valid"
|
||||
"Balance amount #{amt_val} specified for #{account_id} "
|
||||
"is not valid"
|
||||
|
||||
if amt.is_native()
|
||||
xrp_balance = @record_xrp(account_id, amt)
|
||||
else
|
||||
@record_iou(account_id, amt)
|
||||
@record_trust(account_id, amt, true)
|
||||
@record_trust(account_id, trust ? amt, true)
|
||||
|
||||
@assert xrp_balance,
|
||||
"No XRP balanced declared for #{account_id}"
|
||||
@@ -249,76 +408,11 @@ exports.LedgerState = class LedgerState
|
||||
|
||||
undefined
|
||||
|
||||
transactor: (fn, args_list, on_each, callback) ->
|
||||
if args_list.length == 0
|
||||
return callback()
|
||||
setup_alias_manager: ->
|
||||
@am = new AliasManager(@config, @remote, Object.keys(@declaration.accounts))
|
||||
@realias_issuer = @am.realias_issuer
|
||||
|
||||
if not callback?
|
||||
callback = on_each
|
||||
on_each = null
|
||||
|
||||
@assert callback?, "Must supply a callback"
|
||||
finalized = {
|
||||
n: args_list.length
|
||||
one: ->
|
||||
if --finalized.n <= 0
|
||||
callback()
|
||||
}
|
||||
|
||||
async.concatSeries(args_list, ((args, callback) =>
|
||||
tx = @remote.transaction()
|
||||
fn.apply(tx, args)
|
||||
on_each?(args..., tx) # after payment() offer_create() etc so set_flags works
|
||||
tx.on("proposed", (m) =>
|
||||
@assert m.engine_result is "tesSUCCESS", "Transactor failure: #{@pretty_json m}"
|
||||
callback()
|
||||
).on('final', (m) =>
|
||||
finalized.one()
|
||||
)
|
||||
.on("error", (m) =>
|
||||
assert false, pretty_json m
|
||||
).submit()
|
||||
),
|
||||
=> testutils.ledger_close @remote, ->
|
||||
)
|
||||
|
||||
requester: (fn, args_list, on_each, callback, on_results) ->
|
||||
if not callback?
|
||||
callback = on_each
|
||||
on_each = null
|
||||
|
||||
@assert callback?, "Must supply a callback"
|
||||
|
||||
async.concatSeries(args_list, ((args, callback) =>
|
||||
req = fn.apply @remote, args
|
||||
on_each?(args..., req)
|
||||
req.on("success", (m) =>
|
||||
if m.status?
|
||||
@assert m.status is "success", "requester failure: #{@pretty_json m}"
|
||||
callback(null, m)
|
||||
).on("error", (m) =>
|
||||
@assert false, @pretty_json m
|
||||
).request()
|
||||
),
|
||||
(error, results_list) ->
|
||||
on_results?(results_list);
|
||||
callback()
|
||||
)
|
||||
|
||||
ensure_config_has_test_accounts: ->
|
||||
for account of @declaration.accounts
|
||||
if not @config.accounts[account]?
|
||||
acc = @config.accounts[account] = {}
|
||||
user = new TestAccount(account)
|
||||
acc.account = user.address
|
||||
acc.secret = user.master_seed
|
||||
# Index by nickname ...
|
||||
@remote.set_secret account, acc.secret
|
||||
# ... and by account ID
|
||||
@remote.set_secret acc.account, acc.secret
|
||||
@setup_issuer_realiaser()
|
||||
|
||||
pretty_json: (v) ->
|
||||
pretty_json: (v) =>
|
||||
@realias_issuer pretty_json(v)
|
||||
|
||||
constructor: (declaration, @assert, @remote, @config) ->
|
||||
@@ -334,13 +428,85 @@ exports.LedgerState = class LedgerState
|
||||
@iou_payments = [] # {$account_id: []}
|
||||
@offers = [] # {$account_id: []}
|
||||
|
||||
@ensure_config_has_test_accounts()
|
||||
@setup_alias_manager()
|
||||
@compile_accounts_balances_and_implicit_trusts()
|
||||
@compile_explicit_trusts()
|
||||
@compile_offers()
|
||||
@check_reserves()
|
||||
@format_payments()
|
||||
@format_trusts()
|
||||
@add_transaction_fees()
|
||||
|
||||
compile_to_rpc_commands: ->
|
||||
passphrase = (src) ->
|
||||
if src == 'root'
|
||||
'masterpassphrase'
|
||||
else
|
||||
src
|
||||
|
||||
make_tx_json = (src, tt) ->
|
||||
{"Account": UInt160.json_rewrite(src), "TransactionType": tt}
|
||||
|
||||
submit_line = (src, tx_json) ->
|
||||
"build/rippled submit #{passphrase(src)} '#{JSON.stringify tx_json}'"
|
||||
|
||||
lines = []
|
||||
ledger_accept = -> lines.push('build/rippled ledger_accept')
|
||||
|
||||
for [src, dst, amount] in @xrp_payments
|
||||
tx_json = make_tx_json(src, 'Payment')
|
||||
tx_json.Destination = UInt160.json_rewrite dst
|
||||
tx_json.Amount = amount.to_json()
|
||||
lines.push submit_line(src, tx_json)
|
||||
|
||||
ledger_accept()
|
||||
|
||||
for [src, limit] in @trusts
|
||||
tx_json = make_tx_json(src, 'TrustSet')
|
||||
tx_json.LimitAmount = limit.to_json()
|
||||
lines.push submit_line(src, tx_json)
|
||||
|
||||
ledger_accept()
|
||||
|
||||
for [src, dst, amount] in @iou_payments
|
||||
tx_json = make_tx_json(src, 'Payment')
|
||||
tx_json.Destination = UInt160.json_rewrite dst
|
||||
tx_json.Amount = amount.to_json()
|
||||
lines.push submit_line(src, tx_json)
|
||||
|
||||
ledger_accept()
|
||||
|
||||
for [src, pays, gets, flags] in @offers
|
||||
tx = new Transaction({secrets: {}})
|
||||
tx.offer_create(src, pays, gets)
|
||||
tx.set_flags(flags)
|
||||
|
||||
# console.log tx.tx_json
|
||||
# process.exit()
|
||||
|
||||
# tx_json = make_tx_json(src, 'OfferCreate')
|
||||
# tx_json.TakerPays = pays.to_json()
|
||||
# tx_json.TakerGets = gets.to_json()
|
||||
lines.push submit_line(src, tx.tx_json)
|
||||
|
||||
ledger_accept()
|
||||
lines.join('\n')
|
||||
|
||||
verifier: (decl) ->
|
||||
new LedgerVerifier(decl ? @declaration, @remote, @config, @assert, @am)
|
||||
|
||||
add_transaction_fees: ->
|
||||
extra_fees = {}
|
||||
fee = Amount.from_json('15')
|
||||
for list in [@trusts, @iou_payments, @offers]
|
||||
for [src, args...] in list
|
||||
extra = extra_fees[src]
|
||||
extra = if extra? then extra.add(fee) else fee
|
||||
extra_fees[src] = extra
|
||||
|
||||
for [src, dst, amount], ix in @xrp_payments
|
||||
if extra_fees[dst]?
|
||||
@xrp_payments[ix][2] = amount.add(extra_fees[dst])
|
||||
|
||||
setup: (log, done) ->
|
||||
LOG = (m) ->
|
||||
@@ -352,80 +518,213 @@ exports.LedgerState = class LedgerState
|
||||
accounts_apply_arguments = ([ac] for ac, _ of @accounts)
|
||||
self = this
|
||||
|
||||
Dump = (v) => console.log @pretty_json(v)
|
||||
Dump = ->
|
||||
|
||||
reqs = new BulkRequests(@remote, @assert, @pretty_json)
|
||||
|
||||
async.waterfall [
|
||||
(cb) ->
|
||||
self.transactor(
|
||||
reqs.transactor(
|
||||
Transaction::payment,
|
||||
self.xrp_payments,
|
||||
((src, dest, amt) ->
|
||||
LOG("Account `#{src}` creating account `#{dest}` by
|
||||
making payment of #{amt.to_text_full()}") ),
|
||||
LOG("Account `#{src}` creating account `#{dest}` by "+
|
||||
"making payment of #{amt.to_text_full()}") ),
|
||||
cb)
|
||||
(cb) ->
|
||||
self.transactor(
|
||||
reqs.transactor(
|
||||
Transaction::ripple_line_set,
|
||||
self.trusts,
|
||||
((src, amt) ->
|
||||
issuer = self.realias_issuer amt.issuer().to_json()
|
||||
currency = amt.currency().to_json()
|
||||
LOG("Account `#{src}` trusts account `#{issuer}` for
|
||||
#{amt.to_text()} #{currency}") ),
|
||||
LOG("Account `#{src}` trusts account `#{issuer}` for "+
|
||||
"#{amt.to_text()} #{currency}") ),
|
||||
cb)
|
||||
(cb) ->
|
||||
self.transactor(
|
||||
reqs.transactor(
|
||||
Transaction::payment,
|
||||
self.iou_payments,
|
||||
((src, dest, amt, tx) ->
|
||||
LOG("Account `#{src}` is making a payment of #{amt.to_text_full()}
|
||||
to `#{dest}`") ),
|
||||
LOG("Account `#{src}` is making a payment of #{amt.to_text_full()} "+
|
||||
"to `#{dest}`") ),
|
||||
cb)
|
||||
(cb) ->
|
||||
self.transactor(
|
||||
reqs.transactor(
|
||||
Transaction::offer_create,
|
||||
self.offers,
|
||||
((src, pays, gets, tx) ->
|
||||
tx.set_flags('Passive')
|
||||
LOG("Account `#{src}` is selling #{gets.to_text_full()}
|
||||
for #{pays.to_text_full()}")),
|
||||
((src, pays, gets, flags, tx) ->
|
||||
if not tx?
|
||||
tx = flags
|
||||
flags = ['Passive']
|
||||
else
|
||||
# TODO: icky ;)
|
||||
delete tx.tx_json.Expiration
|
||||
|
||||
tx.set_flags(flags)
|
||||
LOG("Account `#{src}` is selling #{gets.to_text_full()} "+
|
||||
"for #{pays.to_text_full()}")),
|
||||
cb)
|
||||
(cb) ->
|
||||
testutils.ledger_close self.remote, cb
|
||||
(cb) ->
|
||||
self.requester(Remote::request_account_lines, accounts_apply_arguments,
|
||||
((acc) ->
|
||||
LOG("Checking account_lines for #{acc}")),
|
||||
cb)
|
||||
(cb) ->
|
||||
self.requester(Remote::request_account_offers, accounts_apply_arguments,
|
||||
((acc) ->
|
||||
LOG("Checking account_offers for #{acc}")),
|
||||
cb, (results) ->
|
||||
|
||||
for [ac], ix in accounts_apply_arguments
|
||||
account = self.declaration.accounts[ac]
|
||||
offers_declared = (account.offers ? []).length
|
||||
actual = results[ix].offers
|
||||
offers_made = actual.length
|
||||
if offers_made != offers_declared
|
||||
shortened = []
|
||||
for offer in actual
|
||||
keys = ['taker_pays', 'taker_gets']
|
||||
pair = (Amount.from_json(offer[k]).to_text_full() for k in keys)
|
||||
shortened.push pair
|
||||
|
||||
shortened_text = self.pretty_json shortened
|
||||
self.assert offers_made == offers_declared,
|
||||
"Account #{ac} has failed offer\n"+
|
||||
"Declared: #{pretty_json account.offers}\n"+
|
||||
"Actual: #{shortened_text}"
|
||||
|
||||
)
|
||||
(cb) ->
|
||||
self.requester(Remote::request_account_info, accounts_apply_arguments,
|
||||
((acc) ->
|
||||
LOG("Checking account_info for #{acc}")),
|
||||
cb)
|
||||
], (error) ->
|
||||
assert !error,
|
||||
"There was an error @ #{self.what}"
|
||||
done()
|
||||
|
||||
################################ LEDGER VERIFIER ###############################
|
||||
|
||||
ensure = (account_id, obj, val) ->
|
||||
if not obj[account_id]?
|
||||
obj[account_id] = val ? {}
|
||||
obj[account_id]
|
||||
|
||||
exports.LedgerVerifier = class LedgerVerifier
|
||||
constructor: (@declaration, @remote, @config, @assert, @am) ->
|
||||
@am ?= new AliasManager(@config, @remote, Object.keys(@declaration.accounts))
|
||||
@requester = new BulkRequests(@remote, @assert, @am.pretty_json)
|
||||
@compile_declaration()
|
||||
|
||||
verify_lines: (errors, account_lines) ->
|
||||
for account in account_lines
|
||||
# For test sweet ;)
|
||||
account_alias = @am.lookup_alias account.account
|
||||
for line in account.lines
|
||||
peer_alias = @am.lookup_alias line.account
|
||||
key = "#{line.currency}/#{peer_alias}"
|
||||
|
||||
asserted = @iou_balances[account_alias]?[key]
|
||||
if asserted?
|
||||
actual = Amount.from_json(
|
||||
"#{line.balance}/#{line.currency}/#{line.account}")
|
||||
|
||||
if not asserted.equals(actual)
|
||||
balance = (((errors[account_alias] ?= {})['balance'] ?= {}))
|
||||
balance[key] =
|
||||
expected: asserted.to_text()
|
||||
actual: actual.to_text()
|
||||
|
||||
asserted = @trusts[account_alias]?[key]
|
||||
if asserted?
|
||||
actual = Amount.from_json(
|
||||
"#{line.limit}/#{line.currency}/#{line.account}")
|
||||
|
||||
if not asserted.equals(actual)
|
||||
limit = (((errors[account_alias] ?= {})['limit'] ?= {}))
|
||||
limit[key] =
|
||||
expected: asserted.to_text()
|
||||
actual: actual.to_text()
|
||||
|
||||
verify_infos: (errors, account_infos) ->
|
||||
for account in account_infos
|
||||
root = account.account_data
|
||||
account_alias = @am.lookup_alias root.Account
|
||||
asserted = @xrp_balances[account_alias]
|
||||
if asserted?
|
||||
actual = Amount.from_json root.Balance
|
||||
|
||||
if not asserted.equals(actual)
|
||||
balance = (((errors[account_alias] ?= {})['balance'] ?= {}))
|
||||
balance['XRP'] =
|
||||
expected: asserted.to_human()
|
||||
actual: actual.to_human()
|
||||
|
||||
verify_offers: (errors, account_offers) ->
|
||||
for account in account_offers
|
||||
account_alias = @am.lookup_alias account.account
|
||||
get_errors = -> (((errors[account_alias] ?= {})['offers'] ?= []))
|
||||
|
||||
assertions = @offers[account_alias]
|
||||
continue if not assertions?
|
||||
|
||||
amount_text = (amt) => @am.realias_issuer amt.to_text_full()
|
||||
|
||||
for asserted, ix in assertions
|
||||
offer = account.offers[ix]
|
||||
|
||||
if not offer?
|
||||
get_errors().push {expected: asserted, actual: 'missing'}
|
||||
continue
|
||||
else
|
||||
# expected_*
|
||||
[epays, egets] = (parse_amount a for a in asserted)
|
||||
|
||||
# actual_*
|
||||
apays = Amount.from_json offer.taker_pays
|
||||
agets = Amount.from_json offer.taker_gets
|
||||
|
||||
err = {}
|
||||
|
||||
if not epays.equals apays
|
||||
pay_err = (err['taker_pays'] = {})
|
||||
pay_err['expected'] = amount_text epays
|
||||
pay_err['actual'] = amount_text apays
|
||||
|
||||
if not egets.equals agets
|
||||
get_err = (err['taker_gets'] = {})
|
||||
get_err['expected'] = amount_text egets
|
||||
get_err['actual'] = amount_text agets
|
||||
|
||||
if Object.keys(err).length > 0
|
||||
offer_errors = get_errors()
|
||||
offer_errors.push err
|
||||
|
||||
verify: (account_infos, account_lines, account_offers) ->
|
||||
errors = {}
|
||||
|
||||
# console.log @am.pretty_json account_infos
|
||||
# console.log @am.pretty_json account_lines
|
||||
# console.log @am.pretty_json account_offers
|
||||
|
||||
@verify_infos errors, account_infos
|
||||
@verify_lines errors, account_lines
|
||||
@verify_offers errors, account_offers
|
||||
|
||||
errors
|
||||
|
||||
do_verify: (done) ->
|
||||
args_from_keys = (obj) -> ([a] for a in Object.keys obj)
|
||||
|
||||
reqs = @requester
|
||||
|
||||
lines_args = args_from_keys @iou_balances
|
||||
info_args = args_from_keys @xrp_balances
|
||||
offers_args = args_from_keys @offers
|
||||
|
||||
async.series [
|
||||
(cb) ->
|
||||
reqs.requester(Remote::request_account_info, info_args, cb)
|
||||
(cb) ->
|
||||
reqs.requester(Remote::request_account_lines, lines_args, cb)
|
||||
(cb) ->
|
||||
reqs.requester(Remote::request_account_offers, offers_args, cb)
|
||||
], (error, results) =>
|
||||
assert !error,
|
||||
"There was an error @ #{error}"
|
||||
|
||||
done(@verify(results...))
|
||||
|
||||
compile_declaration: ->
|
||||
@offers = {}
|
||||
@xrp_balances = {}
|
||||
@iou_balances = {}
|
||||
@trusts = {}
|
||||
@realias_issuer = @am.realias_issuer
|
||||
|
||||
record_amount = (account_id, to, amt) =>
|
||||
key = @am.amount_key amt
|
||||
ensure(account_id, to)[key] = amt
|
||||
|
||||
for account_id, account of @declaration.accounts
|
||||
if account.offers?
|
||||
@offers[account_id] = account.offers
|
||||
if Array.isArray(account.balance)
|
||||
for value in account.balance
|
||||
balance = new Balance(value)
|
||||
if balance.is_native
|
||||
@xrp_balances[account_id] = balance.amount
|
||||
else
|
||||
if balance.limit?
|
||||
record_amount account_id, @trusts, balance.limit
|
||||
record_amount account_id, @iou_balances, balance.amount
|
||||
|
||||
19
test/mocha-loader-patch.js
Normal file
19
test/mocha-loader-patch.js
Normal file
@@ -0,0 +1,19 @@
|
||||
mocha = require("mocha")
|
||||
// Stash a reference away to this
|
||||
old_loader = mocha.prototype.loadFiles
|
||||
|
||||
if (!old_loader.monkey_patched) {
|
||||
// Gee thanks Mocha ...
|
||||
mocha.prototype.loadFiles = function() {
|
||||
try {
|
||||
old_loader.apply(this, arguments);
|
||||
} catch (e) {
|
||||
// Normally mocha just silently bails
|
||||
console.error(e.stack);
|
||||
// We throw, so mocha doesn't continue trying to run the test suite
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
mocha.prototype.loadFiles.monkey_patched = true;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
--require ./test/mocha-loader-patch.js
|
||||
--reporter spec
|
||||
--compilers coffee:coffee-script
|
||||
--ui tdd
|
||||
|
||||
114
test/offer-tests-json.js
Normal file
114
test/offer-tests-json.js
Normal file
@@ -0,0 +1,114 @@
|
||||
module.exports = {
|
||||
"Partially crossed completely via bridging": {
|
||||
|
||||
"pre_ledger": {"accounts": {"G1": {"balance": ["1000.0"]},
|
||||
"G2": {"balance": ["1000.0"]},
|
||||
"alice": {"balance": ["500000.0", "200/USD/G1"],
|
||||
"offers": [["100/USD/G1", "88.0"]]},
|
||||
"bob": {"balance": ["500000.0", "500/USD/G2"],
|
||||
"offers": [["88.0", "100/USD/G2"]]},
|
||||
"takerJoe": {"balance": ["500000.0", "500/USD/G1"]}}},
|
||||
|
||||
"offer": ["takerJoe", "500/USD/G2", "500/USD/G1"],
|
||||
|
||||
"post_ledger": {"accounts": {"takerJoe": {"balance": ["400/USD/G1", "100/USD/G2"],
|
||||
"offers": [["400/USD/G2", "400/USD/G1"]]}}}
|
||||
},
|
||||
|
||||
"Partially crossed (halt)": {
|
||||
"pre_ledger": {"accounts": {"G1": {"balance": ["1000.0"]},
|
||||
"G2": {"balance": ["1000.0"]},
|
||||
"alice": {"balance": ["500000.0", "200/USD/G1"],
|
||||
"offers": [["100/USD/G1", "88.0"], ["100/USD/G1", "88.0"]]},
|
||||
//----/ (2) (3) (5) there's no offers left. Halt
|
||||
"bob": {"balance": ["500000.0", "500/USD/G2"],
|
||||
"offers": [["88.0", "100/USD/G2"]]},
|
||||
// (4)
|
||||
"takerJoe": {"balance": ["500000.0", "500/USD/G1"]}}},
|
||||
// (1)
|
||||
"offer": ["takerJoe", "500/USD/G2", "500/USD/G1"],
|
||||
|
||||
// 500,000-88 200+100/USD/G1
|
||||
"post_ledger": {"accounts": {"alice": {"balance": ["499912.0", "300/USD/G1"],
|
||||
"offers": [["100/USD/G1", "88.0"]]},
|
||||
|
||||
"bob": {"balance": ["500088.0", "400/USD/G2"],
|
||||
"offers": [/*["88.0", "100/USD/G2"]*/]},
|
||||
|
||||
"takerJoe": {"balance": ["100/USD/G2", "400/USD/G1"],
|
||||
"offers": [["400/USD/G2", "400/USD/G1"]]}}}
|
||||
},
|
||||
|
||||
"Partially crossed completely via bridging (Sell)": {
|
||||
|
||||
"pre_ledger": {"accounts": {"G1": {"balance": ["1000.0"]},
|
||||
"G2": {"balance": ["1000.0"]},
|
||||
"alice": {"balance": ["500000.0", "200/USD/G1"],
|
||||
"offers": [["200/USD/G1", "176.0", "Sell"]]},
|
||||
"bob": {"balance": ["500000.0", "500/USD/G2"],
|
||||
"offers": [["88.0", "100/USD/G2"]]},
|
||||
"takerJoe": {"balance": ["500000.0", "500/USD/G1"]}}},
|
||||
|
||||
"offer": ["takerJoe", "500/USD/G2", "500/USD/G1", "Sell"],
|
||||
|
||||
"post_ledger": {"accounts": {"alice": {"balance": ["499912.0", "299.9999999999999/USD/G1"],
|
||||
"offers": [["100.0000000000001/USD/G1", "88.0"]]},
|
||||
"takerJoe": {"balance": ["100/USD/G2", "400.0000000000001/USD/G1"],
|
||||
"offers": [["400.0000000000001/USD/G2", "400.0000000000001/USD/G1"]]}}}
|
||||
},
|
||||
|
||||
"Completely crossed via bridging + direct": {
|
||||
|
||||
"pre_ledger": {"accounts": {"G1": {"balance": ["1000.0"]},
|
||||
"G2": {"balance": ["1000.0"]},
|
||||
"alice": {"balance": ["500000.0", "500/USD/G1", "500/USD/G2"],
|
||||
"offers": [["50/USD/G1", "50/USD/G2"],
|
||||
["49/USD/G1", "50/USD/G2"],
|
||||
["48/USD/G1", "50/USD/G2"],
|
||||
["47/USD/G1", "50/USD/G2"],
|
||||
["46/USD/G1", "50/USD/G2"],
|
||||
["45/USD/G1", "50/USD/G2"],
|
||||
["44/USD/G1", "50/USD/G2"],
|
||||
["43/USD/G1", "50/USD/G2"],
|
||||
["100/USD/G1", "88.0"]]},
|
||||
"bob": {"balance": ["500000.0", "500/USD/G2"],
|
||||
"offers": [["88.0", "100/USD/G2"]]},
|
||||
"takerJoe": {"balance": ["500000.0", "600/USD/G1"]}}},
|
||||
|
||||
"offer": ["takerJoe", "500/USD/G2", "500/USD/G1"],
|
||||
|
||||
"post_ledger": {"accounts": {"takerJoe": {"balance": ["500/USD/G2", "128/USD/G1"]}}}
|
||||
},
|
||||
|
||||
"Partially crossed via bridging + direct": {
|
||||
"pre_ledger": {"accounts": {"G1": {"balance": ["1000.0"]},
|
||||
"G2": {"balance": ["1000.0"]},
|
||||
"alice": {"balance": ["500000.0", "500/USD/G1", "500/USD/G2"],
|
||||
"offers": [["372/USD/G1", "400/USD/G2"],
|
||||
["100/USD/G1", "88.0"]]},
|
||||
"bob": {"balance": ["500000.0", "500/USD/G2"],
|
||||
"offers": [["88.0", "100/USD/G2"]]},
|
||||
"takerJoe": {"balance": ["500000.0", "600/USD/G1"]}}},
|
||||
|
||||
"offer": ["takerJoe", "600/USD/G2", "600/USD/G1"],
|
||||
|
||||
"post_ledger": {"accounts": {"takerJoe": {"balance": ["500/USD/G2", "128/USD/G1"],
|
||||
"offers": [["100/USD/G2", "100/USD/G1"]]}}}
|
||||
},
|
||||
|
||||
"Partially crossed via bridging + direct": {
|
||||
"pre_ledger": {"accounts": {"G1": {"balance": ["1000.0"]},
|
||||
"G2": {"balance": ["1000.0"]},
|
||||
"alice": {"balance": ["500000.0", "500/USD/G1", "500/USD/G2"],
|
||||
"offers": [["372/USD/G1", "400/USD/G2"],
|
||||
["100/USD/G1", "88.0"]]},
|
||||
"bob": {"balance": ["500000.0", "500/USD/G2"],
|
||||
"offers": [["88.0", "100/USD/G2"]]},
|
||||
"takerJoe": {"balance": ["500000.0", "600/USD/G1"]}}},
|
||||
|
||||
"offer": ["takerJoe", "600/USD/G2", "600/USD/G1", "Sell"],
|
||||
|
||||
"post_ledger": {"accounts": {"takerJoe": {"balance": ["500/USD/G2", "128/USD/G1"],
|
||||
"offers": [["128/USD/G2", "128/USD/G1"]]}}}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
module.exports = {
|
||||
"Path Tests #1 (XRP -> XRP) and #2 (XRP -> IOU)": {
|
||||
|
||||
"ledger": {"accounts": {"A1": {"balance": ["100000.0",
|
||||
@@ -30,6 +30,7 @@ function Server(name, config, verbose) {
|
||||
this.started = false;
|
||||
this.quiet = !verbose;
|
||||
this.stopping = false;
|
||||
this.ledger_file = null;
|
||||
|
||||
var nodejs_version = process.version.match(/^v(\d+)+\.(\d+)\.(\d+)$/).slice(1,4);
|
||||
var wanted_version = [ 0, 8, 18 ];
|
||||
@@ -78,6 +79,10 @@ Server.prototype._writeConfig = function(done) {
|
||||
'utf8', done);
|
||||
};
|
||||
|
||||
Server.prototype.set_ledger_file = function(fn) {
|
||||
this.ledger_file = __dirname + '/fixtures/' + fn;
|
||||
}
|
||||
|
||||
// Spawn the server.
|
||||
Server.prototype._serverSpawnSync = function() {
|
||||
var self = this;
|
||||
@@ -88,6 +93,10 @@ Server.prototype._serverSpawnSync = function() {
|
||||
"--conf=rippled.cfg"
|
||||
];
|
||||
|
||||
if (this.ledger_file != null) {
|
||||
args.push('--ledgerfile=' + this.ledger_file)
|
||||
};
|
||||
|
||||
var options = {
|
||||
cwd: this.serverPath(),
|
||||
env: process.env,
|
||||
@@ -177,6 +186,7 @@ Server.prototype.stop = function () {
|
||||
// Update the on exit to invoke done.
|
||||
this.child.on('exit', function (code, signal) {
|
||||
if (!self.quiet) console.log("server: stop: server exited");
|
||||
self.stopped = true;
|
||||
self.emit('stopped');
|
||||
delete self.child;
|
||||
});
|
||||
|
||||
@@ -96,6 +96,11 @@ function build_setup(opts, host) {
|
||||
|
||||
data.server = Server.from_config(host, server_config, !!opts.verbose_server);
|
||||
|
||||
// Setting undefined is a noop here
|
||||
if (data.opts.ledger_file != null) {
|
||||
data.server.set_ledger_file(data.opts.ledger_file);
|
||||
};
|
||||
|
||||
data.server.once('started', function() {
|
||||
callback();
|
||||
});
|
||||
@@ -164,7 +169,11 @@ function build_teardown(host) {
|
||||
}
|
||||
];
|
||||
|
||||
if (!opts.no_server && data.server.stopped) {
|
||||
done()
|
||||
} else {
|
||||
async.series(series, done);
|
||||
}
|
||||
};
|
||||
|
||||
return teardown;
|
||||
|
||||
Reference in New Issue
Block a user