cosmwasm: accountant: Allow transfers on the same chain
While sending tokens to another address on the same chain via wormhole is quite inefficient, it's not strictly disallowed and we do have some VAAs on solana that do this. Explicitly check for this case and allow it. There are no restrictions on native tokens sent in this way but wrapped transfers are still subject to some checks: the wrapped account for the token must exist and it must have a balance larger than the amount being transferred. If either of those checks fails then that means that the sender acquired some wrapped tokens that did not go through the accountant and so that transfer should be blocked and require manual intervention.
This commit is contained in:
parent
4c7df41984
commit
9f0109388a
|
@ -155,7 +155,10 @@ fn transfer<C: CustomQuery>(deps: Deps<C>, t: &Transfer) -> anyhow::Result<(Acco
|
|||
Account { key, balance }
|
||||
};
|
||||
|
||||
let mut dst = {
|
||||
let mut dst = if t.key.emitter_chain() == t.data.recipient_chain {
|
||||
// This is a self-transfer so the source and destination accounts are the same.
|
||||
src.clone()
|
||||
} else {
|
||||
let key = account::Key::new(
|
||||
t.data.recipient_chain,
|
||||
t.data.token_chain,
|
||||
|
@ -180,8 +183,15 @@ fn transfer<C: CustomQuery>(deps: Deps<C>, t: &Transfer) -> anyhow::Result<(Acco
|
|||
|
||||
src.lock_or_burn(t.data.amount)
|
||||
.context(TransferError::InsufficientSourceBalance)?;
|
||||
dst.unlock_or_mint(t.data.amount)
|
||||
.context(TransferError::InsufficientDestinationBalance)?;
|
||||
|
||||
// If we're doing a transfer on the same chain then apply both changes to the same account.
|
||||
if src.key == dst.key {
|
||||
src.unlock_or_mint(t.data.amount)
|
||||
.context(TransferError::InsufficientDestinationBalance)?;
|
||||
} else {
|
||||
dst.unlock_or_mint(t.data.amount)
|
||||
.context(TransferError::InsufficientDestinationBalance)?;
|
||||
}
|
||||
|
||||
Ok((src, dst))
|
||||
}
|
||||
|
@ -467,6 +477,120 @@ mod tests {
|
|||
assert_eq!(tx.data, query_transfer(deps.as_ref(), tx.key).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_native_transfer() {
|
||||
let mut deps = mock_dependencies();
|
||||
let tx = Transfer {
|
||||
key: transfer::Key::new(3, [1u8; 32].into(), 5),
|
||||
data: transfer::Data {
|
||||
amount: Uint256::from(400u128),
|
||||
token_chain: 3,
|
||||
token_address: [3u8; 32].into(),
|
||||
recipient_chain: 3,
|
||||
},
|
||||
};
|
||||
|
||||
validate_transfer(deps.as_ref(), &tx).unwrap();
|
||||
commit_transfer(deps.as_mut(), tx.clone()).unwrap();
|
||||
|
||||
// Since we transfered within the same chain, the balance should be 0.
|
||||
let src = account::Key::new(
|
||||
tx.key.emitter_chain(),
|
||||
tx.data.token_chain,
|
||||
tx.data.token_address,
|
||||
);
|
||||
assert_eq!(Balance::zero(), query_balance(deps.as_ref(), src).unwrap());
|
||||
|
||||
let dst = account::Key::new(
|
||||
tx.data.recipient_chain,
|
||||
tx.data.token_chain,
|
||||
tx.data.token_address,
|
||||
);
|
||||
assert_eq!(Balance::zero(), query_balance(deps.as_ref(), dst).unwrap());
|
||||
|
||||
assert_eq!(tx.data, query_transfer(deps.as_ref(), tx.key).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_wrapped_transfer() {
|
||||
let mut deps = mock_dependencies();
|
||||
let mut tx = Transfer {
|
||||
key: transfer::Key::new(9, [1u8; 32].into(), 5),
|
||||
data: transfer::Data {
|
||||
amount: Uint256::from(400u128),
|
||||
token_chain: 3,
|
||||
token_address: [3u8; 32].into(),
|
||||
recipient_chain: 9,
|
||||
},
|
||||
};
|
||||
|
||||
// A local wrapped transfer is only allowed if we've already issued wrapped tokens.
|
||||
let e = validate_transfer(deps.as_ref(), &tx).unwrap_err();
|
||||
assert!(matches!(
|
||||
e.downcast().unwrap(),
|
||||
TransferError::MissingWrappedAccount
|
||||
));
|
||||
|
||||
let e = commit_transfer(deps.as_mut(), tx.clone())
|
||||
.expect_err("successfully committed duplicate transfer");
|
||||
assert!(matches!(
|
||||
e.downcast().unwrap(),
|
||||
TransferError::MissingWrappedAccount
|
||||
));
|
||||
|
||||
// Issue some wrapped tokens.
|
||||
let wrapped = tx.data.amount.checked_div(Uint256::from(2u128)).unwrap();
|
||||
let m = Modification {
|
||||
sequence: 1,
|
||||
chain_id: tx.key.emitter_chain(),
|
||||
token_chain: tx.data.token_chain,
|
||||
token_address: tx.data.token_address,
|
||||
kind: Kind::Add,
|
||||
amount: wrapped,
|
||||
reason: "test".into(),
|
||||
};
|
||||
modify_balance(deps.as_mut(), m).unwrap();
|
||||
|
||||
// The transfer should still fail because we're trying to move more wrapped tokens than
|
||||
// were issued.
|
||||
let e = validate_transfer(deps.as_ref(), &tx).unwrap_err();
|
||||
assert!(matches!(
|
||||
e.downcast().unwrap(),
|
||||
TransferError::InsufficientSourceBalance
|
||||
));
|
||||
|
||||
let e = commit_transfer(deps.as_mut(), tx.clone())
|
||||
.expect_err("successfully committed duplicate transfer");
|
||||
assert!(matches!(
|
||||
e.downcast().unwrap(),
|
||||
TransferError::InsufficientSourceBalance
|
||||
));
|
||||
|
||||
// Lower the amount in the transfer.
|
||||
tx.data.amount = wrapped;
|
||||
|
||||
// Now the transfer should be fine.
|
||||
validate_transfer(deps.as_ref(), &tx).unwrap();
|
||||
commit_transfer(deps.as_mut(), tx.clone()).unwrap();
|
||||
|
||||
// The balance should not have changed.
|
||||
let src = account::Key::new(
|
||||
tx.key.emitter_chain(),
|
||||
tx.data.token_chain,
|
||||
tx.data.token_address,
|
||||
);
|
||||
assert_eq!(wrapped, *query_balance(deps.as_ref(), src).unwrap());
|
||||
|
||||
let dst = account::Key::new(
|
||||
tx.data.recipient_chain,
|
||||
tx.data.token_chain,
|
||||
tx.data.token_address,
|
||||
);
|
||||
assert_eq!(wrapped, *query_balance(deps.as_ref(), dst).unwrap());
|
||||
|
||||
assert_eq!(tx.data, query_transfer(deps.as_ref(), tx.key).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_transfer() {
|
||||
let mut deps = mock_dependencies();
|
||||
|
|
Loading…
Reference in New Issue