diff --git a/.github/workflows/build_and_publish.yml b/.github/workflows/build_and_publish.yml new file mode 100644 index 0000000000..4625895bd4 --- /dev/null +++ b/.github/workflows/build_and_publish.yml @@ -0,0 +1,89 @@ +name: Test, Build Image & Push to ECR + +on: + push: + branches: + - main # Trigger the workflow on pushes to the main branch + tags: + - "**" # Trigger the workflow on tags including hierarchical tags like v1.0/beta + pull_request: + types: [opened, synchronize] # Trigger the workflow when a PR is opened or updated + +env: + RELEASE_REVISION: ${{ github.sha }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.AWS_REGION }} + ECR_REPOSITORY: ll-geth + +jobs: + # test: + # name: Run Go Tests + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v4 + + # - name: Set up Go + # uses: actions/setup-go@v5 + # with: + # go-version: 1.22.0 + + # - name: Test + # run: go test -v ./... + + release: + # needs: test + name: Build Image & Push to ECR + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.sha }} + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v3 + + - name: Docker cache layers + uses: actions/cache@v4 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-single-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-single-buildx + + - name: Get the version tag or short SHA + id: get-tag + run: | + if [[ "${{ github.ref }}" == refs/tags/* ]]; then + echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + else + echo "version=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT + fi + + - name: Push Image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ steps.get-tag.outputs.version }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache-new + build-args: VERSION=${{ steps.get-tag.outputs.version }} + + - name: Move cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache diff --git a/.gitignore b/.gitignore index 7000fedd25..33e87f2db6 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,6 @@ profile.cov .vscode tests/spec-tests/ +devdata/ +password.txt +plain_key.txt diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 04dfa87b8b..0d89f9b03f 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -1362,7 +1362,7 @@ func testEIP155Transition(t *testing.T, scheme string) { tx *types.Transaction err error basicTx = func(signer types.Signer) (*types.Transaction, error) { - return types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{}, new(big.Int), 21000, new(big.Int), nil), signer, key) + return types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{}, new(big.Int), 21000, big.NewInt(1), nil), signer, key) } ) switch i { @@ -1433,7 +1433,7 @@ func testEIP155Transition(t *testing.T, scheme string) { tx *types.Transaction err error basicTx = func(signer types.Signer) (*types.Transaction, error) { - return types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{}, new(big.Int), 21000, new(big.Int), nil), signer, key) + return types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{}, new(big.Int), 21000, big.NewInt(1), nil), signer, key) } ) if i == 0 { diff --git a/core/gas_station.go b/core/gas_station.go new file mode 100644 index 0000000000..cedeffa81b --- /dev/null +++ b/core/gas_station.go @@ -0,0 +1,182 @@ +package core + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" +) + +// Event signature +var CreditsUsedEventSignature = crypto.Keccak256Hash([]byte("CreditsUsed(address,address,uint256)")) + +var ( + addressType, _ = abi.NewType("address", "", nil) + uint256Type, _ = abi.NewType("uint256", "", nil) + + // Pre-built arguments for CreditsUsed event + CreditsUsedEventArgs = abi.Arguments{ + {Type: addressType}, // caller (not indexed) + {Type: uint256Type}, // gasUsed (not indexed) + } +) + +// StateReader defines the minimal interface needed for gasless transaction validation +// This allows ValidateGaslessTx to work with any state implementation that can read storage +type StateReader interface { + GetState(addr common.Address, hash common.Hash) common.Hash +} + +// GasStation struct storage slots structs +type GasStationStorageSlots struct { + StructBaseSlotHash common.Hash + CreditSlotHash common.Hash + WhitelistEnabledSlotHash common.Hash + NestedWhitelistMapBaseSlotHash common.Hash + SingleUseEnabledSlotHash common.Hash + UsedAddressesMapBaseSlotHash common.Hash +} + +// calculateGasStationSlots computes the storage slot hashes for a specific +// registered contract within the GasStation's `contracts` mapping. +// It returns the base slot for the struct (holding packed fields), the slot for credits, +// the slot for whitelistEnabled, and the base slot for the nested whitelist mapping. +func CalculateGasStationSlots(registeredContractAddress common.Address) GasStationStorageSlots { + gasStationStorageSlots := GasStationStorageSlots{} + + // ERC-7201 storage location for GasStationStorage + // keccak256(abi.encode(uint256(keccak256("gasstation.main")) - 1)) & ~bytes32(uint256(0xff)); + gasStationStorageLocation := common.HexToHash("0x64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e000") + + // The 'contracts' mapping is at offset 1 from the storage location + // (dao is at offset 0, contracts is at offset 1) + contractsMapSlot := new(big.Int).Add(gasStationStorageLocation.Big(), big.NewInt(1)) + + // Calculate the base slot for the struct entry in the mapping + keyPadded := common.LeftPadBytes(registeredContractAddress.Bytes(), 32) + mapSlotPadded := common.LeftPadBytes(contractsMapSlot.Bytes(), 32) + combined := append(keyPadded, mapSlotPadded...) + gasStationStorageSlots.StructBaseSlotHash = crypto.Keccak256Hash(combined) + + // Calculate subsequent slots by adding offsets to the base slot hash + // New struct layout: bool registered, bool active, address admin (all packed in slot 0) + // uint256 credits (slot 1), bool whitelistEnabled (slot 2), mapping whitelist (slot 3) + // bool singleUseEnabled (slot 4), mapping usedAddresses (slot 5) + structBaseSlotBig := gasStationStorageSlots.StructBaseSlotHash.Big() + + // Slot for 'credits' (offset 1 from base - after the packed bools and address) + creditsSlotBig := new(big.Int).Add(structBaseSlotBig, big.NewInt(1)) + gasStationStorageSlots.CreditSlotHash = common.BigToHash(creditsSlotBig) + + // Slot for 'whitelistEnabled' (offset 2 from base) + whitelistEnabledSlotBig := new(big.Int).Add(structBaseSlotBig, big.NewInt(2)) + gasStationStorageSlots.WhitelistEnabledSlotHash = common.BigToHash(whitelistEnabledSlotBig) + + // Base slot for the nested 'whitelist' mapping (offset 3 from base) + nestedWhitelistMapBaseSlotBig := new(big.Int).Add(structBaseSlotBig, big.NewInt(3)) + gasStationStorageSlots.NestedWhitelistMapBaseSlotHash = common.BigToHash(nestedWhitelistMapBaseSlotBig) + + // Slot for 'singleUseEnabled' (offset 4 from base) + singleUseEnabledSlotBig := new(big.Int).Add(structBaseSlotBig, big.NewInt(4)) + gasStationStorageSlots.SingleUseEnabledSlotHash = common.BigToHash(singleUseEnabledSlotBig) + + // Base slot for the nested 'usedAddresses' mapping (offset 5 from base) + usedAddressesMapBaseSlotBig := new(big.Int).Add(structBaseSlotBig, big.NewInt(5)) + gasStationStorageSlots.UsedAddressesMapBaseSlotHash = common.BigToHash(usedAddressesMapBaseSlotBig) + + return gasStationStorageSlots +} + +func ValidateGaslessTx(to *common.Address, from common.Address, gasLimit uint64, sdb StateReader) (*big.Int, *big.Int, *GasStationStorageSlots, error) { + if to == nil { + return nil, nil, nil, fmt.Errorf("gasless txn must have a valid to address") + } + + if gasLimit == 0 { + return nil, nil, nil, fmt.Errorf("gasless txn must have a non-zero gas limit") + } + + // Calculate GasStation storage slots + gasStationStorageSlots := CalculateGasStationSlots(*to) + + // Get the storage for the GaslessContract struct for the given address + storageBaseSlot := sdb.GetState(params.GasStationAddress, gasStationStorageSlots.StructBaseSlotHash) + + // Extract the registered and active bytes from the storage slot + isRegistered := storageBaseSlot[31] == 0x01 + isActive := storageBaseSlot[30] == 0x01 + + if !isRegistered { + return nil, nil, nil, fmt.Errorf("gasless transaction to unregistered address") + } + + if !isActive { + return nil, nil, nil, fmt.Errorf("gasless transaction to inactive address") + } + + // Get the available credits from the credits storage slot in the GasStation contract for the given address + availableCredits := sdb.GetState(params.GasStationAddress, gasStationStorageSlots.CreditSlotHash) + + // Convert credits (Hash) and tx gas (uint64) to big.Int for comparison + availableCreditsBig := new(big.Int).SetBytes(availableCredits.Bytes()) + txRequiredCreditsBig := new(big.Int).SetUint64(gasLimit) + + // Check if contract has enough available credits to cover the cost of the tx + if availableCreditsBig.Cmp(txRequiredCreditsBig) < 0 { + return nil, nil, nil, fmt.Errorf("gasless transaction has insufficient credits: have %v, need %v", availableCreditsBig, txRequiredCreditsBig) + } + + // Get the whitelist enabled slot + whitelistEnabled := sdb.GetState(params.GasStationAddress, gasStationStorageSlots.WhitelistEnabledSlotHash) + + // Get the whitelist enabled byte from the whitelist enabled slot + isWhitelistEnabled := whitelistEnabled[31] == 0x01 + + if isWhitelistEnabled { + // Calculate slot for the specific user in the nested whitelist map + userWhitelistSlotHash := calculateNestedMappingSlot(from, gasStationStorageSlots.NestedWhitelistMapBaseSlotHash) + + // Get the whitelist status for the specific user + userWhitelist := sdb.GetState(params.GasStationAddress, userWhitelistSlotHash) + + // Check if the user is whitelisted + userWhitelistByte := userWhitelist[31] + isUserWhitelistStorage := userWhitelistByte == 0x01 + + if !isUserWhitelistStorage { + return nil, nil, nil, fmt.Errorf("gasless transaction to non-whitelisted address") + } + } + + // Check single-use mode if enabled + singleUseEnabled := sdb.GetState(params.GasStationAddress, gasStationStorageSlots.SingleUseEnabledSlotHash) + isSingleUseEnabled := singleUseEnabled[31] == 0x01 + + if isSingleUseEnabled { + // Calculate slot for the specific user in the nested usedAddresses map + userUsedSlotHash := calculateNestedMappingSlot(from, gasStationStorageSlots.UsedAddressesMapBaseSlotHash) + + // Get the used status for the specific user + userUsed := sdb.GetState(params.GasStationAddress, userUsedSlotHash) + + // Check if the user has already used gasless transactions + isUserAlreadyUsed := userUsed[31] == 0x01 + + if isUserAlreadyUsed { + return nil, nil, nil, fmt.Errorf("gasless transaction from address that has already used single-use gasless functionality") + } + } + + return availableCreditsBig, txRequiredCreditsBig, &gasStationStorageSlots, nil +} + +// calculateNestedMappingSlot computes the storage slot hash for a nested mapping +func calculateNestedMappingSlot(key common.Address, baseSlot common.Hash) common.Hash { + keyPadded := common.LeftPadBytes(key.Bytes(), 32) + mapBaseSlotPadded := common.LeftPadBytes(baseSlot.Bytes(), 32) + combined := append(keyPadded, mapBaseSlotPadded...) + return crypto.Keccak256Hash(combined) +} diff --git a/core/gas_station_test.go b/core/gas_station_test.go new file mode 100644 index 0000000000..80dbf782c2 --- /dev/null +++ b/core/gas_station_test.go @@ -0,0 +1,176 @@ +package core + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/triedb" + "github.com/holiman/uint256" +) + +// TestGaslessTransactionGasUsage verifies that gasless transactions use the same amount of gas as standard transactions +func TestGaslessTransactionGasUsage(t *testing.T) { + // Create test database and state + db := rawdb.NewMemoryDatabase() + statedb, err := state.New(types.EmptyRootHash, state.NewDatabase(triedb.NewDatabase(db, nil), nil)) + if err != nil { + t.Fatalf("Failed to create state: %v", err) + } + + // Create test accounts + key, _ := crypto.GenerateKey() + from := crypto.PubkeyToAddress(key.PublicKey) + to := common.HexToAddress("0x1000000000000000000000000000000000000001") + gasStationAddr := params.GasStationAddress + + // Setup account balances + statedb.SetBalance(from, uint256.NewInt(1000000000000000000), tracing.BalanceChangeUnspecified) // 1 ETH + statedb.SetBalance(to, uint256.NewInt(0), tracing.BalanceChangeUnspecified) + + // Setup gasless contract as registered and active with credits + gasStationSlots := CalculateGasStationSlots(to) + + // Set registered (byte 31) and active (byte 30) to 0x01 + var statusSlot common.Hash + statusSlot[31] = 0x01 // registered + statusSlot[30] = 0x01 // active + statedb.SetState(gasStationAddr, gasStationSlots.StructBaseSlotHash, statusSlot) + + // Set credits to 1M gas + creditsAmount := big.NewInt(1000000) + statedb.SetState(gasStationAddr, gasStationSlots.CreditSlotHash, common.BigToHash(creditsAmount)) + + // Set whitelist disabled (default) + statedb.SetState(gasStationAddr, gasStationSlots.WhitelistEnabledSlotHash, common.Hash{}) + + // Set single-use disabled (default) + statedb.SetState(gasStationAddr, gasStationSlots.SingleUseEnabledSlotHash, common.Hash{}) + + // Test data: simple storage operation + testData := common.Hex2Bytes("6060604052600560005500") // SSTORE operation + + // Create chainConfig + chainConfig := params.MainnetChainConfig + chainConfig.LondonBlock = big.NewInt(0) // Enable London fork + + // Create block context + blockCtx := vm.BlockContext{ + CanTransfer: func(vm.StateDB, common.Address, *uint256.Int) bool { return true }, + Transfer: func(vm.StateDB, common.Address, common.Address, *uint256.Int) {}, + GetHash: func(uint64) common.Hash { return common.Hash{} }, + Coinbase: common.Address{}, + BlockNumber: big.NewInt(1), + Time: 1, + Difficulty: big.NewInt(1), + BaseFee: big.NewInt(1000000000), // 1 gwei + GasLimit: 10000000, + } + + // Test 1: Standard transaction + standardMsg := &Message{ + To: &to, + From: from, + Nonce: 0, + Value: big.NewInt(0), + GasLimit: 100000, + GasPrice: big.NewInt(1000000000), + GasFeeCap: big.NewInt(1000000000), + GasTipCap: big.NewInt(1000000000), + Data: testData, + AccessList: nil, + BlobGasFeeCap: big.NewInt(0), + BlobHashes: nil, + SetCodeAuthorizations: nil, + SkipNonceChecks: false, + SkipFromEOACheck: false, + IsSystemTx: false, + IsDepositTx: false, + Mint: nil, + RollupCostData: types.RollupCostData{}, + IsGaslessTx: false, + } + + // Execute standard transaction + statedb1 := statedb.Copy() + evm1 := vm.NewEVM(blockCtx, statedb1, chainConfig, vm.Config{}) + evm1.SetTxContext(NewEVMTxContext(standardMsg)) + gp1 := new(GasPool).AddGas(10000000) + + result1, err := ApplyMessage(evm1, standardMsg, gp1) + if err != nil { + t.Fatalf("Standard transaction failed: %v", err) + } + + // Test 2: Gasless transaction + gaslessMsg := &Message{ + To: &to, + From: from, + Nonce: 0, + Value: big.NewInt(0), + GasLimit: 100000, + GasPrice: big.NewInt(0), // No gas price for gasless + GasFeeCap: big.NewInt(0), + GasTipCap: big.NewInt(0), + Data: testData, + AccessList: nil, + BlobGasFeeCap: big.NewInt(0), + BlobHashes: nil, + SetCodeAuthorizations: nil, + SkipNonceChecks: false, + SkipFromEOACheck: false, + IsSystemTx: false, + IsDepositTx: false, + Mint: nil, + RollupCostData: types.RollupCostData{}, + IsGaslessTx: true, + } + + // Execute gasless transaction + statedb2 := statedb.Copy() + evm2 := vm.NewEVM(blockCtx, statedb2, chainConfig, vm.Config{}) + evm2.SetTxContext(NewEVMTxContext(gaslessMsg)) + gp2 := new(GasPool).AddGas(10000000) + + result2, err := ApplyMessage(evm2, gaslessMsg, gp2) + if err != nil { + t.Fatalf("Gasless transaction failed: %v", err) + } + + // Compare gas usage + t.Logf("Standard transaction gas used: %d", result1.UsedGas) + t.Logf("Gasless transaction gas used: %d", result2.UsedGas) + + if result1.UsedGas != result2.UsedGas { + t.Errorf("Gas usage mismatch: standard=%d, gasless=%d, difference=%d", + result1.UsedGas, result2.UsedGas, int64(result2.UsedGas)-int64(result1.UsedGas)) + } + + // Verify both transactions succeeded + if result1.Failed() { + t.Errorf("Standard transaction failed: %v", result1.Err) + } + if result2.Failed() { + t.Errorf("Gasless transaction failed: %v", result2.Err) + } + + // Verify that credits were deducted for gasless transaction + remainingCredits := statedb2.GetState(gasStationAddr, gasStationSlots.CreditSlotHash) + remainingCreditsBig := new(big.Int).SetBytes(remainingCredits.Bytes()) + expectedRemaining := new(big.Int).Sub(creditsAmount, big.NewInt(int64(result2.UsedGas))) + + if remainingCreditsBig.Cmp(expectedRemaining) != 0 { + t.Errorf("Credits not properly deducted: expected=%s, actual=%s", + expectedRemaining.String(), remainingCreditsBig.String()) + } + + t.Logf("✅ Gas usage is identical: %d gas", result1.UsedGas) + t.Logf("✅ Credits properly deducted: %s -> %s", creditsAmount.String(), remainingCreditsBig.String()) +} diff --git a/core/genesis.go b/core/genesis.go index 37b3e0b203..64b5dc1569 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -815,6 +815,8 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis { params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode, Balance: common.Big0}, params.WithdrawalQueueAddress: {Nonce: 1, Code: params.WithdrawalQueueCode, Balance: common.Big0}, params.ConsolidationQueueAddress: {Nonce: 1, Code: params.ConsolidationQueueCode, Balance: common.Big0}, + // Initialize GasStation: Set Nonce and Code, storage init empty (nil map) + params.GasStationAddress: {Nonce: 1, Code: params.GasStationCode, Balance: common.Big0, Storage: nil}, }, } if faucet != nil { diff --git a/core/state_transition.go b/core/state_transition.go index cfa818a4ac..02acf51580 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" @@ -169,6 +170,7 @@ type Message struct { IsDepositTx bool // IsDepositTx indicates the message is force-included and can persist a mint. Mint *big.Int // Mint is the amount to mint before EVM processing, or nil if there is no minting. RollupCostData types.RollupCostData // RollupCostData caches data to compute the fee we charge for data availability + IsGaslessTx bool // IsGaslessTx indicates the message is a gasless transaction. } // TransactionToMessage converts a transaction into a Message. @@ -193,6 +195,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In IsDepositTx: tx.IsDepositTx(), Mint: tx.Mint(), RollupCostData: tx.RollupCostData(), + IsGaslessTx: tx.IsGaslessTx(), } // If baseFee provided, set gasPrice to effectiveGasPrice. if baseFee != nil { @@ -215,7 +218,20 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In // state and would never be accepted within a block. func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, error) { evm.SetTxContext(NewEVMTxContext(msg)) - return newStateTransition(evm, msg, gp).execute() + + // Pre-validate gasless transaction before gas metering starts + var gasStationStorageSlots *GasStationStorageSlots + if msg.IsGaslessTx { + _, _, slots, err := ValidateGaslessTx(msg.To, msg.From, msg.GasLimit, evm.StateDB) + if err != nil { + return nil, err + } + gasStationStorageSlots = slots + } + + st := newStateTransition(evm, msg, gp) + st.gasStationStorageSlots = gasStationStorageSlots + return st.execute() } // stateTransition represents a state transition. @@ -241,12 +257,13 @@ func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, err // 5. Run Script section // 6. Derive new state root type stateTransition struct { - gp *GasPool - msg *Message - gasRemaining uint64 - initialGas uint64 - state vm.StateDB - evm *vm.EVM + gp *GasPool + msg *Message + gasRemaining uint64 + initialGas uint64 + state vm.StateDB + evm *vm.EVM + gasStationStorageSlots *GasStationStorageSlots } // newStateTransition initialises and returns a new state transition object. @@ -371,6 +388,14 @@ func (st *stateTransition) preCheck() error { return fmt.Errorf("%w: address %v, len(code): %d", ErrSenderNoEOA, msg.From.Hex(), len(code)) } } + + // Give EVM gas for free if gasless txn + if st.msg.IsGaslessTx { + st.initialGas = st.msg.GasLimit + st.gasRemaining += st.msg.GasLimit + return st.gp.SubGas(st.msg.GasLimit) + } + // Make sure that transaction gasFeeCap is greater than the baseFee (post london) if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) { // Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call) @@ -628,10 +653,15 @@ func (st *stateTransition) innerExecute() (*ExecutionResult, error) { } st.returnGas() + // Handle gasless transaction credit deduction AFTER refunds are applied + if msg.IsGaslessTx && st.gasStationStorageSlots != nil { + st.handleGaslessPostExecution() + } + // OP-Stack: Note for deposit tx there is no ETH refunded for unused gas, but that's taken care of by the fact that gasPrice // is always 0 for deposit tx. So calling refundGas will ensure the gasUsed accounting is correct without actually // changing the sender's balance. - if st.msg.IsDepositTx && rules.IsOptimismRegolith { + if (st.msg.IsDepositTx && rules.IsOptimismRegolith) || st.msg.IsGaslessTx { // Skip coinbase payments for deposit tx in Regolith return &ExecutionResult{ UsedGas: st.gasUsed(), @@ -812,3 +842,53 @@ func (st *stateTransition) gasUsed() uint64 { func (st *stateTransition) blobGasUsed() uint64 { return uint64(len(st.msg.BlobHashes) * params.BlobTxBlobGasPerBlob) } + +// handleGaslessPostExecution handles credit deduction and state updates for gasless transactions +// This method is called OUTSIDE of gas metering to avoid affecting gas usage +func (st *stateTransition) handleGaslessPostExecution() { + slots := st.gasStationStorageSlots + + // Read current credits and deduct gas used + availableCredits := st.state.GetState(params.GasStationAddress, slots.CreditSlotHash) + availableCreditsBig := new(big.Int).SetBytes(availableCredits.Bytes()) + txRequiredCreditsBig := new(big.Int).SetUint64(st.gasUsed()) + + // Calculate new credits after transaction + newCredits := new(big.Int).Sub(availableCreditsBig, txRequiredCreditsBig) + if newCredits.Sign() < 0 { + // Safety net – should never happen but avoids corrupting state + newCredits = big.NewInt(0) + } + + // Update credit balance + st.state.SetState(params.GasStationAddress, slots.CreditSlotHash, common.BigToHash(newCredits)) + + // Handle single-use marking if enabled + singleUseEnabled := st.state.GetState(params.GasStationAddress, slots.SingleUseEnabledSlotHash) + isSingleUseEnabled := singleUseEnabled[31] == 0x01 + + if isSingleUseEnabled { + // Calculate slot for the specific user in the nested usedAddresses map + userKeyPadded := common.LeftPadBytes(st.msg.From.Bytes(), 32) + mapBaseSlotPadded := common.LeftPadBytes(slots.UsedAddressesMapBaseSlotHash.Bytes(), 32) + userCombined := append(userKeyPadded, mapBaseSlotPadded...) + userUsedSlotHash := crypto.Keccak256Hash(userCombined) + + // Mark the user as having used gasless transactions + st.state.SetState(params.GasStationAddress, userUsedSlotHash, common.HexToHash("0x01")) + } + + // // Emit credits used event + // gasUsedBig := new(big.Int).SetUint64(st.gasUsed()) + // data, err := CreditsUsedEventArgs.Pack(st.msg.From, gasUsedBig) + // if err == nil { + // st.state.AddLog(&types.Log{ + // Address: params.GasStationAddress, + // Topics: []common.Hash{ + // CreditsUsedEventSignature, + // common.BytesToHash(st.msg.To.Bytes()), // contractAddress (indexed) + // }, + // Data: data, + // }) + // } +} diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index c9309580e9..3de7aef758 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -261,6 +261,8 @@ type LegacyPool struct { changesSinceReorg int // A counter for how many drops we've performed in-between reorg. rollupCostFn txpool.RollupCostFunc // Additional rollup cost function, optional field, may be nil. + + pendingCreditUsage map[common.Address]*big.Int // Track total pending credits to be used per gasless contract } type txpoolResetRequest struct { @@ -275,20 +277,21 @@ func New(config Config, chain BlockChain) *LegacyPool { // Create the transaction pool with its initial settings pool := &LegacyPool{ - config: config, - chain: chain, - chainconfig: chain.Config(), - signer: types.LatestSigner(chain.Config()), - pending: make(map[common.Address]*list), - queue: make(map[common.Address]*list), - beats: make(map[common.Address]time.Time), - all: newLookup(), - reqResetCh: make(chan *txpoolResetRequest), - reqPromoteCh: make(chan *accountSet), - queueTxEventCh: make(chan *types.Transaction), - reorgDoneCh: make(chan chan struct{}), - reorgShutdownCh: make(chan struct{}), - initDoneCh: make(chan struct{}), + config: config, + chain: chain, + chainconfig: chain.Config(), + signer: types.LatestSigner(chain.Config()), + pending: make(map[common.Address]*list), + queue: make(map[common.Address]*list), + beats: make(map[common.Address]time.Time), + all: newLookup(), + reqResetCh: make(chan *txpoolResetRequest), + reqPromoteCh: make(chan *accountSet), + queueTxEventCh: make(chan *types.Transaction), + reorgDoneCh: make(chan chan struct{}), + reorgShutdownCh: make(chan struct{}), + initDoneCh: make(chan struct{}), + pendingCreditUsage: make(map[common.Address]*big.Int), } pool.priced = newPricedList(pool.all) @@ -564,6 +567,9 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address] // If the miner requests tip enforcement, cap the lists now if minTipBig != nil { for i, tx := range txs { + if tx.IsGaslessTx() { + continue // skip gasless txns + } if tx.EffectiveGasTipIntCmp(minTipBig, baseFeeBig) < 0 { txs = txs[:i] break @@ -653,6 +659,9 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error { return nil }, RollupCostFn: pool.rollupCostFn, + PendingCreditUsage: func(contractAddr common.Address) *big.Int { + return pool.pendingCreditUsage[contractAddr] + }, } if err := txpool.ValidateTransactionWithState(tx, pool.signer, opts); err != nil { return err @@ -720,6 +729,25 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) { // already validated by this point from, _ := types.Sender(pool.signer, tx) + // Increment the pending credit usage for the given contract address by the amount specified. + if tx.IsGaslessTx() && tx.To() != nil { + currentCreditUsage, found := pool.pendingCreditUsage[*tx.To()] + if !found { + // If the address is not in the map, initialize its credit usage with txGasValue. + // A new big.Int is created. If txGasValue is 0, an entry for 0 is created. + pool.pendingCreditUsage[*tx.To()] = new(big.Int).SetUint64(tx.Gas()) + } else { + // If the address exists, add txGasValue to its current credit usage. + // Only perform the addition if txGasValue is greater than 0 to avoid + // unnecessary allocation and computation for adding zero. + if tx.Gas() > 0 { + gasToAdd := new(big.Int).SetUint64(tx.Gas()) + // Add modifies currentCreditUsage in place. + currentCreditUsage.Add(currentCreditUsage, gasToAdd) + } + } + } + // If the address is not yet known, request exclusivity to track the account // only by this subpool until all transactions are evicted var ( @@ -1094,6 +1122,25 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo } addr, _ := types.Sender(pool.signer, tx) // already validated during insertion + // Decrement pending usage when a tx is removed from the pool + if tx.IsGaslessTx() && tx.To() != nil { + currentCreditUsage, found := pool.pendingCreditUsage[*tx.To()] + if found { + // If the address exists, subtract txGasValue from its current credit usage. + // Only perform the subtraction if txGasValue is greater than 0 to avoid + // unnecessary allocation and computation for subtracting zero. + if tx.Gas() > 0 { + gasToSubtract := new(big.Int).SetUint64(tx.Gas()) + // Subtract modifies currentCreditUsage in place. + currentCreditUsage.Sub(currentCreditUsage, gasToSubtract) + // If negative, set to 0 + if currentCreditUsage.Sign() < 0 { + currentCreditUsage.SetUint64(0) + } + } + } + } + // If after deletion there are no more transactions belonging to this account, // relinquish the address reservation. It's a bit convoluted do this, via a // defer, but it's safer vs. the many return pathways. @@ -1935,4 +1982,5 @@ func (pool *LegacyPool) Clear() { pool.pending = make(map[common.Address]*list) pool.queue = make(map[common.Address]*list) pool.pendingNonces = newNoncer(pool.currentState) + pool.pendingCreditUsage = make(map[common.Address]*big.Int) } diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 9015685633..99dc131a23 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -160,8 +160,10 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrFloorDataGas, tx.Gas(), floorDataGas) } } - // Ensure the gasprice is high enough to cover the requirement of the calling pool - if tx.GasTipCapIntCmp(opts.MinTip) < 0 { + // Ensure the gasprice is high enough to cover the requirement of the calling pool (except for gasless txns) + // This means we can continue to enforce the minimum tip required for the miner for all non-gasless txns + // i.e. txns must either: 1. have a valid minimum tip/gasPrice or be a gasless txn + if tx.GasTipCapIntCmp(opts.MinTip) < 0 && !tx.IsGaslessTx() { return fmt.Errorf("%w: gas tip cap %v, minimum needed %v", ErrUnderpriced, tx.GasTipCap(), opts.MinTip) } if tx.Type() == types.BlobTxType { @@ -251,6 +253,35 @@ type ValidationOptionsWithState struct { // RollupCostFn is an optional extension, to validate total rollup costs of a tx RollupCostFn RollupCostFunc + + // PendingCreditUsage is an optional callback to retrieve the total credits used for a specific contract + // i.e. the amount of credits that will be used when all pending transactions for a specific contract are processed + PendingCreditUsage func(contractAddr common.Address) *big.Int +} + +func validateGaslessTx(tx *types.Transaction, from common.Address, opts *ValidationOptionsWithState) error { + // Validate the gasless transaction + availableCredits, txRequiredCredits, _, err := core.ValidateGaslessTx(tx.To(), from, tx.Gas(), opts.State) + if err != nil { + return err + } + + // Check if the contract has enough available credits to cover the cost of the tx + // including any pending credit usage from queued mempool transactions + if opts.PendingCreditUsage != nil { + pendingCreditUsage := opts.PendingCreditUsage(*tx.To()) + + // If there's positive pending credit usage, an additional check is needed + if pendingCreditUsage != nil && pendingCreditUsage.Sign() > 0 { + // Calculate total credits needed only if there's positive pending usage. + totalRequiredCreditsWithPending := new(big.Int).Add(txRequiredCredits, pendingCreditUsage) + if availableCredits.Cmp(totalRequiredCreditsWithPending) < 0 { + return fmt.Errorf("gasless contract has insufficient credits (including pending): pendingCreditUsage %v, txCreditsRequired %v, availableCredits %v", pendingCreditUsage, txRequiredCredits, availableCredits) + } + } + } + + return nil } // ValidateTransactionWithState is a helper method to check whether a transaction @@ -276,6 +307,12 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op return fmt.Errorf("%w: tx nonce %v, gapped nonce %v", core.ErrNonceTooHigh, tx.Nonce(), gap) } } + + // If gasless txn, validate and skip balance check below + if tx.IsGaslessTx() { + return validateGaslessTx(tx, from, opts) + } + // Ensure the transactor has enough funds to cover the transaction costs var ( balance = opts.State.GetBalance(from).ToBig() diff --git a/core/types/deposit_tx.go b/core/types/deposit_tx.go index 4131ee7af0..f4fce504de 100644 --- a/core/types/deposit_tx.go +++ b/core/types/deposit_tx.go @@ -79,6 +79,7 @@ func (tx *DepositTx) value() *big.Int { return tx.Value } func (tx *DepositTx) nonce() uint64 { return 0 } func (tx *DepositTx) to() *common.Address { return tx.To } func (tx *DepositTx) isSystemTx() bool { return tx.IsSystemTransaction } +func (tx *DepositTx) isGaslessTx() bool { return false } func (tx *DepositTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { return dst.Set(new(big.Int)) diff --git a/core/types/receipt.go b/core/types/receipt.go index a445399c03..07b68d3dc3 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -585,7 +585,7 @@ func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, nu return err } for i := 0; i < len(rs); i++ { - if txs[i].IsDepositTx() { + if txs[i].IsDepositTx() || txs[i].IsGaslessTx() { continue } rs[i].L1GasPrice = gasParams.l1BaseFee diff --git a/core/types/rollup_cost_test.go b/core/types/rollup_cost_test.go index b595a14f62..ef5213a3d5 100644 --- a/core/types/rollup_cost_test.go +++ b/core/types/rollup_cost_test.go @@ -44,8 +44,8 @@ func TestBedrockL1CostFunc(t *testing.T) { costFunc0 := newL1CostFuncBedrockHelper(baseFee, overhead, scalar, false /*isRegolith*/) costFunc1 := newL1CostFuncBedrockHelper(baseFee, overhead, scalar, true) - c0, g0 := costFunc0(emptyTx.RollupCostData()) // pre-Regolith - c1, g1 := costFunc1(emptyTx.RollupCostData()) + c0, g0 := costFunc0(emptyTxWithGasPrice.RollupCostData()) // pre-Regolith + c1, g1 := costFunc1(emptyTxWithGasPrice.RollupCostData()) require.Equal(t, bedrockFee, c0) require.Equal(t, bedrockGas, g0) // gas-used @@ -57,7 +57,7 @@ func TestBedrockL1CostFunc(t *testing.T) { func TestEcotoneL1CostFunc(t *testing.T) { costFunc := newL1CostFuncEcotone(baseFee, blobBaseFee, baseFeeScalar, blobBaseFeeScalar) - c0, g0 := costFunc(emptyTx.RollupCostData()) + c0, g0 := costFunc(emptyTxWithGasPrice.RollupCostData()) require.Equal(t, ecotoneGas, g0) require.Equal(t, ecotoneFee, c0) @@ -138,10 +138,10 @@ func TestExtractBedrockGasParams(t *testing.T) { costFuncRegolith := gasparams.costFunc require.NoError(t, err) - c, _ := costFuncPreRegolith(emptyTx.RollupCostData()) + c, _ := costFuncPreRegolith(emptyTxWithGasPrice.RollupCostData()) require.Equal(t, bedrockFee, c) - c, _ = costFuncRegolith(emptyTx.RollupCostData()) + c, _ = costFuncRegolith(emptyTxWithGasPrice.RollupCostData()) require.Equal(t, regolithFee, c) // try to extract from data which has not enough params, should get error. @@ -171,7 +171,7 @@ func TestExtractEcotoneGasParams(t *testing.T) { require.NoError(t, err) costFunc := gasparams.costFunc - c, g := costFunc(emptyTx.RollupCostData()) + c, g := costFunc(emptyTxWithGasPrice.RollupCostData()) require.Equal(t, ecotoneGas, g) require.Equal(t, ecotoneFee, c) @@ -261,7 +261,7 @@ func TestFirstBlockEcotoneGasParams(t *testing.T) { gasparams, err := extractL1GasParams(config, zeroTime, data) require.NoError(t, err) oldCostFunc := gasparams.costFunc - c, g := oldCostFunc(emptyTx.RollupCostData()) + c, g := oldCostFunc(emptyTxWithGasPrice.RollupCostData()) require.Equal(t, regolithGas, g) require.Equal(t, regolithFee, c) } @@ -380,7 +380,7 @@ func TestNewL1CostFunc(t *testing.T) { require.Nil(t, fee) // emptyTx fee w/ bedrock config should be the bedrock fee - fee = costFunc(emptyTx.RollupCostData(), time) + fee = costFunc(emptyTxWithGasPrice.RollupCostData(), time) require.NotNil(t, fee) require.Equal(t, bedrockFee, fee) @@ -388,21 +388,21 @@ func TestNewL1CostFunc(t *testing.T) { config.RegolithTime = &time costFunc = NewL1CostFunc(config, statedb) require.NotNil(t, costFunc) - fee = costFunc(emptyTx.RollupCostData(), time) + fee = costFunc(emptyTxWithGasPrice.RollupCostData(), time) require.NotNil(t, fee) require.Equal(t, regolithFee, fee) // emptyTx fee w/ ecotone config should be the ecotone fee config.EcotoneTime = &time costFunc = NewL1CostFunc(config, statedb) - fee = costFunc(emptyTx.RollupCostData(), time) + fee = costFunc(emptyTxWithGasPrice.RollupCostData(), time) require.NotNil(t, fee) require.Equal(t, ecotoneFee, fee) // emptyTx fee w/ fjord config should be the fjord fee config.FjordTime = &time costFunc = NewL1CostFunc(config, statedb) - fee = costFunc(emptyTx.RollupCostData(), time) + fee = costFunc(emptyTxWithGasPrice.RollupCostData(), time) require.NotNil(t, fee) require.Equal(t, fjordFee, fee) @@ -413,7 +413,7 @@ func TestNewL1CostFunc(t *testing.T) { statedb.blobBaseFeeScalar = 0 statedb.blobBaseFee = new(big.Int) costFunc = NewL1CostFunc(config, statedb) - fee = costFunc(emptyTx.RollupCostData(), time) + fee = costFunc(emptyTxWithGasPrice.RollupCostData(), time) require.NotNil(t, fee) require.Equal(t, regolithFee, fee) @@ -425,7 +425,7 @@ func TestNewL1CostFunc(t *testing.T) { statedb.blobBaseFeeScalar = 0 statedb.blobBaseFee = new(big.Int) costFunc = NewL1CostFunc(config, statedb) - fee = costFunc(emptyTx.RollupCostData(), time) + fee = costFunc(emptyTxWithGasPrice.RollupCostData(), time) require.NotNil(t, fee) require.Equal(t, regolithFee, fee) } @@ -508,7 +508,15 @@ func TestFlzCompressLen(t *testing.T) { var emptyTxWithGas = NewTransaction( 0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), - big.NewInt(0), bedrockGas.Uint64(), big.NewInt(0), + big.NewInt(0), bedrockGas.Uint64(), big.NewInt(1), + nil, +) + +// copy of emptyTx with non-zero gas +var emptyTxWithGasPrice = NewTransaction( + 0, + common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), + big.NewInt(0), 0, big.NewInt(1), nil, ) diff --git a/core/types/transaction.go b/core/types/transaction.go index bca2cc03a9..c49efeba89 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -97,6 +97,7 @@ type TxData interface { nonce() uint64 to() *common.Address isSystemTx() bool + isGaslessTx() bool rawSignatureValues() (v, r, s *big.Int) setSignatureValues(chainID, v, r, s *big.Int) @@ -374,6 +375,11 @@ func (tx *Transaction) IsSystemTx() bool { return tx.inner.isSystemTx() } +// IsGasless returns true if the transaction is a gasless transaction. +func (tx *Transaction) IsGaslessTx() bool { + return tx.inner.isGaslessTx() +} + // Cost returns (gas * gasPrice) + (blobGas * blobGasPrice) + value. func (tx *Transaction) Cost() *big.Int { total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas())) @@ -386,6 +392,9 @@ func (tx *Transaction) Cost() *big.Int { // RollupCostData caches the information needed to efficiently compute the data availability fee func (tx *Transaction) RollupCostData() RollupCostData { + if tx.IsGaslessTx() { + return RollupCostData{} + } if tx.Type() == DepositTxType { return RollupCostData{} } diff --git a/core/types/tx_access_list.go b/core/types/tx_access_list.go index 59880b0eab..172e29d967 100644 --- a/core/types/tx_access_list.go +++ b/core/types/tx_access_list.go @@ -108,6 +108,7 @@ func (tx *AccessListTx) value() *big.Int { return tx.Value } func (tx *AccessListTx) nonce() uint64 { return tx.Nonce } func (tx *AccessListTx) to() *common.Address { return tx.To } func (tx *AccessListTx) isSystemTx() bool { return false } +func (tx *AccessListTx) isGaslessTx() bool { return false } func (tx *AccessListTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { return dst.Set(tx.GasPrice) diff --git a/core/types/tx_blob.go b/core/types/tx_blob.go index 7ab2d68cbd..fa46302ef0 100644 --- a/core/types/tx_blob.go +++ b/core/types/tx_blob.go @@ -163,6 +163,7 @@ func (tx *BlobTx) nonce() uint64 { return tx.Nonce } func (tx *BlobTx) to() *common.Address { tmp := tx.To; return &tmp } func (tx *BlobTx) blobGas() uint64 { return params.BlobTxBlobGasPerBlob * uint64(len(tx.BlobHashes)) } func (tx *BlobTx) isSystemTx() bool { return false } +func (tx *BlobTx) isGaslessTx() bool { return false } func (tx *BlobTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { if baseFee == nil { diff --git a/core/types/tx_dynamic_fee.go b/core/types/tx_dynamic_fee.go index 273d9452ba..5b9bbd8444 100644 --- a/core/types/tx_dynamic_fee.go +++ b/core/types/tx_dynamic_fee.go @@ -97,6 +97,9 @@ func (tx *DynamicFeeTx) value() *big.Int { return tx.Value } func (tx *DynamicFeeTx) nonce() uint64 { return tx.Nonce } func (tx *DynamicFeeTx) to() *common.Address { return tx.To } func (tx *DynamicFeeTx) isSystemTx() bool { return false } +func (tx *DynamicFeeTx) isGaslessTx() bool { + return tx.GasTipCap.Cmp(big.NewInt(0)) == 0 && tx.GasFeeCap.Cmp(big.NewInt(0)) == 0 +} func (tx *DynamicFeeTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { if baseFee == nil { diff --git a/core/types/tx_legacy.go b/core/types/tx_legacy.go index bcb65c3934..ed5f76f231 100644 --- a/core/types/tx_legacy.go +++ b/core/types/tx_legacy.go @@ -104,6 +104,7 @@ func (tx *LegacyTx) value() *big.Int { return tx.Value } func (tx *LegacyTx) nonce() uint64 { return tx.Nonce } func (tx *LegacyTx) to() *common.Address { return tx.To } func (tx *LegacyTx) isSystemTx() bool { return false } +func (tx *LegacyTx) isGaslessTx() bool { return tx.GasPrice.Cmp(big.NewInt(0)) == 0 } func (tx *LegacyTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { return dst.Set(tx.GasPrice) diff --git a/core/types/tx_setcode.go b/core/types/tx_setcode.go index a4a626fdf4..f5fe041c71 100644 --- a/core/types/tx_setcode.go +++ b/core/types/tx_setcode.go @@ -194,6 +194,7 @@ func (tx *SetCodeTx) value() *big.Int { return tx.Value.ToBig() } func (tx *SetCodeTx) nonce() uint64 { return tx.Nonce } func (tx *SetCodeTx) to() *common.Address { tmp := tx.To; return &tmp } func (tx *SetCodeTx) isSystemTx() bool { return false } +func (tx *SetCodeTx) isGaslessTx() bool { return false } func (tx *SetCodeTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { if baseFee == nil { diff --git a/miner/ordering.go b/miner/ordering.go index bcf7af46e8..38cca58f87 100644 --- a/miner/ordering.go +++ b/miner/ordering.go @@ -38,6 +38,11 @@ type txWithMinerFee struct { // Returns error in case of a negative effective miner gasTipCap. func newTxWithMinerFee(tx *txpool.LazyTransaction, from common.Address, baseFee *uint256.Int) (*txWithMinerFee, error) { tip := new(uint256.Int).Set(tx.GasTipCap) + // This func is called on all pending txns in the txpool to set the order by fees + // Miner should not enforce baseFee for gasless txns + if tx.Tx.IsGaslessTx() { + baseFee = nil + } if baseFee != nil { if tx.GasFeeCap.Cmp(baseFee) < 0 { return nil, types.ErrGasFeeCapTooLow diff --git a/miner/ordering_test.go b/miner/ordering_test.go index 3587a835c8..0e5fb0ef01 100644 --- a/miner/ordering_test.go +++ b/miner/ordering_test.go @@ -62,6 +62,9 @@ func testTransactionPriceNonceSort(t *testing.T, baseFee *big.Int) { for i := 0; i < 25; i++ { var tx *types.Transaction gasFeeCap := rand.Intn(50) + if gasFeeCap == 0 { + gasFeeCap = 1 + } if baseFee == nil { tx = types.NewTx(&types.LegacyTx{ Nonce: uint64(start + i), diff --git a/params/protocol_params.go b/params/protocol_params.go index 00a83375ac..b68e9cf146 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -18,6 +18,7 @@ package params import ( "math/big" + "os" "github.com/ethereum/go-ethereum/common" ) @@ -230,4 +231,9 @@ var ( // EIP-7251 - Increase the MAX_EFFECTIVE_BALANCE ConsolidationQueueAddress = common.HexToAddress("0x0000BBdDc7CE488642fb579F8B00f3a590007251") ConsolidationQueueCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd") + + // LLIP-2 - Gasless transactions + // Temp get from env var for testing + GasStationAddress = common.HexToAddress(os.Getenv("GAS_STATION")) + GasStationCode = common.FromHex("6080604052600436106101c8575f3560e01c806399c6066c116100f2578063c55b6bb711610092578063e559afd911610062578063e559afd9146107c3578063e73a914c146107e2578063f6e4b62b14610801578063fac2c62114610820575f80fd5b8063c55b6bb7146106ee578063d124d1bc1461070d578063d7e5fbf31461073e578063d9ba32fc1461075d575f80fd5b8063ad3e080a116100cd578063ad3e080a1461062e578063b6b352721461064d578063c375c2ef1461066c578063c3c5a5471461068b575f80fd5b806399c6066c146105865780639e4f8ab8146105a55780639f8a13d7146105c6575f80fd5b80633b66e9f61161016857806364efb22b1161013857806364efb22b1461040e57806369dc9ff3146104775780637901868e14610548578063871ff40514610567575f80fd5b80633b66e9f6146103035780634162169f146103665780634782f779146103d05780635e35359e146103ef575f80fd5b806315ea16ad116101a357806315ea16ad146102695780631c5d647c146102a65780632ce962cf146102c5578063368da168146102e4575f80fd5b8063108f5c69146101d3578063139e0aa7146101f457806314695ea414610207575f80fd5b366101cf57005b5f80fd5b3480156101de575f80fd5b506101f26101ed366004612e30565b61083f565b005b6101f2610202366004612ea8565b610a4f565b348015610212575f80fd5b50610254610221366004612ed2565b5f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db02602052604090205460ff1690565b60405190151581526020015b60405180910390f35b348015610274575f80fd5b507fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db03545b604051908152602001610260565b3480156102b1575f80fd5b506101f26102c0366004612ef6565b610cb6565b3480156102d0575f80fd5b506101f26102df366004612f24565b610e02565b3480156102ef575f80fd5b506101f26102fe366004612ea8565b611000565b34801561030e575f80fd5b5061029861031d366004612f50565b73ffffffffffffffffffffffffffffffffffffffff165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090206001015490565b348015610371575f80fd5b507fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610260565b3480156103db575f80fd5b506101f26103ea366004612ea8565b611200565b3480156103fa575f80fd5b506101f2610409366004612f72565b611352565b348015610419575f80fd5b506103ab610428366004612f50565b73ffffffffffffffffffffffffffffffffffffffff9081165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db0160205260409020546201000090041690565b348015610482575f80fd5b50610501610491366004612f50565b73ffffffffffffffffffffffffffffffffffffffff9081165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090208054600182015460029092015460ff808316956101008404821695620100009094049093169392911690565b604080519515158652931515602086015273ffffffffffffffffffffffffffffffffffffffff9092169284019290925260608301919091521515608082015260a001610260565b348015610553575f80fd5b506101f2610562366004612fb0565b611672565b348015610572575f80fd5b506101f2610581366004612ea8565b6118bc565b348015610591575f80fd5b506101f26105a0366004612ea8565b6119d7565b3480156105b0575f80fd5b506105b9611ac1565b604051610260919061301e565b3480156105d1575f80fd5b506102546105e0366004612f50565b73ffffffffffffffffffffffffffffffffffffffff165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db016020526040902054610100900460ff1690565b348015610639575f80fd5b506101f2610648366004613061565b611bcf565b348015610658575f80fd5b506102546106673660046130e2565b611d6e565b348015610677575f80fd5b506101f2610686366004612f50565b611e21565b348015610696575f80fd5b506102546106a5366004612f50565b73ffffffffffffffffffffffffffffffffffffffff165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090205460ff1690565b3480156106f9575f80fd5b506101f26107083660046130e2565b611f53565b348015610718575f80fd5b5061072c610727366004612ed2565b6121a0565b6040516102609695949392919061310e565b348015610749575f80fd5b506101f26107583660046130e2565b6122b6565b348015610768575f80fd5b50610254610777366004612f50565b73ffffffffffffffffffffffffffffffffffffffff165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090206002015460ff1690565b3480156107ce575f80fd5b506101f26107dd366004613061565b6124ee565b3480156107ed575f80fd5b506101f26107fc366004612f50565b612692565b34801561080c575f80fd5b506101f261081b366004612f24565b6127e6565b34801561082b575f80fd5b506101f261083a366004612f50565b612957565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff1633146108af576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f8590036108e9576040517f8dad8de600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612710811115610925576040517fe05f723400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f8781527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db02602052604090206001810180546109609061319c565b90505f0361099a576040517fbdc474c300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600181016109a9878983613265565b5060028101859055600381018490556004810180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff85161790556005810182905560405188907f73d628d7a9f63d75ab3f23c4bf349bfec022e61cc2ad8dc72f7ca093b45723e890610a3d908a908a908a908a908a908a9061337b565b60405180910390a25050505050505050565b73ffffffffffffffffffffffffffffffffffffffff82165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db016020526040902054829060ff16610ace576040517faba4733900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f8281527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db0260205260409020600181018054610b099061319c565b90505f03610b43576040517fbdc474c300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805460ff16610b7e576040517fd1d5af5600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600481015473ffffffffffffffffffffffffffffffffffffffff16610bab57610ba681612a89565b610bec565b3415610be3576040517ffbccebae00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610bec81612b12565b600381015473ffffffffffffffffffffffffffffffffffffffff85165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604081206001018054909190610c47908490613428565b92505081905550828473ffffffffffffffffffffffffffffffffffffffff167f7852f393fd6a99c61648e39af92ae0e784b77281fc2af871edce1b51304ecd7c83600301548460020154604051610ca8929190918252602082015260400190565b60405180910390a350505050565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff163314610d26576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f8281527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db0260205260409020600181018054610d619061319c565b90505f03610d9b576040517fbdc474c300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016821515908117825560405190815283907f10ae08733732b5e10d63d501510950b2a5967607149b3608881ecde96515780c906020015b60405180910390a2505050565b73ffffffffffffffffffffffffffffffffffffffff8083165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090205483917fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db0091620100009004163314801590610e985750805473ffffffffffffffffffffffffffffffffffffffff163314155b15610ecf576040517fea8e4eb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db016020526040902054849060ff16610f4e576040517faba4733900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff85165f8181527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101008915159081029190911790915591519182527fa5ab8b72c18a722b7e92b557d227ba48dc2985b22fce6d0f95804be26703b595910160405180910390a25050505050565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff163314611070576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff82165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db016020526040902054829060ff166110ef576040517faba4733900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff83165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090206001015482811015611170576040517f43fb945300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61117a838261343b565b73ffffffffffffffffffffffffffffffffffffffff85165f8181527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db016020908152604091829020600101939093555185815290917f30a9d8d098632f590e4953b6171a6c999d2b1c4170ebde38136c9e27e6976b8191015b60405180910390a250505050565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff163314611270576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff81166112be576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b475f83156112cc57836112ce565b815b90508181111561130a576040517f43fb945300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405173ffffffffffffffffffffffffffffffffffffffff86169082156108fc029083905f818181858888f1935050505015801561134a573d5f803e3d5ffd5b505050505050565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff1633146113c2576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff8116611410576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84166114bf57475f8315611439578361143b565b815b905081811115611477576040517f43fb945300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405173ffffffffffffffffffffffffffffffffffffffff86169082156108fc029083905f818181858888f193505050501580156114b7573d5f803e3d5ffd5b50505061166c565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015284905f9073ffffffffffffffffffffffffffffffffffffffff8316906370a0823190602401602060405180830381865afa15801561152b573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061154f919061344e565b90505f841561155e5784611560565b815b90508181111561159c576040517f43fb945300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff87811660048301526024820183905284169063a9059cbb906044016020604051808303815f875af115801561160e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116329190613465565b611668576040517f045c4b0200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5050505b50505050565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff1633146116e2576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f85900361171c576040517f8dad8de600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612710811115611758576040517fe05f723400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db03545f8181527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db026020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811782557fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db009291908101611802898b83613265565b506002810187905560038082018790556004820180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff88161790556005820185905583018054905f61186a83613480565b9190505550817f85855a4353e16703440df33dd6903f8689955fe665d2ed5b918f7a272286c8b98a8a8a8a8a8a6040516118a99695949392919061337b565b60405180910390a2505050505050505050565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff16331461192c576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff82165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604081206001018054839290611982908490613428565b909155505060405181815273ffffffffffffffffffffffffffffffffffffffff8316907fed46984c46e11f42ec323727ba7d99dc16be2d248a8aaa8982d492688497f09d906020015b60405180910390a25050565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff163314611a47576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff82165f8181527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602090815260409182902060010184905590518381527fc2748283b871105da37ea4bdc2cc08eff4b3b0f472f66f2728cdf4a1b845ef7791016119cb565b60607fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005f60015b8260030154811015611b22575f81815260028401602052604090205460ff1615611b1a5781611b1681613480565b9250505b600101611ae8565b505f8167ffffffffffffffff811115611b3d57611b3d6131ed565b604051908082528060200260200182016040528015611b66578160200160208202803683370190505b5090505f60015b8460030154811015611bc5575f81815260028601602052604090205460ff1615611bbd5780838381518110611ba457611ba46134b7565b602090810291909101015281611bb981613480565b9250505b600101611b6d565b5090949350505050565b73ffffffffffffffffffffffffffffffffffffffff8084165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090205484917fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db0091620100009004163314801590611c655750805473ffffffffffffffffffffffffffffffffffffffff163314155b15611c9c576040517fea8e4eb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8381101561134a5773ffffffffffffffffffffffffffffffffffffffff86165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db016020526040812060030181878785818110611cff57611cff6134b7565b9050602002016020810190611d149190612f50565b73ffffffffffffffffffffffffffffffffffffffff16815260208101919091526040015f2080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016911515919091179055600101611c9e565b73ffffffffffffffffffffffffffffffffffffffff82165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604081206002015460ff161580611e18575073ffffffffffffffffffffffffffffffffffffffff8381165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db0160209081526040808320938616835260039093019052205460ff165b90505b92915050565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff163314611e91576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff81165f8181527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604080822080547fffffffffffffffffffff000000000000000000000000000000000000000000001681556001810183905560020180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055517f8d30d41865a0b811b9545d879520d2dde9f4cc49e4241f486ad9752bc904b5659190a250565b73ffffffffffffffffffffffffffffffffffffffff8083165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090205483917fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db0091620100009004163314801590611fe95750805473ffffffffffffffffffffffffffffffffffffffff163314155b15612020576040517fea8e4eb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db016020526040902054849060ff1661209f576040517faba4733900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8373ffffffffffffffffffffffffffffffffffffffff81166120ed576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8681165f8181527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db0160205260408082208054620100008b87168181027fffffffffffffffffffff0000000000000000000000000000000000000000ffff84161790935592519290049094169392849290917f4eb572e99196bed0270fbd5b17a948e19c3f50a97838cb0d2a75a823ff8e6c509190a450505050505050565b5f606081808080807fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005f89815260029182016020526040902080549181015460038201546004830154600584015460018501805495975060ff909616959473ffffffffffffffffffffffffffffffffffffffff9092169185906122229061319c565b80601f016020809104026020016040519081016040528092919081815260200182805461224e9061319c565b80156122995780601f1061227057610100808354040283529160200191612299565b820191905f5260205f20905b81548152906001019060200180831161227c57829003601f168201915b505050505094509650965096509650965096505091939550919395565b8173ffffffffffffffffffffffffffffffffffffffff8116612304576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff8116612352576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090205460ff16156123d0576040517f3a81d6fc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b833b5f81900361240c576040517f6eefed2000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8581165f8181527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604080822080546101017fffffffffffffffffffff0000000000000000000000000000000000000000000090911662010000968b169687021717815560018082018490556002820180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690911790559051909392917f768fb430a0d4b201cb764ab221c316dd14d8babf2e4b2348e05964c6565318b691a3505050505050565b73ffffffffffffffffffffffffffffffffffffffff8084165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090205484917fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db00916201000090041633148015906125845750805473ffffffffffffffffffffffffffffffffffffffff163314155b156125bb576040517fea8e4eb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8381101561134a5773ffffffffffffffffffffffffffffffffffffffff86165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db0160205260408120600191600390910190878785818110612623576126236134b7565b90506020020160208101906126389190612f50565b73ffffffffffffffffffffffffffffffffffffffff16815260208101919091526040015f2080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169115159190911790556001016125bd565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff163314612702576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff8116612750576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db00805473ffffffffffffffffffffffffffffffffffffffff8481167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f0429168a83556e356cd18563753346b9c9567cbf0fbea148d40aeb84a76cc5b9905f90a3505050565b73ffffffffffffffffffffffffffffffffffffffff8083165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090205483917fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db009162010000900416331480159061287c5750805473ffffffffffffffffffffffffffffffffffffffff163314155b156128b3576040517fea8e4eb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84165f8181527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602090815260409182902060020180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001687151590811790915591519182527f8daaf060c3306c38e068a75c054bf96ecd85a3db1252712c4d93632744c42e0d91016111f2565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff1633146129c7576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff81165f8181527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604080822080547fffffffffffffffffffff000000000000000000000000000000000000000000001681556001810183905560020180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055517f3475b9891ecf29e996feed01eeb42a860ec225283a439d214ffaeac5e006be7d9190a250565b8060020154341015612ac7576040517fcd1c886700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060020154341115612b0f57600281015433906108fc90612ae8903461343b565b6040518115909202915f818181858888f19350505050158015612b0d573d5f803e3d5ffd5b505b50565b60048181015460028301546040517fdd62ed3e000000000000000000000000000000000000000000000000000000008152339381019390935230602484015273ffffffffffffffffffffffffffffffffffffffff90911691829063dd62ed3e90604401602060405180830381865afa158015612b90573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612bb4919061344e565b1015612bec576040517fcd1c886700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60028201546040517f23b872dd000000000000000000000000000000000000000000000000000000008152336004820152306024820152604481019190915273ffffffffffffffffffffffffffffffffffffffff8216906323b872dd906064016020604051808303815f875af1158015612c68573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612c8c9190613465565b612cc2576040517f045c4b0200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600582015415612b0d575f61271083600501548460020154612ce491906134e4565b612cee91906134fb565b6004808501546040517f42966c6800000000000000000000000000000000000000000000000000000000815292935073ffffffffffffffffffffffffffffffffffffffff16916342966c6891612d4a9185910190815260200190565b5f604051808303815f87803b158015612d61575f80fd5b505af1925050508015612d72575060015b15612dc557600483015460405182815273ffffffffffffffffffffffffffffffffffffffff909116907ffd38818f5291bf0bb3a2a48aadc06ba8757865d1dabd804585338aab3009dcb690602001610df5565b505050565b5f8083601f840112612dda575f80fd5b50813567ffffffffffffffff811115612df1575f80fd5b602083019150836020828501011115612e08575f80fd5b9250929050565b73ffffffffffffffffffffffffffffffffffffffff81168114612b0f575f80fd5b5f805f805f805f60c0888a031215612e46575f80fd5b87359650602088013567ffffffffffffffff811115612e63575f80fd5b612e6f8a828b01612dca565b90975095505060408801359350606088013592506080880135612e9181612e0f565b8092505060a0880135905092959891949750929550565b5f8060408385031215612eb9575f80fd5b8235612ec481612e0f565b946020939093013593505050565b5f60208284031215612ee2575f80fd5b5035919050565b8015158114612b0f575f80fd5b5f8060408385031215612f07575f80fd5b823591506020830135612f1981612ee9565b809150509250929050565b5f8060408385031215612f35575f80fd5b8235612f4081612e0f565b91506020830135612f1981612ee9565b5f60208284031215612f60575f80fd5b8135612f6b81612e0f565b9392505050565b5f805f60608486031215612f84575f80fd5b8335612f8f81612e0f565b92506020840135612f9f81612e0f565b929592945050506040919091013590565b5f805f805f8060a08789031215612fc5575f80fd5b863567ffffffffffffffff811115612fdb575f80fd5b612fe789828a01612dca565b9097509550506020870135935060408701359250606087013561300981612e0f565b80925050608087013590509295509295509295565b602080825282518282018190525f9190848201906040850190845b8181101561305557835183529284019291840191600101613039565b50909695505050505050565b5f805f60408486031215613073575f80fd5b833561307e81612e0f565b9250602084013567ffffffffffffffff8082111561309a575f80fd5b818601915086601f8301126130ad575f80fd5b8135818111156130bb575f80fd5b8760208260051b85010111156130cf575f80fd5b6020830194508093505050509250925092565b5f80604083850312156130f3575f80fd5b82356130fe81612e0f565b91506020830135612f1981612e0f565b861515815260c060208201525f86518060c0840152806020890160e085015e5f60e0828501015260e07fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011684010191505085604083015284606083015273ffffffffffffffffffffffffffffffffffffffff841660808301528260a0830152979650505050505050565b600181811c908216806131b057607f821691505b6020821081036131e7577f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b50919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b601f821115612dc557805f5260205f20601f840160051c8101602085101561323f5750805b601f840160051c820191505b8181101561325e575f815560010161324b565b5050505050565b67ffffffffffffffff83111561327d5761327d6131ed565b6132918361328b835461319c565b8361321a565b5f601f8411600181146132e1575f85156132ab5750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b17835561325e565b5f838152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08716915b8281101561332e578685013582556020948501946001909201910161330e565b5086821015613369577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555050505050565b60a081528560a0820152858760c08301375f60c087830101525f60c07fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f890116830101905085602083015284604083015273ffffffffffffffffffffffffffffffffffffffff84166060830152826080830152979650505050505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b80820180821115611e1b57611e1b6133fb565b81810381811115611e1b57611e1b6133fb565b5f6020828403121561345e575f80fd5b5051919050565b5f60208284031215613475575f80fd5b8151612f6b81612ee9565b5f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036134b0576134b06133fb565b5060010190565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b8082028115828204841417611e1b57611e1b6133fb565b5f8261352e577f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b50049056fea164736f6c6343000819000a") )