-
Notifications
You must be signed in to change notification settings - Fork 13
Description
Large amounts of native NEAR will always fail to finalize and, thus, remain stuck in omni-bridge contract forever:
omni-bridge/near/omni-bridge/src/lib.rs
Lines 1042 to 1046 in 707df6f
| .near_withdraw(amount_to_transfer) | |
| .then( | |
| Promise::new(recipient) | |
| .transfer(NearToken::from_yoctonear(amount_to_transfer.0)), | |
| ) |
An example situation when this might happen would be if omni-bridge has only 100 native NEAR balance, while relayer tries to finalize 1000 native NEAR withdrawal, even if the contract owns 1000+ wNEAR.
The reason for that is sharded Near architecture: when Near VM schedules a native transfer Promise to increase balance of recipient, it also subtracts the amount from current_account_id during execution of current receipt, but not when scheduled promise is executed. If there is not enough native NEAR balance on the contract at the time of Promise creation, then current receipt will fail with ExecutionError("Exceeded the account balance.").
To mitigate this issue, an additional Promise should be scheduled as a callback to near_withdraw, and the actual transfer Promise should only be created in this callback:
// first withdraw
.near_withdraw(U128(withdraw.amount.as_yoctonear()))
.then(
// do_native_withdraw only after unwrapping NEAR
Contract::ext(CURRENT_ACCOUNT_ID.clone())
.with_static_gas(Contract::DO_NATIVE_WITHDRAW_GAS)
.do_native_withdraw(withdraw),
)
// ...
#[private]
pub fn do_native_withdraw(&mut self, withdraw: NativeWithdraw) -> Promise {
require!(
matches!(env::promise_result(0), PromiseResult::Successful(data) if data.is_empty()),
"near_withdraw failed",
);
Promise::new(withdraw.receiver_id).transfer(withdraw.amount)
}PS: please, note that current implementation of #147 also suffers from such issue.