Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Check out the full documentation in [`/docs`](./docs)!
- [Assertions](./docs/api-reference/assertions.md) - Static & fluent assertion API
- [Blockchain API](./docs/api-reference/blockchain.md) - Blockchain simulator
- [Contract Runtime](./docs/api-reference/contract-runtime.md) - Custom contract wrappers
- [Advanced Topics](./docs/advanced/cross-contract-calls.md) - Upgrades, signatures, gas profiling, consensus rules
- [Advanced Topics](./docs/advanced/cross-contract-calls.md) - Updates, signatures, gas profiling, consensus rules
- [Examples](./docs/examples/nativeswap-testing.md) - Real-world test examples
- [API Reference](./docs/api-reference/types-interfaces.md) - Full type reference

Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ This policy covers:
- The OP_VM integration layer (`ContractRuntime`, `RustContract`)
- State management (`StateHandler`, `BytecodeManager`)
- Gas accounting and consensus rule enforcement
- Contract upgrade mechanisms (`updateFromAddress`, `applyPendingBytecodeUpgrade`)
- Contract update mechanisms (`updateFromAddress`, `applyPendingBytecodeUpdate`)

## Out of Scope

Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Welcome to the documentation for `@btc-vision/unit-test-framework`. This framewo
### Advanced Topics

- [Cross-Contract Calls](./advanced/cross-contract-calls.md) - Multi-contract testing
- [Upgradeable Contracts](./advanced/upgradeable-contracts.md) - Testing contract upgrades
- [Updatable Contracts](./advanced/updatable-contracts.md) - Testing contract updates
- [Transaction Simulation](./advanced/transaction-simulation.md) - Bitcoin transaction inputs/outputs
- [Signature Verification](./advanced/signature-verification.md) - ML-DSA, Schnorr, ECDSA
- [State Management](./advanced/state-management.md) - State overrides and block replay
Expand Down
2 changes: 1 addition & 1 deletion docs/advanced/consensus-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { ConsensusRules, ConsensusManager } from '@btc-vision/unit-test-framewor
|------|-------|-------------|
| `NONE` | `0b00000000` | No flags |
| `ALLOW_CLASSICAL_SIGNATURES` | `0b00000001` | Allow Schnorr/ECDSA (non-quantum) |
| `UPDATE_CONTRACT_BY_ADDRESS` | `0b00000010` | Allow contract upgrades |
| `UPDATE_CONTRACT_BY_ADDRESS` | `0b00000010` | Allow contract updates |
| `RESERVED_FLAG_2` | `0b00000100` | Reserved for future use |

### Creating Rules
Expand Down
2 changes: 1 addition & 1 deletion docs/advanced/cross-contract-calls.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,4 @@ await vm.it('should compare two scenarios', async () => {

---

[<- Previous: Utilities](../api-reference/utilities.md) | [Next: Upgradeable Contracts ->](./upgradeable-contracts.md)
[<- Previous: Utilities](../api-reference/utilities.md) | [Next: Updatable Contracts ->](./updatable-contracts.md)
2 changes: 1 addition & 1 deletion docs/advanced/transaction-simulation.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,4 @@ This is handled automatically by the framework when `Blockchain.transaction` is

---

[<- Previous: Upgradeable Contracts](./upgradeable-contracts.md) | [Next: Signature Verification ->](./signature-verification.md)
[<- Previous: Updatable Contracts](./updatable-contracts.md) | [Next: Signature Verification ->](./signature-verification.md)
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Upgradeable Contracts
# Updatable Contracts

OPNet supports contract upgrades via `updateFromAddress`. The new bytecode is sourced from another registered contract and takes effect on the next block.
OPNet supports contract updates via `updateFromAddress`. The new bytecode is sourced from another registered contract and takes effect on the next block.

---

## How Upgrades Work
## How Updates Work

```mermaid
sequenceDiagram
Expand All @@ -13,46 +13,46 @@ sequenceDiagram
participant V2Source as V2 Source
participant BC as Blockchain

Test->>V1: upgrade(v2SourceAddress)
Note over V1: Upgrade queued
Test->>V1: update(v2SourceAddress)
Note over V1: Update queued
Test->>BC: mineBlock()
Note over V1: Bytecode replaced with V2
Test->>V1: getValue()
V1-->>Test: Returns V2 behavior
```

Key points:
- Upgrades are **queued**, not immediate
- Updates are **queued**, not immediate
- The new bytecode takes effect after `Blockchain.mineBlock()`
- Storage is **preserved** across upgrades
- Only **one upgrade per block** is allowed
- Storage is **preserved** across updates
- Only **one update per block** is allowed
- The contract **address stays the same**
- Cross-contract calls are **blocked** after upgrading in the same execution
- Cross-contract calls are **blocked** after updating in the same execution

---

## Internal Mechanism

The upgrade process has two phases:
The update process has two phases:

### Phase 1: Upgrade Request (`updateFromAddress`)
### Phase 1: Update Request (`updateFromAddress`)

When a contract calls `updateFromAddress`:

1. A **temporary WASM instance** is created with the **current** bytecode (using `bypassCache: true` to avoid reusing the paused instance)
2. `onUpdate(calldata)` is called on this temporary instance, giving the current contract a chance to run migration logic
3. If `onUpdate` succeeds, the new bytecode is **queued** as a pending upgrade for the current block
4. The `_hasUpgradedInCurrentExecution` flag is set, **blocking** any further cross-contract calls (`Blockchain.call`) in the same transaction
3. If `onUpdate` succeeds, the new bytecode is **queued** as a pending update for the current block
4. The `_hasUpdatedInCurrentExecution` flag is set, **blocking** any further cross-contract calls (`Blockchain.call`) in the same transaction

### Phase 2: Bytecode Swap (`applyPendingBytecodeUpgrade`)
### Phase 2: Bytecode Swap (`applyPendingBytecodeUpdate`)

On the **next block** (when `Blockchain.blockNumber > pendingBytecodeBlock`):

1. The bytecode is swapped to the new version
2. A **temporary WASM instance** is created with the **new** bytecode (using `bypassCache: true` to ensure the fresh bytecode is loaded without hitting the module cache)
3. `onUpdate(calldata)` is called on the new bytecode, allowing the new version to run its own initialization/migration logic
4. If `onUpdate` fails on the new bytecode, the **upgrade is reverted** back to the previous bytecode
5. The pending upgrade state is cleared
4. If `onUpdate` fails on the new bytecode, the **update is reverted** back to the previous bytecode
5. The pending update state is cleared

### Response Format

Expand All @@ -67,7 +67,7 @@ You need two contracts: the main contract and a source contract that holds the V
```typescript
import { Address } from '@btc-vision/transaction';
import { BytecodeManager, ContractRuntime, opnet, OPNetUnit, Assert, Blockchain } from '@btc-vision/unit-test-framework';
import { UpgradeableContractRuntime } from './UpgradeableContractRuntime.js';
import { UpdatableContractRuntime } from './UpdatableContractRuntime.js';

// Source contract just provides bytecode
class V2SourceContract extends ContractRuntime {
Expand All @@ -89,11 +89,11 @@ class V2SourceContract extends ContractRuntime {

## Test Examples

### Basic Upgrade
### Basic Update

```typescript
await opnet('Upgrade Tests', async (vm: OPNetUnit) => {
let contract: UpgradeableContractRuntime;
await opnet('Update Tests', async (vm: OPNetUnit) => {
let contract: UpdatableContractRuntime;
let v2Source: V2SourceContract;

const deployer = Blockchain.generateRandomAddress();
Expand All @@ -105,7 +105,7 @@ await opnet('Upgrade Tests', async (vm: OPNetUnit) => {
Blockchain.clearContracts();
await Blockchain.init();

contract = new UpgradeableContractRuntime(deployer, contractAddress);
contract = new UpdatableContractRuntime(deployer, contractAddress);
v2Source = new V2SourceContract(deployer, v2Address);

Blockchain.register(contract);
Expand All @@ -124,17 +124,17 @@ await opnet('Upgrade Tests', async (vm: OPNetUnit) => {
Blockchain.dispose();
});

await vm.it('should not apply upgrade on same block', async () => {
await vm.it('should not apply update on same block', async () => {
Assert.expect(await contract.getValue()).toEqual(1);

await contract.upgrade(v2Address);
await contract.update(v2Address);

// Same block: still V1 behavior
Assert.expect(await contract.getValue()).toEqual(1);
});

await vm.it('should apply upgrade after mining', async () => {
await contract.upgrade(v2Address);
await vm.it('should apply update after mining', async () => {
await contract.update(v2Address);
Blockchain.mineBlock();

// Now V2 behavior
Expand All @@ -146,7 +146,7 @@ await opnet('Upgrade Tests', async (vm: OPNetUnit) => {
### Storage Persistence

```typescript
await vm.it('should preserve storage across upgrade', async () => {
await vm.it('should preserve storage across update', async () => {
const key = new Uint8Array(32);
key[31] = 42;
const value = new Uint8Array(32);
Expand All @@ -155,8 +155,8 @@ await vm.it('should preserve storage across upgrade', async () => {
// Store value with V1
await contract.storeValue(key, value);

// Upgrade to V2
await contract.upgrade(v2Address);
// Update to V2
await contract.update(v2Address);
Blockchain.mineBlock();

// Value persists with V2
Expand All @@ -172,26 +172,26 @@ await vm.it('should revert for non-existent source', async () => {
const fakeAddress = Blockchain.generateRandomAddress();

await Assert.expect(async () => {
await contract.upgrade(fakeAddress);
await contract.update(fakeAddress);
}).toThrow();
});

await vm.it('should reject second upgrade in same block', async () => {
await contract.upgrade(v2Address);
await vm.it('should reject second update in same block', async () => {
await contract.update(v2Address);

await Assert.expect(async () => {
await contract.upgrade(v2Address);
await contract.update(v2Address);
}).toThrow();
});
```

### Gas Tracking

```typescript
await vm.it('should measure upgrade gas cost', async () => {
const response = await contract.upgrade(v2Address);
await vm.it('should measure update gas cost', async () => {
const response = await contract.update(v2Address);
Assert.expect(response.usedGas).toBeGreaterThan(0n);
vm.info(`Upgrade gas: ${response.usedGas}`);
vm.info(`Update gas: ${response.usedGas}`);
});
```

Expand Down
6 changes: 3 additions & 3 deletions docs/api-reference/blockchain.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,12 @@ Median block timestamp.
mineBlock(): void
```

Advances `blockNumber` by 1. Used to test block-boundary behavior like upgrades:
Advances `blockNumber` by 1. Used to test block-boundary behavior like updates:

```typescript
await contract.upgrade(v2Address);
await contract.update(v2Address);
Blockchain.mineBlock();
// Upgrade now takes effect
// Update now takes effect
const value = await contract.getValue();
```

Expand Down
2 changes: 1 addition & 1 deletion docs/writing-tests/custom-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ await opnet('TestContract', async (vm: OPNetUnit) => {

- [Contract Runtime API Reference](../api-reference/contract-runtime.md) - Full API details
- [Cross-Contract Calls](../advanced/cross-contract-calls.md) - Multi-contract testing
- [Upgradeable Contracts](../advanced/upgradeable-contracts.md) - Testing upgrades
- [Updatable Contracts](../advanced/updatable-contracts.md) - Testing updates

---

Expand Down
4 changes: 2 additions & 2 deletions scripts/buildTestContracts.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ execSync('asc index.ts --target debug --measure --uncheckedBehavior never');
process.chdir(`${root}/test/e2e/contracts/gas-test-contract/contract`);
execSync('asc GasTestContract.ts --target debug --measure --uncheckedBehavior never');

process.chdir(`${root}/test/e2e/contracts/upgradeable-contract/contract`);
process.chdir(`${root}/test/e2e/contracts/updatable-contract/contract`);
execSync('asc index.ts --target debug --measure --uncheckedBehavior never');

process.chdir(`${root}/test/e2e/contracts/upgradeable-contract-v2/contract`);
process.chdir(`${root}/test/e2e/contracts/updatable-contract-v2/contract`);
execSync('asc index.ts --target debug --measure --uncheckedBehavior never');

process.chdir(`${root}/test/e2e/contracts/malicious-v2/contract`);
Expand Down
Loading