Patterns

Upgrading a contract

No proxy, no delegatecall. You deploy new wasm to the same account; the state stays and an optional migrate reshapes it.

Solidity contracts are immutable per address, so upgrades mean a proxy that delegatecalls to a swappable implementation. NEAR has no such dance: you deploy new code to the same account and it replaces the old code in place. The account id and its stored state are untouched.

Proxy delegatecall vs a migrate method
Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

// minimal upgradeable proxy: forward every call to `implementation`
contract Proxy {
    address public implementation;

    fallback() external payable {
        address impl = implementation;
        assembly {
            calldatacopy(0, 0, calldatasize())
            let ok := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch ok
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
}
Rust
use near_sdk::{near, env, AccountId, PanicOnDefault};

// v2 of the contract — a new field was added
#[near(contract_state)]
#[derive(PanicOnDefault)]
pub struct Contract {
    owner: AccountId,
    version: u32,
}

#[near]
impl Contract {
    // After deploying the new wasm to the SAME account, call this once to
    // reshape the persisted state from the old layout to the new one.
    #[private]
    #[init(ignore_state)]
    pub fn migrate() -> Self {
        let old: OldContract = env::state_read().expect("no state");
        Self { owner: old.owner, version: 2 }
    }
}

// the previous version's state layout, for reading during migration
#[near(serializers = [borsh])]
struct OldContract {
    owner: AccountId,
}
Coming from EVM

No proxy, no delegatecall. Deploying new wasm to the account swaps the code; env::current_account_id() and the state survive. There is nothing like an "implementation address" to point at.

If the state *layout* changed, run a one-time migrate marked #[init(ignore_state)] (so it may overwrite existing state) that reads the old struct and writes the new one. #[private] keeps it callable only by the account itself.

Upgradeability is really a governance question: who holds the full-access key that can deploy? Often a DAO or multisig — the same trust question as who controls a proxy admin on the EVM.