Skip to content

Commit

Permalink
Fix(xcc): Store the first upgradable XCC version in state (#877)
Browse files Browse the repository at this point in the history
## Description

In #866 I introduced a change to the XCC router where the contract
became upgradable. In that PR I hardcoded that any XCC Router that was
version 1 could not be upgraded, however this logic did not work on
testnet where the XCC Router code had already been set twice, so it was
actually version 3 which became the first upgradable version.

In this PR I add a new key to the engine which stores the first
upgradable XCC version so that it does not need to be hardcoded. This
should allow the logic to work properly on both testnet and mainnet.

## Performance / NEAR gas cost considerations

N/A

## Testing

Existing tests + testnet deployment (manual test)
  • Loading branch information
birchmd authored Dec 5, 2023
1 parent 668de1f commit 755a341
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 6 deletions.
2 changes: 1 addition & 1 deletion engine-types/src/parameters/xcc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
36 changes: 31 additions & 5 deletions engine/src/xcc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -361,7 +365,17 @@ pub fn update_router_code<I: IO>(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
Expand Down Expand Up @@ -436,19 +450,31 @@ fn set_latest_code_version<I: IO>(io: &mut I, version: CodeVersion) {
io.write_storage(&key, &value_bytes);
}

fn get_first_upgradable_version<I: IO>(io: &I) -> Option<CodeVersion> {
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,
DeployNeeded { create_needed: bool },
}

impl AddressVersionStatus {
fn new(latest_code_version: CodeVersion, target_code_version: Option<CodeVersion>) -> Self {
fn new<I: IO>(
io: &I,
latest_code_version: CodeVersion,
target_code_version: Option<CodeVersion>,
) -> 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
Expand Down

0 comments on commit 755a341

Please sign in to comment.