Skip to content
Merged
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
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,54 @@ published under the previous merge-driven lifecycle predates that record and has
none, so `src/generated/` holds no directory for it; those versions stay
published, and consumers pin exact versions and are unaffected.

## Crediting the deployer's HyperCore account

HyperEVM interleaves small fast blocks with large slow ones, and a deployment
too big for the fast block's gas cap has to go in a big block. Big blocks are
opted into with an `evmUserModify` action, which HyperCore accepts only from an
address it already knows — one that holds a Core asset. A deployer funded purely
to pay EVM gas is not that address, so the deploy above cannot reach HyperEVM at
all until something puts an asset on Core for it.

Nothing external has to. HYPE is HyperEVM's native gas token rather than an
ERC20, and value sent to the system contract at
`0x2222222222222222222222222222222222222222` is credited on Core to whoever sent
it. The deployer already holds HYPE, because that is what it pays gas in, so it
credits itself:

```sh
HYPERCORE_CREDIT_WEI=10000000000000000 DEPLOYMENT_KEY=0x... \
nix develop -c forge script script/CreditHyperCore.sol:CreditHyperCore --legacy
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Run exactly that first, without `--broadcast`: it is a dry run against a fork of
HyperEVM that executes every guard and the transfer itself and sends nothing, so
anything the real run would refuse is refused there for free. Add `--broadcast`
to send it. `--legacy` for the same reason the deploy workflow carries a
`legacy` input — HyperEVM's RPC rejects the fee-history ranges EIP-1559
estimation asks for. No `--rpc-url`: the script forks the `hyperevm` alias
itself.

`HYPERCORE_CREDIT_WEI` is EVM wei, and required — an amount of real money is not
something to default. It has to be a whole number of Core wei, which is
`10 ** 10` EVM wei, because HYPE has 8 wei decimals on Core against 18 on the
EVM and the remainder is **burned** rather than credited. An amount smaller than
that is burned in full: the transfer succeeds, the HYPE is gone, and the address
is still not a HyperCore user. `LibHyperCore` refuses it rather than rounding.

It also refuses to run anywhere but chain 999. That address is a system contract
on HyperEVM and an ordinary unowned address on every other chain, where value
sent to it is not rejected, just unrecoverable — so the chain id is checked
before anything else, and the code hash at the system address is checked against
a pin straight after, because a chain id alone does not say the contract behind
it is the one that emits the log Core credits from.

This is not on `Manual sol artifacts`. That workflow exports `DEPLOYMENT_SUITE`,
`DEPLOYMENT_NETWORK` and `DEPLOYMENT_KEY` and nothing else, and an amount of
money travelling under one of those names would be worse than a hand-run script.
It is run once per deployer address and never again: a HyperCore user does not
stop being one, so a second run is more money for no further effect.

## Install

Via [soldeer](https://soldeer.xyz):
Expand Down
84 changes: 84 additions & 0 deletions script/CreditHyperCore.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {Script} from "forge-std-1.16.2/src/Script.sol";

import {LibHyperCore} from "../src/lib/LibHyperCore.sol";

/// @title CreditHyperCore
/// @notice Makes the deployer a HyperCore user, by sending it some of its own
/// HYPE.
///
/// Deploying anything sizeable to HyperEVM needs big blocks, which are opted
/// into with an `evmUserModify` action that HyperCore accepts only from an
/// address that is already a HyperCore user. An address becomes one by holding
/// a Core asset, and HYPE sent to the system contract from HyperEVM arrives on
/// Core for the same address — so the deployer, which holds HYPE already
/// because it pays gas in it, credits itself. `LibHyperCore` carries the
/// mechanism and every guard; see there for what each one is for.
///
/// This is a sibling of `script/Deploy.sol` rather than a part of it. It moves
/// the deployer's own funds instead of deploying anything, it touches exactly
/// one network where the deploy touches all of them, and it is run ONCE per
/// deployer address for the lifetime of that address — a second run is more
/// money for no further effect, because a HyperCore user does not stop being
/// one.
///
/// ## Running it
///
/// Not on `Manual sol artifacts`. That workflow exports `DEPLOYMENT_SUITE`,
/// `DEPLOYMENT_NETWORK` and `DEPLOYMENT_KEY` and nothing else, so the amount
/// has no way through it, and an amount squeezed into one of those names would
/// be a real-money argument travelling under a name that means something else.
/// It is run by hand instead:
///
/// ```sh
/// HYPERCORE_CREDIT_WEI=10000000000000000 DEPLOYMENT_KEY=0x... \
/// forge script script/CreditHyperCore.sol:CreditHyperCore --legacy
/// ```
///
/// Without `--broadcast` that is a dry run against a fork of HyperEVM, which
/// executes every guard and the transfer itself and sends nothing. Do that
/// first: it is the same code path, so anything it refuses is something the
/// real run would have refused after paying for it. `--legacy` because
/// HyperEVM's RPC rejects the fee-history ranges EIP-1559 estimation asks for,
/// which is the same reason the deploy workflow carries a `legacy` input. No
/// `--rpc-url`: the fork comes from the `hyperevm` alias in `foundry.toml`.
///
/// ## The amount
///
/// `HYPERCORE_CREDIT_WEI` is EVM wei — 18 decimals, the units the deployer's
/// gas balance is in — and it is required rather than defaulted. A default
/// would be an amount of real money nobody typed, and how much a deployer
/// should hold on Core is a decision about that deployer rather than a fact
/// about this mechanism. It has to be a whole number of Core wei; `10 ** 10`
/// EVM wei is one of them, and `LibHyperCore.CreditNotRound` says why anything
/// else is refused.
///
/// ## Why the body is three lines and no test drives it
///
/// Everything with behaviour is in `LibHyperCore` and is covered there, without
/// an env var in sight. What is left here is two reads by name, and driving
/// them from a test would mean writing `DEPLOYMENT_KEY` — a process-wide
/// variable that `rainix-sol-test` exports onto the job and that
/// `RainDeployBroadcastTest` already sequences its own writes of. Forge runs
/// test contracts concurrently, so a second writer of that name is a race
/// against a suite that is currently green, which is a worse trade than this
/// buys.
///
/// So the body is written to make its own mistake impossible rather than
/// caught. Both env reads are `uint256`, and a key and an amount transposed
/// between them would send a private key's worth of HYPE — so the key is turned
/// into an `address` on its own line first, and the call takes that address.
/// The two arguments no longer have the same type, and the transposition that
/// no test is watching for does not compile.
contract CreditHyperCore is Script {
/// Credits the `DEPLOYMENT_KEY` deployer's HyperCore account with
/// `HYPERCORE_CREDIT_WEI` of its own HYPE.
function run() external {
address deployer = vm.rememberKey(vm.envUint("DEPLOYMENT_KEY"));
uint256 amount = vm.envUint("HYPERCORE_CREDIT_WEI");
LibHyperCore.creditCoreOnHyperEvm(vm, deployer, amount);
}
}
Loading
Loading