diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f05f935..029fbc8 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,10 +1,8 @@ name: Build on: - push: - # branches: [ main ] - # pull_request: - # branches: [ main ] + pull_request: + branches: [ main, develop ] jobs: diff --git a/.github/workflows/publish-docker-image.yaml b/.github/workflows/publish-docker-image.yaml new file mode 100644 index 0000000..823ba79 --- /dev/null +++ b/.github/workflows/publish-docker-image.yaml @@ -0,0 +1,34 @@ +name: Publish Docker Image to GitHub Package Registry + +on: + push: + tags: + - '*' + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Get the version + id: get_version + run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/} + + - name: Build and push Docker images + uses: docker/build-push-action@v2 + with: + context: . + push: true + tags: ghcr.io/${{ github.repository }}:${{ steps.get_version.outputs.VERSION }} diff --git a/Dockerfile b/Dockerfile index 8028784..46cd622 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,10 +9,12 @@ WORKDIR /app RUN go mod download +COPY ./bindings /app/bindings COPY ./core /app/core COPY ./cmd /app/cmd COPY ./Makefile /app/Makefile -COPY ./bot.toml /app/bot.toml +COPY ./bot.testnet.toml /app/bot.testnet.toml +COPY ./bot.mainnet.toml /app/bot.mainnet.toml WORKDIR /app/ @@ -20,9 +22,8 @@ RUN make build-go FROM alpine:3.18 -COPY --from=builder /app/bot /usr/local/bin -COPY --from=builder /app/bot.toml /app/bot.toml +COPY --from=builder /app/bot /usr/local/bin +COPY --from=builder /app/bot.testnet.toml /bot.testnet.toml +COPY --from=builder /app/bot.mainnet.toml /bot.mainnet.toml WORKDIR /app - -CMD ["bot", "run", "--config", "/app/bot.toml"] diff --git a/Makefile b/Makefile index cc3d6db..382e7dc 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,10 @@ build-solidity: forge build; \ popd; +bindings: build-solidity + jq '.abi' contracts/out/L2StandardBridgeBot.sol/L2StandardBridgeBot.json > contracts/out/L2StandardBridgeBot.sol/L2StandardBridgeBot.abi; \ + abigen --abi contracts/out/L2StandardBridgeBot.sol/L2StandardBridgeBot.abi --pkg bindings --type L2StandardBridgeBot --out bindings/L2StandardBridgeBot.go + .PHONY: \ bot \ build-go \ diff --git a/README.md b/README.md index df61391..37cb381 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ references ### Getting Started at opBNB testnet -1. Prepare a PostgreSQL database +1. Prepare a MySQL database ``` docker-compose up -d diff --git a/bindings/L2StandardBridgeBot.go b/bindings/L2StandardBridgeBot.go new file mode 100644 index 0000000..a2212d9 --- /dev/null +++ b/bindings/L2StandardBridgeBot.go @@ -0,0 +1,896 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package bindings + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// L2StandardBridgeBotMetaData contains all meta data concerning the L2StandardBridgeBot contract. +var L2StandardBridgeBotMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"addresspayable\",\"name\":\"_owner\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_delegationFee\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"}],\"name\":\"AddressEmptyCode\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"AddressInsufficientBalance\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedInnerCall\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"OwnableInvalidOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"OwnableUnauthorizedAccount\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"SafeERC20FailedOperation\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_delegationFee\",\"type\":\"uint256\"}],\"name\":\"SetDelegationFee\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"l2Token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"minGasLimit\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"}],\"name\":\"WithdrawTo\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"L2_STANDARD_BRIDGE\",\"outputs\":[{\"internalType\":\"contractIL2StandardBridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"L2_STANDARD_BRIDGE_ADDRESS\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"delegationFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_delegationFee\",\"type\":\"uint256\"}],\"name\":\"setDelegationFee\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_l2Token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"},{\"internalType\":\"uint32\",\"name\":\"_minGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"_extraData\",\"type\":\"bytes\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_recipient\",\"type\":\"address\"}],\"name\":\"withdrawFee\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_recipient\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"_minGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"_extraData\",\"type\":\"bytes\"}],\"name\":\"withdrawFeeToL1\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_l2Token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"},{\"internalType\":\"uint32\",\"name\":\"_minGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"_extraData\",\"type\":\"bytes\"}],\"name\":\"withdrawTo\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"}]", +} + +// L2StandardBridgeBotABI is the input ABI used to generate the binding from. +// Deprecated: Use L2StandardBridgeBotMetaData.ABI instead. +var L2StandardBridgeBotABI = L2StandardBridgeBotMetaData.ABI + +// L2StandardBridgeBot is an auto generated Go binding around an Ethereum contract. +type L2StandardBridgeBot struct { + L2StandardBridgeBotCaller // Read-only binding to the contract + L2StandardBridgeBotTransactor // Write-only binding to the contract + L2StandardBridgeBotFilterer // Log filterer for contract events +} + +// L2StandardBridgeBotCaller is an auto generated read-only Go binding around an Ethereum contract. +type L2StandardBridgeBotCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// L2StandardBridgeBotTransactor is an auto generated write-only Go binding around an Ethereum contract. +type L2StandardBridgeBotTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// L2StandardBridgeBotFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type L2StandardBridgeBotFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// L2StandardBridgeBotSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type L2StandardBridgeBotSession struct { + Contract *L2StandardBridgeBot // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// L2StandardBridgeBotCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type L2StandardBridgeBotCallerSession struct { + Contract *L2StandardBridgeBotCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// L2StandardBridgeBotTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type L2StandardBridgeBotTransactorSession struct { + Contract *L2StandardBridgeBotTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// L2StandardBridgeBotRaw is an auto generated low-level Go binding around an Ethereum contract. +type L2StandardBridgeBotRaw struct { + Contract *L2StandardBridgeBot // Generic contract binding to access the raw methods on +} + +// L2StandardBridgeBotCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type L2StandardBridgeBotCallerRaw struct { + Contract *L2StandardBridgeBotCaller // Generic read-only contract binding to access the raw methods on +} + +// L2StandardBridgeBotTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type L2StandardBridgeBotTransactorRaw struct { + Contract *L2StandardBridgeBotTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewL2StandardBridgeBot creates a new instance of L2StandardBridgeBot, bound to a specific deployed contract. +func NewL2StandardBridgeBot(address common.Address, backend bind.ContractBackend) (*L2StandardBridgeBot, error) { + contract, err := bindL2StandardBridgeBot(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &L2StandardBridgeBot{L2StandardBridgeBotCaller: L2StandardBridgeBotCaller{contract: contract}, L2StandardBridgeBotTransactor: L2StandardBridgeBotTransactor{contract: contract}, L2StandardBridgeBotFilterer: L2StandardBridgeBotFilterer{contract: contract}}, nil +} + +// NewL2StandardBridgeBotCaller creates a new read-only instance of L2StandardBridgeBot, bound to a specific deployed contract. +func NewL2StandardBridgeBotCaller(address common.Address, caller bind.ContractCaller) (*L2StandardBridgeBotCaller, error) { + contract, err := bindL2StandardBridgeBot(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &L2StandardBridgeBotCaller{contract: contract}, nil +} + +// NewL2StandardBridgeBotTransactor creates a new write-only instance of L2StandardBridgeBot, bound to a specific deployed contract. +func NewL2StandardBridgeBotTransactor(address common.Address, transactor bind.ContractTransactor) (*L2StandardBridgeBotTransactor, error) { + contract, err := bindL2StandardBridgeBot(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &L2StandardBridgeBotTransactor{contract: contract}, nil +} + +// NewL2StandardBridgeBotFilterer creates a new log filterer instance of L2StandardBridgeBot, bound to a specific deployed contract. +func NewL2StandardBridgeBotFilterer(address common.Address, filterer bind.ContractFilterer) (*L2StandardBridgeBotFilterer, error) { + contract, err := bindL2StandardBridgeBot(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &L2StandardBridgeBotFilterer{contract: contract}, nil +} + +// bindL2StandardBridgeBot binds a generic wrapper to an already deployed contract. +func bindL2StandardBridgeBot(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := L2StandardBridgeBotMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_L2StandardBridgeBot *L2StandardBridgeBotRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _L2StandardBridgeBot.Contract.L2StandardBridgeBotCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_L2StandardBridgeBot *L2StandardBridgeBotRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _L2StandardBridgeBot.Contract.L2StandardBridgeBotTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_L2StandardBridgeBot *L2StandardBridgeBotRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _L2StandardBridgeBot.Contract.L2StandardBridgeBotTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_L2StandardBridgeBot *L2StandardBridgeBotCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _L2StandardBridgeBot.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_L2StandardBridgeBot *L2StandardBridgeBotTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _L2StandardBridgeBot.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_L2StandardBridgeBot *L2StandardBridgeBotTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _L2StandardBridgeBot.Contract.contract.Transact(opts, method, params...) +} + +// L2STANDARDBRIDGE is a free data retrieval call binding the contract method 0x21d12763. +// +// Solidity: function L2_STANDARD_BRIDGE() view returns(address) +func (_L2StandardBridgeBot *L2StandardBridgeBotCaller) L2STANDARDBRIDGE(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _L2StandardBridgeBot.contract.Call(opts, &out, "L2_STANDARD_BRIDGE") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// L2STANDARDBRIDGE is a free data retrieval call binding the contract method 0x21d12763. +// +// Solidity: function L2_STANDARD_BRIDGE() view returns(address) +func (_L2StandardBridgeBot *L2StandardBridgeBotSession) L2STANDARDBRIDGE() (common.Address, error) { + return _L2StandardBridgeBot.Contract.L2STANDARDBRIDGE(&_L2StandardBridgeBot.CallOpts) +} + +// L2STANDARDBRIDGE is a free data retrieval call binding the contract method 0x21d12763. +// +// Solidity: function L2_STANDARD_BRIDGE() view returns(address) +func (_L2StandardBridgeBot *L2StandardBridgeBotCallerSession) L2STANDARDBRIDGE() (common.Address, error) { + return _L2StandardBridgeBot.Contract.L2STANDARDBRIDGE(&_L2StandardBridgeBot.CallOpts) +} + +// L2STANDARDBRIDGEADDRESS is a free data retrieval call binding the contract method 0x2cb7cb06. +// +// Solidity: function L2_STANDARD_BRIDGE_ADDRESS() view returns(address) +func (_L2StandardBridgeBot *L2StandardBridgeBotCaller) L2STANDARDBRIDGEADDRESS(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _L2StandardBridgeBot.contract.Call(opts, &out, "L2_STANDARD_BRIDGE_ADDRESS") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// L2STANDARDBRIDGEADDRESS is a free data retrieval call binding the contract method 0x2cb7cb06. +// +// Solidity: function L2_STANDARD_BRIDGE_ADDRESS() view returns(address) +func (_L2StandardBridgeBot *L2StandardBridgeBotSession) L2STANDARDBRIDGEADDRESS() (common.Address, error) { + return _L2StandardBridgeBot.Contract.L2STANDARDBRIDGEADDRESS(&_L2StandardBridgeBot.CallOpts) +} + +// L2STANDARDBRIDGEADDRESS is a free data retrieval call binding the contract method 0x2cb7cb06. +// +// Solidity: function L2_STANDARD_BRIDGE_ADDRESS() view returns(address) +func (_L2StandardBridgeBot *L2StandardBridgeBotCallerSession) L2STANDARDBRIDGEADDRESS() (common.Address, error) { + return _L2StandardBridgeBot.Contract.L2STANDARDBRIDGEADDRESS(&_L2StandardBridgeBot.CallOpts) +} + +// DelegationFee is a free data retrieval call binding the contract method 0xc5f0a58f. +// +// Solidity: function delegationFee() view returns(uint256) +func (_L2StandardBridgeBot *L2StandardBridgeBotCaller) DelegationFee(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _L2StandardBridgeBot.contract.Call(opts, &out, "delegationFee") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// DelegationFee is a free data retrieval call binding the contract method 0xc5f0a58f. +// +// Solidity: function delegationFee() view returns(uint256) +func (_L2StandardBridgeBot *L2StandardBridgeBotSession) DelegationFee() (*big.Int, error) { + return _L2StandardBridgeBot.Contract.DelegationFee(&_L2StandardBridgeBot.CallOpts) +} + +// DelegationFee is a free data retrieval call binding the contract method 0xc5f0a58f. +// +// Solidity: function delegationFee() view returns(uint256) +func (_L2StandardBridgeBot *L2StandardBridgeBotCallerSession) DelegationFee() (*big.Int, error) { + return _L2StandardBridgeBot.Contract.DelegationFee(&_L2StandardBridgeBot.CallOpts) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_L2StandardBridgeBot *L2StandardBridgeBotCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _L2StandardBridgeBot.contract.Call(opts, &out, "owner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_L2StandardBridgeBot *L2StandardBridgeBotSession) Owner() (common.Address, error) { + return _L2StandardBridgeBot.Contract.Owner(&_L2StandardBridgeBot.CallOpts) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_L2StandardBridgeBot *L2StandardBridgeBotCallerSession) Owner() (common.Address, error) { + return _L2StandardBridgeBot.Contract.Owner(&_L2StandardBridgeBot.CallOpts) +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotTransactor) RenounceOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _L2StandardBridgeBot.contract.Transact(opts, "renounceOwnership") +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotSession) RenounceOwnership() (*types.Transaction, error) { + return _L2StandardBridgeBot.Contract.RenounceOwnership(&_L2StandardBridgeBot.TransactOpts) +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotTransactorSession) RenounceOwnership() (*types.Transaction, error) { + return _L2StandardBridgeBot.Contract.RenounceOwnership(&_L2StandardBridgeBot.TransactOpts) +} + +// SetDelegationFee is a paid mutator transaction binding the contract method 0x55bfc81c. +// +// Solidity: function setDelegationFee(uint256 _delegationFee) returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotTransactor) SetDelegationFee(opts *bind.TransactOpts, _delegationFee *big.Int) (*types.Transaction, error) { + return _L2StandardBridgeBot.contract.Transact(opts, "setDelegationFee", _delegationFee) +} + +// SetDelegationFee is a paid mutator transaction binding the contract method 0x55bfc81c. +// +// Solidity: function setDelegationFee(uint256 _delegationFee) returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotSession) SetDelegationFee(_delegationFee *big.Int) (*types.Transaction, error) { + return _L2StandardBridgeBot.Contract.SetDelegationFee(&_L2StandardBridgeBot.TransactOpts, _delegationFee) +} + +// SetDelegationFee is a paid mutator transaction binding the contract method 0x55bfc81c. +// +// Solidity: function setDelegationFee(uint256 _delegationFee) returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotTransactorSession) SetDelegationFee(_delegationFee *big.Int) (*types.Transaction, error) { + return _L2StandardBridgeBot.Contract.SetDelegationFee(&_L2StandardBridgeBot.TransactOpts, _delegationFee) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotTransactor) TransferOwnership(opts *bind.TransactOpts, newOwner common.Address) (*types.Transaction, error) { + return _L2StandardBridgeBot.contract.Transact(opts, "transferOwnership", newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) { + return _L2StandardBridgeBot.Contract.TransferOwnership(&_L2StandardBridgeBot.TransactOpts, newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotTransactorSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) { + return _L2StandardBridgeBot.Contract.TransferOwnership(&_L2StandardBridgeBot.TransactOpts, newOwner) +} + +// Withdraw is a paid mutator transaction binding the contract method 0x32b7006d. +// +// Solidity: function withdraw(address _l2Token, uint256 _amount, uint32 _minGasLimit, bytes _extraData) payable returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotTransactor) Withdraw(opts *bind.TransactOpts, _l2Token common.Address, _amount *big.Int, _minGasLimit uint32, _extraData []byte) (*types.Transaction, error) { + return _L2StandardBridgeBot.contract.Transact(opts, "withdraw", _l2Token, _amount, _minGasLimit, _extraData) +} + +// Withdraw is a paid mutator transaction binding the contract method 0x32b7006d. +// +// Solidity: function withdraw(address _l2Token, uint256 _amount, uint32 _minGasLimit, bytes _extraData) payable returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotSession) Withdraw(_l2Token common.Address, _amount *big.Int, _minGasLimit uint32, _extraData []byte) (*types.Transaction, error) { + return _L2StandardBridgeBot.Contract.Withdraw(&_L2StandardBridgeBot.TransactOpts, _l2Token, _amount, _minGasLimit, _extraData) +} + +// Withdraw is a paid mutator transaction binding the contract method 0x32b7006d. +// +// Solidity: function withdraw(address _l2Token, uint256 _amount, uint32 _minGasLimit, bytes _extraData) payable returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotTransactorSession) Withdraw(_l2Token common.Address, _amount *big.Int, _minGasLimit uint32, _extraData []byte) (*types.Transaction, error) { + return _L2StandardBridgeBot.Contract.Withdraw(&_L2StandardBridgeBot.TransactOpts, _l2Token, _amount, _minGasLimit, _extraData) +} + +// WithdrawFee is a paid mutator transaction binding the contract method 0x1ac3ddeb. +// +// Solidity: function withdrawFee(address _recipient) returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotTransactor) WithdrawFee(opts *bind.TransactOpts, _recipient common.Address) (*types.Transaction, error) { + return _L2StandardBridgeBot.contract.Transact(opts, "withdrawFee", _recipient) +} + +// WithdrawFee is a paid mutator transaction binding the contract method 0x1ac3ddeb. +// +// Solidity: function withdrawFee(address _recipient) returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotSession) WithdrawFee(_recipient common.Address) (*types.Transaction, error) { + return _L2StandardBridgeBot.Contract.WithdrawFee(&_L2StandardBridgeBot.TransactOpts, _recipient) +} + +// WithdrawFee is a paid mutator transaction binding the contract method 0x1ac3ddeb. +// +// Solidity: function withdrawFee(address _recipient) returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotTransactorSession) WithdrawFee(_recipient common.Address) (*types.Transaction, error) { + return _L2StandardBridgeBot.Contract.WithdrawFee(&_L2StandardBridgeBot.TransactOpts, _recipient) +} + +// WithdrawFeeToL1 is a paid mutator transaction binding the contract method 0x244cafe0. +// +// Solidity: function withdrawFeeToL1(address _recipient, uint32 _minGasLimit, bytes _extraData) returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotTransactor) WithdrawFeeToL1(opts *bind.TransactOpts, _recipient common.Address, _minGasLimit uint32, _extraData []byte) (*types.Transaction, error) { + return _L2StandardBridgeBot.contract.Transact(opts, "withdrawFeeToL1", _recipient, _minGasLimit, _extraData) +} + +// WithdrawFeeToL1 is a paid mutator transaction binding the contract method 0x244cafe0. +// +// Solidity: function withdrawFeeToL1(address _recipient, uint32 _minGasLimit, bytes _extraData) returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotSession) WithdrawFeeToL1(_recipient common.Address, _minGasLimit uint32, _extraData []byte) (*types.Transaction, error) { + return _L2StandardBridgeBot.Contract.WithdrawFeeToL1(&_L2StandardBridgeBot.TransactOpts, _recipient, _minGasLimit, _extraData) +} + +// WithdrawFeeToL1 is a paid mutator transaction binding the contract method 0x244cafe0. +// +// Solidity: function withdrawFeeToL1(address _recipient, uint32 _minGasLimit, bytes _extraData) returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotTransactorSession) WithdrawFeeToL1(_recipient common.Address, _minGasLimit uint32, _extraData []byte) (*types.Transaction, error) { + return _L2StandardBridgeBot.Contract.WithdrawFeeToL1(&_L2StandardBridgeBot.TransactOpts, _recipient, _minGasLimit, _extraData) +} + +// WithdrawTo is a paid mutator transaction binding the contract method 0xa3a79548. +// +// Solidity: function withdrawTo(address _l2Token, address _to, uint256 _amount, uint32 _minGasLimit, bytes _extraData) payable returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotTransactor) WithdrawTo(opts *bind.TransactOpts, _l2Token common.Address, _to common.Address, _amount *big.Int, _minGasLimit uint32, _extraData []byte) (*types.Transaction, error) { + return _L2StandardBridgeBot.contract.Transact(opts, "withdrawTo", _l2Token, _to, _amount, _minGasLimit, _extraData) +} + +// WithdrawTo is a paid mutator transaction binding the contract method 0xa3a79548. +// +// Solidity: function withdrawTo(address _l2Token, address _to, uint256 _amount, uint32 _minGasLimit, bytes _extraData) payable returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotSession) WithdrawTo(_l2Token common.Address, _to common.Address, _amount *big.Int, _minGasLimit uint32, _extraData []byte) (*types.Transaction, error) { + return _L2StandardBridgeBot.Contract.WithdrawTo(&_L2StandardBridgeBot.TransactOpts, _l2Token, _to, _amount, _minGasLimit, _extraData) +} + +// WithdrawTo is a paid mutator transaction binding the contract method 0xa3a79548. +// +// Solidity: function withdrawTo(address _l2Token, address _to, uint256 _amount, uint32 _minGasLimit, bytes _extraData) payable returns() +func (_L2StandardBridgeBot *L2StandardBridgeBotTransactorSession) WithdrawTo(_l2Token common.Address, _to common.Address, _amount *big.Int, _minGasLimit uint32, _extraData []byte) (*types.Transaction, error) { + return _L2StandardBridgeBot.Contract.WithdrawTo(&_L2StandardBridgeBot.TransactOpts, _l2Token, _to, _amount, _minGasLimit, _extraData) +} + +// L2StandardBridgeBotOwnershipTransferredIterator is returned from FilterOwnershipTransferred and is used to iterate over the raw logs and unpacked data for OwnershipTransferred events raised by the L2StandardBridgeBot contract. +type L2StandardBridgeBotOwnershipTransferredIterator struct { + Event *L2StandardBridgeBotOwnershipTransferred // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *L2StandardBridgeBotOwnershipTransferredIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(L2StandardBridgeBotOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(L2StandardBridgeBotOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *L2StandardBridgeBotOwnershipTransferredIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *L2StandardBridgeBotOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// L2StandardBridgeBotOwnershipTransferred represents a OwnershipTransferred event raised by the L2StandardBridgeBot contract. +type L2StandardBridgeBotOwnershipTransferred struct { + PreviousOwner common.Address + NewOwner common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOwnershipTransferred is a free log retrieval operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_L2StandardBridgeBot *L2StandardBridgeBotFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, previousOwner []common.Address, newOwner []common.Address) (*L2StandardBridgeBotOwnershipTransferredIterator, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _L2StandardBridgeBot.contract.FilterLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule) + if err != nil { + return nil, err + } + return &L2StandardBridgeBotOwnershipTransferredIterator{contract: _L2StandardBridgeBot.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +// WatchOwnershipTransferred is a free log subscription operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_L2StandardBridgeBot *L2StandardBridgeBotFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *L2StandardBridgeBotOwnershipTransferred, previousOwner []common.Address, newOwner []common.Address) (event.Subscription, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _L2StandardBridgeBot.contract.WatchLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(L2StandardBridgeBotOwnershipTransferred) + if err := _L2StandardBridgeBot.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOwnershipTransferred is a log parse operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_L2StandardBridgeBot *L2StandardBridgeBotFilterer) ParseOwnershipTransferred(log types.Log) (*L2StandardBridgeBotOwnershipTransferred, error) { + event := new(L2StandardBridgeBotOwnershipTransferred) + if err := _L2StandardBridgeBot.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// L2StandardBridgeBotSetDelegationFeeIterator is returned from FilterSetDelegationFee and is used to iterate over the raw logs and unpacked data for SetDelegationFee events raised by the L2StandardBridgeBot contract. +type L2StandardBridgeBotSetDelegationFeeIterator struct { + Event *L2StandardBridgeBotSetDelegationFee // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *L2StandardBridgeBotSetDelegationFeeIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(L2StandardBridgeBotSetDelegationFee) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(L2StandardBridgeBotSetDelegationFee) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *L2StandardBridgeBotSetDelegationFeeIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *L2StandardBridgeBotSetDelegationFeeIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// L2StandardBridgeBotSetDelegationFee represents a SetDelegationFee event raised by the L2StandardBridgeBot contract. +type L2StandardBridgeBotSetDelegationFee struct { + DelegationFee *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterSetDelegationFee is a free log retrieval operation binding the contract event 0x0322f3257c2afe5fe8da7ab561f0d3384148487412fe2751678f2188731c0815. +// +// Solidity: event SetDelegationFee(uint256 _delegationFee) +func (_L2StandardBridgeBot *L2StandardBridgeBotFilterer) FilterSetDelegationFee(opts *bind.FilterOpts) (*L2StandardBridgeBotSetDelegationFeeIterator, error) { + + logs, sub, err := _L2StandardBridgeBot.contract.FilterLogs(opts, "SetDelegationFee") + if err != nil { + return nil, err + } + return &L2StandardBridgeBotSetDelegationFeeIterator{contract: _L2StandardBridgeBot.contract, event: "SetDelegationFee", logs: logs, sub: sub}, nil +} + +// WatchSetDelegationFee is a free log subscription operation binding the contract event 0x0322f3257c2afe5fe8da7ab561f0d3384148487412fe2751678f2188731c0815. +// +// Solidity: event SetDelegationFee(uint256 _delegationFee) +func (_L2StandardBridgeBot *L2StandardBridgeBotFilterer) WatchSetDelegationFee(opts *bind.WatchOpts, sink chan<- *L2StandardBridgeBotSetDelegationFee) (event.Subscription, error) { + + logs, sub, err := _L2StandardBridgeBot.contract.WatchLogs(opts, "SetDelegationFee") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(L2StandardBridgeBotSetDelegationFee) + if err := _L2StandardBridgeBot.contract.UnpackLog(event, "SetDelegationFee", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseSetDelegationFee is a log parse operation binding the contract event 0x0322f3257c2afe5fe8da7ab561f0d3384148487412fe2751678f2188731c0815. +// +// Solidity: event SetDelegationFee(uint256 _delegationFee) +func (_L2StandardBridgeBot *L2StandardBridgeBotFilterer) ParseSetDelegationFee(log types.Log) (*L2StandardBridgeBotSetDelegationFee, error) { + event := new(L2StandardBridgeBotSetDelegationFee) + if err := _L2StandardBridgeBot.contract.UnpackLog(event, "SetDelegationFee", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// L2StandardBridgeBotWithdrawToIterator is returned from FilterWithdrawTo and is used to iterate over the raw logs and unpacked data for WithdrawTo events raised by the L2StandardBridgeBot contract. +type L2StandardBridgeBotWithdrawToIterator struct { + Event *L2StandardBridgeBotWithdrawTo // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *L2StandardBridgeBotWithdrawToIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(L2StandardBridgeBotWithdrawTo) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(L2StandardBridgeBotWithdrawTo) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *L2StandardBridgeBotWithdrawToIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *L2StandardBridgeBotWithdrawToIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// L2StandardBridgeBotWithdrawTo represents a WithdrawTo event raised by the L2StandardBridgeBot contract. +type L2StandardBridgeBotWithdrawTo struct { + From common.Address + L2Token common.Address + To common.Address + Amount *big.Int + MinGasLimit uint32 + ExtraData []byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterWithdrawTo is a free log retrieval operation binding the contract event 0x56f66275d9ebc94b7d6895aa0d96a3783550d0183ba106408d387d19f2e877f1. +// +// Solidity: event WithdrawTo(address indexed from, address indexed l2Token, address to, uint256 amount, uint32 minGasLimit, bytes extraData) +func (_L2StandardBridgeBot *L2StandardBridgeBotFilterer) FilterWithdrawTo(opts *bind.FilterOpts, from []common.Address, l2Token []common.Address) (*L2StandardBridgeBotWithdrawToIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var l2TokenRule []interface{} + for _, l2TokenItem := range l2Token { + l2TokenRule = append(l2TokenRule, l2TokenItem) + } + + logs, sub, err := _L2StandardBridgeBot.contract.FilterLogs(opts, "WithdrawTo", fromRule, l2TokenRule) + if err != nil { + return nil, err + } + return &L2StandardBridgeBotWithdrawToIterator{contract: _L2StandardBridgeBot.contract, event: "WithdrawTo", logs: logs, sub: sub}, nil +} + +// WatchWithdrawTo is a free log subscription operation binding the contract event 0x56f66275d9ebc94b7d6895aa0d96a3783550d0183ba106408d387d19f2e877f1. +// +// Solidity: event WithdrawTo(address indexed from, address indexed l2Token, address to, uint256 amount, uint32 minGasLimit, bytes extraData) +func (_L2StandardBridgeBot *L2StandardBridgeBotFilterer) WatchWithdrawTo(opts *bind.WatchOpts, sink chan<- *L2StandardBridgeBotWithdrawTo, from []common.Address, l2Token []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var l2TokenRule []interface{} + for _, l2TokenItem := range l2Token { + l2TokenRule = append(l2TokenRule, l2TokenItem) + } + + logs, sub, err := _L2StandardBridgeBot.contract.WatchLogs(opts, "WithdrawTo", fromRule, l2TokenRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(L2StandardBridgeBotWithdrawTo) + if err := _L2StandardBridgeBot.contract.UnpackLog(event, "WithdrawTo", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseWithdrawTo is a log parse operation binding the contract event 0x56f66275d9ebc94b7d6895aa0d96a3783550d0183ba106408d387d19f2e877f1. +// +// Solidity: event WithdrawTo(address indexed from, address indexed l2Token, address to, uint256 amount, uint32 minGasLimit, bytes extraData) +func (_L2StandardBridgeBot *L2StandardBridgeBotFilterer) ParseWithdrawTo(log types.Log) (*L2StandardBridgeBotWithdrawTo, error) { + event := new(L2StandardBridgeBotWithdrawTo) + if err := _L2StandardBridgeBot.contract.UnpackLog(event, "WithdrawTo", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/bot.mainnet.toml b/bot.mainnet.toml new file mode 100644 index 0000000..8f531dc --- /dev/null +++ b/bot.mainnet.toml @@ -0,0 +1,36 @@ +l2-starting-number = 12602733 +propose-time-window = 3600 +challenge-time-window = 604800 + +[rpcs] +l1-rpc = "$OPBNB_BRIDGE_BOT_L1_RPC" +l2-rpc = "$OPBNB_BRIDGE_BOT_L2_RPC" + +[tx-signer] +privkey = "$OPBNB_BRIDGE_BOT_PRIVKEY" +gas-price = 3000000000 # 3 gwei + +[db] +host = "$OPBNB_BRIDGE_BOT_DB_HOST" +port = $OPBNB_BRIDGE_BOT_DB_PORT +user = "$OPBNB_BRIDGE_BOT_DB_USER" +password = "$OPBNB_BRIDGE_BOT_DB_PASSWORD" +name = "$OPBNB_BRIDGE_BOT_DB_NAME" + +[l1-contracts] +optimism-portal = "0x1876EA7702C0ad0C6A2ae6036DE7733edfBca519" +l2-output-oracle = "0x153CAB79f4767E2ff862C94aa49573294B13D169" +l1-cross-domain-messenger = "0xd95D508f13f7029CCF0fb61984d5dfD11b879c4f" + +[l2-standard-bridge-bot] +contract-address = "0x1aa15a27115589e107ec1d2b581adbb92bbc04b7" +log-filter-block-range = 1000 + +# See https://github.com/bnb-chain/opbnb-bridge-tokens/blob/main/opbnb.tokenlist.json +whitelist-l2-token-list = [ + "0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000", # Native token + "0x7c6b91d9be155a6db01f749217d76ff02a7227f2", # BTCB + "0xe7798f023fc62146e8aa1b36da45fb70855a77ea", # ETH + "0x50c5725949a6f0c72e6c4a641f24049a917db0cb", # FDUSD + "0x9e5aac1ba1a2e6aed6b32689dfcf62a509ca96f3" # USDT +] diff --git a/bot.testnet.toml b/bot.testnet.toml new file mode 100644 index 0000000..6cdd46d --- /dev/null +++ b/bot.testnet.toml @@ -0,0 +1,39 @@ +propose-time-window = 240 +challenge-time-window = 120 # in seconds. Adjust the setting to 120 seconds for convenience during testing. +l2-starting-number = $OPBNB_BRIDGE_BOT_L2_STARTING_NUMBER + +[rpcs] +l1-rpc = "$OPBNB_BRIDGE_BOT_L1_RPC" +l2-rpc = "$OPBNB_BRIDGE_BOT_L2_RPC" + +[tx-signer] +privkey = "$OPBNB_BRIDGE_BOT_PRIVKEY" +gas-price = 5000000000 # 5 gwei + +[db] +host = "$OPBNB_BRIDGE_BOT_DB_HOST" +port = $OPBNB_BRIDGE_BOT_DB_PORT +user = "$OPBNB_BRIDGE_BOT_DB_USER" +password = "$OPBNB_BRIDGE_BOT_DB_PASSWORD" +name = "$OPBNB_BRIDGE_BOT_DB_NAME" + +[l1-contracts] +optimism-portal = "0x4386c8abf2009ac0c263462da568dd9d46e52a31" +l2-output-oracle = "0xff2394bb843012562f4349c6632a0ecb92fc8810" +l1-cross-domain-messenger = "0xd506952e78eecd5d4424b1990a0c99b1568e7c2c" + +[l2-standard-bridge-bot] +contract-address = "0xE750d1f9180294473baCd960Ce5F9576eFBd70f2" +log-filter-block-range = 1000 + +# See https://github.com/bnb-chain/opbnb-bridge-tokens#opbnb-testnet-token-list +whitelist-l2-token-list = [ + "0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000", # Native token + "0xa9aD1484D9Bfb27adbc2bf50A6E495777CC8cFf2", # BUSD + "0x3AB4E696E31173409dbfBb1FEB5b9A7cC55A212c", # BTCB + "0x584f7b986d9942B0859a1E6921efA5342A673d04", # ETH + "0xCF712f20c85421d00EAa1B6F6545AaEEb4492B75", # USDT + "0x845E27B8A4ad1Fe3dc0b41b900dC8C1Bb45141C3", # USDC + "0xf46896fbEf6478eaCcFB1C815915daa7e6f87b22", # DAI + "0x2C58b64b4BA448A9b60e9398E58d17F1824da962" # tBEP20 +] diff --git a/bot.toml b/bot.toml deleted file mode 100644 index 05852ee..0000000 --- a/bot.toml +++ /dev/null @@ -1,25 +0,0 @@ -[rpcs] -l1-rpc = -l2-rpc = - -[db] -host = "0.0.0.0" -port = 5434 -user = "db_username" -password = "db_password" -name = "db_name" - -[l1-contracts] -address-manager = "0x1111111111111111111111111111111111111111" -system-config = "0x406ac857817708eaf4ca3a82317ef4ae3d1ea23b" -optimism-portal = "0x4386c8abf2009ac0c263462da568dd9d46e52a31" -l2-output-oracle = "0xff2394bb843012562f4349c6632a0ecb92fc8810" -l1-cross-domain-messenger = "0xd506952e78eecd5d4424b1990a0c99b1568e7c2c" -l1-standard-bridge = "0x1111111111111111111111111111111111111111" -# l1-erc721-bridge = "0x17e1454015bfb3377c75be7b6d47b236fd2ddbe7" -l1-erc721-bridge = "0x1111111111111111111111111111111111111111" - -[misc] -l2-standard-bridge-bot = "0x4c3171982cd7f62690f4d6e086b91f434b97332f" -propose-time-window = 60 -challenge-time-window = 60 diff --git a/cmd/bot/run.go b/cmd/bot/run.go index 88756ae..6b6c426 100644 --- a/cmd/bot/run.go +++ b/cmd/bot/run.go @@ -17,7 +17,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/urfave/cli/v2" - "gorm.io/driver/postgres" + "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/clause" ) @@ -54,12 +54,12 @@ func RunCommand(ctx *cli.Context) error { if err != nil { return fmt.Errorf("failed to migrate l2_scanned_blocks: %w", err) } - err = db.AutoMigrate(&core.L2ContractEvent{}) + err = db.AutoMigrate(&core.BotDelegatedWithdrawal{}) if err != nil { - return fmt.Errorf("failed to migrate l2_contract_events: %w", err) + return fmt.Errorf("failed to migrate withdrawals: %w", err) } - l2ScannedBlock, err := queryL2ScannedBlock(db, &cfg) + l2ScannedBlock, err := queryL2ScannedBlock(db, cfg.L2StartingNumber) if err != nil { return err } @@ -76,77 +76,119 @@ func RunCommand(ctx *cli.Context) error { // and it will finalize the withdrawal when the challenge time window has passed. func ProcessBotDelegatedWithdrawals(ctx context.Context, log log.Logger, db *gorm.DB, l1Client *core.ClientExt, l2Client *core.ClientExt, cfg core.Config) { ticker := time.NewTicker(3 * time.Second) + pending := core.NewPendingTxsManager() for { select { case <-ticker.C: - ProcessUnprovenBotDelegatedWithdrawals(ctx, log, db, l1Client, l2Client, cfg) - ProcessUnfinalizedBotDelegatedWithdrawals(ctx, log, db, l1Client, l2Client, cfg) + // In order to avoid re-processing the same withdrawal, we need to check if the pending nonce is + // the chain nonce. If they are not equal, it means that there are some pending transactions that + // been confirmed yet. + _, signerAddress, _ := cfg.SignerKeyPair() + if equal, err := isPendingAndChainNonceEqual(l1Client, signerAddress); err != nil { + log.Error("failed to check pending and chain nonce", "error", err) + continue + } else if !equal { + log.Info("pending nonce is not equal to chain nonce, skip processing") + continue + } + + currentNonce, err := l1Client.NonceAt(ctx, *signerAddress, nil) + if err != nil { + log.Error("failed to get chain nonce", "error", err) + continue + } + + ProcessUnprovenBotDelegatedWithdrawals(ctx, log, db, l1Client, l2Client, cfg, pending, ¤tNonce) + ProcessUnfinalizedBotDelegatedWithdrawals(ctx, log, db, l1Client, l2Client, cfg, pending, ¤tNonce) case <-ctx.Done(): return } } } -func ProcessUnprovenBotDelegatedWithdrawals(ctx context.Context, log log.Logger, db *gorm.DB, l1Client *core.ClientExt, l2Client *core.ClientExt, cfg core.Config) { +func ProcessUnprovenBotDelegatedWithdrawals(ctx context.Context, log log.Logger, db *gorm.DB, l1Client *core.ClientExt, l2Client *core.ClientExt, cfg core.Config, pending *core.PendingTxnCheck, currentNonce *uint64) { + latestProposedNumber, err := core.L2OutputOracleLatestBlockNumber(cfg.L1Contracts.L2OutputOracleProxy, l1Client) + if err != nil { + log.Error("failed to get latest proposed block number", "error", err) + return + } + processor := core.NewProcessor(log, l1Client, l2Client, cfg) limit := 1000 - maxBlockTime := time.Now().Unix() - cfg.Misc.ProposeTimeWindow - unprovens := make([]core.L2ContractEvent, 0) - result := db.Order("id asc").Where("proven = false AND block_time < ? AND failure_reason IS NULL", maxBlockTime).Limit(limit).Find(&unprovens) + unprovens := make([]core.BotDelegatedWithdrawal, 0) + result := db.Order("id asc").Where("proven_time IS NULL AND initiated_block_number <= ? AND failure_reason IS NULL", latestProposedNumber.Uint64()).Limit(limit).Find(&unprovens) if result.Error != nil { - log.Error("failed to query l2_contract_events", "error", result.Error) + log.Error("failed to query withdrawals", "error", result.Error) return } + pending.Prune(*currentNonce) for _, unproven := range unprovens { - err := processor.ProveWithdrawalTransaction(ctx, &unproven) + // Avoid re-processing the same withdrawal + if pending.IsPendingTxn(unproven.ID) { + continue + } + + now := time.Now() + err := processor.ProveWithdrawalTransaction(ctx, &unproven, *currentNonce) if err != nil { if strings.Contains(err.Error(), "OptimismPortal: withdrawal hash has already been proven") { // The withdrawal has already proven, mark it - result := db.Model(&unproven).Update("proven", true) + result := db.Model(&unproven).Update("proven_time", now) if result.Error != nil { - log.Error("failed to update proven l2_contract_events", "error", result.Error) + log.Error("failed to update proven withdrawals", "error", result.Error) } } else if strings.Contains(err.Error(), "L2OutputOracle: cannot get output for a block that has not been proposed") { // Since the unproven withdrawals are sorted by the on-chain order, we can break here because we know // that the subsequent of the withdrawals are not ready to be proven yet. return - } else if strings.Contains(err.Error(), "execution reverted") { + } else if strings.Contains(err.Error(), "execution reverted") || strings.Contains(err.Error(), "filtered") { // Proven transaction reverted, mark it with the failure reason result := db.Model(&unproven).Update("failure_reason", err.Error()) if result.Error != nil { - log.Error("failed to update failure reason of l2_contract_events", "error", result.Error) + log.Error("failed to update failure reason of withdrawals", "error", result.Error) } } else { // non-revert error, stop processing the subsequent withdrawals log.Error("ProveWithdrawalTransaction", "non-revert error", err.Error()) return } + } else { + pending.AddPendingTxn(unproven.ID, *currentNonce) + *currentNonce = *currentNonce + 1 } } } -func ProcessUnfinalizedBotDelegatedWithdrawals(ctx context.Context, log log.Logger, db *gorm.DB, l1Client *core.ClientExt, l2Client *core.ClientExt, cfg core.Config) { +func ProcessUnfinalizedBotDelegatedWithdrawals(ctx context.Context, log log.Logger, db *gorm.DB, l1Client *core.ClientExt, l2Client *core.ClientExt, cfg core.Config, pending *core.PendingTxnCheck, currentNonce *uint64) { processor := core.NewProcessor(log, l1Client, l2Client, cfg) limit := 1000 - maxBlockTime := time.Now().Unix() - cfg.Misc.ChallengeTimeWindow - unfinalizeds := make([]core.L2ContractEvent, 0) - result := db.Order("block_time asc").Where("proven = true AND finalized = false AND block_time < ? AND failure_reason IS NULL", maxBlockTime).Limit(limit).Find(&unfinalizeds) + now := time.Now() + maxProvenTime := now.Add(-time.Duration(cfg.ChallengeTimeWindow) * time.Second) + + unfinalizeds := make([]core.BotDelegatedWithdrawal, 0) + result := db.Order("id asc").Where("finalized_time IS NULL AND proven_time IS NOT NULL AND proven_time < ? AND failure_reason IS NULL", maxProvenTime).Limit(limit).Find(&unfinalizeds) if result.Error != nil { - log.Error("failed to query l2_contract_events", "error", result.Error) + log.Error("failed to query withdrawals", "error", result.Error) return } + pending.Prune(*currentNonce) for _, unfinalized := range unfinalizeds { + // In order to avoid re-processing the same withdrawal + if pending.IsPendingTxn(unfinalized.ID) { + continue + } + err := processor.FinalizeMessage(ctx, &unfinalized) if err != nil { if strings.Contains(err.Error(), "OptimismPortal: withdrawal has already been finalized") { // The withdrawal has already finalized, mark it - result := db.Model(&unfinalized).Update("finalized", true) + result := db.Model(&unfinalized).Update("finalized_time", now) if result.Error != nil { - log.Error("failed to update finalized l2_contract_events", "error", result.Error) + log.Error("failed to update finalized withdrawals", "error", result.Error) } } else if strings.Contains(err.Error(), "OptimismPortal: withdrawal has not been proven yet") { log.Error("detected a unproven withdrawal when send finalized transaction", "withdrawal", unfinalized) @@ -158,13 +200,16 @@ func ProcessUnfinalizedBotDelegatedWithdrawals(ctx context.Context, log log.Logg // Finalized transaction reverted, mark it with the failure reason result := db.Model(&unfinalized).Update("failure_reason", err.Error()) if result.Error != nil { - log.Error("failed to update failure reason of l2_contract_events", "error", result.Error) + log.Error("failed to update failure reason of withdrawals", "error", result.Error) } } else { // non-revert error, stop processing the subsequent withdrawals log.Error("FinalizedMessage", "non-revert error", err.Error()) return } + } else { + pending.AddPendingTxn(unfinalized.ID, *currentNonce) + *currentNonce = *currentNonce + 1 } } } @@ -178,13 +223,10 @@ func storeLogs(db *gorm.DB, client *core.ClientExt, logs []types.Log) error { return err } - event := core.L2ContractEvent{ - BlockTime: int64(header.Time), - BlockHash: vLog.BlockHash.Hex(), - ContractAddress: vLog.Address.Hex(), - TransactionHash: vLog.TxHash.Hex(), - LogIndex: int(vLog.Index), - EventSignature: vLog.Topics[0].Hex(), + event := core.BotDelegatedWithdrawal{ + TransactionHash: vLog.TxHash.Hex(), + LogIndex: int(vLog.Index), + InitiatedBlockNumber: int64(header.Number.Uint64()), } deduped := db.Clauses( @@ -200,9 +242,9 @@ func storeLogs(db *gorm.DB, client *core.ClientExt, logs []types.Log) error { } // WatchBotDelegatedWithdrawals watches for new bot-delegated withdrawals and stores them in the database. -func WatchBotDelegatedWithdrawals(ctx context.Context, log log.Logger, db *gorm.DB, client *core.ClientExt, l2ScannedBlock *core.L2ScannedBlock, cfg core.Config) { +func WatchBotDelegatedWithdrawals(ctx context.Context, log log.Logger, db *gorm.DB, client *core.ClientExt, l2StartingBlock *core.L2ScannedBlock, cfg core.Config) { timer := time.NewTimer(0) - fromBlockNumber := big.NewInt(l2ScannedBlock.Number) + fromBlockNumber := big.NewInt(l2StartingBlock.Number) for { select { @@ -212,17 +254,14 @@ func WatchBotDelegatedWithdrawals(ctx context.Context, log log.Logger, db *gorm. timer.Reset(time.Second) } - toBlockNumber := new(big.Int).Add(fromBlockNumber, big.NewInt(cfg.Misc.LogFilterBlockRange)) - latestNumber, err := client.BlockNumber(context.Background()) + toBlockNumber := new(big.Int).Add(fromBlockNumber, big.NewInt(cfg.L2StandardBridgeBot.LogFilterBlockRange)) + finalizedHeader, err := client.GetHeaderByTag(context.Background(), "finalized") if err != nil { log.Error("call eth_blockNumber", "error", err) continue } - - if latestNumber < uint64(cfg.Misc.ConfirmBlocks) { - toBlockNumber = big.NewInt(0) - } else if latestNumber-uint64(cfg.Misc.ConfirmBlocks) < toBlockNumber.Uint64() { - toBlockNumber = big.NewInt(int64(latestNumber - uint64(cfg.Misc.ConfirmBlocks))) + if toBlockNumber.Uint64() > finalizedHeader.Number.Uint64() { + toBlockNumber = finalizedHeader.Number } if fromBlockNumber.Uint64() > toBlockNumber.Uint64() { @@ -231,7 +270,7 @@ func WatchBotDelegatedWithdrawals(ctx context.Context, log log.Logger, db *gorm. } log.Info("Fetching logs from blocks", "fromBlock", fromBlockNumber, "toBlock", toBlockNumber) - logs, err := getLogs(client, fromBlockNumber, toBlockNumber, common.HexToAddress(cfg.Misc.L2StandardBridgeBot), core.WithdrawToEventSig()) + logs, err := getLogs(client, fromBlockNumber, toBlockNumber, common.HexToAddress(cfg.L2StandardBridgeBot.ContractAddress), core.WithdrawToEventSig()) if err != nil { log.Error("eth_getLogs", "error", err) continue @@ -249,8 +288,8 @@ func WatchBotDelegatedWithdrawals(ctx context.Context, log log.Logger, db *gorm. } } - l2ScannedBlock.Number = toBlockNumber.Int64() - result := db.Where("number >= 0").Updates(l2ScannedBlock) + l2StartingBlock.Number = toBlockNumber.Int64() + result := db.Save(l2StartingBlock) if result.Error != nil { log.Error("update l2_scanned_blocks", "error", result.Error) } @@ -274,31 +313,16 @@ func getLogs(client *core.ClientExt, fromBlock *big.Int, toBlock *big.Int, contr // connect connects to the database func connect(log log.Logger, dbConfig config.DBConfig) (*gorm.DB, error) { - dsn := fmt.Sprintf("host=%s dbname=%s sslmode=disable", dbConfig.Host, dbConfig.Name) - if dbConfig.Port != 0 { - dsn += fmt.Sprintf(" port=%d", dbConfig.Port) - } - if dbConfig.User != "" { - dsn += fmt.Sprintf(" user=%s", dbConfig.User) - } - if dbConfig.Password != "" { - dsn += fmt.Sprintf(" password=%s", dbConfig.Password) - } - + dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Name) gormConfig := gorm.Config{ Logger: core.NewGormLogger(log), SkipDefaultTransaction: true, - - // The postgres parameter counter for a given query is represented with uint16, - // resulting in a parameter limit of 65535. In order to avoid reaching this limit - // we'll utilize a batch size of 3k for inserts, well below the limit as long as - // the number of columns < 20. - CreateBatchSize: 3_000, + CreateBatchSize: 3_000, } retryStrategy := &retry.ExponentialStrategy{Min: 1000, Max: 20_000, MaxJitter: 250} gorm_, err := retry.Do[*gorm.DB](context.Background(), 10, retryStrategy, func() (*gorm.DB, error) { - gorm_, err := gorm.Open(postgres.Open(dsn), &gormConfig) + gorm_, err := gorm.Open(mysql.Open(dsn), &gormConfig) if err != nil { return nil, fmt.Errorf("failed to connect to database: %w", err) } @@ -314,8 +338,8 @@ func connect(log log.Logger, dbConfig config.DBConfig) (*gorm.DB, error) { } // queryL2ScannedBlock queries the l2_scanned_blocks table for the last scanned block -func queryL2ScannedBlock(db *gorm.DB, cfg *core.Config) (*core.L2ScannedBlock, error) { - l2ScannedBlock := core.L2ScannedBlock{Number: 0} +func queryL2ScannedBlock(db *gorm.DB, l2StartingNumber int64) (*core.L2ScannedBlock, error) { + l2ScannedBlock := core.L2ScannedBlock{Number: l2StartingNumber} result := db.Order("number desc").Last(&l2ScannedBlock) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { @@ -323,12 +347,21 @@ func queryL2ScannedBlock(db *gorm.DB, cfg *core.Config) (*core.L2ScannedBlock, e } else { return nil, fmt.Errorf("failed to query l2_scanned_blocks: %w", result.Error) } - } else { - if l2ScannedBlock.Number < cfg.Misc.ConfirmBlocks { - l2ScannedBlock.Number = 0 - } else { - l2ScannedBlock.Number -= cfg.Misc.ConfirmBlocks - } } return &l2ScannedBlock, nil } + +// isPendingAndChainNonceEqual checks if the pending nonce and the chain nonce are equal. +func isPendingAndChainNonceEqual(l1Client *core.ClientExt, address *common.Address) (bool, error) { + pendingNonce, err := l1Client.PendingNonceAt(context.Background(), *address) + if err != nil { + return false, fmt.Errorf("failed to get pending nonce: %w", err) + } + + latestNonce, err := l1Client.NonceAt(context.Background(), *address, nil) + if err != nil { + return false, fmt.Errorf("failed to get latest nonce: %w", err) + } + + return pendingNonce == latestNonce, nil +} diff --git a/contracts/src/L2StandardBridgeBot.sol b/contracts/src/L2StandardBridgeBot.sol index e31887c..8a54bc9 100644 --- a/contracts/src/L2StandardBridgeBot.sol +++ b/contracts/src/L2StandardBridgeBot.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.20; import { Ownable } from "openzeppelin-contracts/contracts/access/Ownable.sol"; import { IERC20 } from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; // See also https://github.com/bnb-chain/opbnb/blob/9505ae88d0ec8f593ee036284c9a13672526a232/packages/contracts-bedrock/contracts/L2/L2StandardBridge.sol#L20 interface IL2StandardBridge { @@ -15,6 +16,8 @@ interface IL2StandardBridge { } contract L2StandardBridgeBot is Ownable { + using SafeERC20 for IERC20; + address public constant L2_STANDARD_BRIDGE_ADDRESS = 0x4200000000000000000000000000000000000010; IL2StandardBridge public L2_STANDARD_BRIDGE = IL2StandardBridge(payable(L2_STANDARD_BRIDGE_ADDRESS)); @@ -22,11 +25,10 @@ contract L2StandardBridgeBot is Ownable { uint256 public delegationFee; - event WithdrawTo(address indexed from, address l2Token, address to, uint256 amount, uint32 minGasLimit, bytes extraData); + event WithdrawTo(address indexed from, address indexed l2Token, address to, uint256 amount, uint32 minGasLimit, bytes extraData); - receive() external payable { } + event SetDelegationFee(uint256 _delegationFee); - fallback() payable external { } constructor(address payable _owner, uint256 _delegationFee) Ownable(_owner) { delegationFee = _delegationFee; @@ -50,8 +52,7 @@ contract L2StandardBridgeBot is Ownable { IERC20 l2Token = IERC20(_l2Token); bool approveSuccess = l2Token.approve(L2_STANDARD_BRIDGE_ADDRESS, _amount + l2Token.allowance(address(this), L2_STANDARD_BRIDGE_ADDRESS)); require(approveSuccess, "BEP20 withdrawal: approve failed"); - bool transferSuccess = l2Token.transferFrom(msg.sender, address(this), _amount); - require(transferSuccess, "BEP20 withdrawal: transferFrom failed"); + l2Token.safeTransferFrom(msg.sender, address(this), _amount); L2_STANDARD_BRIDGE.withdrawTo{value: 0}(_l2Token, _to, _amount, _minGasLimit, _extraData); } @@ -91,6 +92,10 @@ contract L2StandardBridgeBot is Ownable { // setDelegationFee set the delegation fee, only owner can call this function. function setDelegationFee(uint256 _delegationFee) external onlyOwner { + require(_delegationFee > 0, "_delegationFee cannot be less than or equal to 0 BNB"); + require(_delegationFee <= 1e18, "_delegationFee cannot be more than 1 BNB"); delegationFee = _delegationFee; + + emit SetDelegationFee(_delegationFee); } } diff --git a/contracts/test/L2StandardBridgeBot.t.sol b/contracts/test/L2StandardBridgeBot.t.sol index 2b3b917..04095b7 100644 --- a/contracts/test/L2StandardBridgeBot.t.sol +++ b/contracts/test/L2StandardBridgeBot.t.sol @@ -15,7 +15,7 @@ contract L2StandardBridgeBotTest is Test { address constant LEGACY_ERC20_ETH = 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000; address constant L2_STANDARD_BRIDGE = 0x4200000000000000000000000000000000000010; - event WithdrawTo(address indexed from, address l2Token, address to, uint256 amount, uint32 minGasLimit, bytes extraData); + event WithdrawTo(address indexed from, address indexed l2Token, address to, uint256 amount, uint32 minGasLimit, bytes extraData); event SentMessageExtension1(address indexed sender , uint256 value); diff --git a/core/bindings.go b/core/bindings.go index 18d39a4..f34aeb3 100644 --- a/core/bindings.go +++ b/core/bindings.go @@ -1,6 +1,9 @@ package core import ( + "math/big" + + "github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum/go-ethereum/common" "golang.org/x/crypto/sha3" ) @@ -17,3 +20,13 @@ func WithdrawToEventSig() common.Hash { eventSignatureHash := keccak256.Sum(nil) return common.BytesToHash(eventSignatureHash) } + +// L2OutputOracleLatestBlockNumber calls the "latestBlockNumber" function on the L2OutputOracle contract at the given address. +func L2OutputOracleLatestBlockNumber(address common.Address, l1Client *ClientExt) (*big.Int, error) { + caller, err := bindings.NewL2OutputOracleCaller(address, l1Client) + if err != nil { + return nil, err + } + + return caller.LatestBlockNumber(nil) +} diff --git a/core/client.go b/core/client.go index fc5c57d..38d8923 100644 --- a/core/client.go +++ b/core/client.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rpc" ) @@ -78,6 +79,18 @@ func (c *ClientExt) CallContract(ctx context.Context, call ethereum.CallMsg, blo return hexutil.Decode(result) } +// New methods by block tag + +func (c *ClientExt) GetHeaderByTag(ctx context.Context, blockTag string) (*types.Header, error) { + var result types.Header + err := c.Client.Client().CallContext(ctx, &result, "eth_getHeaderByNumber", blockTag) + if err != nil { + return nil, err + } + + return &result, nil +} + // Needed private utils from geth func toBlockNumArg(number *big.Int) string { diff --git a/core/config.go b/core/config.go index b726b3e..2df612c 100644 --- a/core/config.go +++ b/core/config.go @@ -16,34 +16,55 @@ import ( ) const ( - defaultConfirmBlocks = 15 defaultLogFilterBlockRange = 100 ) type Config struct { - Misc MiscConfig `toml:"misc"` - RPCs config.RPCsConfig `toml:"rpcs"` - DB config.DBConfig `toml:"db"` - L1Contracts config.L1Contracts `toml:"l1-contracts"` - Signer SignerConfig `toml:"signer"` + L2StartingNumber int64 `toml:"l2-starting-number"` + ProposeTimeWindow int64 `toml:"propose-time-window"` + ChallengeTimeWindow int64 `toml:"challenge-time-window"` + + RPCs config.RPCsConfig `toml:"rpcs"` + DB config.DBConfig `toml:"db"` + L1Contracts config.L1Contracts `toml:"l1-contracts"` + L2StandardBridgeBot L2StandardBridgeBotConfig `toml:"l2-standard-bridge-bot"` + TxSigner TxSignerConfig `toml:"tx-signer"` } -type MiscConfig struct { - L2StandardBridgeBot string `toml:"l2-standard-bridge-bot"` - ProposeTimeWindow int64 `toml:"propose-time-window"` - ChallengeTimeWindow int64 `toml:"challenge-time-window"` - ConfirmBlocks int64 `toml:"confirm-blocks"` - LogFilterBlockRange int64 `toml:"log-filter-block-range"` -} - -type SignerConfig struct { +type TxSignerConfig struct { Privkey string `toml:"privkey"` GasPrice int64 `toml:"gas-price"` } +type L2StandardBridgeBotConfig struct { + // ContractAddress is the address of the L2StandardBridgeBot contract. + ContractAddress string `toml:"contract-address"` + + // LogFilterBlockRange is the number of blocks to filter for events + LogFilterBlockRange int64 `toml:"log-filter-block-range"` + + // WhitelistL2TokenList is the list of L2 tokens to whitelist for the L2StandardBridgeBot contract. + // + // L2StandardBridgeBot contract doesn't limit the L2 tokens to be bridged, but this off-chain bot + // process only whitelists the tokens in this list and ignore tokens not in this list. + // + // **IMPORTANT: If this list is unset, all L2 tokens are whitelisted.** + WhitelistL2TokenList *[]string `toml:"whitelist-l2-token-list"` + + // UpperMinGasLimit is the upper limit of the minimum gas limit of the L2StandardBridgeBot contract. + // + // **IMPORTANT: If this value is unset, the L2StandardBridgeBot contract doesn't limit the minimum gas limit.** + UpperMinGasLimit *uint32 `toml:"upper-min-gas-limit"` + + // UpperExtraDataSize is the upper limit of the extra data size of the L2StandardBridgeBot contract. + // + // **IMPORTANT: If this value is unset, the L2StandardBridgeBot contract doesn't limit the extra data size.** + UpperExtraDataSize *uint32 `toml:"upper-extra-data-size"` +} + // LoadConfig loads the `bot.toml` config file from a given path func LoadConfig(log log.Logger, path string) (Config, error) { - log.Debug("loading config", "path", path) + log.Info("loading config", "path", path) var conf Config data, err := os.ReadFile(path) @@ -58,25 +79,21 @@ func LoadConfig(log log.Logger, path string) (Config, error) { return conf, err } - if conf.Misc.ConfirmBlocks == 0 { - log.Info("setting default confirm blocks", "confirm-blocks", defaultConfirmBlocks) - conf.Misc.ConfirmBlocks = defaultConfirmBlocks - } - if conf.Misc.LogFilterBlockRange == 0 { + if conf.L2StandardBridgeBot.LogFilterBlockRange == 0 { log.Info("setting default log filter block range", "log-filter-block-range", defaultLogFilterBlockRange) - conf.Misc.LogFilterBlockRange = defaultLogFilterBlockRange + conf.L2StandardBridgeBot.LogFilterBlockRange = defaultLogFilterBlockRange } - if conf.Misc.ProposeTimeWindow == 0 { + if conf.ProposeTimeWindow == 0 { return conf, errors.New("propose-time-window must be set") } - if conf.Misc.ChallengeTimeWindow == 0 { + if conf.ChallengeTimeWindow == 0 { return conf, errors.New("challenge-time-window must be set") } if _, _, err = conf.SignerKeyPair(); err != nil { return conf, err } - if conf.Signer.GasPrice == 0 { + if conf.TxSigner.GasPrice == 0 { return conf, errors.New("gas-price must be set") } @@ -85,7 +102,7 @@ func LoadConfig(log log.Logger, path string) (Config, error) { } func (c *Config) SignerKeyPair() (*ecdsa.PrivateKey, *common.Address, error) { - privkey, err := crypto.HexToECDSA(c.Signer.Privkey) + privkey, err := crypto.HexToECDSA(c.TxSigner.Privkey) if err != nil { return nil, nil, fmt.Errorf("failed to parse privkey: %w", err) } diff --git a/core/pending_txn_check.go b/core/pending_txn_check.go new file mode 100644 index 0000000..8b2735b --- /dev/null +++ b/core/pending_txn_check.go @@ -0,0 +1,30 @@ +package core + +type PendingTxnCheck struct { + inner map[uint]uint64 // #{withdrawalId=>nonce} +} + +// NewPendingTxsManager creates a new PendingTxnCheck +func NewPendingTxsManager() *PendingTxnCheck { + return &PendingTxnCheck{inner: make(map[uint]uint64)} +} + +// IsPendingTxn checks whether there is pending transaction for the specific event id. +func (c *PendingTxnCheck) IsPendingTxn(id uint) bool { + _, ok := c.inner[id] + return ok +} + +// AddPendingTxn adds a pending item. +func (c *PendingTxnCheck) AddPendingTxn(id uint, nonce uint64) { + c.inner[id] = nonce +} + +// Prune removes the transactions with staled nonce. +func (c *PendingTxnCheck) Prune(chainNonce uint64) { + for id, nonce := range c.inner { + if nonce <= chainNonce { + delete(c.inner, id) + } + } +} diff --git a/core/processor.go b/core/processor.go index 7d3c4af..82d5843 100644 --- a/core/processor.go +++ b/core/processor.go @@ -1,9 +1,12 @@ package core import ( + bindings2 "bnbchain/opbnb-bridge-bot/bindings" "context" "errors" "fmt" + "math/big" + "github.com/ethereum-optimism/optimism/indexer/config" "github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum/go-ethereum/accounts/abi" @@ -12,7 +15,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" - "math/big" ) type Processor struct { @@ -23,6 +25,8 @@ type Processor struct { cfg Config L2Contracts config.L2Contracts + + whitelistL2TokenMap map[common.Address]struct{} } func NewProcessor( @@ -31,13 +35,20 @@ func NewProcessor( l2Client *ClientExt, cfg Config, ) *Processor { - log = log.New("processor", "Processor") - l2Contracts := config.L2ContractsFromPredeploys() - return &Processor{log, l1Client, l2Client, cfg, l2Contracts} + + var whitelistL2TokenMap map[common.Address]struct{} = nil + if cfg.L2StandardBridgeBot.WhitelistL2TokenList != nil { + whitelistL2TokenMap = make(map[common.Address]struct{}) + for _, l2Token := range *cfg.L2StandardBridgeBot.WhitelistL2TokenList { + whitelistL2TokenMap[common.HexToAddress(l2Token)] = struct{}{} + } + } + + return &Processor{log, l1Client, l2Client, cfg, l2Contracts, whitelistL2TokenMap} } -func (b *Processor) toWithdrawal(botDelegatedWithdrawToEvent *L2ContractEvent, receipt *types.Receipt) (*bindings.TypesWithdrawalTransaction, error) { +func (b *Processor) toWithdrawal(botDelegatedWithdrawToEvent *BotDelegatedWithdrawal, receipt *types.Receipt) (*bindings.TypesWithdrawalTransaction, error) { // Events flow: // // event[i-5]: WithdrawalInitiated @@ -46,13 +57,16 @@ func (b *Processor) toWithdrawal(botDelegatedWithdrawToEvent *L2ContractEvent, r // event[i-2]: SentMessage // event[i-1]: SentMessageExtension1 // event[i] : L2StandardBridgeBot.WithdrawTo - if botDelegatedWithdrawToEvent.LogIndex < 5 || len(receipt.Logs) < 5 { + if botDelegatedWithdrawToEvent.LogIndex < 5 || len(receipt.Logs) < 6 { return nil, fmt.Errorf("invalid botDelegatedWithdrawToEvent: %v", botDelegatedWithdrawToEvent) } - messagePassedLog := receipt.Logs[botDelegatedWithdrawToEvent.LogIndex-3] - sentMessageLog := receipt.Logs[botDelegatedWithdrawToEvent.LogIndex-2] - sentMessageExtension1Log := receipt.Logs[botDelegatedWithdrawToEvent.LogIndex-1] + messagePassedLog := GetLogByLogIndex(receipt, uint(botDelegatedWithdrawToEvent.LogIndex-3)) + sentMessageLog := GetLogByLogIndex(receipt, uint(botDelegatedWithdrawToEvent.LogIndex-2)) + sentMessageExtension1Log := GetLogByLogIndex(receipt, uint(botDelegatedWithdrawToEvent.LogIndex-1)) + if messagePassedLog == nil || sentMessageLog == nil || sentMessageExtension1Log == nil { + return nil, fmt.Errorf("invalid botDelegatedWithdrawToEvent: %v", botDelegatedWithdrawToEvent) + } sentMessageEvent, err := b.toL2CrossDomainMessengerSentMessageExtension1(sentMessageLog, sentMessageExtension1Log) if err != nil { @@ -71,12 +85,22 @@ func (b *Processor) toWithdrawal(botDelegatedWithdrawToEvent *L2ContractEvent, r return withdrawalTx, nil } -func (b *Processor) ProveWithdrawalTransaction(ctx context.Context, botDelegatedWithdrawToEvent *L2ContractEvent) error { +func (b *Processor) ProveWithdrawalTransaction(ctx context.Context, botDelegatedWithdrawToEvent *BotDelegatedWithdrawal, nonce uint64) error { receipt, err := b.L2Client.TransactionReceipt(ctx, common.HexToHash(botDelegatedWithdrawToEvent.TransactionHash)) if err != nil { return err } + vlog := GetLogByLogIndex(receipt, uint(botDelegatedWithdrawToEvent.LogIndex)) + if vlog == nil { + return fmt.Errorf("cannot find log within receipt, logIndex: %d, receitp: %v", botDelegatedWithdrawToEvent.LogIndex, receipt) + } + + err = b.CheckByFilterOptions(vlog) + if err != nil { + return err + } + l2BlockNumber := receipt.BlockNumber withdrawalTx, err := b.toWithdrawal(botDelegatedWithdrawToEvent, receipt) if err != nil { @@ -115,6 +139,10 @@ func (b *Processor) ProveWithdrawalTransaction(ctx context.Context, botDelegated return fmt.Errorf("get output proposal block error: %v", err) } + if len(accountResult.StorageProof) == 0 { + return fmt.Errorf("no storage proof") + } + withdrawalProof := accountResult.StorageProof[0] withdrawalProof2Bytes := make([][]byte, 0) for _, p1 := range withdrawalProof.Proof { @@ -133,7 +161,7 @@ func (b *Processor) ProveWithdrawalTransaction(ctx context.Context, botDelegated return err } - gasPrice := big.NewInt(b.cfg.Signer.GasPrice) + gasPrice := big.NewInt(b.cfg.TxSigner.GasPrice) signerPrivkey, signerAddress, err := b.cfg.SignerKeyPair() if err != nil { return err @@ -150,6 +178,7 @@ func (b *Processor) ProveWithdrawalTransaction(ctx context.Context, botDelegated Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { return types.SignTx(tx, types.NewEIP155Signer(l1ChainId), signerPrivkey) }, + Nonce: big.NewInt(int64(nonce)), }, *withdrawalTx, l2OutputIndex, @@ -165,12 +194,22 @@ func (b *Processor) ProveWithdrawalTransaction(ctx context.Context, botDelegated } // FinalizeMessage https://github.com/ethereum-optimism/optimism/blob/d90e7818de894f0bc93ae7b449b9049416bda370/packages/sdk/src/cross-chain-messenger.ts#L1611 -func (b *Processor) FinalizeMessage(ctx context.Context, botDelegatedWithdrawToEvent *L2ContractEvent) error { +func (b *Processor) FinalizeMessage(ctx context.Context, botDelegatedWithdrawToEvent *BotDelegatedWithdrawal) error { receipt, err := b.L2Client.TransactionReceipt(ctx, common.HexToHash(botDelegatedWithdrawToEvent.TransactionHash)) if err != nil { return err } + vlog := GetLogByLogIndex(receipt, uint(botDelegatedWithdrawToEvent.LogIndex)) + if vlog == nil { + return fmt.Errorf("cannot find log within receipt, logIndex: %d, receitp: %v", botDelegatedWithdrawToEvent.LogIndex, receipt) + } + + err = b.CheckByFilterOptions(vlog) + if err != nil { + return err + } + withdrawalTx, err := b.toWithdrawal(botDelegatedWithdrawToEvent, receipt) if err != nil { return fmt.Errorf("toWithdrawal err: %v", err) @@ -181,7 +220,7 @@ func (b *Processor) FinalizeMessage(ctx context.Context, botDelegatedWithdrawToE return err } - gasPrice := big.NewInt(b.cfg.Signer.GasPrice) + gasPrice := big.NewInt(b.cfg.TxSigner.GasPrice) signerPrivkey, signerAddress, err := b.cfg.SignerKeyPair() if err != nil { return err @@ -503,3 +542,67 @@ func (b *Processor) toLowLevelMessage( } return &withdrawalTx, nil } + +func (b *Processor) CheckByFilterOptions(vlog *types.Log) error { + L2StandardBridgeBotAbi, _ := bindings2.L2StandardBridgeBotMetaData.GetAbi() + withdrawToEvent := bindings2.L2StandardBridgeBotWithdrawTo{} + indexedArgs := func(arguments abi.Arguments) abi.Arguments { + indexedArgs := abi.Arguments{} + for _, arg := range arguments { + if arg.Indexed { + indexedArgs = append(indexedArgs, arg) + } + } + return indexedArgs + } + + err := abi.ParseTopics(&withdrawToEvent, indexedArgs(L2StandardBridgeBotAbi.Events["WithdrawTo"].Inputs), vlog.Topics[1:]) + if err != nil { + return fmt.Errorf("parse indexed event arguments from log.topics of L2StandardBridgeBotWithdrawTo event, err: %v", err) + } + + err = L2StandardBridgeBotAbi.UnpackIntoInterface(&withdrawToEvent, "WithdrawTo", vlog.Data) + if err != nil { + return fmt.Errorf("parse non-indexed event arguments from log.data of L2StandardBridgeBotWithdrawTo event, err: %v", err) + } + + if !IsL2TokenWhitelisted(b.whitelistL2TokenMap, &withdrawToEvent.L2Token) { + return fmt.Errorf("filtered: token is not whitelisted, l2-token: %s", withdrawToEvent.L2Token) + } + if !IsMinGasLimitValid(b.cfg.L2StandardBridgeBot.UpperMinGasLimit, withdrawToEvent.MinGasLimit) { + return fmt.Errorf("filtered: minGasLimit is too large, minGasLimit: %d", withdrawToEvent.MinGasLimit) + } + if !IsExtraDataValid(b.cfg.L2StandardBridgeBot.UpperMinGasLimit, &withdrawToEvent.ExtraData) { + return fmt.Errorf("filtered: extraData is too large, extraDataSize: %d", len(withdrawToEvent.ExtraData)) + } + + return nil +} + +func IsL2TokenWhitelisted(whitelistL2TokenMap map[common.Address]struct{}, l2Token *common.Address) bool { + // nil means all L2 tokens are whitelisted + if whitelistL2TokenMap == nil { + return true + } + + _, exists := whitelistL2TokenMap[*l2Token] + return exists +} + +func IsMinGasLimitValid(upperMinGasLimit *uint32, minGasLimit uint32) bool { + // nil means no limit + if upperMinGasLimit == nil { + return true + } + + return minGasLimit <= *upperMinGasLimit +} + +func IsExtraDataValid(upperExtraDataSize *uint32, extraData *[]byte) bool { + // nil means no limit + if upperExtraDataSize == nil { + return true + } + + return len(*extraData) <= int(*upperExtraDataSize) +} diff --git a/core/processor_test.go b/core/processor_test.go new file mode 100644 index 0000000..6d666b7 --- /dev/null +++ b/core/processor_test.go @@ -0,0 +1,52 @@ +package core_test + +import ( + "bnbchain/opbnb-bridge-bot/core" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" +) + +func TestIsL2TokenWhitelisted(t *testing.T) { + // Test case 1: whitelistL2TokenMap is nil + var whitelistL2TokenMap map[common.Address]struct{} = nil + l2Token := common.HexToAddress("0x1234abcdef") + assert.True(t, core.IsL2TokenWhitelisted(whitelistL2TokenMap, &l2Token)) + + // Test case 2: l2Token is whitelisted + whitelistL2TokenMap = make(map[common.Address]struct{}) + whitelistL2TokenMap[l2Token] = struct{}{} + assert.True(t, core.IsL2TokenWhitelisted(whitelistL2TokenMap, &l2Token)) + + // Test case 3: l2Token is not whitelisted + whitelistL2TokenMap = make(map[common.Address]struct{}) + anotherL2Token := &common.Address{} + assert.False(t, core.IsL2TokenWhitelisted(whitelistL2TokenMap, anotherL2Token)) + + // Test case 4: checksum formated l2Token is whitelisted + whitelistL2TokenMap = make(map[common.Address]struct{}) + whitelistL2TokenMap[l2Token] = struct{}{} + checksumL2Token := common.HexToAddress("0x1234ABCDEF") + assert.True(t, core.IsL2TokenWhitelisted(whitelistL2TokenMap, &checksumL2Token)) +} +func TestIsMinGasLimitValid(t *testing.T) { + // Test case 1: upperMinGasLimit is nil + var upperMinGasLimit *uint32 = nil + minGasLimit := uint32(100) + assert.True(t, core.IsMinGasLimitValid(upperMinGasLimit, minGasLimit)) + + // Test case 2: minGasLimit is less than or equal to upperMinGasLimit + upperMinGasLimit = uint32Ptr(200) + minGasLimit = uint32(200) + assert.True(t, core.IsMinGasLimitValid(upperMinGasLimit, minGasLimit)) + + // Test case 3: minGasLimit is greater than upperMinGasLimit + upperMinGasLimit = uint32Ptr(200) + minGasLimit = uint32(250) + assert.False(t, core.IsMinGasLimitValid(upperMinGasLimit, minGasLimit)) +} + +func uint32Ptr(n uint32) *uint32 { + return &n +} diff --git a/core/types.go b/core/types.go index e844f4a..1078ff0 100644 --- a/core/types.go +++ b/core/types.go @@ -1,18 +1,28 @@ package core +import "time" + type L2ScannedBlock struct { Number int64 `gorm:"type:integer;primarykey"` } -type L2ContractEvent struct { - ID uint `gorm:"primarykey"` - BlockTime int64 `gorm:"type:integer;not null;index:idx_l2_contract_events_block_time"` - BlockHash string `gorm:"type:varchar(256);not null;uniqueIndex:idx_l2_contract_events_block_hash_log_index_key,priority:1;"` - ContractAddress string `gorm:"type:varchar(256);not null"` - TransactionHash string `gorm:"type:varchar(256);not null"` - LogIndex int `gorm:"type:integer;not null;uniqueIndex:idx_l2_contract_events_block_hash_log_index_key,priority:2"` - EventSignature string `gorm:"type:varchar(256);not null"` - Proven bool `gorm:"type:boolean;not null;default:false"` - Finalized bool `gorm:"type:boolean;not null;default:false"` - FailureReason string `gorm:"type:text"` +type BotDelegatedWithdrawal struct { + // ID is the incrementing primary key. + ID uint `gorm:"primarykey"` + + // TransactionHash and LogIndex are the L2 transaction hash and log index of the withdrawal event. + TransactionHash string `gorm:"type:varchar(256);not null;uniqueIndex:idx_bot_delegated_withdrawals_transaction_hash_log_index_key,priority:1"` + LogIndex int `gorm:"type:integer;not null;uniqueIndex:idx_bot_delegated_withdrawals_transaction_hash_log_index_key,priority:2"` + + // InitiatedBlockNumber is the l2 block number at which the withdrawal was initiated on L2. + InitiatedBlockNumber int64 `gorm:"type:integer;not null;index:idx_withdrawals_initiated_block_number"` + + // ProvenTime is the local time at which the withdrawal was proven on L1. NULL if not yet proven. + ProvenTime *time.Time `gorm:"type:datetime;index:idx_withdrawals_proven_time"` + + // FinalizedTime is the local time at which the withdrawal was finalized on L1. NULL if not yet finalized. + FinalizedTime *time.Time `gorm:"type:datetime;index:idx_withdrawals_finalized_time"` + + // FailureReason is the reason for the withdrawal failure, including sending transaction error and off-chain configured filter error. NULL if not yet failed. + FailureReason *string `gorm:"type:text"` } diff --git a/core/utils.go b/core/utils.go new file mode 100644 index 0000000..19dfd3d --- /dev/null +++ b/core/utils.go @@ -0,0 +1,21 @@ +package core + +import "github.com/ethereum/go-ethereum/core/types" + +// GetLogByLogIndex searches through receipt.Logs using the provided logIndex and return the corresponding log. +// +// Be aware that: +// - Log.Index is accumulated for the entire block. +// - There is no guarantee that receipt.Logs will be ordered by index. +func GetLogByLogIndex(receipt *types.Receipt, logIndex uint) *types.Log { + if receipt == nil { + return nil + } + + for _, vlog := range receipt.Logs { + if vlog.Index == logIndex { + return vlog + } + } + return nil +} diff --git a/docker-compose.yml b/docker-compose.yml index 26ddeef..c4db5b3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,21 +1,18 @@ version: '3.8' services: - postgres: - image: postgres:latest + mysql: + image: mysql:latest environment: - - POSTGRES_USER=db_username - - POSTGRES_PASSWORD=db_password - - POSTGRES_DB=db_name - - PGDATA=/data/postgres - - POSTGRES_HOST_AUTH_METHOD=trust - healthcheck: - test: [ "CMD-SHELL", "pg_isready -q -U db_username -d db_name" ] + - MYSQL_ROOT_PASSWORD=db_password + - MYSQL_DATABASE=db_name + - MYSQL_USER=db_username + - MYSQL_PASSWORD=db_password + command: --default-authentication-plugin=mysql_native_password ports: - - "5434:5432" + - "3306:3306" volumes: - - postgres_data:/data/postgres + - mysql_data:/var/lib/mysql volumes: - postgres_data: - + mysql_data: diff --git a/go.mod b/go.mod index e8630f4..e16752e 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,10 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/ethereum-optimism/optimism v1.2.0 github.com/ethereum/go-ethereum v1.13.4 + github.com/stretchr/testify v1.8.4 github.com/urfave/cli/v2 v2.25.7 - gorm.io/driver/postgres v1.5.4 + golang.org/x/crypto v0.14.0 + gorm.io/driver/mysql v1.5.2 gorm.io/gorm v1.25.5 ) @@ -28,12 +30,14 @@ require ( github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/ethereum/c-kzg-4844 v0.3.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/getsentry/sentry-go v0.18.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -42,9 +46,6 @@ require ( github.com/google/uuid v1.3.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/holiman/uint256 v1.2.3 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgx/v5 v5.4.3 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/klauspost/compress v1.16.7 // indirect @@ -55,6 +56,7 @@ require ( github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.17.0 // indirect github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect github.com/prometheus/common v0.44.0 // indirect @@ -69,7 +71,6 @@ require ( github.com/tklauser/numcpus v0.6.1 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - golang.org/x/crypto v0.14.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/sync v0.3.0 // indirect @@ -78,5 +79,6 @@ require ( golang.org/x/text v0.13.0 // indirect golang.org/x/tools v0.13.0 // indirect google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/go.sum b/go.sum index 5ff7067..19316d4 100644 --- a/go.sum +++ b/go.sum @@ -114,6 +114,8 @@ github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3Bop github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -193,12 +195,6 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/ github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= -github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -566,8 +562,9 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo= -gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0= +gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= +gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= +gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=