The spec describes the technical solution of the XRPL two-way bridge.
The account holds the tokens issued on the XRPL on its balance. Depending on the workflow it either uses the received tokens balance to send to XRPL accounts (in case the account is not an issuer) or mints and sends the tokens to XRPL accounts (in case it is the Coreum token representation issued by the address). The account uses the multi-signing and public keys associated with each relayer from the contract for the transaction signing.
The bridge contract is the major state transition of the bridge. It holds the operations state and protects the execution using the trusted addresses voting/signing mechanisms. Also, it has an owner account that can change the settings of the contract or request operation execution from the relayers.
Before the bridging, a token (XRPL or Coreum) should be manually registered for the bridging. The tokens that are not registered can't be bridged.
All tokens issued on XRPL that can be bridged from the XRPL to the Coreum and back must have a representation on the
Coreum. Such tokens should be registered by owner on the contract side with the XRPL issuer
, XRPL currency
,
bridging fees
, sending precision
and max holding amount
. The token decimals
(always 15) will be set by the
contract. The sending precision
and max holding amount
should be provided taking into account
the Amount rounding handling.
The token's denom
is unique and is built by the contract using the XRPL issuer
, XRPL currency
, block time
hash
and xrpl
prefix.
Required features for the issuance are minting
and IBC
. During the registration, the contract issues a
token and will be responsible for its minting when a token is bridged from the XRPL to Coreum. After the registration,
the token is put in Processing
state and the contract triggers the submit trust set for xrpl token
operation
to allow the XRPL bridge account to receive that token. If this operation succeeds, the token will be Enabled
,
if it fails, it will be put to Inactive
state awaiting a token registration recovery requested operation by the owner.
The value of the trustset limit amount will be provided during contract instantiation and saved in the config of the
contract.
Check register-token workflow for more details.
The XRP token is registered in the token registry on the contract instantiation. That token uses the constant issuer
rrrrrrrrrrrrrrrrrrrrrhoLvTp
and currency XRP
token. That token can be enabled or disabled by the owner similar to
other tokens. Similar to XRPL originated tokens the XRP token has the sending precision
and max holding amount
which
is set to default values on the contact instantiation.
The XRP token has a bit of a different nature than other tokens. That token doesn't need approval (TrustSet) to be
received and is used by the XRPL bridge account to pay fees.
All tokens issued on the Coreum that can be bridged from the Coreum to XRPL and back must have a representation on the
XRPL, managed by the XRPL bridge account. Such tokens should be registered by owner on the contract side with the
Coreum denom
, token decimals
, sending precision
, max holding amount
and bridging fees
. The sending precision
and max holding amount
should be provided taking into account
the Amount rounding handling.
The XRPL currency
will be uniquely generated by the contract using the denom
, decimals
, block time
hash with
a coreum
prefix and encoding it into the hexadecimal currency notation used in XRPL. This will be used as a
representation of that token on the XRPL side.
Check workflow for more details.
It is possible to update the token state
, sending precision
, max holding amount
and bridging fee
. The owner can
do it by calling the contract for both XRPL and Coreum originated tokens.
In the case of Token state, a token can be Enabled/Disabled only if it's not in Inactive/Processing State (for the first
case it requires a recovery operation and for the second one it's in the middle of TrustSet operation). In the case
of sending precision
, the owner can change it to another valid sending precision value. For max holding amount
, the
owner can change it to another max holding amount as long as the bridge holds equal or less than the new value. In the
case of bridging fee
, the owner can change it to a different value.
The evidence queue is a queue that contains bridge actions which should be confirmed before execution. Each action has a type, associated ID (unique identifier/hash of the action data in the scope of type), and a list of trusted relayer addresses that provide the evidence. Once the contract receives enough evidences it removes the action from the queue and passes its data to the next step of a workflow.
The pending operations queue
is a queue that contains bridge operations that should be signed before sending to XRPL
and later sent. The signing queue is a part of each operation. Each operation has a type, associated ID (unique
identifier),
data to the operation and a list of signatures. Each relayer picks such operation, builds corresponding XRPL
transaction, signs it and provides the signature. The operation keeps receiving signatures and shares them with other
relayers (using the contract). Each relayer validates the provided signatures, filters valid, and checks whether it's
possible to submit the transaction. If it is possible it builds the transaction with valid signatures and submits
the transaction to the XRPL. If multiple relayers execute the same transaction and at the same time they receive
a specific error which is an indicator for them, go to the next operation in the queue.
An additional sub-process of each relayer observes all XRPL bridge account transactions and once it reaches that
submitted transaction it provides evidence with transaction status and data (using the
evidence queue). Once such evidence is confirmed, the tx result and data will be passed to the next step of a workflow,
(operation confirmation/rejection/cancellation) and operation removed from the signing queue.
The XRPL tickets allow us to execute a transaction with non-sequential sequence numbers, hence we can execute multiple
transactions in parallel. Any workflow can allocate a ticket and the ticket allocation mechanism either returns a ticket
number, or errors out, in case of lack of the free tickets. The ticket re-allocation will be triggered by the tx
confirmation (Submit XRPL transaction last step) once the used tickets count is greater than the allowed threshold. The
contract initiates the allocate tickets
operation to increase the amount. Once the operation is confirmed, the
contract increases the free slots on the contract as well (based on the tx result). In the case the allocate tickets
operation is rejected, another allocate tickets
operation will be initiated by the contract. If no tickets are
available, the contract will finish execution but notify with an event that it has run out of tickets. If this happens,
the contract owner must request the ticket recovery.
Check workflow for more details.
The contract receives a save evidence
request with an XRPL to Coreum transfer
evidence and starts the
corresponding workflow.
The Coreum bridge contract receives coins attached to the send to XRPL
command from a user, and
initiates workflow.
Each token in the registry contains a bridging fee config. The bridging fee is the fee that relayers earn for the transaction relaying. That fee covers their costs and provides some profit on top. The fee will be taken from the amount a user sends. The bridging fees are distributed across the relayer addresses after the execution of the sending, and locked until each relayer manually requests it (indicating how much of each denom he's requesting).
When a user transfers a token from the XRPL to Coreum we can compute the expected received amount based on the formula:
coreum_amount = xrpl_amount * 1e(token_decimals-15)
amount_after_bridging_fee = coreum_amount - bridging_fee
received_amount = truncate_amount(coreum_amount, ...)
The truncate_amount
is described here .
A relayer relays the amount sent as a part of the evidence. The contract does a pre-validation of the value.
If, after the calculation, the received_amount = 0
or received_amount > max allowed value
returns an error.
Once the evidence threshold is reached, the contract executes the calculation one more time, and sends the amount
to the recipient. The truncation reminder will be added to the bridge fees that the relayers can claim.
When a user transfers a token from the Coreum to an XRPL account we can compute the expected received amount based on formula:
amount_after_bridging_fee = amount - bridging_fee
truncated_amount = truncate_amount(amount_after_bridging_fee)
received_amount = truncated_amount * 1e(15-token_decimals)
The contract receives the send to XRPL
request for a user, executes the formula and checks, if after the calculation
the received_amount = 0
or amount + current bridged balance > max allowed bridged value
(only applied for Coreum
originated tokens) returns an error.
If all validation pass, the contract creates a sending operation with received_amount, and distributes relayers fees.
The send-to-XRPL
request has an optional field deliver_amount
that is used to indicate that the amount to be sent on
XRPL is less than the amount sent to the contract. This is a way to deal with tokens that have a transfer fee.
The fees will be calculated normally but the operation created will adjust the amount and max amount that
needs to be considered for the transaction on XRPL.
The owner can change a token bridging fees at any time. Since the price of a token can change the fee should be adjusted correspondingly.
At the time of the contract instantiation the owner sets the initial xrpl_base_fee
used for the XRPL transaction fee.
The formula for the fee is xrpl_tx_fee = (1 + number of signatures) * xrpl_base_fee
. The fee should be the same for
all relayers since it influences the operation signature. It is required for the fee to be updated since at some point
in time the XRPL chain might be under a high load and transactions from the Coreum to XRPL might be not accepted by the
nodes and get stuck. The fee update process helps to resolve such issues:
- owner calls the contract a provides new
xrpl_base_fee
- the contract
- updates the
xrpl_base_fee
in config - removes signatures from all pending operations
- increment the version of the pending operations
- updates the
Since the version of the operations is updated and xrpl_base_fee
is changed (increased for example) the relayers will
resign the transaction and a new fee will be used for the XRPL node to execute the transaction.
It is possible for any relayer or owner to halt the bridge contract at any time. The reason for it might be unexpected behavior of any bridge component. Only the owner can resume the bridge.
All accounts that can interact with the contract or multi-signing account are registered on the contract. And can be rotated using the keys rotation. The workflow is started by the owner, and can be confirmed by the current relayer set. The owner provides the new proposed relayer Coreum addresses, XRPL public keys and signing/evidence threshold. This action will automatically halt the bridge in case it is not halted yet and start the keys rotation workflow. If there is a keys rotation in process, the owner cannot trigger another keys rotation. Once the keys rotation operation has been confirmed by the relayers, the owner can trigger another keys rotation (if needed/it failed) and/or resume the bridge. Check workflow for more details.
The relayer is a connector of the XRPL bridge account on XRPL chain and smart contract. There are multiple instances of relayers, one for each key pair in the smart contract and multi-signing account. Most of the workflows are implemented as event processing produced by the contract and multi-signing account. The relayer source code is open-sourced and public, so can be updatable at any time by any relayer.
The number on the XRPL chain is handled by the string-numbers type. As the result some discrepancy might happen, examples:
- When a recipient balance is high and the recipient receives too low amount the delivery amount contains that amount, but recipient balance remains the same.
- When a sender sends too low amount to a recipient with high balance the sender's balance is changed but recipient's balance remains the same.
- It is possible to mint tokens on the XRPL by sending too low amounts to a recipient with low balance from an account with high balance.
- Send low and high amount to Coreum and return high and low back. 1.1. XRPLUser sends 10 XRPL originated token to Coreum user (bridge account balance: 10, Coreum user balance: 10) 1.2. XRPLUser sends 1e17 XRPL originated token to Coreum user (bridge account balance: 1e17, Coreum user balance: 1e17 + 10) 1.3. Coreum user sends 1e17 XRPL originated token to XRPLUser (bridge account balance: 0, XRPLUser balance: 1e17) 1.3. Coreum user sends 10 XRPL originated token to XRPLUser (bridge account: fail since we have nothing to send)
The same issue might be in case the bridge account holds 1e20 and receive 1 XRPL originated token. The delivered amount will contain the 1 XRPL originated token, but the bridge balance will remain the same.
- Send Coreum originated token to XRPL, mint it there, and send back. 1.1. CoreumUser sends 1e17 Coreum originated token to XRPLUser (bridge account balance: 0, bridge contract: 1e17, XRPLUser balance: 1e17) 1.2. XRPLUser mints 100 tokens on the XRPL by sending 49, 49, 2 amounts to a recipient with low balance and back. 1.3. XRPLUser sends 100 tokens back to Coreum user (bridge account balance: 0, bridge contract: 1e17 - 100, CoreumUser balance: 100) 1.3. XRPLUser sends 1e17 tokens back to Coreum user (bridge account balance: 0, bridge contract: fail not enough balance locked on the contract)
The sending precision
is the number in range -15:15
we use for each registered token. That value defines the min
precision for the amount you can send (round with sending precision
formula). Additionally, we define the
max holding amount
for to hold per token. We have both limits to make hardly possible both issues described before.
The rounding with sending precision
will eliminate the risk of sending to low amount which could influence on the
bridge, and the max holding amount
will significantly limit the chance to hold and amount using which a holder can
produce on XRPL the amount which is greater than rounded amount with the sending precision
(significant amount
).
That sending precision
can be calculated using the formula:
ratio = (total_token_supply) / 1e16
if ratio < 1 {
sending_precision = (count of zeros after `0.`)
}
if ratio == 1 {
sending_precision = 0
}
if ratio > 1 {
sending_precision = (count of integer numbers)
}
sending_precision = sending_precision - complexity_coefficient.
The 1e16
is the minimum amount a holder should have to produce one token on the XRPL submitting one transaction.
The total_token_supply
here is the total issued amount on the chain. For the XRPL chain we take simply total supply,
but for the Coreum chain we take the total supply divided by the token decimals, since the Coreum chain uses the integer
for the amounts, but XRPL uses float.
The complexity_coefficient
is coefficient we use to improve the risks handling
. Without that coefficient the formula
provides a risks handling
when an account holding the total supply is able to produce significant amount
submitting on XRPL transaction just one transaction. But with that coefficient, the account will have to submit
1e complexity_coefficient
transactions minimum to do it. The recommended value for the complexity_coefficient
is 4
, so it means 10000
transactions minimum are needed to produce the significant amount
.
The recommended max holding amount
should be less or equal 1e(16 - sending precision)
.
The round with sending precision
formula is:
// for the simplicity the formula doesn't include zero division handling
truncate_amount(
sending_precision,
decimals,
amount,
) {
exponent = decimals - sending_precision;
return amount / 1e(exponent) * 1e(exponent)
}
Rounding examples:
sending precision: 0
sending value: 99999.99999
rounded value: 99999
sending precision: 2
sending value: 99999.99999
rounded value: 99999.99
sending precision: -2
sending value: 99999.99999
rounded value: 99900