Skip to content
Closed

c #18

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
d953dc4
add accruedRecipient, add test
j6i Oct 4, 2025
da33c4e
follow naming
j6i Oct 4, 2025
4d8b26f
update comment
j6i Oct 4, 2025
e3c9f43
Merge pull request #12 from shape-network/j6i/add-accruedRecipient
j6i Oct 4, 2025
dd69a02
format
j6i Oct 4, 2025
d0f180a
snapshot
j6i Oct 4, 2025
5eae3fd
Merge remote-tracking branch 'upstream/main'
j6i Jan 16, 2026
18c54d8
Create DeployGasback.s.sol
j6i Jan 17, 2026
ae5f745
Merge pull request #13 from shape-network/j6i/deploy-gasback-script
j6i Jan 17, 2026
f2fda19
use our own system address
j6i Jan 17, 2026
24364dd
Merge pull request #14 from shape-network/j6i/use-our-own-system-address
j6i Jan 17, 2026
0784ce1
add payment splitter
j6i Jan 24, 2026
ebfcdd3
Merge pull request #15 from shape-network/j6i/add-payment-splitter
j6i Jan 24, 2026
bb2db8e
release on receive
j6i Jan 24, 2026
dafc793
Merge pull request #16 from shape-network/j6i/release-on-recieve
j6i Jan 24, 2026
295849d
set up test
j6i Jan 27, 2026
59d3d63
update comment
j6i Jan 27, 2026
3f21332
bump sol version
j6i Jan 27, 2026
54cae0f
fix tests
j6i Jan 27, 2026
d05d42f
test basics
j6i Jan 27, 2026
6aa065d
fuzzing
j6i Jan 27, 2026
22d1e30
fail tests
j6i Jan 27, 2026
0192a04
more tests
j6i Jan 27, 2026
94c4e26
test
j6i Jan 27, 2026
8238212
remaining coverage
j6i Jan 27, 2026
ed972da
update snapshot
j6i Jan 27, 2026
c6dc3b3
Merge pull request #17 from shape-network/j6i/test-splitter
j6i Jan 27, 2026
7fcd47d
add deploy splitter
j6i Jan 27, 2026
a7347c2
Merge pull request #18 from shape-network/j6i/add-deploy-splitter
j6i Jan 27, 2026
a1c6080
push
j6i Jan 30, 2026
83547cd
add comments
j6i Jan 30, 2026
b9a5a80
Merge pull request #19 from shape-network/j6i/add-distribute-function
j6i Jan 30, 2026
270bb70
test distribute
j6i Jan 30, 2026
ecde32b
Merge pull request #20 from shape-network/j6i/test-distribute
j6i Jan 30, 2026
6e6a3ef
add invariant test
j6i Jan 30, 2026
e2657ed
Merge pull request #21 from shape-network/j6i/testing-invariants
j6i Jan 30, 2026
798fbc8
remove accruedRecipient
j6i Jan 31, 2026
1f34ddc
rm from test
j6i Jan 31, 2026
d192f5e
Merge pull request #22 from shape-network/j6i/remove-accruedRecipient
j6i Jan 31, 2026
140154e
move check
j6i Jan 31, 2026
c9a29ec
Merge pull request #23 from shape-network/j6i/move-accrued-check
j6i Jan 31, 2026
a343b21
Merge branch 'j5i/remove-minVaultBalance' of https://github.com/shape…
j6i Feb 5, 2026
5f77c11
rm minVaultBalance
j6i Feb 5, 2026
de14232
Merge pull request #24 from shape-network/j5i/remove-minVaultBalance
j6i Feb 5, 2026
97588e2
add baseFeeVaultShareNumerator
j6i Feb 5, 2026
6c9a0fa
Merge pull request #25 from shape-network/j6i/add-baseFeeVaultShareNu…
j6i Feb 6, 2026
c0d25ee
add GasbackExtendedTest
j6i Feb 6, 2026
0808d88
Merge pull request #26 from shape-network/j6i/test-gasback
j6i Feb 6, 2026
bf52f42
add coverage
j6i Feb 6, 2026
e824a28
tests
j6i Feb 6, 2026
26afb17
tests
j6i Feb 6, 2026
fa66213
tests
j6i Feb 6, 2026
e3d4fd6
Merge pull request #27 from shape-network/j6i/coverage
j6i Feb 6, 2026
9002691
enforce solvency invariant
j6i Feb 6, 2026
894d11a
Merge pull request #28 from shape-network/j6i/enforce-solvency-invariant
j6i Feb 6, 2026
fb9aa02
update readmen
j6i Feb 6, 2026
27c3e66
Merge pull request #29 from shape-network/j6i/update-readme
j6i Feb 6, 2026
aaeba18
missing tests
j6i Feb 6, 2026
85804b7
Merge pull request #30 from shape-network/j6i/add-missing-tests
j6i Feb 6, 2026
fe93a81
add GasbackTestCaller
j6i Feb 7, 2026
4fe2ec2
Merge pull request #31 from shape-network/j6i/testnet-contract
j6i Feb 7, 2026
5d14fdb
deploy testcaller
j6i Feb 7, 2026
32e067f
Merge pull request #32 from shape-network/j6i/deploy-testcaller
j6i Feb 7, 2026
74d1a98
move contract, fix script
j6i Feb 7, 2026
4ddeec6
Merge pull request #33 from shape-network/j6i/fix-deploy
j6i Feb 7, 2026
039c3d6
testnet tests
j6i Feb 7, 2026
9dd986b
Merge pull request #34 from shape-network/j6i/testnet-tests
j6i Feb 7, 2026
38c48d9
update accrued comment, update gasbackRatioNumerator
j6i Feb 10, 2026
6e84fe2
add ethFromVaultShare, update accrued calc
j6i Feb 10, 2026
f51a6c3
Merge pull request #35 from shape-network/j6i/more-accurate-accrued
j6i Feb 10, 2026
89b5ff5
update to 0.5, fix tests
j6i Feb 10, 2026
435c833
Merge pull request #36 from shape-network/j6i/update-setGasbackRatioN…
j6i Feb 10, 2026
75c4ecf
add DeployGasbackStackScript
j6i Feb 10, 2026
08a968d
Merge pull request #37 from shape-network/j6i/testnet-script
j6i Feb 10, 2026
2a83820
testnet testing
j6i Feb 14, 2026
8eb8fc7
Merge pull request #38 from shape-network/j6i/testnet-testing
j6i Feb 14, 2026
e24f0a8
sepolia test
j6i May 8, 2026
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
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
SHAPE_SEPOLIA_RPC_URL=https://shape-sepolia.g.alchemy.com/v2/YOUR_KEY
GASBACK_ADDRESS=0x21e34c5bea9253CDCd57671A1970BB31df4aBe83
SPLITTER_ADDRESS=0x658E643b379b52Cd21605bFaF9C81e84713d8427
GASBACK_TEST_CALLER_ADDRESS=0xA53D127f193858f5ef2Cf50dd1B3A94198ef811d
GASBACK_LIVE_PROBE_ADDRESS=
PRIVATE_KEY=
MAX_WEI_SPEND=1000000000000000
MAX_GAS_TO_BURN=120000
REPORT_PATH=gasback-live-report.json
47 changes: 44 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,22 @@ on:
branches: [main]
paths:
- '**.sol'
- '**.ts'
- '**.yml'
- '.env.example'
- 'bun.lock'
- 'package.json'
- 'tsconfig.json'
push:
branches: [main]
paths:
- '**.sol'
- '**.ts'
- '**.yml'
- '.env.example'
- 'bun.lock'
- 'package.json'
- 'tsconfig.json'
jobs:
tests:
name: Forge Testing
Expand All @@ -34,12 +44,44 @@ jobs:
- name: Run Tests with ${{ matrix.profile }}
run: >
( [ "${{ matrix.profile }}" = "regular" ] &&
forge test
forge test --disable-labels
) ||
( [ "${{ matrix.profile }}" = "intense" ] &&
forge test --fuzz-runs 5000
forge test --disable-labels --fuzz-runs 5000
)

gasback-live-system:
name: Gasback live system checks
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Install Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.9

- name: Install Foundry Dependencies
run: forge install

- name: Install TypeScript Dependencies
run: bun install --frozen-lockfile

- name: Run TypeScript Tests
run: bun run lint

- name: Run Local Gasback Suite
run: bun run gasback:local

- name: Run Fork Gasback Suite
run: bun run gasback:fork

codespell:
runs-on: ${{ matrix.os }}
strategy:
Expand All @@ -57,4 +99,3 @@ jobs:
check_filenames: true
ignore_words_list: usera
skip: ./.git,package-lock.json,ackee-blockchain-solady-report.pdf,EIP712Mock.sol

6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,8 @@ wake-coverage.cov
create2

# Coverage report
report
report

# Gasback live test reports
gasback-live-report.json
gasback-live-report.*.json
34 changes: 19 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,37 @@ A barebones implementation of a gasback contract that implements [RIP-7767](http

- The `baseFeeVault` is deployed at `0x4200000000000000000000000000000000000019`.
- The `WITHDRAWAL_NETWORK` of the `baseFeeVault` is set to `1`.
- The `baseFeeVault` recipient is set to `ShapePaymentSplitter`.
- `Gasback` receives only its configured share from `ShapePaymentSplitter`.

### Via script

See `script/Delegate7702.s.sol` for an automated script that can help you deploy.
See `script/DeployGasback.s.sol` and `script/DeployShapePaymentSplitter.s.sol` for deployment scripts.

This script requires you to have the private key of the `baseFeeVault` recipient in your environment.
These scripts require you to have `PRIVATE_KEY` in your environment.

For more information on how to run a foundry script, see `https://getfoundry.sh/guides/scripting-with-solidity`.

### Manual steps

1. Deploy the `gasback` contract which will be used as an implementation via EIP-7702.
1. Deploy the `Gasback` contract.

2. Use EIP-7702 to make the EOA `RECIPIENT` of the `baseFeeVault` delegated to the `gasback` implementation.
After delegating, use the EOA to call functions on itself to initialize the parameters:

2. Deploy `ShapePaymentSplitter` with `Gasback` as one of the payees.

3. Set the `baseFeeVault` recipient to the deployed `ShapePaymentSplitter`.

4. Configure `Gasback` via authorized calls:

- `setBaseFeeVault(address)`
`0x4200000000000000000000000000000000000019`
- `setBaseFeeVaultShareNumerator(uint256)`
`600000000000000000` (`0.6 ether`) and ensure it matches the splitter allocation for `Gasback`.
- `setGasbackRatioNumerator(uint256)`
`900000000000000000`
Must be less than or equal to `setBaseFeeVaultShareNumerator`.
- `setGasbackMaxBaseFee(uint256)`
`115792089237316195423570985008687907853269984665640564039457584007913129639935`
- `setBaseFeeVault(address)`
`0x4200000000000000000000000000000000000019`

4. Put or leave some ETH into the EOA `RECIPIENT`, which will be the actual `gasback` contract.
The ETH will act as a buffer that will be temporarily dished out to contracts calling the EOA `RECIPIENT` in the span of a single block.
5. Put or leave some ETH in `Gasback`.
The ETH acts as a buffer that is temporarily dished out to contracts calling `Gasback` in the span of a single block.
The base fees collected in a block will only be accrued into the `baseFeeVault` at the end of a block.
Try not to empty ETH from the `RECIPIENT` when you are actually taking out ETH from it.

5. For better discoverabiity (for the devX), deploy the `gasbackBeacon` and use the system address to set the EOA `RECIPIENT`.
The exact CREATE2 instructions are in [`./deployments.md`](./deployments.md).
Try not to empty ETH from `Gasback` while actively serving gasback payouts.
53 changes: 53 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# The Default Profile
[profile.default]
solc_version = "0.8.30"
solc_version = "0.8.28"
evm_version = "prague"
auto_detect_solc = false
optimizer = true
Expand Down
110 changes: 110 additions & 0 deletions live/gasback-live.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { describe, expect, test } from "bun:test";
import {
DENOMINATOR,
assertSpendWithinBudget,
buildCanaryGasValues,
computeOracle,
parseWei,
normalizePrivateKeyInput,
receiptFee,
stringifyReport,
} from "./gasback-live";
import type { TransactionReceipt } from "viem";

describe("gasback oracle", () => {
test("computes payout and accrued delta with integer rounding", () => {
const result = computeOracle({
gasToBurn: 333n,
baseFee: 100n,
ratioNumerator: 600_000_000_000_000_000n,
shareNumerator: 700_000_000_000_000_000n,
maxBaseFee: 1_000n,
gasbackBalanceBefore: 1_000_000n,
});

expect(result.ethFromGas).toBe(33_300n);
expect(result.expectedPayout).toBe(19_980n);
expect(result.expectedShare).toBe(23_310n);
expect(result.expectedAccruedDelta).toBe(3_330n);
expect(result.passThrough).toBe(false);
});

test("passes through when base fee exceeds max", () => {
const result = computeOracle({
gasToBurn: 30_000n,
baseFee: 101n,
ratioNumerator: DENOMINATOR,
shareNumerator: DENOMINATOR,
maxBaseFee: 100n,
gasbackBalanceBefore: 3_030_000n,
});

expect(result.expectedPayout).toBe(0n);
expect(result.expectedAccruedDelta).toBe(0n);
expect(result.passThrough).toBe(true);
});

test("passes through when the local gasback buffer is insufficient", () => {
const result = computeOracle({
gasToBurn: 30_000n,
baseFee: 100n,
ratioNumerator: DENOMINATOR,
shareNumerator: DENOMINATOR,
maxBaseFee: 100n,
gasbackBalanceBefore: 2_999_999n,
});

expect(result.expectedPayout).toBe(0n);
expect(result.expectedAccruedDelta).toBe(0n);
expect(result.passThrough).toBe(true);
});
});

describe("canary guards", () => {
test("selects zero, small, and bounded medium canaries", () => {
expect(buildCanaryGasValues(120_000n)).toEqual([0n, 30_000n, 120_000n]);
expect(buildCanaryGasValues(30_000n)).toEqual([0n, 30_000n]);
expect(buildCanaryGasValues(1n)).toEqual([0n]);
});

test("parses decimal and hex wei values", () => {
expect(parseWei("123", "VALUE")).toBe(123n);
expect(parseWei("0x10", "VALUE")).toBe(16n);
expect(() => parseWei("", "VALUE")).toThrow("VALUE is empty");
expect(() => parseWei("-1", "VALUE")).toThrow("VALUE must be non-negative");
expect(() => parseWei("1.2", "VALUE")).toThrow("VALUE must be a decimal or hex integer");
});

test("normalizes private keys without accepting malformed input", () => {
const raw = "1".repeat(64);
expect(normalizePrivateKeyInput(raw)).toBe(`0x${raw}`);
expect(normalizePrivateKeyInput(`0x${raw}`)).toBe(`0x${raw}`);
expect(() => normalizePrivateKeyInput("0x1")).toThrow("PRIVATE_KEY must be 32 bytes");
});

test("rejects estimated spend over the remaining budget", () => {
expect(() => assertSpendWithinBudget(10n, 10n, "case")).not.toThrow();
expect(() => assertSpendWithinBudget(11n, 10n, "case")).toThrow(
"case estimated cost 11 exceeds remaining budget 10",
);
});
});

describe("reporting", () => {
test("serializes bigint values as strings", () => {
expect(stringifyReport({ value: 1n })).toBe('{\n "value": "1"\n}');
});

test("adds OP Stack fee fields to execution fees when present", () => {
const receipt = {
gasUsed: 10n,
effectiveGasPrice: 20n,
l1Fee: "0x64",
} as unknown as TransactionReceipt;
expect(receiptFee(receipt)).toEqual({
executionFee: 200n,
extraReceiptFee: 100n,
totalFee: 300n,
});
});
});
Loading
Loading