diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b627c1474..f5d33af51 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,6 +59,12 @@ jobs: just semver-lock-no-build git diff --exit-code snapshots/semver-lock.json + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Check Rust bindings + run: just bindings-check + - name: Run Forge tests run: just test id: test diff --git a/.gitignore b/.gitignore index 73a2e81b4..c272e65fa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Artifacts and Cache artifacts +!bindings/rust/artifacts/ forge-artifacts +target +Cargo.lock cache broadcast kout-proofs diff --git a/bindings/balance_tracker.go b/bindings/go/balance_tracker.go similarity index 100% rename from bindings/balance_tracker.go rename to bindings/go/balance_tracker.go diff --git a/bindings/fee_disburser.go b/bindings/go/fee_disburser.go similarity index 100% rename from bindings/fee_disburser.go rename to bindings/go/fee_disburser.go diff --git a/bindings/go.mod b/bindings/go/go.mod similarity index 96% rename from bindings/go.mod rename to bindings/go/go.mod index 96c1023e9..96acf234e 100644 --- a/bindings/go.mod +++ b/bindings/go/go.mod @@ -1,4 +1,4 @@ -module github.com/base/contracts/bindings +module github.com/base/contracts/bindings/go go 1.23.0 diff --git a/bindings/go.sum b/bindings/go/go.sum similarity index 100% rename from bindings/go.sum rename to bindings/go/go.sum diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml new file mode 100644 index 000000000..5de725986 --- /dev/null +++ b/bindings/rust/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "base-contracts-bindings" +version = "0.1.0" +edition = "2021" + +[dependencies] +alloy-contract = "1.5.0" +alloy-sol-types = { version = "1.5.0", features = ["json"] } diff --git a/bindings/rust/artifacts/FlashblockIndex.json b/bindings/rust/artifacts/FlashblockIndex.json new file mode 100644 index 000000000..aa15b3d9a --- /dev/null +++ b/bindings/rust/artifacts/FlashblockIndex.json @@ -0,0 +1,118 @@ +{ + "abi": [ + { + "type": "constructor", + "inputs": [ + { + "name": "builder", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "fallback", + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "BUILDER", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "get", + "inputs": [], + "outputs": [ + { + "name": "flashblockIndex", + "type": "uint8", + "internalType": "uint8" + }, + { + "name": "blockNumber", + "type": "uint48", + "internalType": "uint48" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "initialize", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "version", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "event", + "name": "FlashblockIndexUpdated", + "inputs": [ + { + "name": "flashblockIndex", + "type": "uint8", + "indexed": true, + "internalType": "uint8" + }, + { + "name": "blockNumber", + "type": "uint48", + "indexed": true, + "internalType": "uint48" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint8", + "indexed": false, + "internalType": "uint8" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "InvalidCalldata", + "inputs": [] + }, + { + "type": "error", + "name": "OnlyBuilder", + "inputs": [] + } + ], + "bytecode": { + "object": "0x60a060405234801561001057600080fd5b506040516105dc3803806105dc83398101604081905261002f9161010a565b6001600160a01b03811660805261004461004a565b5061013a565b600054610100900460ff16156100b65760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b60005460ff9081161015610108576000805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b60006020828403121561011c57600080fd5b81516001600160a01b038116811461013357600080fd5b9392505050565b60805161048161015b60003960008181606401526101fd01526104816000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c806354fd4d50146101745780636d4ce63c146101c65780638129fc1c146101ee578063ecfd1b63146101f8575b3373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016146100bb576040517f357a555d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600136146100f5576040517f8129bbcd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000366000818110610109576101096103d2565b9091013560f81c43600881901b66ffffffffffff00169190911760015565ffffffffffff1690506000368181610141576101416103d2565b60405192013560f81c917f9e31f0bef6b07087c9e7d21830c330cc2d87343d2915024f0a0f832f45cf25779150600090a3005b6101b06040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b6040516101bd9190610401565b60405180910390f35b600154600881901c6040805160ff909316835265ffffffffffff9091166020830152016101bd565b6101f6610244565b005b61021f7f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101bd565b600054610100900460ff16158080156102645750600054600160ff909116105b8061027e5750303b15801561027e575060005460ff166001145b61030e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a6564000000000000000000000000000000000000606482015260840160405180910390fd5b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561036c57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b80156103cf57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060208083528351808285015260005b8181101561042e57858101830151858201604001528201610412565b81811115610440576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01692909201604001939250505056fea164736f6c634300080f000a" + }, + "deployedBytecode": { + "object": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c806354fd4d50146101745780636d4ce63c146101c65780638129fc1c146101ee578063ecfd1b63146101f8575b3373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016146100bb576040517f357a555d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600136146100f5576040517f8129bbcd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000366000818110610109576101096103d2565b9091013560f81c43600881901b66ffffffffffff00169190911760015565ffffffffffff1690506000368181610141576101416103d2565b60405192013560f81c917f9e31f0bef6b07087c9e7d21830c330cc2d87343d2915024f0a0f832f45cf25779150600090a3005b6101b06040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b6040516101bd9190610401565b60405180910390f35b600154600881901c6040805160ff909316835265ffffffffffff9091166020830152016101bd565b6101f6610244565b005b61021f7f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101bd565b600054610100900460ff16158080156102645750600054600160ff909116105b8061027e5750303b15801561027e575060005460ff166001145b61030e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a6564000000000000000000000000000000000000606482015260840160405180910390fd5b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561036c57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b80156103cf57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060208083528351808285015260005b8181101561042e57858101830151858201604001528201610412565b81811115610440576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01692909201604001939250505056fea164736f6c634300080f000a" + } +} diff --git a/bindings/rust/src/l2/flashblock_index.rs b/bindings/rust/src/l2/flashblock_index.rs new file mode 100644 index 000000000..6976d89df --- /dev/null +++ b/bindings/rust/src/l2/flashblock_index.rs @@ -0,0 +1,10 @@ +use alloy_sol_types::sol; + +sol!( + #[sol(rpc, abi)] + FlashblockIndex, + concat!( + env!("CARGO_MANIFEST_DIR"), + "/artifacts/FlashblockIndex.json" + ) +); diff --git a/bindings/rust/src/l2/mod.rs b/bindings/rust/src/l2/mod.rs new file mode 100644 index 000000000..15a59fd87 --- /dev/null +++ b/bindings/rust/src/l2/mod.rs @@ -0,0 +1,2 @@ +mod flashblock_index; +pub use flashblock_index::FlashblockIndex; diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs new file mode 100644 index 000000000..a9f32026d --- /dev/null +++ b/bindings/rust/src/lib.rs @@ -0,0 +1 @@ +pub mod l2; diff --git a/justfile b/justfile index 45fd3ac79..54bcab6f8 100644 --- a/justfile +++ b/justfile @@ -222,6 +222,72 @@ snapshots-no-build: snapshots-abi-storage-no-build semver-lock-no-build snapshots: build-source snapshots-no-build + +######################################################## +# BINDINGS # +######################################################## + +# Adds a new Rust binding for a Solidity contract. +# Usage: just bindings-add src/L2/FlashblockIndex.sol +bindings-add SOL_PATH: build-source + #!/bin/bash + set -euo pipefail + + sol_path="{{SOL_PATH}}" + + # Extract contract name (e.g. FlashblockIndex from src/L2/FlashblockIndex.sol) + contract=$(basename "$sol_path" .sol) + + # Extract module (e.g. l2 from src/L2/FlashblockIndex.sol) + module=$(echo "$sol_path" | sed 's|^src/||' | xargs dirname | tr '[:upper:]' '[:lower:]') + + # Convert PascalCase to snake_case by inserting _ at lower|upper and acronym|word boundaries + snake=$(echo "$contract" | sed -E 's/([a-z0-9])([A-Z])/\1_\2/g' | sed -E 's/([A-Z]+)([A-Z][a-z])/\1_\2/g' | tr '[:upper:]' '[:lower:]') + + rust_dir="bindings/rust/src/${module}" + rust_file="${rust_dir}/${snake}.rs" + mod_file="${rust_dir}/mod.rs" + artifact="bindings/rust/artifacts/${contract}.json" + + # 1. Strip artifact + jq '{abi, bytecode: {object: .bytecode.object}, deployedBytecode: {object: .deployedBytecode.object}}' \ + "forge-artifacts/${contract}.sol/${contract}.json" > "$artifact" + + # 2. Create module directory if needed + mkdir -p "$rust_dir" + + # 3. Create .rs file + cat > "$rust_file" << EOF + use alloy_sol_types::sol; + + sol!( + #[sol(rpc, abi)] + ${contract}, + concat!( + env!("CARGO_MANIFEST_DIR"), + "/artifacts/${contract}.json" + ) + ); + EOF + + # 4. Add to mod.rs (skip if already present) + if ! grep -q "mod ${snake};" "$mod_file" 2>/dev/null; then + echo "" >> "$mod_file" + echo "mod ${snake};" >> "$mod_file" + echo "pub use ${snake}::${contract};" >> "$mod_file" + fi + + # 5. Add module to lib.rs (skip if already present) + if ! grep -q "pub mod ${module};" "bindings/rust/src/lib.rs" 2>/dev/null; then + echo "pub mod ${module};" >> "bindings/rust/src/lib.rs" + fi + + # 6. Format generated code + cd bindings/rust && cargo fmt + + echo "Added binding: ${contract} -> ${rust_file}" + + ######################################################## # CHECKS # ######################################################## @@ -232,6 +298,21 @@ snapshots-check-no-build: snapshots-no-build # Checks if the snapshots are up to date. snapshots-check: build snapshots-check-no-build +# Checks that the Rust bindings crate compiles. +bindings-check: + cd bindings/rust && cargo check + +# Checks that committed Rust binding artifacts match forge-artifacts. +bindings-artifacts-check-no-build: + #!/bin/bash + set -euo pipefail + for src in bindings/rust/artifacts/*.json; do + name=$(basename "$src" .json) + jq '{abi, bytecode: {object: .bytecode.object}, deployedBytecode: {object: .deployedBytecode.object}}' \ + "forge-artifacts/${name}.sol/${name}.json" > "$src" + done + git diff --exit-code bindings/rust/artifacts/ + # Checks interface correctness without building. interfaces-check-no-build: go run ./scripts/checks/interfaces @@ -338,7 +419,8 @@ check: validate-spacers-no-build \ reinitializer-check-no-build \ interfaces-check-no-build \ - lint-forge-tests-check-no-build + lint-forge-tests-check-no-build \ + bindings-artifacts-check-no-build ######################################################## # DEV TOOLS # diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 5f3043e6d..2f9e1219d 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -67,6 +67,10 @@ "initCodeHash": "0xdaae3903628f760e36da47c8f8d75d20962d1811fb5129cb09eb01803e67c095", "sourceCodeHash": "0x95dd8da08e907fa398c98710bb12fda9fb50d9688c5d2144fd9a424c99e672c5" }, + "src/L2/FlashblockIndex.sol:FlashblockIndex": { + "initCodeHash": "0x16c2bc1f2e6526eac40de3636464f8f3899f087773cb63106f5ebea38ea2160e", + "sourceCodeHash": "0x71c264e6ef9cbcd9f82dcbddcda6ad8b253ab23b10b317caead4a0d6efa94b2c" + }, "src/L2/GasPriceOracle.sol:GasPriceOracle": { "initCodeHash": "0xf72c23d9c3775afd7b645fde429d09800622d329116feb5ff9829634655123ca", "sourceCodeHash": "0xb4d1bf3669ba87bbeaf4373145c7e1490478c4a05ba4838a524aa6f0ce7348a6"