datahaven/operator/precompiles/call-permit
Ahmad Kaouk 55e973b8f0
fix: change pallet_evm alias to EVM to fix eth_getCode (#213)
## Summary
- rename the FRAME alias for `pallet_evm` from `Evm` to `EVM` across the
mainnet, stagenet, and testnet runtimes
- adjust benchmarks, configuration modules, genesis builders, and
runtime tests to rely on the new alias
- keep precompile genesis setup and proxy/precompile tests aligned with
the updated names

  ## Context
Frontier’s `StorageOverrideHandler` (see
`fc_storage::StorageQuerier::account_code`) reads contract bytecode from
`pallet_evm::AccountCodes` using the constant `PALLET_EVM = b"EVM"` to
build the storage key:
  `twox_128("EVM") ++ twox_128("AccountCodes") ++ …`

Our runtimes exported `pallet_evm` as `Evm`, so substrate stored
bytecode under the *camel-cased* prefix (`twox_128("Evm")`). Every call
that ultimately hits the storage override—including `eth_getCode`,
`eth_call`, and state queries during replay—therefore failed to locate
code for *any* account (deployed contracts and precompiles alike).
Renaming the alias to `EVM` realigns the storage prefix with Frontier’s
  expectations so the override layers can pull bytecode correctly.

  ## Testing
  - `cargo check -p datahaven-node`
  - `cargo build --release -p datahaven-node`
- `eth_getCode 0x0000000000000000000000000000000000000802` → returns
`0x60006000fd`
  
## Storage Migration
Renaming a pallet alias changes the storage prefix for all pallet data.
Without migration, existing EVM data (smart contracts, account codes,
storage) would become inaccessible.

  **Migration details:**

  - **Type**: Multi-Block Migration (MBM)
- **Storage migrated**: `AccountCodes`, `AccountCodesMetadata`,
`AccountStorages`
  - **Migration ID**: `datahaven-evm-mbm` (version 0 → 1)

  **Testing the migration:**

  ```bash
  # Build runtime with try-runtime
cargo build --release --features try-runtime -p
datahaven-stagenet-runtime

  # Test against stagenet
try-runtime \
--runtime
./target/release/wbuild/datahaven-stagenet-runtime/datahaven_stagenet_runtime.wasm
\
      on-runtime-upgrade \
      --blocktime 6000 \
      --checks all \
      --disable-spec-version-check \
      live --uri wss://dh-validator-0.datahaven-kt.xyz
```

  Test results from stagenet:
  -  Migration completes in 1 block
  -  PoV size: ~5.3 KB
  -  Weight consumption: <0.1% of block capacity
  -  All 39 keys successfully migrated

  ## ⚠️ Breaking Changes ⚠️
If you are manually computing storage keys for the EVM pallet (e.g., directly querying chain state), you must update your code to use the new storage prefix:
  - Old prefix: twox128("Evm") = 0x8b90cb...
  - New prefix: twox128("EVM") = 0x6a5e91...

  All EVM-facing interfaces remain unchanged.
2025-10-10 17:48:52 +00:00
..
src fix: change pallet_evm alias to EVM to fix eth_getCode (#213) 2025-10-10 17:48:52 +00:00
CallPermit.sol feat: Add Moonbeam CallPermit precompile (#140) 2025-09-07 15:00:37 +02:00
Cargo.toml feat: Bump client version to v0.2.0 & runtime spec_version to 200 (#194) 2025-09-29 23:35:12 +02:00
README.md feat: Add Moonbeam CallPermit precompile (#140) 2025-09-07 15:00:37 +02:00

Call Permit Precompile

This precompile aims to be a general-purpose tool to perform gas-less transactions.

It allows a user (we'll call her Alice) to sign a call permit with MetaMask (using the EIP712 standard), which can then be dispatched by another user (we'll call him Bob) with a transaction.

Bob can make a transaction to the Call Permit Precompile with the call data and Alice's signature. If the permit and signature are valid, the precompile will perform the call on the behalf of Alice, as if Alice made a transaction herself. Bob is thus paying the transaction fees and Alice can perform a call without having any native currency to pay for fees (she'll still need to have some if the call includes a transfer).

How to sign the permit

The following code is an example that is working in a Metamask-injected webpage. Bob then need to make a transaction towards the precompile address with the same data and Alice's signature.

await window.ethereum.enable();
const accounts = await window.ethereum.request({
  method: "eth_requestAccounts",
});

const from = accounts[0];
const to = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
const value = 42;
const data = "0xdeadbeef";
const gaslimit = 100000;
const nonce = 0;
const deadline = 1000;

const createPermitMessageData = function () {
  const message = {
    from: from,
    to: to,
    value: value,
    data: data,
    gaslimit: gaslimit,
    nonce: nonce,
    deadline: deadline,
  };

  const typedData = JSON.stringify({
    types: {
      EIP712Domain: [
        { name: "name", type: "string" },
        { name: "version", type: "string" },
        { name: "chainId", type: "uint256" },
        { name: "verifyingContract", type: "address" },
      ],
      CallPermit: [
        { name: "from", type: "address" },
        { name: "to", type: "address" },
        { name: "value", type: "uint256" },
        { name: "data", type: "bytes" },
        { name: "gaslimit", type: "uint64" },
        { name: "nonce", type: "uint256" },
        { name: "deadline", type: "uint256" },
      ],
    },
    primaryType: "CallPermit",
    domain: {
      name: "Call Permit Precompile",
      version: "1",
      chainId: 0,
      verifyingContract: "0x000000000000000000000000000000000000080a",
    },
    message: message,
  });

  return {
    typedData,
    message,
  };
};

const method = "eth_signTypedData_v4";
const messageData = createPermitMessageData();
const params = [from, messageData.typedData];

web3.currentProvider.sendAsync(
  {
    method,
    params,
    from,
  },
  function (err, result) {
    if (err) return console.dir(err);
    if (result.error) {
      alert(result.error.message);
      return console.error("ERROR", result);
    }
    console.log("Signature:" + JSON.stringify(result.result));
  }
);