Transfers
There are some complex interactions involved with transfers of restricted coins.
General
Accounting of restricted coins is handled by the bank module. Restricted funds can be moved using the bank module's MsgSend
or MsgMutliSend
. They can also be moved using the marker module's MsgTransferRequest
.
During such transfers several things are checked using a SendRestrictionFn
injected into the bank module. This restriction is applied in almost all instances when funds are being moved between accounts. The exceptions are delegations, undelegations, minting, burning, and marker withdrawals. A MsgTransferRequest
also bypasses the SendRestrictionFn
in order to include the admin
account in the logic.
Definitions
Transfer Permission
One permission that can be granted to an address is transfer
. The transfer
permission is granted to accounts that represent a "Transfer Agent" or "Transfer Authority" for restricted marker tokens. An address with transfer
permission can utilize MsgTransferRequest
to move restricted funds from one account to another. The source account must be the admin's own account, or else there must be a MarkerTransferAuthorization
grant (in the authz
module) from the source account to the admin.
MsgSend
and MsgMultiSend
can also be used by an address with transfer
permission to move funds out of their own account.
Force Transfer Permission
If a restricted marker allows forced transfers, the force_transfer
permission grants an account the ability to use the Transfer
endpoint to move marker funds out of almost any account. An account with force_transfer
cannot use other means to move marker funds (e.g. MsgSend
) unless they also have transfer
access.
Forced Transfers
A restricted coin marker can be configured to allow forced transfers. If allowed, an account with force_transfer
permission can use a MsgTransferRequest
to transfer the restricted coins out of almost any account to another. Forced transfer cannot be used to move restricted coins out of module accounts or smart contract accounts, though. Forced transfers can only be made using a MsgTransferRequest
.
Required Attributes
Required attributes allow a marker Transfer Authority to define a set of account attestations created with the name/attribute modules to certify an account as an approved holder of the token. Accounts that possess all of the required attributes are considered authorized by the Transfer Authority to receive the token from normal bank send operations without a specific Transfer Authority approval. Required attributes are only supported on restricted markers.
For example, say account A has some restricted coins of a marker that has required attributes. Also say account B has all of those required attributes, and account C does not. Account A could use a MsgSend
to send those restricted coins to account B. However, account B could not send them to account C (unless B also has transfer
permission).
If a restricted coin marker does not have any required attributes defined, the only way the funds can be moved is by someone with transfer
permission.
Individuality
If multiple restricted coin denoms are being moved at once, each denom is considered separately.
For example, if the sender has transfer
permission on one of them, it does not also apply to the other(s).
Deposits
A deposit is when any funds are being sent to a marker's account. The funds being sent do not have to be in the denom of the destination marker.
Whenever funds are being deposited into a marker, the sender (or transfer authority) must have deposit
permission on the target marker. If the funds to deposit are restricted coins, the sender (or transfer authority) also needs transfer
permission on the funds being moved; required attributes are not taken into account.
Withdraws
A withdrawal is when any funds are being sent out of a marker's account. The funds being sent do not have to be in the denom of the source marker.
Withdraws can be made using the Withdraw
endpoint, or another endpoint that utilizes a transfer agent (e.g. the exchange module's MarketCommitmentSettle
).
Whenever funds are being withdrawn, the transfer agent must have withdraw
permission on the source marker. If the funds to withdraw are of the source marker's denom, the source marker must be active. The transfer agent must also have transfer
permission on any restricted coins being moved.
Bypass Accounts
There are several hard-coded module account addresses that are given special consideration in the marker module's SendRestrictionFn
:
authtypes.FeeCollectorName
- Allows paying fees with restricted coins.reward
- Allows reward programs to use restricted coins.quarantine
- Allows quarantine and acceptance of quarantined coins.gov
- Allows deposits to have quarantined coins.distribution
- Allows collection of delegation rewards in restricted coins.stakingtypes.BondedPoolName
- Allows delegation of restricted coins.stakingtypes.NotBondedPoolName
- Allows delegation of restricted coins.
All of these are treated equally in the application of a marker's send restrictions.
For restricted markers without required attributes:
- If the
toAddr
is a bypass account, thefromAddr
must have transfer authority. - If the
fromAddr
is a bypass account, it's assumed that the funds got where they currently are because someone with transfer authority got them there, so this transfer is allowed.
For restricted markers with required attributes:
- If the
toAddr
is a bypass account, the transfer is allowed regardless of whether thefromAddr
has transfer authority. It's assumed that the next destination's attributes will be properly checked before allowing the funds to leave the bypass account. - If the
fromAddr
is a bypass account, thetoAddr
must have the required attributes.
Bypass accounts are not considered during a MsgTransferRequest
.
Send Restrictions
The marker module injects a SendRestrictionFn
into the bank module. This function is responsible for deciding whether any given movement of funds (e.g. a MsgSend
) is allowed from the marker module's point of view. However, it is bypassed for movements initiated within the marker module (e.g. during a Transfer
).
Flowcharts
The SendRestrictionFn
The SendRestrictionFn
uses the following flow to decide whether a send is allowed. It utilizes the checkSenderMarker, checkReceiverMarker, and validateSendDenom flows.
%%{ init: { 'flowchart': { 'curve': 'monotoneY'} } }%%
flowchart TD
start[["SendRestrictionFn(Sender, Receiver, Amount)"]]
qhasbp{{"Does context have bypass, or is the Sender either\nthe marker module or ibc transfer account?"}}
qfc{{"Is the Receiver the fee collector?"}}
qrc{{"Is there a restricted coin in the Amount?"}}
gta["Get Transfer Agent from the context if possible."]
csm[["checkSenderMarker(Sender, Transfer Agent)"]]
issmok{{"Proceed?"}}
crm[["checkReceiverMarker(Receiver, Sender, Transfer Agent)"]]
isrmok{{"Proceed?"}}
nextd["Get next Denom from Amount."]
vsd[["validateSendDenom(Sender, Receiver, Denom)"]]
isdok{{"Is Denom transfer allowed?"}}
mored{{"Does Amount have another Denom?"}}
ok(["Send allowed."])
style ok fill:#bbffaa,stroke:#1b8500,stroke-width:3px
denied(["Send denied."])
style denied fill:#ffaaaa,stroke:#b30000,stroke-width:3px
start --> qhasbp
qhasbp -.->|no| gta
qhasbp -->|yes| qfc
qfc -->|yes| qrc
qfc -.->|no| ok
qrc -------->|yes| denied
qrc -.->|no| ok
gta --> csm
csm --> issmok
issmok -->|yes| crm
issmok -.->|no| denied
crm --> isrmok
isrmok -->|yes| denomloop
isrmok -.->|no| denied
subgraph denomloop ["Denom Loop"]
isdok -->|yes| mored
vsd --> isdok
mored -->|yes| nextd
nextd --> vsd
end
mored -....->|no| ok
isdok -.->|no| denied
style denomloop fill:#bbffff
linkStyle 5,10,13,19 stroke:#b30000,color:#b30000
linkStyle 4,6,18 stroke:#1b8500,color:#1b8500
checkSenderMarker
This flow checks that, if this is a withdrawal, nothing (yet) prevents the send. It is used in the SendRestrictionFn flow.
%%{ init: { 'flowchart': { 'curve': 'monotoneY'} } }%%
flowchart TD
start[["checkSenderMarker(Sender, Transfer Agent)"]]
issm{{"Is Sender a marker?"}}
haveta{{"Is there a Transfer Agent?"}}
istaw{{"Does the Transfer Agent\nhave withdraw access?"}}
isasm{{"Does the Amount have\nthe Sender marker's denom?"}}
issma{{"Is Sender marker active?"}}
ok(["Proceed."])
style ok fill:#bbffaa,stroke:#1b8500,stroke-width:3px
denied(["Send denied."])
style denied fill:#ffaaaa,stroke:#b30000,stroke-width:3px
start --> issm
issm -->|yes| haveta
haveta -->|yes| istaw
haveta -.->|no| denied
istaw -.->|no| denied
istaw -->|yes| isasm
isasm -->|yes| issma
issma -->|yes| ok
isasm -.->|no| ok
issma -.->|no| denied
issm -.->|no| ok
linkStyle 3,4,9 stroke:#b30000,color:#b30000
linkStyle 7,8,10 stroke:#1b8500,color:#1b8500
checkReceiverMarker
This flow checks that, if this is a deposit, nothing (yet) prevents the send. It is used in the SendRestrictionFn and MsgTransferRequest flows.
%%{ init: { 'flowchart': { 'curve': 'monotoneY'} } }%%
flowchart TD
start[["checkReceiverMarker(Receiver, Sender, Transfer Agent)"]]
issm{{"Is Receiver a restricted marker?"}}
haveta{{"Is there a Transfer Agent?"}}
isrd{{"Does Sender\nhave deposit access?"}}
istad{{"Does Transfer Agent\nhave deposit access?"}}
ok(["Proceed."])
style ok fill:#bbffaa,stroke:#1b8500,stroke-width:3px
denied(["Send denied."])
style denied fill:#ffaaaa,stroke:#b30000,stroke-width:3px
start --> issm
issm -->|yes| haveta
haveta -.->|no| isrd
haveta -->|yes| istad
isrd -.->|no| denied
isrd -->|yes| ok
istad -.->|no| denied
istad -->|yes| ok
issm -.->|no| ok
linkStyle 4,6 stroke:#b30000,color:#b30000
linkStyle 5,7,8 stroke:#1b8500,color:#1b8500
validateSendDenom
Each Denom
is checked using validateSendDenom
, which has this flow. It is used in the SendRestrictionFn flow.
%%{ init: { 'flowchart': { 'curve': 'monotoneY'} } }%%
flowchart TD
start[["validateSendDenom(Sender, Receiver, Denom)"]]
isdm{{"Is there a marker for Denom?"}}
isma{{"Is the marker active?"}}
qisrc{{"Is Denom a restricted coin?"}}
qistofc{{"Is Receiver the fee collector?"}}
ista{{"Is there a Transfer Agent\nwith transfer access?"}}
qisdeny{{"Is Sender on marker's deny list?"}}
qhastrans{{"Does Sender have\ntransfer for Denom?"}}
qisdep{{"Is Receiver a marker account?"}}
qmhasattr{{"Does Denom have\nrequired attributes?"}}
qissbp{{"Is Sender a\nbypass account?"}}
qisrbp{{"Is Receiver a\nbypass account?"}}
qrhasattr{{"Does Receiver have\nthe required attributes?"}}
ok(["Denom transfer allowed."])
style ok fill:#bbffaa,stroke:#1b8500,stroke-width:3px
denied(["Send denied."])
style denied fill:#ffaaaa,stroke:#b30000,stroke-width:3px
start --> isdm
isdm -->|yes| isma
isdm -.->|no| ok
isma -.->|no| denied
isma -->|yes| qisrc
qisrc -->|yes| qistofc
qisrc -.->|no| ok
qistofc -->|yes| denied
qistofc -.->|no| ista
ista -.->|no| qisdeny
ista -->|yes| ok
qisdeny -->|yes| denied
qisdeny -.->|no| qhastrans
qhastrans -.->|no| qisdep
qhastrans -->|yes| ok
qisdep -->|yes| denied
qisdep -.->|no| qmhasattr
qmhasattr -.->|no| qissbp
qmhasattr -->|yes| qisrbp
qissbp -..->|no| denied
qissbp --->|yes| ok
qisrbp -.->|no| qrhasattr
qisrbp -->|yes| ok
qrhasattr -.->|no| denied
qrhasattr -->|yes| ok
linkStyle 3,7,11,15,19,23 stroke:#b30000,color:#b30000
linkStyle 2,6,10,14,20,22,24 stroke:#1b8500,color:#1b8500
Note that force_transfer
access is not considered at all in the SendRestrictionFn
.
Only a MsgTransferRequest
can be used to force a transfer.
MsgTransferRequest
A MsgTransferRequest
bypasses the SendRestrictionFn
and applies its own logic. A MsgTransferRequest
only allows for a single coin amount, i.e. there's only one Denom
to consider. It makes use of the checkReceiverMarker flow.
%%{ init: { 'flowchart': { 'curve': 'monotoneY'} } }%%
flowchart TD
start[["TransferCoin(Sender, Receiver, Admin)"]]
qisrc{{"Is Denom a restricted coin?"}}
qhast{{"Does Admin have transfer\nor force-transfer for Denom?"}}
crm[["checkReceiverMarker(Receiver, Sender, Admin)"]]
iscrmbad{{"Proceed?"}}
qadminfrom{{"Does Sender == Admin?"}}
qallowft{{"Is forced transfer allowed for Denom?"}}
qhasft{{"Does Admin have force-transfer?"}}
qauthz{{"Has Sender granted Admin\npermission with authz?"}}
qmodacc{{"Is Sender a\nmodule account?"}}
qblocked{{"Is Receiver an\naddress blocked by\nthe bank module?"}}
ok(["Transfer allowed."])
style ok fill:#bbffaa,stroke:#1b8500,stroke-width:3px
denied(["Transfer denied."])
style denied fill:#ffaaaa,stroke:#b30000,stroke-width:3px
start --> qisrc
qisrc -.->|no| denied
qisrc -->|yes| qhast
qhast -.->|no| denied
qhast -->|yes| crm
crm --> iscrmbad
iscrmbad -.->|no| denied
iscrmbad -->|yes| qadminfrom
qadminfrom -.->|no| qallowft
qadminfrom -->|yes| qblocked
qallowft -.->|no| qauthz
qallowft -->|yes| qhasft
qhasft -.->|no| qauthz
qhasft -->|yes| qmodacc
qmodacc -->|yes| denied
qmodacc -.->|no| qblocked
qauthz -.->|no| denied
qauthz -->|yes| qblocked
qblocked -->|yes| denied
qblocked -.->|no| ok
linkStyle 1,3,6,14,16,18 stroke:#b30000,color:#b30000
linkStyle 19 stroke:#1b8500,color:#1b8500
Quarantine Complexities
There are some notable complexities involving restricted coins and quarantined accounts.
Sending Restricted Coins to a Quarantined Account
The marker module's SendRestrictionFn
is applied before the quarantine module's. So, when funds are being sent to a quarantined account, the marker module runs its check using the original Sender
and Receiver
(i.e. the Receiver
is not QFH
).
If the Receiver
is a quarantined account, we can assume that it is neither a marker, nor a bypass account. Then, (as long as the Sender
is not on the deny list), the validateSendDenom
flow can be simplified to this for restricted coins.
%%{ init: { 'flowchart': { 'curve': 'monotoneY'} } }%%
flowchart LR
vsd[["validateSendDenom(Sender, Receiver, Denom)"]]
transq{{"Does Sender have\ntransfer for Denom?"}}
mreqattr{{"Does Denom have\nrequired attributes?"}}
treqattr{{"Does Receiver have\nthose attributes?"}}
ok(["Denom transfer allowed."])
style ok fill:#bbffaa,stroke:#1b8500,stroke-width:3px
denied(["Send denied."])
style denied fill:#ffaaaa,stroke:#b30000,stroke-width:3px
transq -->|yes| ok
transq -.->|no| mreqattr
mreqattr -->|yes| treqattr
mreqattr -.->|no| denied
treqattr -->|yes| ok
treqattr -.->|no| denied
linkStyle 3,5 stroke:#b30000,color:#b30000
linkStyle 0,4 stroke:#1b8500,color:#1b8500
If the Send
is allowed, and the Receiver
is a quarantined account, the quarantine module's SendRestrictionFn
will then change the Send
's destination to QFH
(the Quarantined-funds-holder account) and make a record of the transfer. The Send
then transfers funds from the Sender
to QFH
.
The marker's SendRestrictionFn
should never have QFH
as a Receiver
. The only way this would happen is if MsgSend
is used to send funds directly to QFH
.
If MsgTransferRequest
is used to transfer a restricted coin to a quarantined account, the standard MsgTransferRequest
logic is applied (bypassing the marker module's SendRestrictionFn
). The quarantine module's SendRestrictionFn
is not bypassed, though, so the funds still go to the QFH
.
Accepting Quarantined Restricted Coins
Once funds have been sent to QFH
, the Receiver
will probably want to accept them, and have them sent to their account. They issue an Accept
to the quarantine module which utilizes the bank module's Send
functionality to try to transfer funds from QFH
to the Receiver
.
QFH
is a bypass account. Since Receiver
is a quarantined account, we can assume that it is neither a marker nor bypass account. So, the validateSendDenom
flow can be simplified to this for restricted coins.
%%{ init: { 'flowchart': { 'curve': 'monotoneY'} } }%%
flowchart LR
vsd[["validateSendDenom(Sender, Receiver, Denom)"]]
mreqattr{{"Does Denom have\nrequired attributes?"}}
treqattr{{"Does Receiver have\nthose attributes?"}}
ok(["Denom transfer allowed."])
style ok fill:#bbffaa,stroke:#1b8500,stroke-width:3px
denied(["Send denied."])
style denied fill:#ffaaaa,stroke:#b30000,stroke-width:3px
mreqattr -->|yes| treqattr
mreqattr -.->|no| ok
treqattr -->|yes| ok
treqattr -.->|no| denied
linkStyle 3 stroke:#b30000,color:#b30000
linkStyle 1,2 stroke:#1b8500,color:#1b8500
If the Send
is allowed, the requested funds are transferred from QFH
to Receiver
.
If the Send
is denied, the funds remain with QFH
.
An important subtle part of this process is the rechecking of Receiver
attributes. It's possible for the initial send to be okay (causing funds to be quarantined), then later, during this Accept
, the send is not okay, and the quarantined funds are effectively locked withQFH
until the Receiver
gets the required attributes.
If the marker does not have required attributes though, it's assumed that they were originally sent by someone with transfer authority, so they are allowed to continue from here too.
Successful Quarantine and Accept Sequence
When restricted coin funds are sent to a quarantined account (1), the marker's SendRestrictionFn
is called using the original Sender
and Receiver
(2). Then, the quarantine's SendRestrictionFn
is called (4) which will return QFH
for the new destination (5). Funds are then transferred from Sender
to QFH
(6).
When the Receiver
attempts to Accept
those quarantined funds (7), the marker's SendRestrictionFn
is called again, this time using QFH
(as the sender) and Receiver
(9). The quarantine's SendRestrictionFn
is bypassed (11), so the destination is not changed (12). Funds are then transferred from QFH
to Receiver
(13).
sequenceDiagram
autonumber
actor Sender
actor Receiver
participant Bank Module
participant Quarantine Module
participant Marker Restriction
participant Quarantine Restriction
participant QFH
Sender ->>+ Bank Module: Send(sender, receiver)
Bank Module ->>+ Marker Restriction: Is this send from Sender to Receiver allowed?
Marker Restriction -->>- Bank Module: Yes
Bank Module ->>+ Quarantine Restriction: Is Receiver quarantined?
Quarantine Restriction -->>- Bank Module: Yes. Change destination to QFH.
Sender ->> QFH: Funds transferred from Sender to QFH.
deactivate Bank Module
Note over Sender,QFH: Some Time Later
Receiver ->>+ Quarantine Module: Accept(receiver, sender)
Quarantine Module ->> Bank Module: Send(QFH, receiver)
activate Bank Module
Bank Module ->>+ Marker Restriction: Is this send from QFH to Receiver allowed?
Marker Restriction -->>- Bank Module: Yes
Bank Module ->>+ Quarantine Restriction: Is Receiver quarantined?
Quarantine Restriction -->>- Bank Module: Restriction bypassed. No change.
QFH ->> Receiver: Funds transferred from QFH to Receiver.
deactivate Bank Module
deactivate Quarantine Module