diff --git a/CHANGES.md b/CHANGES.md index 1ea294d19..059da95c8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.5.0] 2023-12-06 + +### Additions + +- Added a new transaction `upgrade` which allows upgrading the contract and invoking the `state_migration` callback + with one call by [@aleksuss]. ([#878]) + +### Fixes + +- Updated the logic of upgrading XCC router which works properly on both `mainnet` and `testnet` by [@birchmd]. ([#877]) + +[#877]: https://github.com/aurora-is-near/aurora-engine/pull/877 +[#878]: https://github.com/aurora-is-near/aurora-engine/pull/878 + ## [3.4.0] 2023-11-28 ### Additions @@ -573,7 +587,8 @@ struct SubmitResult { ## [1.0.0] - 2021-05-12 -[Unreleased]: https://github.com/aurora-is-near/aurora-engine/compare/3.4.0...develop +[Unreleased]: https://github.com/aurora-is-near/aurora-engine/compare/3.5.0...develop +[3.5.0]: https://github.com/aurora-is-near/aurora-engine/compare/3.5.0...3.4.0 [3.4.0]: https://github.com/aurora-is-near/aurora-engine/compare/3.3.1...3.4.0 [3.3.1]: https://github.com/aurora-is-near/aurora-engine/compare/3.3.0...3.3.1 [3.3.0]: https://github.com/aurora-is-near/aurora-engine/compare/3.2.0...3.3.0 diff --git a/Cargo.lock b/Cargo.lock index 8175d8d4b..566f2f125 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,7 +279,7 @@ dependencies = [ [[package]] name = "aurora-engine" -version = "3.4.0" +version = "3.5.0" dependencies = [ "aurora-engine-hashchain", "aurora-engine-modexp", @@ -3771,9 +3771,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.57" +version = "0.10.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" +checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800" dependencies = [ "bitflags 2.4.0", "cfg-if 1.0.0", @@ -3803,9 +3803,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.93" +version = "0.9.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" +checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" dependencies = [ "cc", "libc", diff --git a/Makefile.toml b/Makefile.toml index 814b8dde2..0842927cd 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -16,6 +16,7 @@ skip_core_tasks = true CARGO = "cargo" ENGINE_CARGO_TARGET = "wasm32-unknown-unknown" SWEEP_DAYS = 30 +BUILDER_HASH_COMMIT = "00226858199419aaa8c99f756bd192851666fb36" # https://hub.docker.com/r/nearprotocol/contract-builder/tags [tasks.sweep] category = "Cleanup" @@ -319,7 +320,7 @@ run_task = "build-engine-flow-docker" [tasks.build-docker] category = "Build" script = ''' -docker run --volume $PWD:/host -w /host -i --rm nearprotocol/contract-builder:master-9b27b37941a4419acdba3b8d5fd1d0ac8c99b37e-amd64 ./scripts/docker-entrypoint.sh ${PROFILE} +docker run --volume $PWD:/host -w /host -i --rm nearprotocol/contract-builder:master-${BUILDER_HASH_COMMIT}-amd64 ./scripts/docker-entrypoint.sh ${PROFILE} ''' [tasks.build-xcc-router-docker-inner] @@ -332,7 +333,7 @@ run_task = "build-xcc-router-flow-docker" condition = { profiles = ["mainnet", "testnet"] } category = "Build" script = ''' -docker run --volume $PWD:/host -w /host -i --rm nearprotocol/contract-builder:master-9b27b37941a4419acdba3b8d5fd1d0ac8c99b37e-amd64 ./scripts/docker-xcc-router-entrypoint.sh ${PROFILE} +docker run --volume $PWD:/host -w /host -i --rm nearprotocol/contract-builder:master-${BUILDER_HASH_COMMIT}-amd64 ./scripts/docker-xcc-router-entrypoint.sh ${PROFILE} ''' [tasks.test-contracts] diff --git a/VERSION b/VERSION index 18091983f..1545d9665 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.4.0 +3.5.0 diff --git a/engine-precompiles/Cargo.toml b/engine-precompiles/Cargo.toml index fd01fd0eb..723f9958c 100644 --- a/engine-precompiles/Cargo.toml +++ b/engine-precompiles/Cargo.toml @@ -34,7 +34,7 @@ serde_json.workspace = true default = ["std"] std = ["aurora-engine-types/std", "aurora-engine-sdk/std", "bn/std", "evm/std", "libsecp256k1/std", "ripemd/std", "sha2/std", "sha3/std", "ethabi/std"] borsh-compat = ["aurora-engine-types/borsh-compat", "aurora-engine-sdk/borsh-compat"] -contract = [] +contract = ["aurora-engine-sdk/contract"] log = [] error_refund = [] ext-connector = [] diff --git a/engine-tests/src/tests/upgrade.rs b/engine-tests/src/tests/upgrade.rs index 3930b363f..c8aa2fa84 100644 --- a/engine-tests/src/tests/upgrade.rs +++ b/engine-tests/src/tests/upgrade.rs @@ -4,6 +4,29 @@ use crate::utils::workspace::deploy_engine; #[tokio::test] async fn test_code_upgrade() { + let aurora = deploy_engine().await; + // do upgrade + let result = aurora + .upgrade(contract_bytes()) + .max_gas() + .transact() + .await + .unwrap(); + assert!(result.is_success()); + + // call a new method + let result = aurora + .as_raw_contract() + .view("some_new_fancy_function") + .await + .unwrap(); + + let output: [u32; 7] = result.borsh().unwrap(); + assert_eq!(output, [3, 1, 4, 1, 5, 9, 2]); +} + +#[tokio::test] +async fn test_code_upgrade_with_stage() { let aurora = deploy_engine().await; // do upgrade let result = aurora diff --git a/engine-transactions/Cargo.toml b/engine-transactions/Cargo.toml index fdc3e805f..0fe9450fe 100644 --- a/engine-transactions/Cargo.toml +++ b/engine-transactions/Cargo.toml @@ -24,3 +24,4 @@ hex.workspace = true [features] std = ["aurora-engine-types/std", "aurora-engine-sdk/std", "aurora-engine-precompiles/std", "evm/std", "rlp/std"] impl-serde = ["aurora-engine-types/impl-serde", "serde"] +contract = ["aurora-engine-sdk/contract", "aurora-engine-precompiles/contract"] diff --git a/engine-types/src/parameters/xcc.rs b/engine-types/src/parameters/xcc.rs index c64b7eb7b..b29724fd4 100644 --- a/engine-types/src/parameters/xcc.rs +++ b/engine-types/src/parameters/xcc.rs @@ -27,7 +27,7 @@ pub struct WithdrawWnearToRouterArgs { pub struct CodeVersion(pub u32); impl CodeVersion { - pub const ONE: Self = Self(1); + pub const ZERO: Self = Self(0); #[must_use] pub const fn increment(self) -> Self { diff --git a/engine-workspace/src/contract.rs b/engine-workspace/src/contract.rs index ccef318b4..8c9ba30ca 100644 --- a/engine-workspace/src/contract.rs +++ b/engine-workspace/src/contract.rs @@ -11,14 +11,14 @@ use crate::operation::{ CallSetEthConnectorContractAccount, CallSetEthConnectorContractData, CallSetFixedGas, CallSetKeyManager, CallSetOwner, CallSetPausedFlags, CallSetSiloParams, CallSetWhitelistStatus, CallStageUpgrade, CallStateMigration, CallStorageDeposit, CallStorageUnregister, - CallStorageWithdraw, CallSubmit, CallWithdraw, ViewAccountsCounter, ViewBalance, ViewBlockHash, - ViewBridgeProver, ViewChainId, ViewCode, ViewErc20FromNep141, ViewFactoryWnearAddress, - ViewFtBalanceOf, ViewFtBalanceOfEth, ViewFtMetadata, ViewFtTotalEthSupplyOnAurora, - ViewFtTotalEthSupplyOnNear, ViewFtTotalSupply, ViewGetErc20Metadata, - ViewGetEthConnectorContractAccount, ViewGetFixedGas, ViewGetSiloParams, ViewGetWhitelistStatus, - ViewIsUsedProof, ViewNep141FromErc20, ViewNonce, ViewOwner, ViewPausedFlags, - ViewPausedPrecompiles, ViewStorageAt, ViewStorageBalanceOf, ViewUpgradeIndex, ViewVersion, - ViewView, + CallStorageWithdraw, CallSubmit, CallUpgrade, CallWithdraw, ViewAccountsCounter, ViewBalance, + ViewBlockHash, ViewBridgeProver, ViewChainId, ViewCode, ViewErc20FromNep141, + ViewFactoryWnearAddress, ViewFtBalanceOf, ViewFtBalanceOfEth, ViewFtMetadata, + ViewFtTotalEthSupplyOnAurora, ViewFtTotalEthSupplyOnNear, ViewFtTotalSupply, + ViewGetErc20Metadata, ViewGetEthConnectorContractAccount, ViewGetFixedGas, ViewGetSiloParams, + ViewGetWhitelistStatus, ViewIsUsedProof, ViewNep141FromErc20, ViewNonce, ViewOwner, + ViewPausedFlags, ViewPausedPrecompiles, ViewStorageAt, ViewStorageBalanceOf, ViewUpgradeIndex, + ViewVersion, ViewView, }; use crate::transaction::{CallTransaction, ViewTransaction}; use aurora_engine_types::account_id::AccountId; @@ -267,6 +267,10 @@ impl EngineContract { CallFactorySetWNearAddress::call(&self.contract).args_borsh(address) } + pub fn upgrade(&self, bytes: Vec) -> CallUpgrade { + CallUpgrade::call(&self.contract).args(bytes) + } + pub fn stage_upgrade(&self, bytes: Vec) -> CallStageUpgrade { CallStageUpgrade::call(&self.contract).args(bytes) } diff --git a/engine-workspace/src/operation.rs b/engine-workspace/src/operation.rs index c0ed09867..f25e7924f 100644 --- a/engine-workspace/src/operation.rs +++ b/engine-workspace/src/operation.rs @@ -36,6 +36,7 @@ impl_call_return![ (CallDeployUpgrade, Call::DeployUpgrade), (CallResumePrecompiles, Call::ResumePrecompiles), (CallPausePrecompiles, Call::PausePrecompiles), + (CallUpgrade, Call::Upgrade), (CallStageUpgrade, Call::StageUpgrade), (CallStateMigration, Call::StateMigration), (CallMintAccount, Call::MintAccount), @@ -127,6 +128,7 @@ pub(crate) enum Call { StorageUnregister, StorageWithdraw, PausePrecompiles, + Upgrade, StageUpgrade, DeployUpgrade, StateMigration, @@ -176,6 +178,7 @@ impl AsRef for Call { Call::StorageUnregister => "storage_unregister", Call::StorageWithdraw => "storage_withdraw", Call::PausePrecompiles => "pause_precompiles", + Call::Upgrade => "upgrade", Call::StageUpgrade => "stage_upgrade", Call::DeployUpgrade => "deploy_upgrade", Call::StateMigration => "state_migration", diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 0695c4510..275a9fda6 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aurora-engine" -version = "3.4.0" +version = "3.5.0" authors.workspace = true edition.workspace = true homepage.workspace = true diff --git a/engine/src/contract_methods/admin.rs b/engine/src/contract_methods/admin.rs index a0db2abe1..50eaf4694 100644 --- a/engine/src/contract_methods/admin.rs +++ b/engine/src/contract_methods/admin.rs @@ -30,6 +30,7 @@ use aurora_engine_sdk::{ promise::PromiseHandler, }; use aurora_engine_types::parameters::engine::FullAccessKeyArgs; +use aurora_engine_types::types::{NearGas, ZERO_YOCTO}; use aurora_engine_types::{ borsh::BorshDeserialize, parameters::{ @@ -37,16 +38,17 @@ use aurora_engine_types::{ NewCallArgs, PausePrecompilesCallArgs, RelayerKeyArgs, RelayerKeyManagerArgs, SetOwnerArgs, SetUpgradeDelayBlocksArgs, StartHashchainArgs, }, - promise::{PromiseAction, PromiseBatchAction}, + promise::{PromiseAction, PromiseBatchAction, PromiseCreateArgs}, }, storage::{self, KeyPrefix}, types::{Address, Yocto}, - vec, + vec, ToString, }; use function_name::named; const CODE_KEY: &[u8; 4] = b"CODE"; const CODE_STAGE_KEY: &[u8; 10] = b"CODE_STAGE"; +const GAS_FOR_STATE_MIGRATION: NearGas = NearGas::new(100_000_000_000_000); #[named] pub fn new(mut io: I, env: &E) -> Result<(), ContractError> { @@ -178,6 +180,38 @@ pub fn stage_upgrade(io: I, env: &E) -> Result<(), Contrac }) } +pub fn upgrade( + io: I, + env: &E, + handler: &mut H, +) -> Result<(), ContractError> { + let state = state::get_state(&io)?; + require_running(&state)?; + require_owner_only(&state, &env.predecessor_account_id())?; + + let code = io.read_input().to_vec(); + let current_account_id = env.current_account_id(); + let batch = PromiseBatchAction { + target_account_id: current_account_id.clone(), + actions: vec![PromiseAction::DeployContract { code }], + }; + let state_migration_callback = PromiseCreateArgs { + target_account_id: current_account_id, + method: "state_migration".to_string(), + args: vec![], + attached_balance: ZERO_YOCTO, + attached_gas: GAS_FOR_STATE_MIGRATION, + }; + let promise_id = unsafe { + let base_id = handler.promise_create_batch(&batch); + handler.promise_attach_callback(base_id, &state_migration_callback) + }; + + handler.promise_return(promise_id); + + Ok(()) +} + #[named] pub fn resume_precompiles(io: I, env: &E) -> Result<(), ContractError> { with_hashchain(io, env, function_name!(), |io| { diff --git a/engine/src/lib.rs b/engine/src/lib.rs index ade4376b7..f85c94fca 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -182,6 +182,18 @@ mod contract { .sdk_unwrap(); } + /// Upgrade the contract with the provided code bytes. + #[no_mangle] + pub extern "C" fn upgrade() { + let io = Runtime; + let env = Runtime; + let mut handler = Runtime; + + contract_methods::admin::upgrade(io, &env, &mut handler) + .map_err(ContractError::msg) + .sdk_unwrap(); + } + /// Stage new code for deployment. #[no_mangle] pub extern "C" fn stage_upgrade() { diff --git a/engine/src/xcc.rs b/engine/src/xcc.rs index fa11798a0..8c7f2aace 100644 --- a/engine/src/xcc.rs +++ b/engine/src/xcc.rs @@ -30,6 +30,10 @@ pub const WITHDRAW_GAS: NearGas = NearGas::new(40_000_000_000_000); /// Solidity selector for the `withdrawToNear` function /// `https://www.4byte.directory/signatures/?bytes4_signature=0x6b351848` pub const WITHDRAW_TO_NEAR_SELECTOR: [u8; 4] = [0x6b, 0x35, 0x18, 0x48]; +// Key for storing the XCC router version where upgradability was first introduced. +// (The initial version of the router was not upgradable, see +// https://github.com/aurora-is-near/aurora-engine/pull/866) +const FIRST_UPGRADABLE: &[u8] = b"first_upgrd"; pub use aurora_engine_precompiles::xcc::state::{ get_code_version_of_address, get_latest_code_version, get_wnear_address, ERR_CORRUPTED_STORAGE, @@ -80,7 +84,7 @@ where let latest_code_version = get_latest_code_version(io); let target_code_version = get_code_version_of_address(io, &args.target); - let deploy_needed = AddressVersionStatus::new(latest_code_version, target_code_version); + let deploy_needed = AddressVersionStatus::new(io, latest_code_version, target_code_version); let fund_amount = Yocto::new(env.attached_deposit()); @@ -205,7 +209,7 @@ where let latest_code_version = get_latest_code_version(io); let sender_code_version = get_code_version_of_address(io, &sender); - let deploy_needed = AddressVersionStatus::new(latest_code_version, sender_code_version); + let deploy_needed = AddressVersionStatus::new(io, latest_code_version, sender_code_version); // 1. If the router contract account does not exist or is out of date then we start // with a batch transaction to deploy the router. This batch also has an attached // callback to update the engine's storage with the new version of that router account. @@ -361,7 +365,17 @@ pub fn update_router_code(io: &mut I, code: &RouterCode) { io.write_storage(&key, &code.0); let current_version = get_latest_code_version(io); - set_latest_code_version(io, current_version.increment()); + let latest_version = current_version.increment(); + + // Store the latest version, this will be the first one where the + // router contract is upgradable. + let key = storage::bytes_to_key(KeyPrefix::CrossContractCall, FIRST_UPGRADABLE); + if io.read_storage(&key).is_none() { + let version_bytes = latest_version.0.to_le_bytes(); + io.write_storage(&key, &version_bytes); + } + + set_latest_code_version(io, latest_version); } /// Set the address of the `wNEAR` ERC-20 contract @@ -436,6 +450,12 @@ fn set_latest_code_version(io: &mut I, version: CodeVersion) { io.write_storage(&key, &value_bytes); } +fn get_first_upgradable_version(io: &I) -> Option { + let key = storage::bytes_to_key(KeyPrefix::CrossContractCall, FIRST_UPGRADABLE); + io.read_u32(&key) + .map_or(None, |value| Some(CodeVersion(value))) +} + /// Private enum used for bookkeeping what actions are needed in the call to the router contract. enum AddressVersionStatus { UpToDate, @@ -443,12 +463,18 @@ enum AddressVersionStatus { } impl AddressVersionStatus { - fn new(latest_code_version: CodeVersion, target_code_version: Option) -> Self { + fn new( + io: &I, + latest_code_version: CodeVersion, + target_code_version: Option, + ) -> Self { + let first_upgradable_version = + get_first_upgradable_version(io).unwrap_or(CodeVersion::ZERO); match target_code_version { None => Self::DeployNeeded { create_needed: true, }, - Some(version) if version == CodeVersion::ONE => { + Some(version) if version < first_upgradable_version => { // It is impossible to upgrade the initial XCC routers because // they lack the upgrade method. Self::UpToDate