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
22 changes: 22 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,25 @@ jobs:
- uses: golangci/golangci-lint-action@v6
with:
version: latest

verify-addresses:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.24'
- name: regenerate addresses from FWSS
run: go generate ./constants/...
- name: check for drift
run: |
if git diff --exit-code constants/addresses_generated.go; then
echo "addresses match on-chain state"
else
echo "::error::addresses_generated.go differs from on-chain FWSS state"
echo "run 'go generate ./constants/...' and commit the result"
echo ""
echo "if using old addresses intentionally, add [skip-address-verify] to commit message"
exit 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generator and CI job will fail if the Glif RPC endpoints are unavailable during go generate. This could block CI for unrelated PRs if there's an RPC outage.

A few options to consider:

  • Add retry logic with backoff in the generator
  • Cache the last known good addresses_generated.go and only fail CI if addresses actually changed (vs just failing to fetch)
  • Mark the verify-addresses job as continue-on-error: true with a warning instead of failure on network errors

Not blocking, but worth considering for resilience.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, I will make it continue if glif is down, it seems like the importance of this mechanism is still somewhat under debate upstream so I'm not going to overindex on it

fi
if: ${{ !contains(github.event.head_commit.message, '[skip-address-verify]') }}
31 changes: 10 additions & 21 deletions constants/addresses.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:generate go run ../internal/generate/addresses.go

package constants

import (
Expand All @@ -7,7 +9,7 @@ import (
type Network string

const (
NetworkMainnet Network = "mainnet"
NetworkMainnet Network = "mainnet"
NetworkCalibration Network = "calibration"
)

Expand All @@ -16,17 +18,8 @@ const (
ChainIDCalibration int64 = 314159
)

// static addresses not derived from FWSS
var (
WarmStorageAddresses = map[Network]common.Address{
NetworkMainnet: common.HexToAddress("0x8408502033C418E1bbC97cE9ac48E5528F371A9f"),
NetworkCalibration: common.HexToAddress("0x02925630df557F957f70E112bA06e50965417CA0"),
}

SPRegistryAddresses = map[Network]common.Address{
NetworkMainnet: common.HexToAddress("0xf55dDbf63F1b55c3F1D4FA7e339a68AB7b64A5eB"),
NetworkCalibration: common.HexToAddress("0x839e5c9988e4e9977d40708d0094103c0839Ac9D"),
}

Multicall3Addresses = map[Network]common.Address{
NetworkMainnet: common.HexToAddress("0xcA11bde05977b3631167028862bE2a173976CA11"),
NetworkCalibration: common.HexToAddress("0xcA11bde05977b3631167028862bE2a173976CA11"),
Expand All @@ -36,16 +29,6 @@ var (
NetworkMainnet: common.HexToAddress("0x80B98d3aa09ffff255c3ba4A241111Ff1262F045"),
NetworkCalibration: common.HexToAddress("0xb3042734b608a1B16e9e86B374A3f3e389B4cDf0"),
}

PaymentsAddresses = map[Network]common.Address{
NetworkMainnet: common.HexToAddress("0x23b1e018F08BB982348b15a86ee926eEBf7F4DAa"),
NetworkCalibration: common.HexToAddress("0x09a0fDc2723fAd1A7b8e3e00eE5DF73841df55a0"),
}

WarmStorageStateViewAddresses = map[Network]common.Address{
NetworkMainnet: common.HexToAddress("0x9e4e6699d8F67dFc883d6b0A7344Bd56F7E80B46"),
NetworkCalibration: common.HexToAddress("0xA5D87b04086B1d591026cCE10255351B5AA4689B"),
}
)

var RPCURLs = map[Network]string{
Expand All @@ -67,3 +50,9 @@ var USDFCAddressesByChainID = map[int64]common.Address{
ChainIDMainnet: common.HexToAddress("0x80B98d3aa09ffff255c3ba4A241111Ff1262F045"),
ChainIDCalibration: common.HexToAddress("0xb3042734b608a1B16e9e86B374A3f3e389B4cDf0"),
}

// WarmStorageAddresses aliases the FWSS addresses (root of trust)
var WarmStorageAddresses = map[Network]common.Address{
NetworkMainnet: FWSSAddressMainnet,
NetworkCalibration: FWSSAddressCalibration,
}
40 changes: 40 additions & 0 deletions constants/addresses_generated.go

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

105 changes: 105 additions & 0 deletions constants/addresses_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//go:build integration

package constants

import (
"context"
"math/big"
"strings"
"testing"
"time"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)

// minimal ABIs - Payments.accounts is the most reliable test
var paymentsABI = `[{"name":"accounts","type":"function","inputs":[{"name":"token","type":"address"},{"name":"owner","type":"address"}],"outputs":[{"name":"balance","type":"uint256"},{"name":"lockedBalance","type":"uint256"}],"stateMutability":"view"}]`

type contractTest struct {
name string
address common.Address
}

func TestGeneratedAddresses_MainnetContracts(t *testing.T) {
testContracts(t, NetworkMainnet, RPCURLs[NetworkMainnet])
}

func TestGeneratedAddresses_CalibrationContracts(t *testing.T) {
testContracts(t, NetworkCalibration, RPCURLs[NetworkCalibration])
}

func testContracts(t *testing.T, network Network, rpcURL string) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

client, err := ethclient.DialContext(ctx, rpcURL)
if err != nil {
t.Fatalf("failed to connect to %s: %v", network, err)
}
defer client.Close()

// verify we can reach the network
chainID, err := client.ChainID(ctx)
if err != nil {
t.Fatalf("failed to get chain ID: %v", err)
}
t.Logf("connected to %s (chain ID: %d)", network, chainID)

tests := []contractTest{
{"Payments", PaymentsAddresses[network]},
{"StateView", WarmStorageStateViewAddresses[network]},
{"PDPVerifier", PDPVerifierAddresses[network]},
{"SPRegistry", SPRegistryAddresses[network]},
{"SessionKeyRegistry", SessionKeyRegistryAddresses[network]},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// verify contract has code deployed
code, err := client.CodeAt(ctx, tc.address, nil)
if err != nil {
t.Fatalf("failed to get code at %s: %v", tc.address.Hex(), err)
}
if len(code) == 0 {
t.Fatalf("no code at %s - not a contract", tc.address.Hex())
}
t.Logf("%s: %s (%d bytes)", tc.name, tc.address.Hex(), len(code))
})
}

// deeper test: verify Payments contract responds to known method
t.Run("Payments_ABI", func(t *testing.T) {
parsed, err := abi.JSON(strings.NewReader(paymentsABI))
if err != nil {
t.Fatalf("failed to parse ABI: %v", err)
}

zeroAddr := common.Address{}
data, err := parsed.Pack("accounts", zeroAddr, zeroAddr)
if err != nil {
t.Fatalf("failed to pack accounts call: %v", err)
}

paymentsAddr := PaymentsAddresses[network]
result, err := client.CallContract(ctx, ethereum.CallMsg{
To: &paymentsAddr,
Data: data,
}, nil)
if err != nil {
t.Fatalf("accounts() call failed: %v", err)
}

// unpack and verify we get two uint256 values
var balance, lockedBalance *big.Int
unpacked, err := parsed.Unpack("accounts", result)
if err != nil {
t.Fatalf("failed to unpack: %v", err)
}
balance = unpacked[0].(*big.Int)
lockedBalance = unpacked[1].(*big.Int)
t.Logf("Payments.accounts(0x0, 0x0) = {balance: %s, locked: %s}", balance, lockedBalance)
})
}
Loading