diff --git a/CHANGES.md b/CHANGES.md index 72239e90a..65c3208eb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.6.3] 2024-04-16 + +### Additions + +- Added a possibility to pause FT transfers for the internal eth connector by [@karim-en]. ([#922]) + +[#922]: https://github.com/aurora-is-near/aurora-engine/pull/922 + ## [3.6.2] 2024-03-27 ### Additions @@ -643,7 +651,8 @@ struct SubmitResult { ## [1.0.0] - 2021-05-12 -[Unreleased]: https://github.com/aurora-is-near/aurora-engine/compare/3.6.2...develop +[Unreleased]: https://github.com/aurora-is-near/aurora-engine/compare/3.6.3...develop +[3.6.3]: https://github.com/aurora-is-near/aurora-engine/compare/3.6.2...3.6.3 [3.6.2]: https://github.com/aurora-is-near/aurora-engine/compare/3.6.1...3.6.2 [3.6.1]: https://github.com/aurora-is-near/aurora-engine/compare/3.6.0...3.6.1 [3.6.0]: https://github.com/aurora-is-near/aurora-engine/compare/3.5.0...3.6.0 diff --git a/Cargo.lock b/Cargo.lock index bf84a78b5..477c00ea9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -270,7 +270,7 @@ dependencies = [ [[package]] name = "aurora-engine" -version = "3.6.2" +version = "3.6.3" dependencies = [ "aurora-engine-hashchain", "aurora-engine-modexp", @@ -2163,9 +2163,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -5974,6 +5974,12 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.91" @@ -6469,11 +6475,12 @@ dependencies = [ [[package]] name = "whoami" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" dependencies = [ - "wasm-bindgen", + "redox_syscall", + "wasite", "web-sys", ] diff --git a/VERSION b/VERSION index b72762837..4a788a01d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.6.2 +3.6.3 diff --git a/engine-tests-connector/src/utils.rs b/engine-tests-connector/src/utils.rs index c3d78e848..254e636cb 100644 --- a/engine-tests-connector/src/utils.rs +++ b/engine-tests-connector/src/utils.rs @@ -31,6 +31,8 @@ pub const UNPAUSE_ALL: PausedMask = 0; pub const PAUSE_DEPOSIT: PausedMask = 1 << 0; /// Admin control flow flag indicates that withdrawal is paused. pub const PAUSE_WITHDRAW: PausedMask = 1 << 1; +/// Admin control flow flag indicates that ft transfers are paused. +pub const PAUSE_FT: PausedMask = 1 << 2; pub struct TestContract { pub engine_contract: Contract, diff --git a/engine-tests/src/tests/erc20_connector.rs b/engine-tests/src/tests/erc20_connector.rs index 98bf20837..721b127b1 100644 --- a/engine-tests/src/tests/erc20_connector.rs +++ b/engine-tests/src/tests/erc20_connector.rs @@ -814,6 +814,145 @@ pub mod workspace { assert_eq!(result, balances); } + #[cfg(not(feature = "ext-connector"))] + #[tokio::test] + async fn test_pause_ft_transfer() { + use aurora_engine::contract_methods::connector::internal::{PAUSE_FT, UNPAUSE_ALL}; + use aurora_engine::parameters::FungibleTokenMetadata; + use aurora_engine_types::account_id::AccountId; + + use crate::utils::workspace::storage_deposit_nep141; + + let aurora = deploy_engine().await; + let metadata = FungibleTokenMetadata::default(); + aurora + .set_eth_connector_contract_data( + aurora.id(), + ETH_CUSTODIAN_ADDRESS.to_string(), + metadata, + ) + .transact() + .await + .unwrap(); + + deposit_balance(&aurora).await; + + let recipient_id = AccountId::new("account1").unwrap(); + let transfer_amount = 10; + + // Pause ft transfers + aurora.set_paused_flags(PAUSE_FT).transact().await.unwrap(); + // Verify that the storage deposit is paused + let result = storage_deposit_nep141(&aurora.id(), &aurora.root(), recipient_id.as_ref()) + .await + .unwrap() + .into_result(); + assert!(result.unwrap_err().to_string().contains("ERR_FT_PAUSED")); + // Try to transfer tokens + let result = aurora + .ft_transfer(&recipient_id, transfer_amount.into(), None) + .deposit(ONE_YOCTO) + .transact() + .await; + assert!(result.unwrap_err().to_string().contains("ERR_FT_PAUSED")); + // Verify that no tokens were transferred + let blanace = aurora.ft_balance_of(&recipient_id).await.unwrap().result; + assert_eq!(blanace.0, 0); + + // Unpause ft transfers + aurora + .set_paused_flags(UNPAUSE_ALL) + .transact() + .await + .unwrap(); + // Transfer tokens + aurora + .ft_transfer(&recipient_id, transfer_amount.into(), None) + .deposit(ONE_YOCTO) + .transact() + .await + .unwrap(); + // Verify that the tokens has been transferred + let blanace = aurora.ft_balance_of(&recipient_id).await.unwrap().result; + assert_eq!(blanace.0, transfer_amount); + } + + #[cfg(not(feature = "ext-connector"))] + #[tokio::test] + async fn test_pause_ft_transfer_call() { + use crate::utils::workspace::transfer_call_nep_141; + use aurora_engine::contract_methods::connector::internal::{PAUSE_FT, UNPAUSE_ALL}; + use aurora_engine::parameters::FungibleTokenMetadata; + + let aurora = deploy_engine().await; + let metadata = FungibleTokenMetadata::default(); + aurora + .set_eth_connector_contract_data( + aurora.id(), + ETH_CUSTODIAN_ADDRESS.to_string(), + metadata, + ) + .transact() + .await + .unwrap(); + + deposit_balance(&aurora).await; + + let ft_owner = create_sub_account(&aurora.root(), "ft_owner", BALANCE) + .await + .unwrap(); + let transfer_amount = 10; + // Transfer tokens to the `ft_owner` account + aurora + .ft_transfer(&ft_owner.id(), transfer_amount.into(), None) + .deposit(ONE_YOCTO) + .transact() + .await + .unwrap(); + let blanace = aurora.ft_balance_of(&ft_owner.id()).await.unwrap().result; + assert_eq!(blanace.0, transfer_amount); + + // Pause ft transfers + aurora.set_paused_flags(PAUSE_FT).transact().await.unwrap(); + // Try to transfer tokens from `ft_owner` to `aurora` contract by `ft_transfer_call` + let transfer_call_msg = "000000000000000000000000000000000000dead"; + let result = transfer_call_nep_141( + &aurora.id(), + &ft_owner, + aurora.id().as_ref(), + transfer_amount, + transfer_call_msg, + ) + .await + .unwrap() + .into_result(); + assert!(result.unwrap_err().to_string().contains("ERR_FT_PAUSED")); + let blanace = aurora.ft_balance_of(&ft_owner.id()).await.unwrap().result; + assert_eq!(blanace.0, transfer_amount); + + // Unpause ft transfers + aurora + .set_paused_flags(UNPAUSE_ALL) + .transact() + .await + .unwrap(); + // Transfer tokens from `ft_owner` to `aurora` contract by `ft_transfer_call` + transfer_call_nep_141( + &aurora.id(), + &ft_owner, + aurora.id().as_ref(), + transfer_amount, + transfer_call_msg, + ) + .await + .unwrap() + .into_result() + .unwrap(); + // Verify that the tokens has been transferred + let blanace = aurora.ft_balance_of(&ft_owner.id()).await.unwrap().result; + assert_eq!(blanace.0, 0); + } + async fn test_exit_to_near_eth_common() -> anyhow::Result { let aurora = deploy_engine().await; let chain_id = aurora.get_chain_id().await?.result.as_u64(); diff --git a/engine-tests/src/utils/workspace.rs b/engine-tests/src/utils/workspace.rs index 105178717..2f7fde05a 100644 --- a/engine-tests/src/utils/workspace.rs +++ b/engine-tests/src/utils/workspace.rs @@ -10,6 +10,8 @@ use aurora_engine_types::parameters::connector::{FungibleTokenMetadata, Withdraw use aurora_engine_types::types::Address; use aurora_engine_types::U256; use aurora_engine_workspace::account::Account; +#[cfg(not(feature = "ext-connector"))] +use aurora_engine_workspace::types::ExecutionFinalResult; use aurora_engine_workspace::{types::NearToken, EngineContract, RawContract}; use serde_json::json; @@ -236,6 +238,45 @@ pub async fn transfer_nep_141( Ok(()) } +#[cfg(not(feature = "ext-connector"))] +pub async fn storage_deposit_nep141( + nep_141: &AccountId, + source: &Account, + dest: &str, +) -> anyhow::Result { + source + .call(nep_141, "storage_deposit") + .args_json(json!({ + "account_id": dest, + })) + .deposit(STORAGE_AMOUNT) + .max_gas() + .transact() + .await +} + +#[cfg(not(feature = "ext-connector"))] +pub async fn transfer_call_nep_141( + nep_141: &AccountId, + source: &Account, + dest: &str, + amount: u128, + msg: &str, +) -> anyhow::Result { + source + .call(nep_141, "ft_transfer_call") + .args_json(json!({ + "receiver_id": dest, + "amount": amount.to_string(), + "memo": "null", + "msg": msg, + })) + .deposit(NearToken::from_yoctonear(1)) + .max_gas() + .transact() + .await +} + #[cfg(feature = "ext-connector")] fn get_aurora_eth_connector_contract() -> Vec { use std::path::Path; diff --git a/engine/Cargo.toml b/engine/Cargo.toml index b28e9ddbc..4f5c5d854 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aurora-engine" -version = "3.6.2" +version = "3.6.3" authors.workspace = true edition.workspace = true homepage.workspace = true diff --git a/engine/src/contract_methods/connector/errors.rs b/engine/src/contract_methods/connector/errors.rs index 6a397502a..7a793a780 100644 --- a/engine/src/contract_methods/connector/errors.rs +++ b/engine/src/contract_methods/connector/errors.rs @@ -91,6 +91,7 @@ pub enum FtTransferCallError { MessageParseFailed(ParseOnTransferMessageError), InsufficientAmountForFee, Transfer(TransferError), + Paused, } impl From for FtTransferCallError { @@ -118,6 +119,7 @@ impl AsRef<[u8]> for FtTransferCallError { Self::InsufficientAmountForFee => errors::ERR_NOT_ENOUGH_BALANCE_FOR_FEE, Self::Transfer(e) => e.as_ref(), Self::BalanceOverflow(e) => e.as_ref(), + Self::Paused => errors::ERR_FT_PAUSED, } } } @@ -200,6 +202,7 @@ pub enum TransferError { SelfTransfer, Deposit(DepositError), Withdraw(WithdrawError), + Paused, } impl AsRef<[u8]> for TransferError { @@ -213,6 +216,7 @@ impl AsRef<[u8]> for TransferError { Self::SelfTransfer => errors::ERR_SENDER_EQUALS_RECEIVER, Self::Deposit(e) => e.as_ref(), Self::Withdraw(e) => e.as_ref(), + Self::Paused => errors::ERR_FT_PAUSED, } } } @@ -239,6 +243,7 @@ pub enum StorageFundingError { NoAvailableBalance, InsufficientDeposit, UnRegisterPositiveBalance, + Paused, } impl AsRef<[u8]> for StorageFundingError { @@ -250,6 +255,7 @@ impl AsRef<[u8]> for StorageFundingError { Self::UnRegisterPositiveBalance => { errors::ERR_FAILED_UNREGISTER_ACCOUNT_POSITIVE_BALANCE } + Self::Paused => errors::ERR_FT_PAUSED, } } } diff --git a/engine/src/contract_methods/connector/internal.rs b/engine/src/contract_methods/connector/internal.rs index 3bfd1e61a..287430cca 100644 --- a/engine/src/contract_methods/connector/internal.rs +++ b/engine/src/contract_methods/connector/internal.rs @@ -57,6 +57,8 @@ pub const UNPAUSE_ALL: PausedMask = 0; pub const PAUSE_DEPOSIT: PausedMask = 1 << 0; /// Admin control flow flag indicates that withdrawal is paused. pub const PAUSE_WITHDRAW: PausedMask = 1 << 1; +/// Admin control flow flag indicates that ft transfers are paused. +pub const PAUSE_FT: PausedMask = 1 << 2; #[named] pub fn new_eth_connector(io: I, env: &E) -> Result<(), ContractError> { @@ -854,6 +856,9 @@ impl EthConnectorContract { predecessor_account_id: &AccountId, args: &TransferCallArgs, ) -> Result<(), errors::TransferError> { + self.assert_not_paused(PAUSE_FT, false) + .map_err(|_| errors::TransferError::Paused)?; + self.ft.internal_transfer_eth_on_near( predecessor_account_id, &args.receiver_id, @@ -904,6 +909,9 @@ impl EthConnectorContract { args: TransferCallCallArgs, prepaid_gas: NearGas, ) -> Result { + self.assert_not_paused(PAUSE_FT, false) + .map_err(|_| errors::FtTransferCallError::Paused)?; + sdk::log!( "Transfer call to {} amount {}", args.receiver_id, @@ -963,6 +971,9 @@ impl EthConnectorContract { amount: Yocto, args: StorageDepositCallArgs, ) -> Result, errors::StorageFundingError> { + self.assert_not_paused(PAUSE_FT, false) + .map_err(|_| errors::StorageFundingError::Paused)?; + let account_id = args .account_id .unwrap_or_else(|| predecessor_account_id.clone()); @@ -983,6 +994,9 @@ impl EthConnectorContract { account_id: AccountId, force: Option, ) -> Result, errors::StorageFundingError> { + self.assert_not_paused(PAUSE_FT, false) + .map_err(|_| errors::StorageFundingError::Paused)?; + let promise = match self.ft.internal_storage_unregister(account_id, force) { Ok((_, p)) => { self.io.return_output(b"true"); diff --git a/engine/src/errors.rs b/engine/src/errors.rs index e8e1ccea1..5737ee481 100644 --- a/engine/src/errors.rs +++ b/engine/src/errors.rs @@ -24,6 +24,7 @@ pub const ERR_NO_UPGRADE: &[u8; 14] = b"ERR_NO_UPGRADE"; pub const ERR_NOT_ALLOWED: &[u8; 15] = b"ERR_NOT_ALLOWED"; pub const ERR_NOT_OWNER: &[u8; 13] = b"ERR_NOT_OWNER"; pub const ERR_PAUSED: &[u8; 10] = b"ERR_PAUSED"; +pub const ERR_FT_PAUSED: &[u8; 13] = b"ERR_FT_PAUSED"; pub const ERR_RUNNING: &[u8; 11] = b"ERR_RUNNING"; pub const ERR_SERIALIZE: &str = "ERR_SERIALIZE"; diff --git a/etc/eth-contracts/yarn.lock b/etc/eth-contracts/yarn.lock index 86030bc1e..43bfb384a 100644 --- a/etc/eth-contracts/yarn.lock +++ b/etc/eth-contracts/yarn.lock @@ -2701,13 +2701,13 @@ bn.js@^5.2.1: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== -body-parser@1.20.1: - version "1.20.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" - integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== +body-parser@1.20.2: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== dependencies: bytes "3.1.2" - content-type "~1.0.4" + content-type "~1.0.5" debug "2.6.9" depd "2.0.0" destroy "1.2.0" @@ -2715,7 +2715,7 @@ body-parser@1.20.1: iconv-lite "0.4.24" on-finished "2.4.1" qs "6.11.0" - raw-body "2.5.1" + raw-body "2.5.2" type-is "~1.6.18" unpipe "1.0.0" @@ -3503,6 +3503,11 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== +content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + convert-source-map@^1.5.1: version "1.8.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" @@ -3515,10 +3520,10 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== +cookie@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== cookie@^0.4.1: version "0.4.1" @@ -5181,16 +5186,16 @@ expand-brackets@^2.1.4: to-regex "^3.0.1" express@^4.14.0: - version "4.18.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" - integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== + version "4.19.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" + integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.1" + body-parser "1.20.2" content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.5.0" + cookie "0.6.0" cookie-signature "1.0.6" debug "2.6.9" depd "2.0.0" @@ -8907,10 +8912,10 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" -raw-body@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== dependencies: bytes "3.1.2" http-errors "2.0.0" @@ -10694,9 +10699,9 @@ underscore@1.9.1: integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg== undici@^5.14.0: - version "5.28.3" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.3.tgz#a731e0eff2c3fcfd41c1169a869062be222d1e5b" - integrity sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA== + version "5.28.4" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068" + integrity sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g== dependencies: "@fastify/busboy" "^2.0.0"