Commit c0c2267a authored by Kelvin Fichter's avatar Kelvin Fichter

maint: remove teleportr

Removes teleportr since it's being deprecated.
parent 49685f81
......@@ -859,11 +859,6 @@ workflows:
name: proxyd-tests
binary_name: proxyd
working_directory: proxyd
- go-lint-test-build:
name: teleportr-tests
binary_name: teleportr
working_directory: teleportr
dependencies: bss-core
- go-lint-test-build:
name: gas-oracle-tests
binary_name: gas-oracle
......
......@@ -31,7 +31,6 @@ jobs:
l2geth-exporter: ${{ steps.packages.outputs.l2geth-exporter }}
batch-submitter-service: ${{ steps.packages.outputs.batch-submitter-service }}
indexer: ${{ steps.packages.outputs.indexer }}
teleportr: ${{ steps.packages.outputs.teleportr }}
endpoint-monitor: ${{ steps.packages.outputs.l2geth-exporter }}
steps:
......@@ -568,43 +567,6 @@ jobs:
GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }}
GITVERSION=${{ steps.build_args.outputs.GITVERSION }}
teleportr:
name: Publish Teleportr Version ${{ needs.canary-publish.outputs.canary-docker-tag }}
needs: canary-publish
if: needs.canary-publish.outputs.teleportr != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Set build args
id: build_args
run: |
echo ::set-output name=GITDATE::"$(date +%d-%m-%Y)"
echo ::set-output name=GITVERSION::$(jq -r .version ./teleportr/package.json)
echo ::set-output name=GITCOMMIT::"$GITHUB_SHA"
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./teleportr/Dockerfile
push: true
tags: ethereumoptimism/teleportr:${{ needs.canary-publish.outputs.teleportr }}
build-args: |
GITDATE=${{ steps.build_args.outputs.GITDATE }}
GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }}
GITVERSION=${{ steps.build_args.outputs.GITVERSION }}
endpoint-monitor:
name: Publish endpoint-monitor Version ${{ needs.canary-publish.outputs.canary-docker-tag }}
needs: canary-publish
......
......@@ -26,7 +26,6 @@ jobs:
l2geth-exporter: ${{ steps.packages.outputs.l2geth-exporter }}
batch-submitter-service: ${{ steps.packages.outputs.batch-submitter-service }}
indexer: ${{ steps.packages.outputs.indexer }}
teleportr: ${{ steps.packages.outputs.teleportr }}
ci-builder: ${{ steps.packages.outputs.ci-builder }}
foundry: ${{ steps.packages.outputs.foundry }}
endpoint-monitor: ${{ steps.packages.outputs.endpoint-monitor }}
......@@ -600,43 +599,6 @@ jobs:
GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }}
GITVERSION=${{ steps.build_args.outputs.GITVERSION }}
teleportr:
name: Publish Teleportr Version ${{ needs.release.outputs.teleportr }}
needs: release
if: needs.release.outputs.teleportr != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Set build args
id: build_args
run: |
echo ::set-output name=GITDATE::"$(date +%d-%m-%Y)"
echo ::set-output name=GITVERSION::$(jq -r .version ./teleportr/package.json)
echo ::set-output name=GITCOMMIT::"$GITHUB_SHA"
- name: Publish Teleportr
uses: docker/build-push-action@v2
with:
context: .
file: ./teleportr/Dockerfile
push: true
tags: ethereumoptimism/teleportr:${{ needs.release.outputs.teleportr }},ethereumoptimism/teleportr:latest
build-args: |
GITDATE=${{ steps.build_args.outputs.GITDATE }}
GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }}
GITVERSION=${{ steps.build_args.outputs.GITVERSION }}
endpoint-monitor:
name: Publish endpoint-monitor Version ${{ needs.release.outputs.endpoint-monitor}}
needs: release
......
......@@ -73,7 +73,6 @@ Refer to the Directory Structure section below to understand which packages are
├── <a href="./op-exporter">op-exporter</a>: A prometheus exporter to collect/serve metrics from an Optimism node
├── <a href="./proxyd">proxyd</a>: Configurable RPC request router and proxy
├── <a href="./technical-documents">technical-documents</a>: audits and post-mortem documents
├── <a href="./teleportr">teleportr</a>: Bridge for teleporting ETH between L1 and L2 at low cost
~~ BEDROCK upgrade - Not production-ready yet, part of next major upgrade ~~
├── <a href="./packages">packages</a>
......
......@@ -17,7 +17,6 @@ use (
./op-proposer
./op-service
./proxyd
./teleportr
)
replace github.com/ethereum/go-ethereum v1.10.26 => github.com/ethereum-optimism/op-geth v0.0.0-20221104231810-30db39cae2be
......
......@@ -86,6 +86,7 @@ github.com/btcsuite/snappy-go v1.0.0 h1:ZxaA6lo2EpxGddsA8JwWOcxlzRybb444sgmeJQMJ
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 h1:D21IyuvjDCshj1/qq+pCNd3VZOAEI9jy6Bi131YlXgI=
github.com/c-bata/go-prompt v0.2.2 h1:uyKRz6Z6DUyj49QVijyM339UJV9yhbr70gESwbNU3e0=
github.com/casbin/casbin/v2 v2.1.2 h1:bTwon/ECRx9dwBy2ewRVr5OiqjeXSGiTUY74sDPQi/g=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
......@@ -424,8 +425,6 @@ github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82 h1:LneqU9PHDsg/
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537 h1:YGaxtkYjb8mnTvtufv2LKLwCQu2/C7qFB7UtrOlTWOY=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133 h1:JtcyT0rk/9PKOdnKQzuDR+FSjh7SGtJwpgVpfZBRKlQ=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smola/gocompat v0.2.0 h1:6b1oIMlUXIpz//VKEDzPVBK8KG7beVwmHIUEBIs/Pns=
......
......@@ -20,7 +20,6 @@
"ops/docker/ci-builder",
"ops/docker/foundry",
"proxyd",
"teleportr",
"endpoint-monitor"
],
"nohoist": [
......
module.exports = {
skipFiles: [
'./test-libraries',
'./foundry-tests'
'./foundry-tests',
'./testing'
],
mocha: {
grep: "@skip-on-coverage",
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
/**
* @custom:attribution https://github.com/0xclem/teleportr
* @title TeleportrDeposit
* @notice A contract meant to manage deposits into Optimism's Teleportr custodial bridge. Deposits
* are rate limited to avoid a situation where too much ETH is flowing through this bridge
* and cannot be properly disbursed on L2. Inspired by 0xclem's original Teleportr system
* (https://github.com/0xclem/teleportr).
*/
contract TeleportrDeposit is Ownable {
/**
* @notice Minimum deposit amount (in wei).
*/
uint256 public minDepositAmount;
/**
* @notice Maximum deposit amount (in wei).
*/
uint256 public maxDepositAmount;
/**
* @notice Maximum balance this contract will hold before it starts rejecting deposits.
*/
uint256 public maxBalance;
/**
* @notice Total number of deposits received.
*/
uint256 public totalDeposits;
/**
* @notice Emitted any time the minimum deposit amount is set.
*
* @param previousAmount The previous minimum deposit amount.
* @param newAmount The new minimum deposit amount.
*/
event MinDepositAmountSet(uint256 previousAmount, uint256 newAmount);
/**
* @notice Emitted any time the maximum deposit amount is set.
*
* @param previousAmount The previous maximum deposit amount.
* @param newAmount The new maximum deposit amount.
*/
event MaxDepositAmountSet(uint256 previousAmount, uint256 newAmount);
/**
* @notice Emitted any time the contract maximum balance is set.
*
* @param previousBalance The previous maximum contract balance.
* @param newBalance The new maximum contract balance.
*/
event MaxBalanceSet(uint256 previousBalance, uint256 newBalance);
/**
* @notice Emitted any time the balance is withdrawn by the owner.
*
* @param owner The current owner and recipient of the funds.
* @param balance The current contract balance paid to the owner.
*/
event BalanceWithdrawn(address indexed owner, uint256 balance);
/**
* @notice Emitted any time a successful deposit is received.
*
* @param depositId A unique sequencer number identifying the deposit.
* @param emitter The sending address of the payer.
* @param amount The amount deposited by the payer.
*/
event EtherReceived(uint256 indexed depositId, address indexed emitter, uint256 indexed amount);
/**
* @custom:semver 0.0.1
*
* @param _minDepositAmount The initial minimum deposit amount.
* @param _maxDepositAmount The initial maximum deposit amount.
* @param _maxBalance The initial maximum contract balance.
*/
constructor(
uint256 _minDepositAmount,
uint256 _maxDepositAmount,
uint256 _maxBalance
) {
minDepositAmount = _minDepositAmount;
maxDepositAmount = _maxDepositAmount;
maxBalance = _maxBalance;
totalDeposits = 0;
emit MinDepositAmountSet(0, _minDepositAmount);
emit MaxDepositAmountSet(0, _maxDepositAmount);
emit MaxBalanceSet(0, _maxBalance);
}
/**
* @notice Accepts deposits that will be disbursed to the sender's address on L2.
*/
receive() external payable {
require(msg.value >= minDepositAmount, "Deposit amount is too small");
require(msg.value <= maxDepositAmount, "Deposit amount is too big");
require(address(this).balance <= maxBalance, "Contract max balance exceeded");
emit EtherReceived(totalDeposits, msg.sender, msg.value);
unchecked {
totalDeposits += 1;
}
}
/**
* @notice Sends the contract's current balance to the owner.
*/
function withdrawBalance() external onlyOwner {
address _owner = owner();
uint256 _balance = address(this).balance;
emit BalanceWithdrawn(_owner, _balance);
payable(_owner).transfer(_balance);
}
/**
* @notice Sets the minimum amount that can be deposited in a receive.
*
* @param _minDepositAmount The new minimum deposit amount.
*/
function setMinAmount(uint256 _minDepositAmount) external onlyOwner {
emit MinDepositAmountSet(minDepositAmount, _minDepositAmount);
minDepositAmount = _minDepositAmount;
}
/**
* @notice Sets the maximum amount that can be deposited in a receive.
*
* @param _maxDepositAmount The new maximum deposit amount.
*/
function setMaxAmount(uint256 _maxDepositAmount) external onlyOwner {
emit MaxDepositAmountSet(maxDepositAmount, _maxDepositAmount);
maxDepositAmount = _maxDepositAmount;
}
/**
* @notice Sets the maximum balance the contract can hold after a receive.
*
* @param _maxBalance The new maximum contract balance.
*/
function setMaxBalance(uint256 _maxBalance) external onlyOwner {
emit MaxBalanceSet(maxBalance, _maxBalance);
maxBalance = _maxBalance;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { AssetReceiver } from "../universal/AssetReceiver.sol";
/**
* @notice Stub interface for Teleportr.
*/
interface Teleportr {
function withdrawBalance() external;
}
/**
* @title TeleportrWithdrawer
* @notice The TeleportrWithdrawer is a simple contract capable of withdrawing funds from the
* TeleportrContract and sending them to some recipient address.
*/
contract TeleportrWithdrawer is AssetReceiver {
/**
* @notice Address of the Teleportr contract.
*/
address public teleportr;
/**
* @notice Address that will receive Teleportr withdrawals.
*/
address public recipient;
/**
* @notice Data to be sent to the recipient address.
*/
bytes public data;
/**
* @param _owner Initial owner of the contract.
*/
constructor(address _owner) AssetReceiver(_owner) {}
/**
* @notice Allows the owner to update the recipient address.
*
* @param _recipient New recipient address.
*/
function setRecipient(address _recipient) external onlyOwner {
recipient = _recipient;
}
/**
* @notice Allows the owner to update the Teleportr contract address.
*
* @param _teleportr New Teleportr contract address.
*/
function setTeleportr(address _teleportr) external onlyOwner {
teleportr = _teleportr;
}
/**
* @notice Allows the owner to update the data to be sent to the recipient address.
*
* @param _data New data to be sent to the recipient address.
*/
function setData(bytes memory _data) external onlyOwner {
data = _data;
}
/**
* @notice Withdraws the full balance of the Teleportr contract to the recipient address.
* Anyone is allowed to trigger this function since the recipient address cannot be
* controlled by the msg.sender.
*/
function withdrawFromTeleportr() external {
Teleportr(teleportr).withdrawBalance();
(bool success, ) = recipient.call{ value: address(this).balance }(data);
require(success, "TeleportrWithdrawer: send failed");
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title TeleportrDisburser
*/
contract TeleportrDisburser is Ownable {
/**
* @notice A struct holding the address and amount to disbursement.
*/
struct Disbursement {
uint256 amount;
address addr;
}
/**
* @notice Total number of disbursements processed.
*/
uint256 public totalDisbursements;
/**
* @notice Emitted any time the balance is withdrawn by the owner.
*
* @param owner The current owner and recipient of the funds.
* @param balance The current contract balance paid to the owner.
*/
event BalanceWithdrawn(address indexed owner, uint256 balance);
/**
* @notice Emitted any time a disbursement is successfuly sent.
*
* @param depositId The unique sequence number identifying the deposit.
* @param to The recipient of the disbursement.
* @param amount The amount sent to the recipient.
*/
event DisbursementSuccess(uint256 indexed depositId, address indexed to, uint256 amount);
/**
* @notice Emitted any time a disbursement fails to send.
*
* @param depositId The unique sequence number identifying the deposit.
* @param to The intended recipient of the disbursement.
* @param amount The amount intended to be sent to the recipient.
*/
event DisbursementFailed(uint256 indexed depositId, address indexed to, uint256 amount);
/**
* @custom:semver 0.0.1
*/
constructor() {
totalDisbursements = 0;
}
/**
* @notice Accepts a list of Disbursements and forwards the amount paid to the contract to each
* recipient. Reverts if there are zero disbursements, the total amount to forward
* differs from the amount sent in the transaction, or the _nextDepositId is
* unexpected. Failed disbursements will not cause the method to revert, but will
* instead be held by the contract and available for the owner to withdraw.
*
* @param _nextDepositId The depositId of the first Dispursement.
* @param _disbursements A list of Disbursements to process.
*/
function disburse(uint256 _nextDepositId, Disbursement[] calldata _disbursements)
external
payable
onlyOwner
{
// Ensure there are disbursements to process.
uint256 _numDisbursements = _disbursements.length;
require(_numDisbursements > 0, "No disbursements");
// Ensure the _nextDepositId matches our expected value.
uint256 _depositId = totalDisbursements;
require(_depositId == _nextDepositId, "Unexpected next deposit id");
unchecked {
totalDisbursements += _numDisbursements;
}
// Ensure the amount sent in the transaction is equal to the sum of the
// disbursements.
uint256 _totalDisbursed = 0;
for (uint256 i = 0; i < _numDisbursements; i++) {
_totalDisbursed += _disbursements[i].amount;
}
require(_totalDisbursed == msg.value, "Disbursement total != amount sent");
// Process disbursements.
for (uint256 i = 0; i < _numDisbursements; i++) {
uint256 _amount = _disbursements[i].amount;
address _addr = _disbursements[i].addr;
// Deliver the dispursement amount to the receiver. If the
// disbursement fails, the amount will be kept by the contract
// rather than reverting to prevent blocking progress on other
// disbursements.
// slither-disable-next-line calls-loop,reentrancy-events
(bool success, ) = _addr.call{ value: _amount, gas: 2300 }("");
if (success) emit DisbursementSuccess(_depositId, _addr, _amount);
else emit DisbursementFailed(_depositId, _addr, _amount);
unchecked {
_depositId += 1;
}
}
}
/**
* @notice Sends the contract's current balance to the owner.
*/
function withdrawBalance() external onlyOwner {
address _owner = owner();
uint256 balance = address(this).balance;
emit BalanceWithdrawn(_owner, balance);
payable(_owner).transfer(balance);
}
}
//SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
/* Testing utilities */
import { Test } from "forge-std/Test.sol";
import { SimpleStorage } from "../testing/helpers/SimpleStorage.sol";
import { MockTeleportr } from "../testing/helpers/MockTeleportr.sol";
import { TeleportrWithdrawer } from "../universal/TeleportrWithdrawer.sol";
contract TeleportrWithdrawer_Initializer is Test {
address alice = address(128);
address bob = address(256);
TeleportrWithdrawer teleportrWithdrawer;
MockTeleportr mockTeleportr;
SimpleStorage simpleStorage;
function _setUp() public {
// Deploy MockTeleportr and SimpleStorage helper contracts
mockTeleportr = new MockTeleportr();
simpleStorage = new SimpleStorage();
// Deploy Transactor contract
teleportrWithdrawer = new TeleportrWithdrawer(address(alice));
vm.label(address(teleportrWithdrawer), "TeleportrWithdrawer");
// Give alice and bob some ETH
vm.deal(alice, 1 ether);
vm.deal(bob, 1 ether);
vm.label(alice, "alice");
vm.label(bob, "bob");
}
}
contract TeleportrWithdrawerTest is TeleportrWithdrawer_Initializer {
function setUp() public {
super._setUp();
}
// Tests if the owner was set correctly during deploy
function test_constructor() external {
assertEq(address(alice), teleportrWithdrawer.owner());
}
// Tests setRecipient function when called by authorized address
function test_setRecipient() external {
// Call setRecipient from alice
vm.prank(alice);
teleportrWithdrawer.setRecipient(address(alice));
assertEq(teleportrWithdrawer.recipient(), address(alice));
}
// setRecipient should fail if called by unauthorized address
function testFail_setRecipient() external {
teleportrWithdrawer.setRecipient(address(alice));
vm.expectRevert("UNAUTHORIZED");
}
// Tests setTeleportr function when called by authorized address
function test_setTeleportr() external {
// Call setRecipient from alice
vm.prank(alice);
teleportrWithdrawer.setTeleportr(address(mockTeleportr));
assertEq(teleportrWithdrawer.teleportr(), address(mockTeleportr));
}
// setTeleportr should fail if called by unauthorized address
function testFail_setTeleportr() external {
teleportrWithdrawer.setTeleportr(address(bob));
vm.expectRevert("UNAUTHORIZED");
}
// Tests setData function when called by authorized address
function test_setData() external {
bytes memory data = "0x1234567890";
// Call setData from alice
vm.prank(alice);
teleportrWithdrawer.setData(data);
assertEq(teleportrWithdrawer.data(), data);
}
// setData should fail if called by unauthorized address
function testFail_setData() external {
bytes memory data = "0x1234567890";
teleportrWithdrawer.setData(data);
vm.expectRevert("UNAUTHORIZED");
}
// Tests withdrawFromTeleportr, when called expected to withdraw the balance
// to the recipient address when the target is an EOA
function test_withdrawFromTeleportrToEOA() external {
// Fund the Teleportr contract with 1 ETH
vm.deal(address(teleportrWithdrawer), 1 ether);
// Set target address and Teleportr
vm.startPrank(alice);
teleportrWithdrawer.setRecipient(address(bob));
teleportrWithdrawer.setTeleportr(address(mockTeleportr));
vm.stopPrank();
// Run withdrawFromTeleportr
assertEq(address(bob).balance, 1 ether);
teleportrWithdrawer.withdrawFromTeleportr();
assertEq(address(bob).balance, 2 ether);
}
// When called from a contract account it should withdraw the balance and trigger the code
function test_withdrawFromTeleportrToContract() external {
bytes32 key = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa;
bytes32 value = 0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb;
bytes memory data = abi.encodeWithSelector(simpleStorage.set.selector, key, value);
// Fund the Teleportr contract with 1 ETH
vm.deal(address(teleportrWithdrawer), 1 ether);
// Set target address and Teleportr
vm.startPrank(alice);
teleportrWithdrawer.setRecipient(address(simpleStorage));
teleportrWithdrawer.setTeleportr(address(mockTeleportr));
teleportrWithdrawer.setData(data);
vm.stopPrank();
// Run withdrawFromTeleportr
assertEq(address(simpleStorage).balance, 0);
teleportrWithdrawer.withdrawFromTeleportr();
assertEq(address(simpleStorage).balance, 1 ether);
assertEq(simpleStorage.get(key), value);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
contract MockTeleportr {
function withdrawBalance() external {
payable(msg.sender).transfer(address(this).balance);
}
}
/* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types'
const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
const { deploy } = await hre.deployments.deterministic(
'TeleportrWithdrawer',
{
salt: hre.ethers.utils.solidityKeccak256(
['string'],
['TeleportrWithdrawer']
),
from: deployer,
args: [hre.deployConfig.ddd],
log: true,
}
)
await deploy()
}
deployFn.tags = ['TeleportrWithdrawer']
export default deployFn
......@@ -19,7 +19,7 @@
"test": "yarn test:contracts",
"test:contracts": "hardhat test --show-stack-traces",
"test:forge": "forge test",
"test:coverage": "NODE_OPTIONS=--max_old_space_size=8192 hardhat coverage && istanbul check-coverage --statements 90 --branches 84 --functions 88 --lines 90",
"test:coverage": "NODE_OPTIONS=--max_old_space_size=8192 hardhat coverage && istanbul check-coverage --statements 90 --branches 82 --functions 88 --lines 90",
"test:slither": "slither .",
"gas-snapshot": "forge snapshot",
"pretest:slither": "rm -f @openzeppelin && rm -f hardhat && ln -s node_modules/@openzeppelin @openzeppelin && ln -s ../../node_modules/hardhat hardhat",
......
import { ethers } from 'hardhat'
import { Contract, BigNumber } from 'ethers'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { expect } from '../../setup'
import { deploy } from '../../helpers'
const initialMinDepositAmount = ethers.utils.parseEther('0.01')
const initialMaxDepositAmount = ethers.utils.parseEther('1')
const initialMaxBalance = ethers.utils.parseEther('2')
describe('TeleportrDeposit', async () => {
let signer1: SignerWithAddress
let signer2: SignerWithAddress
before(async () => {
;[signer1, signer2] = await ethers.getSigners()
})
let TeleportrDeposit: Contract
before(async () => {
TeleportrDeposit = await deploy('TeleportrDeposit', {
args: [
initialMinDepositAmount,
initialMaxDepositAmount,
initialMaxBalance,
],
})
})
describe('receive', async () => {
const oneETH = ethers.utils.parseEther('1.0')
const twoETH = ethers.utils.parseEther('2.0')
it('should revert if deposit amount is less than min amount', async () => {
await expect(
signer1.sendTransaction({
to: TeleportrDeposit.address,
value: ethers.utils.parseEther('0.001'),
})
).to.be.revertedWith('Deposit amount is too small')
})
it('should revert if deposit amount is greater than max amount', async () => {
await expect(
signer1.sendTransaction({
to: TeleportrDeposit.address,
value: ethers.utils.parseEther('1.1'),
})
).to.be.revertedWith('Deposit amount is too big')
})
it('should emit EtherReceived if called by non-owner', async () => {
await expect(
signer2.sendTransaction({
to: TeleportrDeposit.address,
value: oneETH,
})
)
.to.emit(TeleportrDeposit, 'EtherReceived')
.withArgs(BigNumber.from('0'), signer2.address, oneETH)
})
it('should increase the contract balance by deposit amount', async () => {
expect(
await ethers.provider.getBalance(TeleportrDeposit.address)
).to.equal(oneETH)
})
it('should emit EtherReceived if called by owner', async () => {
await expect(
signer1.sendTransaction({
to: TeleportrDeposit.address,
value: oneETH,
})
)
.to.emit(TeleportrDeposit, 'EtherReceived')
.withArgs(BigNumber.from('1'), signer1.address, oneETH)
})
it('should increase the contract balance by deposit amount', async () => {
expect(
await ethers.provider.getBalance(TeleportrDeposit.address)
).to.equal(twoETH)
})
it('should revert if deposit will exceed max balance', async () => {
await expect(
signer1.sendTransaction({
to: TeleportrDeposit.address,
value: initialMinDepositAmount,
})
).to.be.revertedWith('Contract max balance exceeded')
})
})
describe('withdrawBalance', async () => {
let initialContractBalance: BigNumber
let initialSignerBalance: BigNumber
before(async () => {
initialContractBalance = await ethers.provider.getBalance(
TeleportrDeposit.address
)
initialSignerBalance = await signer1.getBalance()
})
it('should revert if called by non-owner', async () => {
await expect(
TeleportrDeposit.connect(signer2).withdrawBalance()
).to.be.revertedWith('Ownable: caller is not the owner')
})
it('should emit BalanceWithdrawn if called by owner', async () => {
await expect(TeleportrDeposit.withdrawBalance())
.to.emit(TeleportrDeposit, 'BalanceWithdrawn')
.withArgs(signer1.address, initialContractBalance)
})
it('should leave the contract with zero balance', async () => {
expect(
await ethers.provider.getBalance(TeleportrDeposit.address)
).to.equal(ethers.utils.parseEther('0'))
})
it('should credit owner with contract balance - fees', async () => {
const expSignerBalance = initialSignerBalance.add(initialContractBalance)
expect(await signer1.getBalance()).to.be.closeTo(
expSignerBalance,
10 ** 15
)
})
})
describe('setMinAmount', async () => {
const newMinDepositAmount = ethers.utils.parseEther('0.02')
it('should revert if called by non-owner', async () => {
await expect(
TeleportrDeposit.connect(signer2).setMinAmount(newMinDepositAmount)
).to.be.revertedWith('Ownable: caller is not the owner')
})
it('should emit MinDepositAmountSet if called by owner', async () => {
await expect(TeleportrDeposit.setMinAmount(newMinDepositAmount))
.to.emit(TeleportrDeposit, 'MinDepositAmountSet')
.withArgs(initialMinDepositAmount, newMinDepositAmount)
})
it('should have updated minDepositAmount after success', async () => {
expect(await TeleportrDeposit.minDepositAmount()).to.be.eq(
newMinDepositAmount
)
})
})
describe('setMaxAmount', async () => {
const newMaxDepositAmount = ethers.utils.parseEther('2')
it('should revert if called non-owner', async () => {
await expect(
TeleportrDeposit.connect(signer2).setMaxAmount(newMaxDepositAmount)
).to.be.revertedWith('Ownable: caller is not the owner')
})
it('should emit MaxDepositAmountSet if called by owner', async () => {
await expect(TeleportrDeposit.setMaxAmount(newMaxDepositAmount))
.to.emit(TeleportrDeposit, 'MaxDepositAmountSet')
.withArgs(initialMaxDepositAmount, newMaxDepositAmount)
})
it('should have an updated maxDepositAmount after success', async () => {
expect(await TeleportrDeposit.maxDepositAmount()).to.be.eq(
newMaxDepositAmount
)
})
})
describe('setMaxBalance', async () => {
const newMaxBalance = ethers.utils.parseEther('2000')
it('should revert if called by non-owner', async () => {
await expect(
TeleportrDeposit.connect(signer2).setMaxBalance(newMaxBalance)
).to.be.revertedWith('Ownable: caller is not the owner')
})
it('should emit MaxBalanceSet if called by owner', async () => {
await expect(TeleportrDeposit.setMaxBalance(newMaxBalance))
.to.emit(TeleportrDeposit, 'MaxBalanceSet')
.withArgs(initialMaxBalance, newMaxBalance)
})
it('should have an updated maxBalance after success', async () => {
expect(await TeleportrDeposit.maxBalance()).to.be.eq(newMaxBalance)
})
})
})
import { ethers } from 'hardhat'
import { Contract, BigNumber } from 'ethers'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { expect } from '../../setup'
import { deploy } from '../../helpers'
const zeroETH = ethers.utils.parseEther('0.0')
const oneETH = ethers.utils.parseEther('1.0')
const twoETH = ethers.utils.parseEther('2.0')
describe('TeleportrDisburser', async () => {
let signer1: SignerWithAddress
let signer2: SignerWithAddress
before(async () => {
;[signer1, signer2] = await ethers.getSigners()
})
let TeleportrDisburser: Contract
let FailingReceiver: Contract
before(async () => {
TeleportrDisburser = await deploy('TeleportrDisburser')
FailingReceiver = await deploy('FailingReceiver')
})
describe('disburse checks', async () => {
it('should revert if called by non-owner', async () => {
await expect(
TeleportrDisburser.connect(signer2).disburse(0, [], { value: oneETH })
).to.be.revertedWith('Ownable: caller is not the owner')
})
it('should revert if no disbursements is zero length', async () => {
await expect(
TeleportrDisburser.disburse(0, [], { value: oneETH })
).to.be.revertedWith('No disbursements')
})
it('should revert if nextDepositId does not match expected value', async () => {
await expect(
TeleportrDisburser.disburse(1, [[oneETH, signer2.address]], {
value: oneETH,
})
).to.be.revertedWith('Unexpected next deposit id')
})
it('should revert if msg.value does not match total to disburse', async () => {
await expect(
TeleportrDisburser.disburse(0, [[oneETH, signer2.address]], {
value: zeroETH,
})
).to.be.revertedWith('Disbursement total != amount sent')
})
})
describe('disburse single success', async () => {
let signer1InitialBalance: BigNumber
let signer2InitialBalance: BigNumber
before(async () => {
signer1InitialBalance = await ethers.provider.getBalance(signer1.address)
signer2InitialBalance = await ethers.provider.getBalance(signer2.address)
})
it('should emit DisbursementSuccess for successful disbursement', async () => {
await expect(
TeleportrDisburser.disburse(0, [[oneETH, signer2.address]], {
value: oneETH,
})
)
.to.emit(TeleportrDisburser, 'DisbursementSuccess')
.withArgs(BigNumber.from(0), signer2.address, oneETH)
})
it('should show one total disbursement', async () => {
expect(await TeleportrDisburser.totalDisbursements()).to.be.equal(
BigNumber.from(1)
)
})
it('should leave contract balance at zero ETH', async () => {
expect(
await ethers.provider.getBalance(TeleportrDisburser.address)
).to.be.equal(zeroETH)
})
it('should increase recipients balance by disbursement amount', async () => {
expect(await ethers.provider.getBalance(signer2.address)).to.be.equal(
signer2InitialBalance.add(oneETH)
)
})
it('should decrease owners balance by disbursement amount - fees', async () => {
expect(await ethers.provider.getBalance(signer1.address)).to.be.closeTo(
signer1InitialBalance.sub(oneETH),
10 ** 15
)
})
})
describe('disburse single failure', async () => {
let signer1InitialBalance: BigNumber
before(async () => {
signer1InitialBalance = await ethers.provider.getBalance(signer1.address)
})
it('should emit DisbursementFailed for failed disbursement', async () => {
await expect(
TeleportrDisburser.disburse(1, [[oneETH, FailingReceiver.address]], {
value: oneETH,
})
)
.to.emit(TeleportrDisburser, 'DisbursementFailed')
.withArgs(BigNumber.from(1), FailingReceiver.address, oneETH)
})
it('should show two total disbursements', async () => {
expect(await TeleportrDisburser.totalDisbursements()).to.be.equal(
BigNumber.from(2)
)
})
it('should leave contract with disbursement amount', async () => {
expect(
await ethers.provider.getBalance(TeleportrDisburser.address)
).to.be.equal(oneETH)
})
it('should leave recipients balance at zero ETH', async () => {
expect(
await ethers.provider.getBalance(FailingReceiver.address)
).to.be.equal(zeroETH)
})
it('should decrease owners balance by disbursement amount - fees', async () => {
expect(await ethers.provider.getBalance(signer1.address)).to.be.closeTo(
signer1InitialBalance.sub(oneETH),
10 ** 15
)
})
})
describe('withdrawBalance', async () => {
let signer1InitialBalance: BigNumber
let disburserInitialBalance: BigNumber
before(async () => {
signer1InitialBalance = await ethers.provider.getBalance(signer1.address)
disburserInitialBalance = await ethers.provider.getBalance(
TeleportrDisburser.address
)
})
it('should revert if called by non-owner', async () => {
await expect(
TeleportrDisburser.connect(signer2).withdrawBalance()
).to.be.revertedWith('Ownable: caller is not the owner')
})
it('should emit BalanceWithdrawn if called by owner', async () => {
await expect(TeleportrDisburser.withdrawBalance())
.to.emit(TeleportrDisburser, 'BalanceWithdrawn')
.withArgs(signer1.address, oneETH)
})
it('should leave contract with zero balance', async () => {
expect(
await ethers.provider.getBalance(TeleportrDisburser.address)
).to.equal(zeroETH)
})
it('should credit owner with contract balance - fees', async () => {
expect(await ethers.provider.getBalance(signer1.address)).to.be.closeTo(
signer1InitialBalance.add(disburserInitialBalance),
10 ** 15
)
})
})
describe('disburse multiple', async () => {
let signer1InitialBalance: BigNumber
let signer2InitialBalance: BigNumber
before(async () => {
signer1InitialBalance = await ethers.provider.getBalance(signer1.address)
signer2InitialBalance = await ethers.provider.getBalance(signer2.address)
})
it('should emit DisbursementSuccess for successful disbursement', async () => {
await expect(
TeleportrDisburser.disburse(
2,
[
[oneETH, signer2.address],
[oneETH, FailingReceiver.address],
],
{ value: twoETH }
)
).to.not.be.reverted
})
it('should show four total disbursements', async () => {
expect(await TeleportrDisburser.totalDisbursements()).to.be.equal(
BigNumber.from(4)
)
})
it('should leave contract balance with failed disbursement amount', async () => {
expect(
await ethers.provider.getBalance(TeleportrDisburser.address)
).to.be.equal(oneETH)
})
it('should increase success recipients balance by disbursement amount', async () => {
expect(await ethers.provider.getBalance(signer2.address)).to.be.equal(
signer2InitialBalance.add(oneETH)
)
})
it('should leave failed recipients balance at zero ETH', async () => {
expect(
await ethers.provider.getBalance(FailingReceiver.address)
).to.be.equal(zeroETH)
})
it('should decrease owners balance by disbursement 2*amount - fees', async () => {
expect(await ethers.provider.getBalance(signer1.address)).to.be.closeTo(
signer1InitialBalance.sub(twoETH),
10 ** 15
)
})
})
})
import hre from 'hardhat'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { Contract } from 'ethers'
import { toRpcHexString } from '@eth-optimism/core-utils'
import { expect } from '../../setup'
import { deploy } from '../../helpers'
describe('TeleportrWithdrawer', () => {
let signer1: SignerWithAddress
let signer2: SignerWithAddress
before('signer setup', async () => {
;[signer1, signer2] = await hre.ethers.getSigners()
})
let SimpleStorage: Contract
let MockTeleportr: Contract
let TeleportrWithdrawer: Contract
beforeEach('deploy contracts', async () => {
SimpleStorage = await deploy('SimpleStorage')
MockTeleportr = await deploy('MockTeleportr')
TeleportrWithdrawer = await deploy('TeleportrWithdrawer', {
signer: signer1,
args: [signer1.address],
})
})
describe('setRecipient', () => {
describe('when called by authorized address', () => {
it('should set the recipient', async () => {
await TeleportrWithdrawer.setRecipient(signer1.address)
expect(await TeleportrWithdrawer.recipient()).to.equal(signer1.address)
})
})
describe('when called by not authorized address', () => {
it('should revert', async () => {
await expect(
TeleportrWithdrawer.connect(signer2).setRecipient(signer2.address)
).to.be.revertedWith('UNAUTHORIZED')
})
})
})
describe('setTeleportr', () => {
describe('when called by authorized address', () => {
it('should set the recipient', async () => {
await TeleportrWithdrawer.setTeleportr(MockTeleportr.address)
expect(await TeleportrWithdrawer.teleportr()).to.equal(
MockTeleportr.address
)
})
})
describe('when called by not authorized address', () => {
it('should revert', async () => {
await expect(
TeleportrWithdrawer.connect(signer2).setTeleportr(signer2.address)
).to.be.revertedWith('UNAUTHORIZED')
})
})
})
describe('setData', () => {
const data = `0x${'ff'.repeat(64)}`
describe('when called by authorized address', () => {
it('should set the data', async () => {
await TeleportrWithdrawer.setData(data)
expect(await TeleportrWithdrawer.data()).to.equal(data)
})
})
describe('when called by not authorized address', () => {
it('should revert', async () => {
await expect(
TeleportrWithdrawer.connect(signer2).setData(data)
).to.be.revertedWith('UNAUTHORIZED')
})
})
})
describe('withdrawTeleportrBalance', () => {
const recipient = `0x${'11'.repeat(20)}`
const amount = hre.ethers.constants.WeiPerEther
beforeEach(async () => {
await hre.ethers.provider.send('hardhat_setBalance', [
MockTeleportr.address,
toRpcHexString(amount),
])
await TeleportrWithdrawer.setRecipient(recipient)
await TeleportrWithdrawer.setTeleportr(MockTeleportr.address)
})
describe('when target is an EOA', () => {
it('should withdraw the balance', async () => {
await TeleportrWithdrawer.withdrawFromTeleportr()
expect(await hre.ethers.provider.getBalance(recipient)).to.equal(amount)
})
})
describe('when target is a contract', () => {
it('should withdraw the balance and trigger code', async () => {
const key = `0x${'dd'.repeat(32)}`
const val = `0x${'ee'.repeat(32)}`
await TeleportrWithdrawer.setRecipient(SimpleStorage.address)
await TeleportrWithdrawer.setData(
SimpleStorage.interface.encodeFunctionData('set', [key, val])
)
await TeleportrWithdrawer.withdrawFromTeleportr()
expect(
await hre.ethers.provider.getBalance(SimpleStorage.address)
).to.equal(amount)
expect(await SimpleStorage.get(key)).to.equal(val)
})
})
})
})
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.9;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title TeleportrDeposit
*
* Shout out to 0xclem for providing the inspiration for this contract:
* https://github.com/0xclem/teleportr/blob/main/contracts/BridgeDeposit.sol
*/
contract TeleportrDeposit is Ownable {
/// The minimum amount that be deposited in a receive.
uint256 public minDepositAmount;
/// The maximum amount that be deposited in a receive.
uint256 public maxDepositAmount;
/// The maximum balance the contract can hold after a receive.
uint256 public maxBalance;
/// The total number of successful deposits received.
uint256 public totalDeposits;
/**
* @notice Emitted any time the minimum deposit amount is set.
* @param previousAmount The previous minimum deposit amount.
* @param newAmount The new minimum deposit amount.
*/
event MinDepositAmountSet(uint256 previousAmount, uint256 newAmount);
/**
* @notice Emitted any time the maximum deposit amount is set.
* @param previousAmount The previous maximum deposit amount.
* @param newAmount The new maximum deposit amount.
*/
event MaxDepositAmountSet(uint256 previousAmount, uint256 newAmount);
/**
* @notice Emitted any time the contract maximum balance is set.
* @param previousBalance The previous maximum contract balance.
* @param newBalance The new maximum contract balance.
*/
event MaxBalanceSet(uint256 previousBalance, uint256 newBalance);
/**
* @notice Emitted any time the balance is withdrawn by the owner.
* @param owner The current owner and recipient of the funds.
* @param balance The current contract balance paid to the owner.
*/
event BalanceWithdrawn(address indexed owner, uint256 balance);
/**
* @notice Emitted any time a successful deposit is received.
* @param depositId A unique sequencer number identifying the deposit.
* @param emitter The sending address of the payer.
* @param amount The amount deposited by the payer.
*/
event EtherReceived(uint256 indexed depositId, address indexed emitter, uint256 indexed amount);
/**
* @notice Initializes a new TeleportrDeposit contract.
* @param _minDepositAmount The initial minimum deposit amount.
* @param _maxDepositAmount The initial maximum deposit amount.
* @param _maxBalance The initial maximum contract balance.
*/
constructor(
uint256 _minDepositAmount,
uint256 _maxDepositAmount,
uint256 _maxBalance
) {
minDepositAmount = _minDepositAmount;
maxDepositAmount = _maxDepositAmount;
maxBalance = _maxBalance;
totalDeposits = 0;
emit MinDepositAmountSet(0, _minDepositAmount);
emit MaxDepositAmountSet(0, _maxDepositAmount);
emit MaxBalanceSet(0, _maxBalance);
}
/**
* @notice Accepts deposits that will be disbursed to the sender's address on L2.
* The method reverts if the amount is less than the current
* minDepositAmount, the amount is greater than the current
* maxDepositAmount, or the amount causes the contract to exceed its maximum
* allowed balance.
*/
receive() external payable {
require(msg.value >= minDepositAmount, "Deposit amount is too small");
require(msg.value <= maxDepositAmount, "Deposit amount is too big");
require(address(this).balance <= maxBalance, "Contract max balance exceeded");
emit EtherReceived(totalDeposits, msg.sender, msg.value);
unchecked {
totalDeposits += 1;
}
}
/**
* @notice Sends the contract's current balance to the owner.
*/
function withdrawBalance() external onlyOwner {
address _owner = owner();
uint256 _balance = address(this).balance;
emit BalanceWithdrawn(_owner, _balance);
payable(_owner).transfer(_balance);
}
/**
* @notice Sets the minimum amount that can be deposited in a receive.
* @param _minDepositAmount The new minimum deposit amount.
*/
function setMinAmount(uint256 _minDepositAmount) external onlyOwner {
emit MinDepositAmountSet(minDepositAmount, _minDepositAmount);
minDepositAmount = _minDepositAmount;
}
/**
* @notice Sets the maximum amount that can be deposited in a receive.
* @param _maxDepositAmount The new maximum deposit amount.
*/
function setMaxAmount(uint256 _maxDepositAmount) external onlyOwner {
emit MaxDepositAmountSet(maxDepositAmount, _maxDepositAmount);
maxDepositAmount = _maxDepositAmount;
}
/**
* @notice Sets the maximum balance the contract can hold after a receive.
* @param _maxBalance The new maximum contract balance.
*/
function setMaxBalance(uint256 _maxBalance) external onlyOwner {
emit MaxBalanceSet(maxBalance, _maxBalance);
maxBalance = _maxBalance;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.9;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title TeleportrDisburser
*/
contract TeleportrDisburser is Ownable {
/**
* @notice A struct holding the address and amount to disbursement.
*/
struct Disbursement {
uint256 amount;
address addr;
}
/// The total number of disbursements processed.
uint256 public totalDisbursements;
/**
* @notice Emitted any time the balance is withdrawn by the owner.
* @param owner The current owner and recipient of the funds.
* @param balance The current contract balance paid to the owner.
*/
event BalanceWithdrawn(address indexed owner, uint256 balance);
/**
* @notice Emitted any time a disbursement is successfuly sent.
* @param depositId The unique sequence number identifying the deposit.
* @param to The recipient of the disbursement.
* @param amount The amount sent to the recipient.
*/
event DisbursementSuccess(uint256 indexed depositId, address indexed to, uint256 amount);
/**
* @notice Emitted any time a disbursement fails to send.
* @param depositId The unique sequence number identifying the deposit.
* @param to The intended recipient of the disbursement.
* @param amount The amount intended to be sent to the recipient.
*/
event DisbursementFailed(uint256 indexed depositId, address indexed to, uint256 amount);
/**
* @notice Initializes a new TeleportrDisburser contract.
*/
constructor() {
totalDisbursements = 0;
}
/**
* @notice Accepts a list of Disbursements and forwards the amount paid to
* the contract to each recipient. The method reverts if there are zero
* disbursements, the total amount to forward differs from the amount sent
* in the transaction, or the _nextDepositId is unexpected. Failed
* disbursements will not cause the method to revert, but will instead be
* held by the contract and availabe for the owner to withdraw.
* @param _nextDepositId The depositId of the first Dispursement.
* @param _disbursements A list of Disbursements to process.
*/
function disburse(uint256 _nextDepositId, Disbursement[] calldata _disbursements)
external
payable
onlyOwner
{
// Ensure there are disbursements to process.
uint256 _numDisbursements = _disbursements.length;
require(_numDisbursements > 0, "No disbursements");
// Ensure the _nextDepositId matches our expected value.
uint256 _depositId = totalDisbursements;
require(_depositId == _nextDepositId, "Unexpected next deposit id");
unchecked {
totalDisbursements += _numDisbursements;
}
// Ensure the amount sent in the transaction is equal to the sum of the
// disbursements.
uint256 _totalDisbursed = 0;
for (uint256 i = 0; i < _numDisbursements; i++) {
_totalDisbursed += _disbursements[i].amount;
}
require(_totalDisbursed == msg.value, "Disbursement total != amount sent");
// Process disbursements.
for (uint256 i = 0; i < _numDisbursements; i++) {
uint256 _amount = _disbursements[i].amount;
address _addr = _disbursements[i].addr;
// Deliver the dispursement amount to the receiver. If the
// disbursement fails, the amount will be kept by the contract
// rather than reverting to prevent blocking progress on other
// disbursements.
// slither-disable-next-line calls-loop,reentrancy-events
(bool success, ) = _addr.call{ value: _amount, gas: 2300 }("");
if (success) emit DisbursementSuccess(_depositId, _addr, _amount);
else emit DisbursementFailed(_depositId, _addr, _amount);
unchecked {
_depositId += 1;
}
}
}
/**
* @notice Sends the contract's current balance to the owner.
*/
function withdrawBalance() external onlyOwner {
address _owner = owner();
uint256 balance = address(this).balance;
emit BalanceWithdrawn(_owner, balance);
payable(_owner).transfer(balance);
}
}
/* Imports: External */
import { ethers } from 'ethers'
import { task } from 'hardhat/config'
import * as types from 'hardhat/internal/core/params/argumentTypes'
task('deploy-teleportr-l1')
.addParam(
'minDepositAmountEth',
'Minimum deposit amount, in ETH.',
undefined,
types.string
)
.addParam(
'maxDepositAmountEth',
'Maximum deposit amount, in ETH.',
undefined,
types.string
)
.addParam(
'maxBalanceEth',
'Maximum contract balance, in ETH.',
undefined,
types.string
)
.addOptionalParam(
'numDeployConfirmations',
'Number of confirmations to wait for each transaction in the deployment. More is safer.',
1,
types.int
)
.setAction(
async (
{
minDepositAmountEth,
maxDepositAmountEth,
maxBalanceEth,
numDeployConfirmations,
},
hre: any
) => {
const { deploy } = hre.deployments
const { deployer } = await hre.getNamedAccounts()
console.log('Deploying TeleportrDeposit... ')
await deploy('TeleportrDeposit', {
from: deployer,
args: [
ethers.utils.parseEther(minDepositAmountEth),
ethers.utils.parseEther(maxDepositAmountEth),
ethers.utils.parseEther(maxBalanceEth),
],
log: true,
waitConfirmations: numDeployConfirmations,
})
console.log('Done.')
}
)
task('deploy-teleportr-l2').setAction(
async ({ numDeployConfirmations }, hre: any) => {
const { deploy } = hre.deployments
const { deployer } = await hre.getNamedAccounts()
console.log('Deploying TeleportrDisburser... ')
await deploy('TeleportrDisburser', {
from: deployer,
args: [],
log: true,
waitConfirmations: numDeployConfirmations,
})
console.log('Done.')
}
)
......@@ -5,5 +5,4 @@ export * from './validate-chugsplash-dictator'
export * from './whitelist'
export * from './withdraw-fees'
export * from './fetch-batches'
export * from './deploy-teleportr'
export * from './inspect'
import { ethers } from 'hardhat'
import { Contract, BigNumber } from 'ethers'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { expect } from '../../../setup'
import { deploy } from '../../../helpers'
const initialMinDepositAmount = ethers.utils.parseEther('0.01')
const initialMaxDepositAmount = ethers.utils.parseEther('1')
const initialMaxBalance = ethers.utils.parseEther('2')
describe('TeleportrDeposit', async () => {
let signer1: SignerWithAddress
let signer2: SignerWithAddress
before(async () => {
;[signer1, signer2] = await ethers.getSigners()
})
let TeleportrDeposit: Contract
before(async () => {
TeleportrDeposit = await deploy('TeleportrDeposit', {
args: [
initialMinDepositAmount,
initialMaxDepositAmount,
initialMaxBalance,
],
})
})
describe('receive', async () => {
const oneETH = ethers.utils.parseEther('1.0')
const twoETH = ethers.utils.parseEther('2.0')
it('should revert if deposit amount is less than min amount', async () => {
await expect(
signer1.sendTransaction({
to: TeleportrDeposit.address,
value: ethers.utils.parseEther('0.001'),
})
).to.be.revertedWith('Deposit amount is too small')
})
it('should revert if deposit amount is greater than max amount', async () => {
await expect(
signer1.sendTransaction({
to: TeleportrDeposit.address,
value: ethers.utils.parseEther('1.1'),
})
).to.be.revertedWith('Deposit amount is too big')
})
it('should emit EtherReceived if called by non-owner', async () => {
await expect(
signer2.sendTransaction({
to: TeleportrDeposit.address,
value: oneETH,
})
)
.to.emit(TeleportrDeposit, 'EtherReceived')
.withArgs(BigNumber.from('0'), signer2.address, oneETH)
})
it('should increase the contract balance by deposit amount', async () => {
expect(
await ethers.provider.getBalance(TeleportrDeposit.address)
).to.equal(oneETH)
})
it('should emit EtherReceived if called by owner', async () => {
await expect(
signer1.sendTransaction({
to: TeleportrDeposit.address,
value: oneETH,
})
)
.to.emit(TeleportrDeposit, 'EtherReceived')
.withArgs(BigNumber.from('1'), signer1.address, oneETH)
})
it('should increase the contract balance by deposit amount', async () => {
expect(
await ethers.provider.getBalance(TeleportrDeposit.address)
).to.equal(twoETH)
})
it('should revert if deposit will exceed max balance', async () => {
await expect(
signer1.sendTransaction({
to: TeleportrDeposit.address,
value: initialMinDepositAmount,
})
).to.be.revertedWith('Contract max balance exceeded')
})
})
describe('withdrawBalance', async () => {
let initialContractBalance: BigNumber
let initialSignerBalance: BigNumber
before(async () => {
initialContractBalance = await ethers.provider.getBalance(
TeleportrDeposit.address
)
initialSignerBalance = await signer1.getBalance()
})
it('should revert if called by non-owner', async () => {
await expect(
TeleportrDeposit.connect(signer2).withdrawBalance()
).to.be.revertedWith('Ownable: caller is not the owner')
})
it('should emit BalanceWithdrawn if called by owner', async () => {
await expect(TeleportrDeposit.withdrawBalance())
.to.emit(TeleportrDeposit, 'BalanceWithdrawn')
.withArgs(signer1.address, initialContractBalance)
})
it('should leave the contract with zero balance', async () => {
expect(
await ethers.provider.getBalance(TeleportrDeposit.address)
).to.equal(ethers.utils.parseEther('0'))
})
it('should credit owner with contract balance - fees', async () => {
const expSignerBalance = initialSignerBalance.add(initialContractBalance)
expect(await signer1.getBalance()).to.be.closeTo(
expSignerBalance,
10 ** 15
)
})
})
describe('setMinAmount', async () => {
const newMinDepositAmount = ethers.utils.parseEther('0.02')
it('should revert if called by non-owner', async () => {
await expect(
TeleportrDeposit.connect(signer2).setMinAmount(newMinDepositAmount)
).to.be.revertedWith('Ownable: caller is not the owner')
})
it('should emit MinDepositAmountSet if called by owner', async () => {
await expect(TeleportrDeposit.setMinAmount(newMinDepositAmount))
.to.emit(TeleportrDeposit, 'MinDepositAmountSet')
.withArgs(initialMinDepositAmount, newMinDepositAmount)
})
it('should have updated minDepositAmount after success', async () => {
expect(await TeleportrDeposit.minDepositAmount()).to.be.eq(
newMinDepositAmount
)
})
})
describe('setMaxAmount', async () => {
const newMaxDepositAmount = ethers.utils.parseEther('2')
it('should revert if called non-owner', async () => {
await expect(
TeleportrDeposit.connect(signer2).setMaxAmount(newMaxDepositAmount)
).to.be.revertedWith('Ownable: caller is not the owner')
})
it('should emit MaxDepositAmountSet if called by owner', async () => {
await expect(TeleportrDeposit.setMaxAmount(newMaxDepositAmount))
.to.emit(TeleportrDeposit, 'MaxDepositAmountSet')
.withArgs(initialMaxDepositAmount, newMaxDepositAmount)
})
it('should have an updated maxDepositAmount after success', async () => {
expect(await TeleportrDeposit.maxDepositAmount()).to.be.eq(
newMaxDepositAmount
)
})
})
describe('setMaxBalance', async () => {
const newMaxBalance = ethers.utils.parseEther('2000')
it('should revert if called by non-owner', async () => {
await expect(
TeleportrDeposit.connect(signer2).setMaxBalance(newMaxBalance)
).to.be.revertedWith('Ownable: caller is not the owner')
})
it('should emit MaxBalanceSet if called by owner', async () => {
await expect(TeleportrDeposit.setMaxBalance(newMaxBalance))
.to.emit(TeleportrDeposit, 'MaxBalanceSet')
.withArgs(initialMaxBalance, newMaxBalance)
})
it('should have an updated maxBalance after success', async () => {
expect(await TeleportrDeposit.maxBalance()).to.be.eq(newMaxBalance)
})
})
})
import { ethers } from 'hardhat'
import { Contract, BigNumber } from 'ethers'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { expect } from '../../../setup'
import { deploy } from '../../../helpers'
const zeroETH = ethers.utils.parseEther('0.0')
const oneETH = ethers.utils.parseEther('1.0')
const twoETH = ethers.utils.parseEther('2.0')
describe('TeleportrDisburser', async () => {
let signer1: SignerWithAddress
let signer2: SignerWithAddress
before(async () => {
;[signer1, signer2] = await ethers.getSigners()
})
let TeleportrDisburser: Contract
let FailingReceiver: Contract
before(async () => {
TeleportrDisburser = await deploy('TeleportrDisburser')
FailingReceiver = await deploy('FailingReceiver')
})
describe('disburse checks', async () => {
it('should revert if called by non-owner', async () => {
await expect(
TeleportrDisburser.connect(signer2).disburse(0, [], { value: oneETH })
).to.be.revertedWith('Ownable: caller is not the owner')
})
it('should revert if no disbursements is zero length', async () => {
await expect(
TeleportrDisburser.disburse(0, [], { value: oneETH })
).to.be.revertedWith('No disbursements')
})
it('should revert if nextDepositId does not match expected value', async () => {
await expect(
TeleportrDisburser.disburse(1, [[oneETH, signer2.address]], {
value: oneETH,
})
).to.be.revertedWith('Unexpected next deposit id')
})
it('should revert if msg.value does not match total to disburse', async () => {
await expect(
TeleportrDisburser.disburse(0, [[oneETH, signer2.address]], {
value: zeroETH,
})
).to.be.revertedWith('Disbursement total != amount sent')
})
})
describe('disburse single success', async () => {
let signer1InitialBalance: BigNumber
let signer2InitialBalance: BigNumber
before(async () => {
signer1InitialBalance = await ethers.provider.getBalance(signer1.address)
signer2InitialBalance = await ethers.provider.getBalance(signer2.address)
})
it('should emit DisbursementSuccess for successful disbursement', async () => {
await expect(
TeleportrDisburser.disburse(0, [[oneETH, signer2.address]], {
value: oneETH,
})
)
.to.emit(TeleportrDisburser, 'DisbursementSuccess')
.withArgs(BigNumber.from(0), signer2.address, oneETH)
})
it('should show one total disbursement', async () => {
expect(await TeleportrDisburser.totalDisbursements()).to.be.equal(
BigNumber.from(1)
)
})
it('should leave contract balance at zero ETH', async () => {
expect(
await ethers.provider.getBalance(TeleportrDisburser.address)
).to.be.equal(zeroETH)
})
it('should increase recipients balance by disbursement amount', async () => {
expect(await ethers.provider.getBalance(signer2.address)).to.be.equal(
signer2InitialBalance.add(oneETH)
)
})
it('should decrease owners balance by disbursement amount - fees', async () => {
expect(await ethers.provider.getBalance(signer1.address)).to.be.closeTo(
signer1InitialBalance.sub(oneETH),
10 ** 15
)
})
})
describe('disburse single failure', async () => {
let signer1InitialBalance: BigNumber
before(async () => {
signer1InitialBalance = await ethers.provider.getBalance(signer1.address)
})
it('should emit DisbursementFailed for failed disbursement', async () => {
await expect(
TeleportrDisburser.disburse(1, [[oneETH, FailingReceiver.address]], {
value: oneETH,
})
)
.to.emit(TeleportrDisburser, 'DisbursementFailed')
.withArgs(BigNumber.from(1), FailingReceiver.address, oneETH)
})
it('should show two total disbursements', async () => {
expect(await TeleportrDisburser.totalDisbursements()).to.be.equal(
BigNumber.from(2)
)
})
it('should leave contract with disbursement amount', async () => {
expect(
await ethers.provider.getBalance(TeleportrDisburser.address)
).to.be.equal(oneETH)
})
it('should leave recipients balance at zero ETH', async () => {
expect(
await ethers.provider.getBalance(FailingReceiver.address)
).to.be.equal(zeroETH)
})
it('should decrease owners balance by disbursement amount - fees', async () => {
expect(await ethers.provider.getBalance(signer1.address)).to.be.closeTo(
signer1InitialBalance.sub(oneETH),
10 ** 15
)
})
})
describe('withdrawBalance', async () => {
let signer1InitialBalance: BigNumber
let disburserInitialBalance: BigNumber
before(async () => {
signer1InitialBalance = await ethers.provider.getBalance(signer1.address)
disburserInitialBalance = await ethers.provider.getBalance(
TeleportrDisburser.address
)
})
it('should revert if called by non-owner', async () => {
await expect(
TeleportrDisburser.connect(signer2).withdrawBalance()
).to.be.revertedWith('Ownable: caller is not the owner')
})
it('should emit BalanceWithdrawn if called by owner', async () => {
await expect(TeleportrDisburser.withdrawBalance())
.to.emit(TeleportrDisburser, 'BalanceWithdrawn')
.withArgs(signer1.address, oneETH)
})
it('should leave contract with zero balance', async () => {
expect(
await ethers.provider.getBalance(TeleportrDisburser.address)
).to.equal(zeroETH)
})
it('should credit owner with contract balance - fees', async () => {
expect(await ethers.provider.getBalance(signer1.address)).to.be.closeTo(
signer1InitialBalance.add(disburserInitialBalance),
10 ** 15
)
})
})
describe('disburse multiple', async () => {
let signer1InitialBalance: BigNumber
let signer2InitialBalance: BigNumber
before(async () => {
signer1InitialBalance = await ethers.provider.getBalance(signer1.address)
signer2InitialBalance = await ethers.provider.getBalance(signer2.address)
})
it('should emit DisbursementSuccess for successful disbursement', async () => {
await expect(
TeleportrDisburser.disburse(
2,
[
[oneETH, signer2.address],
[oneETH, FailingReceiver.address],
],
{ value: twoETH }
)
).to.not.be.reverted
})
it('should show four total disbursements', async () => {
expect(await TeleportrDisburser.totalDisbursements()).to.be.equal(
BigNumber.from(4)
)
})
it('should leave contract balance with failed disbursement amount', async () => {
expect(
await ethers.provider.getBalance(TeleportrDisburser.address)
).to.be.equal(oneETH)
})
it('should increase success recipients balance by disbursement amount', async () => {
expect(await ethers.provider.getBalance(signer2.address)).to.be.equal(
signer2InitialBalance.add(oneETH)
)
})
it('should leave failed recipients balance at zero ETH', async () => {
expect(
await ethers.provider.getBalance(FailingReceiver.address)
).to.be.equal(zeroETH)
})
it('should decrease owners balance by disbursement 2*amount - fees', async () => {
expect(await ethers.provider.getBalance(signer1.address)).to.be.closeTo(
signer1InitialBalance.sub(twoETH),
10 ** 15
)
})
})
})
......@@ -34,7 +34,6 @@ Go modules which are not yet versioned:
./l2geth-exporter (changesets)
./op-exporter (changesets)
./proxyd (changesets)
./teleportr (changesets)
./state-surgery
```
......
# @eth-optimism/teleportr
## 0.0.11
### Patch Changes
- 29ff7462: Revert es target back to 2017
## 0.0.10
### Patch Changes
- ed3a39fb: Fix panic
## 0.0.9
### Patch Changes
- 23dcba53: Better availability endpoint + retries
## 0.0.8
### Patch Changes
- 487a9731: Improve metrics
- b5ee3c70: Increase max disbursements to 15
## 0.0.7
### Patch Changes
- 6f458607: Bump go-ethereum to 1.10.17
- cd15c40a: Only do 5 disbursements at a time
## 0.0.6
### Patch Changes
- df61d215: Add disburser balance to status
- 32639605: Fix teleportr FailedDatabaseOperations method for upsert_disbursement
- 32639605: Expose metrics server
## 0.0.5
### Patch Changes
- 44c293d8: Fix confs_remaining calculation to prevent underflow
## 0.0.4
### Patch Changes
- 7a320e22: Use L2 gas price in driver
## 0.0.3
### Patch Changes
- 160f4c3d: Update docker image to use golang 1.18.0
## 0.0.2
### Patch Changes
- f101d38b: Add metrics for balances
## 0.0.1
### Patch Changes
- 172c3d74: Add SuggestGasTipCap fallback
- 6856b215: Count reverted transactions in failed_submissions
- f4f3054a: Add teleportr API server
- 3e57f559: Bump `go-ethereum` to `v1.10.16`
- bf1cc8f4: Restructure Deposit and CompletedTeleport to use struct embeddings
- bced4fa9: Add LoadInTeleport method to database
- e5732d97: Add btree index on deposit.txn_hash and deposit.address
FROM golang:1.18.0-alpine3.15 as builder
RUN apk add --no-cache make gcc musl-dev linux-headers git jq bash
COPY ./bss-core /go/bss-core
COPY ./teleportr /go/teleportr
COPY ./teleportr/docker.go.work /go/go.work
WORKDIR /go/teleportr
RUN make teleportr teleportr-api
FROM alpine:3.15
RUN apk add --no-cache ca-certificates jq curl
COPY --from=builder /go/teleportr/teleportr /usr/local/bin/
COPY --from=builder /go/teleportr/teleportr-api /usr/local/bin/
CMD ["teleportr"]
GITCOMMIT := $(shell git rev-parse HEAD)
GITDATE := $(shell git show -s --format='%ct')
GITVERSION := $(shell cat package.json | jq .version)
LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT)
LDFLAGSSTRING +=-X main.GitDate=$(GITDATE)
LDFLAGSSTRING +=-X main.GitVersion=$(GITVERSION)
LDFLAGS := -ldflags "$(LDFLAGSSTRING)"
DEPOSIT_ARTIFACT := ../../packages/contracts/artifacts/contracts/L1/teleportr/TeleportrDeposit.sol/TeleportrDeposit.json
DISBURSER_ARTIFACT := ../../packages/contracts/artifacts/contracts/L2/teleportr/TeleportrDisburser.sol/TeleportrDisburser.json
teleportr:
env GO111MODULE=on go build -v $(LDFLAGS) ./cmd/teleportr
teleportr-api:
env GO111MODULE=on go build -v $(LDFLAGS) ./cmd/teleportr-api
clean:
rm teleportr
rm api
test:
go test -v ./...
lint:
golangci-lint run ./...
bindings: bindings-deposit bindings-disburser
bindings-deposit:
$(eval temp := $(shell mktemp))
cat $(DEPOSIT_ARTIFACT) | jq -r .bytecode > $(temp)
cat $(DEPOSIT_ARTIFACT) | jq .abi | \
abigen \
--pkg deposit \
--abi - \
--out bindings/deposit/teleportr_deposit.go \
--type TeleportrDeposit \
--bin $(temp)
bindings-disburser:
$(eval temp := $(shell mktemp))
cat $(DISBURSER_ARTIFACT) | jq -r .bytecode > $(temp)
cat $(DISBURSER_ARTIFACT) | jq .abi | \
abigen \
--pkg disburse \
--abi - \
--out bindings/disburse/teleportr_disburser.go \
--type TeleportrDisburser \
--bin $(temp)
.PHONY: \
teleportr \
teleportr-api \
bindings \
bindings-deposit \
bindings-disburser \
clean \
test \
lint
package api
import (
"context"
"math/big"
"sync"
"time"
"github.com/ethereum-optimism/optimism/teleportr/bindings/deposit"
"github.com/ethereum-optimism/optimism/teleportr/bindings/disburse"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
type ChainDataReader interface {
Get(ctx context.Context) (*ChainData, error)
}
type ChainData struct {
MaxBalance *big.Int
DisburserBalance *big.Int
NextDisbursementID uint64
DepositContractBalance *big.Int
NextDepositID uint64
MaxDepositAmount *big.Int
MinDepositAmount *big.Int
}
type chainDataReaderImpl struct {
l1Client *ethclient.Client
l2Client *ethclient.Client
depositContract *deposit.TeleportrDeposit
depositContractAddr common.Address
disburserContract *disburse.TeleportrDisburser
disburserWalletAddr common.Address
}
func NewChainDataReader(
l1Client, l2Client *ethclient.Client,
depositContractAddr, disburserWalletAddr common.Address,
depositContract *deposit.TeleportrDeposit,
disburserContract *disburse.TeleportrDisburser,
) ChainDataReader {
return &chainDataReaderImpl{
l1Client: l1Client,
l2Client: l2Client,
depositContract: depositContract,
depositContractAddr: depositContractAddr,
disburserContract: disburserContract,
disburserWalletAddr: disburserWalletAddr,
}
}
func (c *chainDataReaderImpl) maxDepositBalance(ctx context.Context) (*big.Int, error) {
return c.depositContract.MaxBalance(&bind.CallOpts{
Context: ctx,
})
}
func (c *chainDataReaderImpl) disburserBalance(ctx context.Context) (*big.Int, error) {
return c.l2Client.BalanceAt(ctx, c.disburserWalletAddr, nil)
}
func (c *chainDataReaderImpl) nextDisbursementID(ctx context.Context) (uint64, error) {
total, err := c.disburserContract.TotalDisbursements(&bind.CallOpts{
Context: ctx,
})
if err != nil {
return 0, err
}
return total.Uint64(), nil
}
func (c *chainDataReaderImpl) depositContractBalance(ctx context.Context) (*big.Int, error) {
return c.l1Client.BalanceAt(ctx, c.depositContractAddr, nil)
}
func (c *chainDataReaderImpl) nextDepositID(ctx context.Context) (uint64, error) {
total, err := c.depositContract.TotalDeposits(&bind.CallOpts{
Context: ctx,
})
if err != nil {
return 0, err
}
return total.Uint64(), nil
}
func (c *chainDataReaderImpl) maxDepositAmount(ctx context.Context) (*big.Int, error) {
return c.depositContract.MaxDepositAmount(&bind.CallOpts{
Context: ctx,
})
}
func (c *chainDataReaderImpl) minDepositAmount(ctx context.Context) (*big.Int, error) {
return c.depositContract.MinDepositAmount(&bind.CallOpts{
Context: ctx,
})
}
func (c *chainDataReaderImpl) Get(ctx context.Context) (*ChainData, error) {
maxBalance, err := c.maxDepositBalance(ctx)
if err != nil {
rpcErrorsTotal.WithLabelValues("max_balance").Inc()
return nil, err
}
disburserBal, err := c.disburserBalance(ctx)
if err != nil {
rpcErrorsTotal.WithLabelValues("disburser_wallet_balance_at").Inc()
return nil, err
}
nextDisbursementID, err := c.nextDisbursementID(ctx)
if err != nil {
rpcErrorsTotal.WithLabelValues("next_disbursement_id").Inc()
return nil, err
}
depositContractBalance, err := c.depositContractBalance(ctx)
if err != nil {
rpcErrorsTotal.WithLabelValues("deposit_balance_at").Inc()
return nil, err
}
nextDepositID, err := c.nextDepositID(ctx)
if err != nil {
rpcErrorsTotal.WithLabelValues("next_deposit_id").Inc()
return nil, err
}
maxDepositAmount, err := c.maxDepositAmount(ctx)
if err != nil {
rpcErrorsTotal.WithLabelValues("max_deposit_amount").Inc()
return nil, err
}
minDepositAmount, err := c.minDepositAmount(ctx)
if err != nil {
rpcErrorsTotal.WithLabelValues("min_deposit_amount").Inc()
return nil, err
}
return &ChainData{
MaxBalance: maxBalance,
DisburserBalance: disburserBal,
NextDisbursementID: nextDisbursementID,
DepositContractBalance: depositContractBalance,
NextDepositID: nextDepositID,
MaxDepositAmount: maxDepositAmount,
MinDepositAmount: minDepositAmount,
}, nil
}
type cachingChainDataReader struct {
inner ChainDataReader
interval time.Duration
last time.Time
data *ChainData
mu sync.Mutex
}
func NewCachingChainDataReader(inner ChainDataReader, interval time.Duration) ChainDataReader {
return &cachingChainDataReader{
inner: inner,
interval: interval,
}
}
func (c *cachingChainDataReader) Get(ctx context.Context) (*ChainData, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.data != nil && time.Since(c.last) < c.interval {
return c.data, nil
}
data, err := c.inner.Get(ctx)
if err != nil {
return nil, err
}
c.data = data
c.last = time.Now()
return c.data, nil
}
package api
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/require"
)
type NoopChainDataReader struct {
CallCount int
Data *ChainData
}
func (n *NoopChainDataReader) Get(ctx context.Context) (*ChainData, error) {
n.CallCount++
return &ChainData{
MaxBalance: n.Data.MaxBalance,
DisburserBalance: n.Data.DisburserBalance,
NextDisbursementID: n.Data.NextDisbursementID,
DepositContractBalance: n.Data.DepositContractBalance,
NextDepositID: n.Data.NextDepositID,
MaxDepositAmount: n.Data.MaxDepositAmount,
MinDepositAmount: n.Data.MinDepositAmount,
}, nil
}
func TestCachingChainDataReaderGet(t *testing.T) {
inner := &NoopChainDataReader{
Data: &ChainData{
NextDisbursementID: 1,
},
}
require.Equal(t, inner.CallCount, 0)
cdr := NewCachingChainDataReader(inner, 5*time.Millisecond)
data, err := cdr.Get(context.Background())
require.NoError(t, err)
require.Equal(t, 1, inner.CallCount)
require.NotNil(t, data)
inner.Data = &ChainData{
NextDisbursementID: 2,
}
data, err = cdr.Get(context.Background())
require.NoError(t, err)
require.Equal(t, 1, inner.CallCount)
require.EqualValues(t, data.NextDisbursementID, 1)
time.Sleep(10 * time.Millisecond)
data, err = cdr.Get(context.Background())
require.NoError(t, err)
require.Equal(t, 2, inner.CallCount)
require.EqualValues(t, data.NextDisbursementID, 2)
}
package api
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
const TeleportrAPINamespace = "teleportr_api"
var (
rpcRequestsTotal = promauto.NewCounter(prometheus.CounterOpts{
Namespace: TeleportrAPINamespace,
Name: "rpc_requests_total",
Help: "Count of total client RPC requests.",
})
httpResponseCodesTotal = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: TeleportrAPINamespace,
Name: "http_response_codes_total",
Help: "Count of total HTTP response codes.",
}, []string{
"status_code",
})
httpRequestDurationSumm = promauto.NewSummary(prometheus.SummaryOpts{
Namespace: TeleportrAPINamespace,
Name: "http_request_duration_seconds",
Help: "Summary of HTTP request durations, in seconds.",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.95: 0.005, 0.99: 0.001},
})
databaseErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: TeleportrAPINamespace,
Name: "database_errors_total",
Help: "Count of total database failures.",
}, []string{
"method",
})
rpcErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: TeleportrAPINamespace,
Name: "rpc_errors_total",
Help: "Count of total L1 rpc failures.",
}, []string{
"method",
})
)
package api
import (
"context"
"encoding/json"
"errors"
"fmt"
"math/big"
"net"
"net/http"
"os"
"os/signal"
"strconv"
"sync"
"syscall"
"time"
bsscore "github.com/ethereum-optimism/optimism/bss-core"
"github.com/ethereum-optimism/optimism/bss-core/dial"
"github.com/ethereum-optimism/optimism/bss-core/drivers"
"github.com/ethereum-optimism/optimism/bss-core/metrics"
"github.com/ethereum-optimism/optimism/bss-core/txmgr"
"github.com/ethereum-optimism/optimism/teleportr/bindings/deposit"
"github.com/ethereum-optimism/optimism/teleportr/bindings/disburse"
"github.com/ethereum-optimism/optimism/teleportr/db"
"github.com/ethereum-optimism/optimism/teleportr/flags"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/google/uuid"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus"
"github.com/rs/cors"
"github.com/urfave/cli"
)
type ContextKey string
const (
ContextKeyReqID ContextKey = "req_id"
MaxLagBeforeUnavailable = 10
)
func Main(gitVersion string) func(*cli.Context) error {
return func(cliCtx *cli.Context) error {
cfg, err := NewConfig(cliCtx)
if err != nil {
return err
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
depositAddr, err := bsscore.ParseAddress(cfg.DepositAddress)
if err != nil {
return err
}
disburserWalletAddr, err := bsscore.ParseAddress(cfg.DisburserWalletAddress)
if err != nil {
return err
}
disburserAddr, err := bsscore.ParseAddress(cfg.DisburserAddress)
if err != nil {
return err
}
l1Client, err := dial.L1EthClientWithTimeout(
ctx, cfg.L1EthRpc, cfg.DisableHTTP2,
)
if err != nil {
return err
}
defer l1Client.Close()
l2Client, err := dial.L1EthClientWithTimeout(
ctx, cfg.L2EthRpc, cfg.DisableHTTP2,
)
if err != nil {
return err
}
defer l2Client.Close()
depositContract, err := deposit.NewTeleportrDeposit(
depositAddr, l1Client,
)
if err != nil {
return err
}
disburserContract, err := disburse.NewTeleportrDisburser(
disburserAddr, l2Client,
)
if err != nil {
return err
}
cdr := NewChainDataReader(
l1Client,
l2Client,
depositAddr,
disburserWalletAddr,
depositContract,
disburserContract,
)
// TODO(conner): make read-only
database, err := db.Open(db.Config{
Host: cfg.PostgresHost,
Port: uint16(cfg.PostgresPort),
User: cfg.PostgresUser,
Password: cfg.PostgresPassword,
DBName: cfg.PostgresDBName,
EnableSSL: cfg.PostgresEnableSSL,
})
if err != nil {
return err
}
defer database.Close()
if cfg.MetricsServerEnable {
go metrics.RunServer(cfg.MetricsHostname, cfg.MetricsPort)
}
server := NewServer(
ctx,
l1Client,
l2Client,
database,
NewCachingChainDataReader(cdr, time.Minute),
depositAddr,
cfg.NumConfirmations,
)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
_ = server.ListenAndServe(cfg.Hostname, cfg.Port)
}()
interruptChannel := make(chan os.Signal, 1)
signal.Notify(interruptChannel, []os.Signal{
os.Interrupt,
os.Kill,
syscall.SIGTERM,
syscall.SIGQUIT,
}...)
select {
case <-interruptChannel:
_ = server.httpServer.Shutdown(ctx)
time.AfterFunc(defaultTimeout, func() {
cancel()
_ = server.httpServer.Close()
})
wg.Wait()
case <-ctx.Done():
}
return nil
}
}
type Config struct {
Hostname string
Port uint16
L1EthRpc string
L2EthRpc string
DepositAddress string
NumConfirmations uint64
DisburserWalletAddress string
DisburserAddress string
PostgresHost string
PostgresPort uint16
PostgresUser string
PostgresPassword string
PostgresDBName string
PostgresEnableSSL bool
MetricsServerEnable bool
MetricsHostname string
MetricsPort uint64
DisableHTTP2 bool
}
func NewConfig(ctx *cli.Context) (Config, error) {
return Config{
Hostname: ctx.GlobalString(flags.APIHostnameFlag.Name),
Port: uint16(ctx.GlobalUint64(flags.APIPortFlag.Name)),
L1EthRpc: ctx.GlobalString(flags.L1EthRpcFlag.Name),
L2EthRpc: ctx.GlobalString(flags.L2EthRpcFlag.Name),
DepositAddress: ctx.GlobalString(flags.DepositAddressFlag.Name),
NumConfirmations: ctx.GlobalUint64(flags.NumDepositConfirmationsFlag.Name),
DisburserWalletAddress: ctx.GlobalString(flags.DisburserWalletAddressFlag.Name),
DisburserAddress: ctx.GlobalString(flags.DisburserAddressFlag.Name),
PostgresHost: ctx.GlobalString(flags.PostgresHostFlag.Name),
PostgresPort: uint16(ctx.GlobalUint64(flags.PostgresPortFlag.Name)),
PostgresUser: ctx.GlobalString(flags.PostgresUserFlag.Name),
PostgresPassword: ctx.GlobalString(flags.PostgresPasswordFlag.Name),
PostgresDBName: ctx.GlobalString(flags.PostgresDBNameFlag.Name),
PostgresEnableSSL: ctx.GlobalBool(flags.PostgresEnableSSLFlag.Name),
MetricsServerEnable: ctx.GlobalBool(flags.MetricsServerEnableFlag.Name),
MetricsHostname: ctx.GlobalString(flags.MetricsHostnameFlag.Name),
MetricsPort: ctx.GlobalUint64(flags.MetricsPortFlag.Name),
DisableHTTP2: ctx.GlobalBool(flags.HTTP2DisableFlag.Name),
}, nil
}
const (
ContentTypeHeader = "Content-Type"
ContentTypeJSON = "application/json"
defaultTimeout = 10 * time.Second
)
type Server struct {
ctx context.Context
l1Client *ethclient.Client
l2Client *ethclient.Client
database *db.Database
chainDataReader ChainDataReader
depositAddr common.Address
numConfirmations uint64
httpServer *http.Server
}
func NewServer(
ctx context.Context,
l1Client *ethclient.Client,
l2Client *ethclient.Client,
database *db.Database,
chainDataReader ChainDataReader,
depositAddr common.Address,
numConfirmations uint64,
) *Server {
if numConfirmations == 0 {
panic("NumConfirmations cannot be zero")
}
return &Server{
ctx: ctx,
l1Client: l1Client,
l2Client: l2Client,
database: database,
chainDataReader: chainDataReader,
depositAddr: depositAddr,
numConfirmations: numConfirmations,
}
}
func (s *Server) ListenAndServe(host string, port uint16) error {
handler := mux.NewRouter()
handler.HandleFunc("/healthz", HandleHealthz).Methods("GET")
handler.HandleFunc(
"/status",
instrumentedErrorHandler(s.HandleStatus),
).Methods("GET")
handler.HandleFunc(
"/estimate/{addr:0x[0-9a-fA-F]{40}}/{amount:[0-9]{1,80}}",
instrumentedErrorHandler(s.HandleEstimate),
).Methods("GET")
handler.HandleFunc(
"/track/{txhash:0x[0-9a-fA-F]{64}}",
instrumentedErrorHandler(s.HandleTrack),
).Methods("GET")
handler.HandleFunc(
"/history/{addr:0x[0-9a-fA-F]{40}}",
instrumentedErrorHandler(s.HandleHistory),
).Methods("GET")
c := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
})
addr := fmt.Sprintf("%s:%d", host, port)
s.httpServer = &http.Server{
Handler: c.Handler(handler),
Addr: addr,
BaseContext: func(_ net.Listener) context.Context {
return s.ctx
},
}
log.Info("Starting HTTP server", "addr", addr)
return s.httpServer.ListenAndServe()
}
func HandleHealthz(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("OK"))
}
type StatusResponse struct {
DisburserWalletBalanceWei string `json:"disburser_wallet_balance_wei"`
DepositContractBalanceWei string `json:"deposit_contract_balance_wei"`
MaximumBalanceWei string `json:"maximum_balance_wei"`
MinDepositAmountWei string `json:"min_deposit_amount_wei"`
MaxDepositAmountWei string `json:"max_deposit_amount_wei"`
DisbursementLag uint64 `json:"disbursement_lag"`
IsAvailable bool `json:"is_available"`
}
func (s *Server) HandleStatus(
ctx context.Context,
w http.ResponseWriter,
r *http.Request,
) error {
chainData, err := s.chainDataReader.Get(ctx)
if err != nil {
return err
}
balanceAfterMaxDeposit := new(big.Int).Add(
chainData.DepositContractBalance, chainData.MaxDepositAmount,
)
disbursementLag := chainData.NextDepositID - chainData.NextDisbursementID
isAvailable := chainData.MaxBalance.Cmp(balanceAfterMaxDeposit) >= 0 &&
chainData.DisburserBalance.Cmp(chainData.MaxDepositAmount) > 0 &&
disbursementLag < MaxLagBeforeUnavailable
resp := StatusResponse{
DisburserWalletBalanceWei: chainData.DisburserBalance.String(),
DepositContractBalanceWei: chainData.DepositContractBalance.String(),
MaximumBalanceWei: chainData.MaxBalance.String(),
MinDepositAmountWei: chainData.MinDepositAmount.String(),
MaxDepositAmountWei: chainData.MaxDepositAmount.String(),
DisbursementLag: disbursementLag,
IsAvailable: isAvailable,
}
jsonResp, err := json.Marshal(resp)
if err != nil {
return err
}
w.Header().Set(ContentTypeHeader, ContentTypeJSON)
_, err = w.Write(jsonResp)
return err
}
type EstimateResponse struct {
BaseFee string `json:"base_fee"`
GasTipCap string `json:"gas_tip_cap"`
GasFeeCap string `json:"gas_fee_cap"`
GasEstimate string `json:"gas_estimate"`
}
func (s *Server) HandleEstimate(
ctx context.Context,
w http.ResponseWriter,
r *http.Request,
) error {
vars := mux.Vars(r)
addressStr, ok := vars["addr"]
if !ok {
return StatusError{
Err: errors.New("missing address parameter"),
Code: http.StatusBadRequest,
}
}
address, err := bsscore.ParseAddress(addressStr)
if err != nil {
return StatusError{
Err: err,
Code: http.StatusBadRequest,
}
}
amountStr, ok := vars["amount"]
if !ok {
return StatusError{
Err: errors.New("missing amount parameter"),
Code: http.StatusBadRequest,
}
}
amount, ok := new(big.Int).SetString(amountStr, 10)
if !ok {
return StatusError{
Err: errors.New("unable to parse amount"),
Code: http.StatusBadRequest,
}
}
gasTipCap, err := s.l1Client.SuggestGasTipCap(ctx)
if err != nil {
rpcErrorsTotal.WithLabelValues("suggest_gas_tip_cap").Inc()
// If the request failed because the backend does not support
// eth_maxPriorityFeePerGas, fallback to using the default constant.
// Currently Alchemy is the only backend provider that exposes this
// method, so in the event their API is unreachable we can fallback to a
// degraded mode of operation. This also applies to our test
// environments, as hardhat doesn't support the query either.
if !drivers.IsMaxPriorityFeePerGasNotFoundError(err) {
return err
}
gasTipCap = drivers.FallbackGasTipCap
}
header, err := s.l1Client.HeaderByNumber(ctx, nil)
if err != nil {
rpcErrorsTotal.WithLabelValues("header_by_number").Inc()
return err
}
gasFeeCap := txmgr.CalcGasFeeCap(header.BaseFee, gasTipCap)
gasUsed, err := s.l1Client.EstimateGas(ctx, ethereum.CallMsg{
From: address,
To: &s.depositAddr,
Gas: 0,
GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap,
Value: amount,
Data: nil,
})
if err != nil {
rpcErrorsTotal.WithLabelValues("estimate_gas").Inc()
return err
}
resp := EstimateResponse{
BaseFee: header.BaseFee.String(),
GasTipCap: gasTipCap.String(),
GasFeeCap: gasFeeCap.String(),
GasEstimate: new(big.Int).SetUint64(gasUsed).String(),
}
jsonResp, err := json.Marshal(resp)
if err != nil {
return err
}
w.Header().Set(ContentTypeHeader, ContentTypeJSON)
_, err = w.Write(jsonResp)
return err
}
type RPCTeleport struct {
ID string `json:"id"`
Address string `json:"address"`
AmountWei string `json:"amount_wei"`
TxHash string `json:"tx_hash"`
BlockNumber string `json:"block_number"`
BlockTimestamp string `json:"block_timestamp_unix"`
Disbursement *RPCDisbursement `json:"disbursement"`
}
func makeRPCTeleport(teleport *db.Teleport) RPCTeleport {
rpcTeleport := RPCTeleport{
ID: strconv.FormatUint(teleport.ID, 10),
Address: teleport.Address.String(),
AmountWei: teleport.Amount.String(),
TxHash: teleport.Deposit.TxnHash.String(),
BlockNumber: strconv.FormatUint(teleport.Deposit.BlockNumber, 10),
BlockTimestamp: strconv.FormatInt(teleport.Deposit.BlockTimestamp.Unix(), 10),
}
if teleport.Disbursement != nil {
rpcTeleport.Disbursement = &RPCDisbursement{
TxHash: teleport.Disbursement.TxnHash.String(),
BlockNumber: strconv.FormatUint(teleport.Disbursement.BlockNumber, 10),
BlockTimestamp: strconv.FormatInt(teleport.Disbursement.BlockTimestamp.Unix(), 10),
Success: teleport.Disbursement.Success,
}
}
return rpcTeleport
}
type RPCDisbursement struct {
TxHash string `json:"tx_hash"`
BlockNumber string `json:"block_number"`
BlockTimestamp string `json:"block_timestamp_unix"`
Success bool `json:"success"`
}
type TrackResponse struct {
CurrentBlockNumber string `json:"current_block_number"`
ConfirmationsRequired string `json:"confirmations_required"`
ConfirmationsRemaining string `json:"confirmations_remaining"`
Teleport RPCTeleport `json:"teleport"`
}
func (s *Server) HandleTrack(
ctx context.Context,
w http.ResponseWriter,
r *http.Request,
) error {
vars := mux.Vars(r)
txHashStr, ok := vars["txhash"]
if !ok {
return StatusError{
Err: errors.New("missing txhash parameter"),
Code: http.StatusBadRequest,
}
}
txHash := common.HexToHash(txHashStr)
blockNumber, err := s.l1Client.BlockNumber(ctx)
if err != nil {
rpcErrorsTotal.WithLabelValues("block_number").Inc()
return err
}
teleport, err := s.database.LoadTeleportByDepositHash(txHash)
if err != nil {
databaseErrorsTotal.WithLabelValues("load_teleport_by_deposit_hash").Inc()
return err
}
if teleport == nil {
return StatusError{
Code: http.StatusNotFound,
}
}
var confsRemaining uint64
if teleport.Deposit.BlockNumber+s.numConfirmations > blockNumber+1 {
confsRemaining = teleport.Deposit.BlockNumber + s.numConfirmations -
(blockNumber + 1)
}
resp := TrackResponse{
CurrentBlockNumber: strconv.FormatUint(blockNumber, 10),
ConfirmationsRequired: strconv.FormatUint(s.numConfirmations, 10),
ConfirmationsRemaining: strconv.FormatUint(confsRemaining, 10),
Teleport: makeRPCTeleport(teleport),
}
jsonResp, err := json.Marshal(resp)
if err != nil {
return err
}
w.Header().Set(ContentTypeHeader, ContentTypeJSON)
_, err = w.Write(jsonResp)
return err
}
type HistoryResponse struct {
Teleports []RPCTeleport `json:"teleports"`
}
func (s *Server) HandleHistory(
ctx context.Context,
w http.ResponseWriter,
r *http.Request,
) error {
vars := mux.Vars(r)
addrStr, ok := vars["addr"]
if !ok {
return StatusError{
Err: errors.New("missing addr parameter"),
Code: http.StatusBadRequest,
}
}
addr := common.HexToAddress(addrStr)
teleports, err := s.database.LoadTeleportsByAddress(addr)
if err != nil {
databaseErrorsTotal.WithLabelValues("load_teleports_by_address").Inc()
return err
}
rpcTeleports := make([]RPCTeleport, 0, len(teleports))
for _, teleport := range teleports {
rpcTeleports = append(rpcTeleports, makeRPCTeleport(&teleport))
}
resp := HistoryResponse{
Teleports: rpcTeleports,
}
jsonResp, err := json.Marshal(resp)
if err != nil {
return err
}
w.Header().Set(ContentTypeHeader, ContentTypeJSON)
_, err = w.Write(jsonResp)
return err
}
type Error interface {
error
Status() int
}
type StatusError struct {
Code int
Err error
}
func (se StatusError) Error() string {
if se.Err != nil {
msg := se.Err.Error()
if msg != "" {
return msg
}
}
return http.StatusText(se.Code)
}
func (se StatusError) Status() int {
return se.Code
}
func instrumentedErrorHandler(
h func(context.Context, http.ResponseWriter, *http.Request) error,
) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
rpcRequestsTotal.Inc()
ctx, cancel := populateContext(w, r)
defer cancel()
reqID := GetReqID(ctx)
log.Info("HTTP request",
"req_id", reqID,
"path", r.URL.Path,
"user_agent", r.UserAgent())
respTimer := prometheus.NewTimer(httpRequestDurationSumm)
err := h(ctx, w, r)
elapsed := respTimer.ObserveDuration()
var statusCode int
switch e := err.(type) {
case nil:
statusCode = 200
log.Info("HTTP success",
"req_id", reqID,
"elapsed", elapsed)
case Error:
statusCode = e.Status()
log.Warn("HTTP error",
"req_id", reqID,
"elapsed", elapsed,
"status", statusCode,
"err", e.Error())
http.Error(w, e.Error(), statusCode)
default:
statusCode = http.StatusInternalServerError
log.Warn("HTTP internal error",
"req_id", reqID,
"elapsed", elapsed,
"status", statusCode,
"err", err)
http.Error(w, http.StatusText(statusCode), statusCode)
}
httpResponseCodesTotal.WithLabelValues(strconv.Itoa(statusCode)).Inc()
}
}
func populateContext(
w http.ResponseWriter,
r *http.Request,
) (context.Context, func()) {
ctx := context.WithValue(r.Context(), ContextKeyReqID, uuid.NewString())
return context.WithTimeout(ctx, defaultTimeout)
}
func GetReqID(ctx context.Context) string {
if reqID, ok := ctx.Value(ContextKeyReqID).(string); ok {
return reqID
}
return ""
}
// Code generated - DO NOT EDIT.
// This file is a generated binding and any manual changes will be lost.
package deposit
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
)
// TeleportrDepositMetaData contains all meta data concerning the TeleportrDeposit contract.
var TeleportrDepositMetaData = &bind.MetaData{
ABI: "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_minDepositAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_maxDepositAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_maxBalance\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"}],\"name\":\"BalanceWithdrawn\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"depositId\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"emitter\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"EtherReceived\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"previousBalance\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newBalance\",\"type\":\"uint256\"}],\"name\":\"MaxBalanceSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"previousAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newAmount\",\"type\":\"uint256\"}],\"name\":\"MaxDepositAmountSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"previousAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newAmount\",\"type\":\"uint256\"}],\"name\":\"MinDepositAmountSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"maxBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maxDepositAmount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minDepositAmount\",\"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\":\"_maxDepositAmount\",\"type\":\"uint256\"}],\"name\":\"setMaxAmount\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_maxBalance\",\"type\":\"uint256\"}],\"name\":\"setMaxBalance\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_minDepositAmount\",\"type\":\"uint256\"}],\"name\":\"setMinAmount\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalDeposits\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"withdrawBalance\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]",
Bin: "0x608060405234801561001057600080fd5b50604051610b4a380380610b4a83398101604081905261002f91610153565b61003833610103565b6001839055600282905560038190556000600481905560408051918252602082018590527f65779d3ca560e9bdec52d08ed75431a84df87cb7796f0e51965f6efc0f556c0f910160405180910390a16040805160008152602081018490527fb1e6cc560df1786578fd4d1fe6e046f089a0c3be401e999b51a5112437911797910160405180910390a16040805160008152602081018390527f185c6391e7218e85de8a9346fc72024a0f88e1f04c186e6351230b93976ad50b910160405180910390a1505050610181565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60008060006060848603121561016857600080fd5b8351925060208401519150604084015190509250925092565b6109ba806101906000396000f3fe6080604052600436106100c05760003560e01c80637d882097116100745780638ed832711161004e5780638ed83271146103445780639d51d9b71461035a578063f2fde38b1461037a57600080fd5b80637d882097146102d9578063897b0637146102ef5780638da5cb5b1461030f57600080fd5b8063645006ca116100a5578063645006ca14610285578063715018a6146102ae57806373ad468a146102c357600080fd5b80634fe47f701461024e5780635fd8c7101461027057600080fd5b3661024957600154341015610136576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f4465706f73697420616d6f756e7420697320746f6f20736d616c6c000000000060448201526064015b60405180910390fd5b6002543411156101a2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f4465706f73697420616d6f756e7420697320746f6f2062696700000000000000604482015260640161012d565b60035447111561020e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f436f6e7472616374206d61782062616c616e6365206578636565646564000000604482015260640161012d565b600454604051349133917f2d27851832fcac28a0d4af1344f01fed7ffcfd15171c14c564a0c42aa57ae5c090600090a4600480546001019055005b600080fd5b34801561025a57600080fd5b5061026e61026936600461092e565b61039a565b005b34801561027c57600080fd5b5061026e61045c565b34801561029157600080fd5b5061029b60015481565b6040519081526020015b60405180910390f35b3480156102ba57600080fd5b5061026e610578565b3480156102cf57600080fd5b5061029b60035481565b3480156102e557600080fd5b5061029b60045481565b3480156102fb57600080fd5b5061026e61030a36600461092e565b610605565b34801561031b57600080fd5b5060005460405173ffffffffffffffffffffffffffffffffffffffff90911681526020016102a5565b34801561035057600080fd5b5061029b60025481565b34801561036657600080fd5b5061026e61037536600461092e565b6106c7565b34801561038657600080fd5b5061026e610395366004610947565b610789565b60005473ffffffffffffffffffffffffffffffffffffffff16331461041b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161012d565b60025460408051918252602082018390527fb1e6cc560df1786578fd4d1fe6e046f089a0c3be401e999b51a5112437911797910160405180910390a1600255565b60005473ffffffffffffffffffffffffffffffffffffffff1633146104dd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161012d565b6000546040805147808252915173ffffffffffffffffffffffffffffffffffffffff9093169283917fddc398b321237a8d40ac914388309c2f52a08c134e4dc4ce61e32f57cb7d80f1919081900360200190a260405173ffffffffffffffffffffffffffffffffffffffff83169082156108fc029083906000818181858888f19350505050158015610573573d6000803e3d6000fd5b505050565b60005473ffffffffffffffffffffffffffffffffffffffff1633146105f9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161012d565b61060360006108b9565b565b60005473ffffffffffffffffffffffffffffffffffffffff163314610686576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161012d565b60015460408051918252602082018390527f65779d3ca560e9bdec52d08ed75431a84df87cb7796f0e51965f6efc0f556c0f910160405180910390a1600155565b60005473ffffffffffffffffffffffffffffffffffffffff163314610748576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161012d565b60035460408051918252602082018390527f185c6391e7218e85de8a9346fc72024a0f88e1f04c186e6351230b93976ad50b910160405180910390a1600355565b60005473ffffffffffffffffffffffffffffffffffffffff16331461080a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161012d565b73ffffffffffffffffffffffffffffffffffffffff81166108ad576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f6464726573730000000000000000000000000000000000000000000000000000606482015260840161012d565b6108b6816108b9565b50565b6000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60006020828403121561094057600080fd5b5035919050565b60006020828403121561095957600080fd5b813573ffffffffffffffffffffffffffffffffffffffff8116811461097d57600080fd5b939250505056fea2646970667358221220b610d8106720e33f96db31f8c5c4f9cecb50144a2cf1e4049ac0216cd47d0eff64736f6c63430008090033",
}
// TeleportrDepositABI is the input ABI used to generate the binding from.
// Deprecated: Use TeleportrDepositMetaData.ABI instead.
var TeleportrDepositABI = TeleportrDepositMetaData.ABI
// TeleportrDepositBin is the compiled bytecode used for deploying new contracts.
// Deprecated: Use TeleportrDepositMetaData.Bin instead.
var TeleportrDepositBin = TeleportrDepositMetaData.Bin
// DeployTeleportrDeposit deploys a new Ethereum contract, binding an instance of TeleportrDeposit to it.
func DeployTeleportrDeposit(auth *bind.TransactOpts, backend bind.ContractBackend, _minDepositAmount *big.Int, _maxDepositAmount *big.Int, _maxBalance *big.Int) (common.Address, *types.Transaction, *TeleportrDeposit, error) {
parsed, err := TeleportrDepositMetaData.GetAbi()
if err != nil {
return common.Address{}, nil, nil, err
}
if parsed == nil {
return common.Address{}, nil, nil, errors.New("GetABI returned nil")
}
address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(TeleportrDepositBin), backend, _minDepositAmount, _maxDepositAmount, _maxBalance)
if err != nil {
return common.Address{}, nil, nil, err
}
return address, tx, &TeleportrDeposit{TeleportrDepositCaller: TeleportrDepositCaller{contract: contract}, TeleportrDepositTransactor: TeleportrDepositTransactor{contract: contract}, TeleportrDepositFilterer: TeleportrDepositFilterer{contract: contract}}, nil
}
// TeleportrDeposit is an auto generated Go binding around an Ethereum contract.
type TeleportrDeposit struct {
TeleportrDepositCaller // Read-only binding to the contract
TeleportrDepositTransactor // Write-only binding to the contract
TeleportrDepositFilterer // Log filterer for contract events
}
// TeleportrDepositCaller is an auto generated read-only Go binding around an Ethereum contract.
type TeleportrDepositCaller struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// TeleportrDepositTransactor is an auto generated write-only Go binding around an Ethereum contract.
type TeleportrDepositTransactor struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// TeleportrDepositFilterer is an auto generated log filtering Go binding around an Ethereum contract events.
type TeleportrDepositFilterer struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// TeleportrDepositSession is an auto generated Go binding around an Ethereum contract,
// with pre-set call and transact options.
type TeleportrDepositSession struct {
Contract *TeleportrDeposit // 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
}
// TeleportrDepositCallerSession is an auto generated read-only Go binding around an Ethereum contract,
// with pre-set call options.
type TeleportrDepositCallerSession struct {
Contract *TeleportrDepositCaller // Generic contract caller binding to set the session for
CallOpts bind.CallOpts // Call options to use throughout this session
}
// TeleportrDepositTransactorSession is an auto generated write-only Go binding around an Ethereum contract,
// with pre-set transact options.
type TeleportrDepositTransactorSession struct {
Contract *TeleportrDepositTransactor // Generic contract transactor binding to set the session for
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
}
// TeleportrDepositRaw is an auto generated low-level Go binding around an Ethereum contract.
type TeleportrDepositRaw struct {
Contract *TeleportrDeposit // Generic contract binding to access the raw methods on
}
// TeleportrDepositCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
type TeleportrDepositCallerRaw struct {
Contract *TeleportrDepositCaller // Generic read-only contract binding to access the raw methods on
}
// TeleportrDepositTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
type TeleportrDepositTransactorRaw struct {
Contract *TeleportrDepositTransactor // Generic write-only contract binding to access the raw methods on
}
// NewTeleportrDeposit creates a new instance of TeleportrDeposit, bound to a specific deployed contract.
func NewTeleportrDeposit(address common.Address, backend bind.ContractBackend) (*TeleportrDeposit, error) {
contract, err := bindTeleportrDeposit(address, backend, backend, backend)
if err != nil {
return nil, err
}
return &TeleportrDeposit{TeleportrDepositCaller: TeleportrDepositCaller{contract: contract}, TeleportrDepositTransactor: TeleportrDepositTransactor{contract: contract}, TeleportrDepositFilterer: TeleportrDepositFilterer{contract: contract}}, nil
}
// NewTeleportrDepositCaller creates a new read-only instance of TeleportrDeposit, bound to a specific deployed contract.
func NewTeleportrDepositCaller(address common.Address, caller bind.ContractCaller) (*TeleportrDepositCaller, error) {
contract, err := bindTeleportrDeposit(address, caller, nil, nil)
if err != nil {
return nil, err
}
return &TeleportrDepositCaller{contract: contract}, nil
}
// NewTeleportrDepositTransactor creates a new write-only instance of TeleportrDeposit, bound to a specific deployed contract.
func NewTeleportrDepositTransactor(address common.Address, transactor bind.ContractTransactor) (*TeleportrDepositTransactor, error) {
contract, err := bindTeleportrDeposit(address, nil, transactor, nil)
if err != nil {
return nil, err
}
return &TeleportrDepositTransactor{contract: contract}, nil
}
// NewTeleportrDepositFilterer creates a new log filterer instance of TeleportrDeposit, bound to a specific deployed contract.
func NewTeleportrDepositFilterer(address common.Address, filterer bind.ContractFilterer) (*TeleportrDepositFilterer, error) {
contract, err := bindTeleportrDeposit(address, nil, nil, filterer)
if err != nil {
return nil, err
}
return &TeleportrDepositFilterer{contract: contract}, nil
}
// bindTeleportrDeposit binds a generic wrapper to an already deployed contract.
func bindTeleportrDeposit(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
parsed, err := abi.JSON(strings.NewReader(TeleportrDepositABI))
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 (_TeleportrDeposit *TeleportrDepositRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
return _TeleportrDeposit.Contract.TeleportrDepositCaller.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 (_TeleportrDeposit *TeleportrDepositRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
return _TeleportrDeposit.Contract.TeleportrDepositTransactor.contract.Transfer(opts)
}
// Transact invokes the (paid) contract method with params as input values.
func (_TeleportrDeposit *TeleportrDepositRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
return _TeleportrDeposit.Contract.TeleportrDepositTransactor.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 (_TeleportrDeposit *TeleportrDepositCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
return _TeleportrDeposit.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 (_TeleportrDeposit *TeleportrDepositTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
return _TeleportrDeposit.Contract.contract.Transfer(opts)
}
// Transact invokes the (paid) contract method with params as input values.
func (_TeleportrDeposit *TeleportrDepositTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
return _TeleportrDeposit.Contract.contract.Transact(opts, method, params...)
}
// MaxBalance is a free data retrieval call binding the contract method 0x73ad468a.
//
// Solidity: function maxBalance() view returns(uint256)
func (_TeleportrDeposit *TeleportrDepositCaller) MaxBalance(opts *bind.CallOpts) (*big.Int, error) {
var out []interface{}
err := _TeleportrDeposit.contract.Call(opts, &out, "maxBalance")
if err != nil {
return *new(*big.Int), err
}
out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
return out0, err
}
// MaxBalance is a free data retrieval call binding the contract method 0x73ad468a.
//
// Solidity: function maxBalance() view returns(uint256)
func (_TeleportrDeposit *TeleportrDepositSession) MaxBalance() (*big.Int, error) {
return _TeleportrDeposit.Contract.MaxBalance(&_TeleportrDeposit.CallOpts)
}
// MaxBalance is a free data retrieval call binding the contract method 0x73ad468a.
//
// Solidity: function maxBalance() view returns(uint256)
func (_TeleportrDeposit *TeleportrDepositCallerSession) MaxBalance() (*big.Int, error) {
return _TeleportrDeposit.Contract.MaxBalance(&_TeleportrDeposit.CallOpts)
}
// MaxDepositAmount is a free data retrieval call binding the contract method 0x8ed83271.
//
// Solidity: function maxDepositAmount() view returns(uint256)
func (_TeleportrDeposit *TeleportrDepositCaller) MaxDepositAmount(opts *bind.CallOpts) (*big.Int, error) {
var out []interface{}
err := _TeleportrDeposit.contract.Call(opts, &out, "maxDepositAmount")
if err != nil {
return *new(*big.Int), err
}
out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
return out0, err
}
// MaxDepositAmount is a free data retrieval call binding the contract method 0x8ed83271.
//
// Solidity: function maxDepositAmount() view returns(uint256)
func (_TeleportrDeposit *TeleportrDepositSession) MaxDepositAmount() (*big.Int, error) {
return _TeleportrDeposit.Contract.MaxDepositAmount(&_TeleportrDeposit.CallOpts)
}
// MaxDepositAmount is a free data retrieval call binding the contract method 0x8ed83271.
//
// Solidity: function maxDepositAmount() view returns(uint256)
func (_TeleportrDeposit *TeleportrDepositCallerSession) MaxDepositAmount() (*big.Int, error) {
return _TeleportrDeposit.Contract.MaxDepositAmount(&_TeleportrDeposit.CallOpts)
}
// MinDepositAmount is a free data retrieval call binding the contract method 0x645006ca.
//
// Solidity: function minDepositAmount() view returns(uint256)
func (_TeleportrDeposit *TeleportrDepositCaller) MinDepositAmount(opts *bind.CallOpts) (*big.Int, error) {
var out []interface{}
err := _TeleportrDeposit.contract.Call(opts, &out, "minDepositAmount")
if err != nil {
return *new(*big.Int), err
}
out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
return out0, err
}
// MinDepositAmount is a free data retrieval call binding the contract method 0x645006ca.
//
// Solidity: function minDepositAmount() view returns(uint256)
func (_TeleportrDeposit *TeleportrDepositSession) MinDepositAmount() (*big.Int, error) {
return _TeleportrDeposit.Contract.MinDepositAmount(&_TeleportrDeposit.CallOpts)
}
// MinDepositAmount is a free data retrieval call binding the contract method 0x645006ca.
//
// Solidity: function minDepositAmount() view returns(uint256)
func (_TeleportrDeposit *TeleportrDepositCallerSession) MinDepositAmount() (*big.Int, error) {
return _TeleportrDeposit.Contract.MinDepositAmount(&_TeleportrDeposit.CallOpts)
}
// Owner is a free data retrieval call binding the contract method 0x8da5cb5b.
//
// Solidity: function owner() view returns(address)
func (_TeleportrDeposit *TeleportrDepositCaller) Owner(opts *bind.CallOpts) (common.Address, error) {
var out []interface{}
err := _TeleportrDeposit.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 (_TeleportrDeposit *TeleportrDepositSession) Owner() (common.Address, error) {
return _TeleportrDeposit.Contract.Owner(&_TeleportrDeposit.CallOpts)
}
// Owner is a free data retrieval call binding the contract method 0x8da5cb5b.
//
// Solidity: function owner() view returns(address)
func (_TeleportrDeposit *TeleportrDepositCallerSession) Owner() (common.Address, error) {
return _TeleportrDeposit.Contract.Owner(&_TeleportrDeposit.CallOpts)
}
// TotalDeposits is a free data retrieval call binding the contract method 0x7d882097.
//
// Solidity: function totalDeposits() view returns(uint256)
func (_TeleportrDeposit *TeleportrDepositCaller) TotalDeposits(opts *bind.CallOpts) (*big.Int, error) {
var out []interface{}
err := _TeleportrDeposit.contract.Call(opts, &out, "totalDeposits")
if err != nil {
return *new(*big.Int), err
}
out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
return out0, err
}
// TotalDeposits is a free data retrieval call binding the contract method 0x7d882097.
//
// Solidity: function totalDeposits() view returns(uint256)
func (_TeleportrDeposit *TeleportrDepositSession) TotalDeposits() (*big.Int, error) {
return _TeleportrDeposit.Contract.TotalDeposits(&_TeleportrDeposit.CallOpts)
}
// TotalDeposits is a free data retrieval call binding the contract method 0x7d882097.
//
// Solidity: function totalDeposits() view returns(uint256)
func (_TeleportrDeposit *TeleportrDepositCallerSession) TotalDeposits() (*big.Int, error) {
return _TeleportrDeposit.Contract.TotalDeposits(&_TeleportrDeposit.CallOpts)
}
// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6.
//
// Solidity: function renounceOwnership() returns()
func (_TeleportrDeposit *TeleportrDepositTransactor) RenounceOwnership(opts *bind.TransactOpts) (*types.Transaction, error) {
return _TeleportrDeposit.contract.Transact(opts, "renounceOwnership")
}
// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6.
//
// Solidity: function renounceOwnership() returns()
func (_TeleportrDeposit *TeleportrDepositSession) RenounceOwnership() (*types.Transaction, error) {
return _TeleportrDeposit.Contract.RenounceOwnership(&_TeleportrDeposit.TransactOpts)
}
// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6.
//
// Solidity: function renounceOwnership() returns()
func (_TeleportrDeposit *TeleportrDepositTransactorSession) RenounceOwnership() (*types.Transaction, error) {
return _TeleportrDeposit.Contract.RenounceOwnership(&_TeleportrDeposit.TransactOpts)
}
// SetMaxAmount is a paid mutator transaction binding the contract method 0x4fe47f70.
//
// Solidity: function setMaxAmount(uint256 _maxDepositAmount) returns()
func (_TeleportrDeposit *TeleportrDepositTransactor) SetMaxAmount(opts *bind.TransactOpts, _maxDepositAmount *big.Int) (*types.Transaction, error) {
return _TeleportrDeposit.contract.Transact(opts, "setMaxAmount", _maxDepositAmount)
}
// SetMaxAmount is a paid mutator transaction binding the contract method 0x4fe47f70.
//
// Solidity: function setMaxAmount(uint256 _maxDepositAmount) returns()
func (_TeleportrDeposit *TeleportrDepositSession) SetMaxAmount(_maxDepositAmount *big.Int) (*types.Transaction, error) {
return _TeleportrDeposit.Contract.SetMaxAmount(&_TeleportrDeposit.TransactOpts, _maxDepositAmount)
}
// SetMaxAmount is a paid mutator transaction binding the contract method 0x4fe47f70.
//
// Solidity: function setMaxAmount(uint256 _maxDepositAmount) returns()
func (_TeleportrDeposit *TeleportrDepositTransactorSession) SetMaxAmount(_maxDepositAmount *big.Int) (*types.Transaction, error) {
return _TeleportrDeposit.Contract.SetMaxAmount(&_TeleportrDeposit.TransactOpts, _maxDepositAmount)
}
// SetMaxBalance is a paid mutator transaction binding the contract method 0x9d51d9b7.
//
// Solidity: function setMaxBalance(uint256 _maxBalance) returns()
func (_TeleportrDeposit *TeleportrDepositTransactor) SetMaxBalance(opts *bind.TransactOpts, _maxBalance *big.Int) (*types.Transaction, error) {
return _TeleportrDeposit.contract.Transact(opts, "setMaxBalance", _maxBalance)
}
// SetMaxBalance is a paid mutator transaction binding the contract method 0x9d51d9b7.
//
// Solidity: function setMaxBalance(uint256 _maxBalance) returns()
func (_TeleportrDeposit *TeleportrDepositSession) SetMaxBalance(_maxBalance *big.Int) (*types.Transaction, error) {
return _TeleportrDeposit.Contract.SetMaxBalance(&_TeleportrDeposit.TransactOpts, _maxBalance)
}
// SetMaxBalance is a paid mutator transaction binding the contract method 0x9d51d9b7.
//
// Solidity: function setMaxBalance(uint256 _maxBalance) returns()
func (_TeleportrDeposit *TeleportrDepositTransactorSession) SetMaxBalance(_maxBalance *big.Int) (*types.Transaction, error) {
return _TeleportrDeposit.Contract.SetMaxBalance(&_TeleportrDeposit.TransactOpts, _maxBalance)
}
// SetMinAmount is a paid mutator transaction binding the contract method 0x897b0637.
//
// Solidity: function setMinAmount(uint256 _minDepositAmount) returns()
func (_TeleportrDeposit *TeleportrDepositTransactor) SetMinAmount(opts *bind.TransactOpts, _minDepositAmount *big.Int) (*types.Transaction, error) {
return _TeleportrDeposit.contract.Transact(opts, "setMinAmount", _minDepositAmount)
}
// SetMinAmount is a paid mutator transaction binding the contract method 0x897b0637.
//
// Solidity: function setMinAmount(uint256 _minDepositAmount) returns()
func (_TeleportrDeposit *TeleportrDepositSession) SetMinAmount(_minDepositAmount *big.Int) (*types.Transaction, error) {
return _TeleportrDeposit.Contract.SetMinAmount(&_TeleportrDeposit.TransactOpts, _minDepositAmount)
}
// SetMinAmount is a paid mutator transaction binding the contract method 0x897b0637.
//
// Solidity: function setMinAmount(uint256 _minDepositAmount) returns()
func (_TeleportrDeposit *TeleportrDepositTransactorSession) SetMinAmount(_minDepositAmount *big.Int) (*types.Transaction, error) {
return _TeleportrDeposit.Contract.SetMinAmount(&_TeleportrDeposit.TransactOpts, _minDepositAmount)
}
// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b.
//
// Solidity: function transferOwnership(address newOwner) returns()
func (_TeleportrDeposit *TeleportrDepositTransactor) TransferOwnership(opts *bind.TransactOpts, newOwner common.Address) (*types.Transaction, error) {
return _TeleportrDeposit.contract.Transact(opts, "transferOwnership", newOwner)
}
// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b.
//
// Solidity: function transferOwnership(address newOwner) returns()
func (_TeleportrDeposit *TeleportrDepositSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) {
return _TeleportrDeposit.Contract.TransferOwnership(&_TeleportrDeposit.TransactOpts, newOwner)
}
// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b.
//
// Solidity: function transferOwnership(address newOwner) returns()
func (_TeleportrDeposit *TeleportrDepositTransactorSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) {
return _TeleportrDeposit.Contract.TransferOwnership(&_TeleportrDeposit.TransactOpts, newOwner)
}
// WithdrawBalance is a paid mutator transaction binding the contract method 0x5fd8c710.
//
// Solidity: function withdrawBalance() returns()
func (_TeleportrDeposit *TeleportrDepositTransactor) WithdrawBalance(opts *bind.TransactOpts) (*types.Transaction, error) {
return _TeleportrDeposit.contract.Transact(opts, "withdrawBalance")
}
// WithdrawBalance is a paid mutator transaction binding the contract method 0x5fd8c710.
//
// Solidity: function withdrawBalance() returns()
func (_TeleportrDeposit *TeleportrDepositSession) WithdrawBalance() (*types.Transaction, error) {
return _TeleportrDeposit.Contract.WithdrawBalance(&_TeleportrDeposit.TransactOpts)
}
// WithdrawBalance is a paid mutator transaction binding the contract method 0x5fd8c710.
//
// Solidity: function withdrawBalance() returns()
func (_TeleportrDeposit *TeleportrDepositTransactorSession) WithdrawBalance() (*types.Transaction, error) {
return _TeleportrDeposit.Contract.WithdrawBalance(&_TeleportrDeposit.TransactOpts)
}
// Receive is a paid mutator transaction binding the contract receive function.
//
// Solidity: receive() payable returns()
func (_TeleportrDeposit *TeleportrDepositTransactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) {
return _TeleportrDeposit.contract.RawTransact(opts, nil) // calldata is disallowed for receive function
}
// Receive is a paid mutator transaction binding the contract receive function.
//
// Solidity: receive() payable returns()
func (_TeleportrDeposit *TeleportrDepositSession) Receive() (*types.Transaction, error) {
return _TeleportrDeposit.Contract.Receive(&_TeleportrDeposit.TransactOpts)
}
// Receive is a paid mutator transaction binding the contract receive function.
//
// Solidity: receive() payable returns()
func (_TeleportrDeposit *TeleportrDepositTransactorSession) Receive() (*types.Transaction, error) {
return _TeleportrDeposit.Contract.Receive(&_TeleportrDeposit.TransactOpts)
}
// TeleportrDepositBalanceWithdrawnIterator is returned from FilterBalanceWithdrawn and is used to iterate over the raw logs and unpacked data for BalanceWithdrawn events raised by the TeleportrDeposit contract.
type TeleportrDepositBalanceWithdrawnIterator struct {
Event *TeleportrDepositBalanceWithdrawn // 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 *TeleportrDepositBalanceWithdrawnIterator) 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(TeleportrDepositBalanceWithdrawn)
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(TeleportrDepositBalanceWithdrawn)
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 *TeleportrDepositBalanceWithdrawnIterator) Error() error {
return it.fail
}
// Close terminates the iteration process, releasing any pending underlying
// resources.
func (it *TeleportrDepositBalanceWithdrawnIterator) Close() error {
it.sub.Unsubscribe()
return nil
}
// TeleportrDepositBalanceWithdrawn represents a BalanceWithdrawn event raised by the TeleportrDeposit contract.
type TeleportrDepositBalanceWithdrawn struct {
Owner common.Address
Balance *big.Int
Raw types.Log // Blockchain specific contextual infos
}
// FilterBalanceWithdrawn is a free log retrieval operation binding the contract event 0xddc398b321237a8d40ac914388309c2f52a08c134e4dc4ce61e32f57cb7d80f1.
//
// Solidity: event BalanceWithdrawn(address indexed owner, uint256 balance)
func (_TeleportrDeposit *TeleportrDepositFilterer) FilterBalanceWithdrawn(opts *bind.FilterOpts, owner []common.Address) (*TeleportrDepositBalanceWithdrawnIterator, error) {
var ownerRule []interface{}
for _, ownerItem := range owner {
ownerRule = append(ownerRule, ownerItem)
}
logs, sub, err := _TeleportrDeposit.contract.FilterLogs(opts, "BalanceWithdrawn", ownerRule)
if err != nil {
return nil, err
}
return &TeleportrDepositBalanceWithdrawnIterator{contract: _TeleportrDeposit.contract, event: "BalanceWithdrawn", logs: logs, sub: sub}, nil
}
// WatchBalanceWithdrawn is a free log subscription operation binding the contract event 0xddc398b321237a8d40ac914388309c2f52a08c134e4dc4ce61e32f57cb7d80f1.
//
// Solidity: event BalanceWithdrawn(address indexed owner, uint256 balance)
func (_TeleportrDeposit *TeleportrDepositFilterer) WatchBalanceWithdrawn(opts *bind.WatchOpts, sink chan<- *TeleportrDepositBalanceWithdrawn, owner []common.Address) (event.Subscription, error) {
var ownerRule []interface{}
for _, ownerItem := range owner {
ownerRule = append(ownerRule, ownerItem)
}
logs, sub, err := _TeleportrDeposit.contract.WatchLogs(opts, "BalanceWithdrawn", ownerRule)
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(TeleportrDepositBalanceWithdrawn)
if err := _TeleportrDeposit.contract.UnpackLog(event, "BalanceWithdrawn", 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
}
// ParseBalanceWithdrawn is a log parse operation binding the contract event 0xddc398b321237a8d40ac914388309c2f52a08c134e4dc4ce61e32f57cb7d80f1.
//
// Solidity: event BalanceWithdrawn(address indexed owner, uint256 balance)
func (_TeleportrDeposit *TeleportrDepositFilterer) ParseBalanceWithdrawn(log types.Log) (*TeleportrDepositBalanceWithdrawn, error) {
event := new(TeleportrDepositBalanceWithdrawn)
if err := _TeleportrDeposit.contract.UnpackLog(event, "BalanceWithdrawn", log); err != nil {
return nil, err
}
event.Raw = log
return event, nil
}
// TeleportrDepositEtherReceivedIterator is returned from FilterEtherReceived and is used to iterate over the raw logs and unpacked data for EtherReceived events raised by the TeleportrDeposit contract.
type TeleportrDepositEtherReceivedIterator struct {
Event *TeleportrDepositEtherReceived // 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 *TeleportrDepositEtherReceivedIterator) 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(TeleportrDepositEtherReceived)
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(TeleportrDepositEtherReceived)
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 *TeleportrDepositEtherReceivedIterator) Error() error {
return it.fail
}
// Close terminates the iteration process, releasing any pending underlying
// resources.
func (it *TeleportrDepositEtherReceivedIterator) Close() error {
it.sub.Unsubscribe()
return nil
}
// TeleportrDepositEtherReceived represents a EtherReceived event raised by the TeleportrDeposit contract.
type TeleportrDepositEtherReceived struct {
DepositId *big.Int
Emitter common.Address
Amount *big.Int
Raw types.Log // Blockchain specific contextual infos
}
// FilterEtherReceived is a free log retrieval operation binding the contract event 0x2d27851832fcac28a0d4af1344f01fed7ffcfd15171c14c564a0c42aa57ae5c0.
//
// Solidity: event EtherReceived(uint256 indexed depositId, address indexed emitter, uint256 indexed amount)
func (_TeleportrDeposit *TeleportrDepositFilterer) FilterEtherReceived(opts *bind.FilterOpts, depositId []*big.Int, emitter []common.Address, amount []*big.Int) (*TeleportrDepositEtherReceivedIterator, error) {
var depositIdRule []interface{}
for _, depositIdItem := range depositId {
depositIdRule = append(depositIdRule, depositIdItem)
}
var emitterRule []interface{}
for _, emitterItem := range emitter {
emitterRule = append(emitterRule, emitterItem)
}
var amountRule []interface{}
for _, amountItem := range amount {
amountRule = append(amountRule, amountItem)
}
logs, sub, err := _TeleportrDeposit.contract.FilterLogs(opts, "EtherReceived", depositIdRule, emitterRule, amountRule)
if err != nil {
return nil, err
}
return &TeleportrDepositEtherReceivedIterator{contract: _TeleportrDeposit.contract, event: "EtherReceived", logs: logs, sub: sub}, nil
}
// WatchEtherReceived is a free log subscription operation binding the contract event 0x2d27851832fcac28a0d4af1344f01fed7ffcfd15171c14c564a0c42aa57ae5c0.
//
// Solidity: event EtherReceived(uint256 indexed depositId, address indexed emitter, uint256 indexed amount)
func (_TeleportrDeposit *TeleportrDepositFilterer) WatchEtherReceived(opts *bind.WatchOpts, sink chan<- *TeleportrDepositEtherReceived, depositId []*big.Int, emitter []common.Address, amount []*big.Int) (event.Subscription, error) {
var depositIdRule []interface{}
for _, depositIdItem := range depositId {
depositIdRule = append(depositIdRule, depositIdItem)
}
var emitterRule []interface{}
for _, emitterItem := range emitter {
emitterRule = append(emitterRule, emitterItem)
}
var amountRule []interface{}
for _, amountItem := range amount {
amountRule = append(amountRule, amountItem)
}
logs, sub, err := _TeleportrDeposit.contract.WatchLogs(opts, "EtherReceived", depositIdRule, emitterRule, amountRule)
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(TeleportrDepositEtherReceived)
if err := _TeleportrDeposit.contract.UnpackLog(event, "EtherReceived", 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
}
// ParseEtherReceived is a log parse operation binding the contract event 0x2d27851832fcac28a0d4af1344f01fed7ffcfd15171c14c564a0c42aa57ae5c0.
//
// Solidity: event EtherReceived(uint256 indexed depositId, address indexed emitter, uint256 indexed amount)
func (_TeleportrDeposit *TeleportrDepositFilterer) ParseEtherReceived(log types.Log) (*TeleportrDepositEtherReceived, error) {
event := new(TeleportrDepositEtherReceived)
if err := _TeleportrDeposit.contract.UnpackLog(event, "EtherReceived", log); err != nil {
return nil, err
}
event.Raw = log
return event, nil
}
// TeleportrDepositMaxBalanceSetIterator is returned from FilterMaxBalanceSet and is used to iterate over the raw logs and unpacked data for MaxBalanceSet events raised by the TeleportrDeposit contract.
type TeleportrDepositMaxBalanceSetIterator struct {
Event *TeleportrDepositMaxBalanceSet // 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 *TeleportrDepositMaxBalanceSetIterator) 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(TeleportrDepositMaxBalanceSet)
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(TeleportrDepositMaxBalanceSet)
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 *TeleportrDepositMaxBalanceSetIterator) Error() error {
return it.fail
}
// Close terminates the iteration process, releasing any pending underlying
// resources.
func (it *TeleportrDepositMaxBalanceSetIterator) Close() error {
it.sub.Unsubscribe()
return nil
}
// TeleportrDepositMaxBalanceSet represents a MaxBalanceSet event raised by the TeleportrDeposit contract.
type TeleportrDepositMaxBalanceSet struct {
PreviousBalance *big.Int
NewBalance *big.Int
Raw types.Log // Blockchain specific contextual infos
}
// FilterMaxBalanceSet is a free log retrieval operation binding the contract event 0x185c6391e7218e85de8a9346fc72024a0f88e1f04c186e6351230b93976ad50b.
//
// Solidity: event MaxBalanceSet(uint256 previousBalance, uint256 newBalance)
func (_TeleportrDeposit *TeleportrDepositFilterer) FilterMaxBalanceSet(opts *bind.FilterOpts) (*TeleportrDepositMaxBalanceSetIterator, error) {
logs, sub, err := _TeleportrDeposit.contract.FilterLogs(opts, "MaxBalanceSet")
if err != nil {
return nil, err
}
return &TeleportrDepositMaxBalanceSetIterator{contract: _TeleportrDeposit.contract, event: "MaxBalanceSet", logs: logs, sub: sub}, nil
}
// WatchMaxBalanceSet is a free log subscription operation binding the contract event 0x185c6391e7218e85de8a9346fc72024a0f88e1f04c186e6351230b93976ad50b.
//
// Solidity: event MaxBalanceSet(uint256 previousBalance, uint256 newBalance)
func (_TeleportrDeposit *TeleportrDepositFilterer) WatchMaxBalanceSet(opts *bind.WatchOpts, sink chan<- *TeleportrDepositMaxBalanceSet) (event.Subscription, error) {
logs, sub, err := _TeleportrDeposit.contract.WatchLogs(opts, "MaxBalanceSet")
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(TeleportrDepositMaxBalanceSet)
if err := _TeleportrDeposit.contract.UnpackLog(event, "MaxBalanceSet", 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
}
// ParseMaxBalanceSet is a log parse operation binding the contract event 0x185c6391e7218e85de8a9346fc72024a0f88e1f04c186e6351230b93976ad50b.
//
// Solidity: event MaxBalanceSet(uint256 previousBalance, uint256 newBalance)
func (_TeleportrDeposit *TeleportrDepositFilterer) ParseMaxBalanceSet(log types.Log) (*TeleportrDepositMaxBalanceSet, error) {
event := new(TeleportrDepositMaxBalanceSet)
if err := _TeleportrDeposit.contract.UnpackLog(event, "MaxBalanceSet", log); err != nil {
return nil, err
}
event.Raw = log
return event, nil
}
// TeleportrDepositMaxDepositAmountSetIterator is returned from FilterMaxDepositAmountSet and is used to iterate over the raw logs and unpacked data for MaxDepositAmountSet events raised by the TeleportrDeposit contract.
type TeleportrDepositMaxDepositAmountSetIterator struct {
Event *TeleportrDepositMaxDepositAmountSet // 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 *TeleportrDepositMaxDepositAmountSetIterator) 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(TeleportrDepositMaxDepositAmountSet)
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(TeleportrDepositMaxDepositAmountSet)
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 *TeleportrDepositMaxDepositAmountSetIterator) Error() error {
return it.fail
}
// Close terminates the iteration process, releasing any pending underlying
// resources.
func (it *TeleportrDepositMaxDepositAmountSetIterator) Close() error {
it.sub.Unsubscribe()
return nil
}
// TeleportrDepositMaxDepositAmountSet represents a MaxDepositAmountSet event raised by the TeleportrDeposit contract.
type TeleportrDepositMaxDepositAmountSet struct {
PreviousAmount *big.Int
NewAmount *big.Int
Raw types.Log // Blockchain specific contextual infos
}
// FilterMaxDepositAmountSet is a free log retrieval operation binding the contract event 0xb1e6cc560df1786578fd4d1fe6e046f089a0c3be401e999b51a5112437911797.
//
// Solidity: event MaxDepositAmountSet(uint256 previousAmount, uint256 newAmount)
func (_TeleportrDeposit *TeleportrDepositFilterer) FilterMaxDepositAmountSet(opts *bind.FilterOpts) (*TeleportrDepositMaxDepositAmountSetIterator, error) {
logs, sub, err := _TeleportrDeposit.contract.FilterLogs(opts, "MaxDepositAmountSet")
if err != nil {
return nil, err
}
return &TeleportrDepositMaxDepositAmountSetIterator{contract: _TeleportrDeposit.contract, event: "MaxDepositAmountSet", logs: logs, sub: sub}, nil
}
// WatchMaxDepositAmountSet is a free log subscription operation binding the contract event 0xb1e6cc560df1786578fd4d1fe6e046f089a0c3be401e999b51a5112437911797.
//
// Solidity: event MaxDepositAmountSet(uint256 previousAmount, uint256 newAmount)
func (_TeleportrDeposit *TeleportrDepositFilterer) WatchMaxDepositAmountSet(opts *bind.WatchOpts, sink chan<- *TeleportrDepositMaxDepositAmountSet) (event.Subscription, error) {
logs, sub, err := _TeleportrDeposit.contract.WatchLogs(opts, "MaxDepositAmountSet")
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(TeleportrDepositMaxDepositAmountSet)
if err := _TeleportrDeposit.contract.UnpackLog(event, "MaxDepositAmountSet", 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
}
// ParseMaxDepositAmountSet is a log parse operation binding the contract event 0xb1e6cc560df1786578fd4d1fe6e046f089a0c3be401e999b51a5112437911797.
//
// Solidity: event MaxDepositAmountSet(uint256 previousAmount, uint256 newAmount)
func (_TeleportrDeposit *TeleportrDepositFilterer) ParseMaxDepositAmountSet(log types.Log) (*TeleportrDepositMaxDepositAmountSet, error) {
event := new(TeleportrDepositMaxDepositAmountSet)
if err := _TeleportrDeposit.contract.UnpackLog(event, "MaxDepositAmountSet", log); err != nil {
return nil, err
}
event.Raw = log
return event, nil
}
// TeleportrDepositMinDepositAmountSetIterator is returned from FilterMinDepositAmountSet and is used to iterate over the raw logs and unpacked data for MinDepositAmountSet events raised by the TeleportrDeposit contract.
type TeleportrDepositMinDepositAmountSetIterator struct {
Event *TeleportrDepositMinDepositAmountSet // 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 *TeleportrDepositMinDepositAmountSetIterator) 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(TeleportrDepositMinDepositAmountSet)
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(TeleportrDepositMinDepositAmountSet)
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 *TeleportrDepositMinDepositAmountSetIterator) Error() error {
return it.fail
}
// Close terminates the iteration process, releasing any pending underlying
// resources.
func (it *TeleportrDepositMinDepositAmountSetIterator) Close() error {
it.sub.Unsubscribe()
return nil
}
// TeleportrDepositMinDepositAmountSet represents a MinDepositAmountSet event raised by the TeleportrDeposit contract.
type TeleportrDepositMinDepositAmountSet struct {
PreviousAmount *big.Int
NewAmount *big.Int
Raw types.Log // Blockchain specific contextual infos
}
// FilterMinDepositAmountSet is a free log retrieval operation binding the contract event 0x65779d3ca560e9bdec52d08ed75431a84df87cb7796f0e51965f6efc0f556c0f.
//
// Solidity: event MinDepositAmountSet(uint256 previousAmount, uint256 newAmount)
func (_TeleportrDeposit *TeleportrDepositFilterer) FilterMinDepositAmountSet(opts *bind.FilterOpts) (*TeleportrDepositMinDepositAmountSetIterator, error) {
logs, sub, err := _TeleportrDeposit.contract.FilterLogs(opts, "MinDepositAmountSet")
if err != nil {
return nil, err
}
return &TeleportrDepositMinDepositAmountSetIterator{contract: _TeleportrDeposit.contract, event: "MinDepositAmountSet", logs: logs, sub: sub}, nil
}
// WatchMinDepositAmountSet is a free log subscription operation binding the contract event 0x65779d3ca560e9bdec52d08ed75431a84df87cb7796f0e51965f6efc0f556c0f.
//
// Solidity: event MinDepositAmountSet(uint256 previousAmount, uint256 newAmount)
func (_TeleportrDeposit *TeleportrDepositFilterer) WatchMinDepositAmountSet(opts *bind.WatchOpts, sink chan<- *TeleportrDepositMinDepositAmountSet) (event.Subscription, error) {
logs, sub, err := _TeleportrDeposit.contract.WatchLogs(opts, "MinDepositAmountSet")
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(TeleportrDepositMinDepositAmountSet)
if err := _TeleportrDeposit.contract.UnpackLog(event, "MinDepositAmountSet", 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
}
// ParseMinDepositAmountSet is a log parse operation binding the contract event 0x65779d3ca560e9bdec52d08ed75431a84df87cb7796f0e51965f6efc0f556c0f.
//
// Solidity: event MinDepositAmountSet(uint256 previousAmount, uint256 newAmount)
func (_TeleportrDeposit *TeleportrDepositFilterer) ParseMinDepositAmountSet(log types.Log) (*TeleportrDepositMinDepositAmountSet, error) {
event := new(TeleportrDepositMinDepositAmountSet)
if err := _TeleportrDeposit.contract.UnpackLog(event, "MinDepositAmountSet", log); err != nil {
return nil, err
}
event.Raw = log
return event, nil
}
// TeleportrDepositOwnershipTransferredIterator is returned from FilterOwnershipTransferred and is used to iterate over the raw logs and unpacked data for OwnershipTransferred events raised by the TeleportrDeposit contract.
type TeleportrDepositOwnershipTransferredIterator struct {
Event *TeleportrDepositOwnershipTransferred // 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 *TeleportrDepositOwnershipTransferredIterator) 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(TeleportrDepositOwnershipTransferred)
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(TeleportrDepositOwnershipTransferred)
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 *TeleportrDepositOwnershipTransferredIterator) Error() error {
return it.fail
}
// Close terminates the iteration process, releasing any pending underlying
// resources.
func (it *TeleportrDepositOwnershipTransferredIterator) Close() error {
it.sub.Unsubscribe()
return nil
}
// TeleportrDepositOwnershipTransferred represents a OwnershipTransferred event raised by the TeleportrDeposit contract.
type TeleportrDepositOwnershipTransferred 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 (_TeleportrDeposit *TeleportrDepositFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, previousOwner []common.Address, newOwner []common.Address) (*TeleportrDepositOwnershipTransferredIterator, 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 := _TeleportrDeposit.contract.FilterLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule)
if err != nil {
return nil, err
}
return &TeleportrDepositOwnershipTransferredIterator{contract: _TeleportrDeposit.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 (_TeleportrDeposit *TeleportrDepositFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *TeleportrDepositOwnershipTransferred, 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 := _TeleportrDeposit.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(TeleportrDepositOwnershipTransferred)
if err := _TeleportrDeposit.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 (_TeleportrDeposit *TeleportrDepositFilterer) ParseOwnershipTransferred(log types.Log) (*TeleportrDepositOwnershipTransferred, error) {
event := new(TeleportrDepositOwnershipTransferred)
if err := _TeleportrDeposit.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil {
return nil, err
}
event.Raw = log
return event, nil
}
// Code generated - DO NOT EDIT.
// This file is a generated binding and any manual changes will be lost.
package disburse
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
)
// TeleportrDisburserDisbursement is an auto generated low-level Go binding around an user-defined struct.
type TeleportrDisburserDisbursement struct {
Amount *big.Int
Addr common.Address
}
// TeleportrDisburserMetaData contains all meta data concerning the TeleportrDisburser contract.
var TeleportrDisburserMetaData = &bind.MetaData{
ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"}],\"name\":\"BalanceWithdrawn\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"depositId\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"DisbursementFailed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"depositId\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"DisbursementSuccess\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_nextDepositId\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"internalType\":\"structTeleportrDisburser.Disbursement[]\",\"name\":\"_disbursements\",\"type\":\"tuple[]\"}],\"name\":\"disburse\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalDisbursements\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"withdrawBalance\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]",
Bin: "0x608060405234801561001057600080fd5b5061001a33610024565b6000600155610074565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6109e1806100836000396000f3fe6080604052600436106100655760003560e01c80638da5cb5b116100435780638da5cb5b146100bf578063ad48144d146100f4578063f2fde38b1461010757600080fd5b806325999e7f1461006a5780635fd8c71014610093578063715018a6146100aa575b600080fd5b34801561007657600080fd5b5061008060015481565b6040519081526020015b60405180910390f35b34801561009f57600080fd5b506100a8610127565b005b3480156100b657600080fd5b506100a8610248565b3480156100cb57600080fd5b5060005460405173ffffffffffffffffffffffffffffffffffffffff909116815260200161008a565b6100a8610102366004610840565b6102d5565b34801561011357600080fd5b506100a86101223660046108bf565b61069b565b60005473ffffffffffffffffffffffffffffffffffffffff1633146101ad576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064015b60405180910390fd5b6000546040805147808252915173ffffffffffffffffffffffffffffffffffffffff9093169283917fddc398b321237a8d40ac914388309c2f52a08c134e4dc4ce61e32f57cb7d80f1919081900360200190a260405173ffffffffffffffffffffffffffffffffffffffff83169082156108fc029083906000818181858888f19350505050158015610243573d6000803e3d6000fd5b505050565b60005473ffffffffffffffffffffffffffffffffffffffff1633146102c9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016101a4565b6102d360006107cb565b565b60005473ffffffffffffffffffffffffffffffffffffffff163314610356576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016101a4565b80806103be576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f4e6f2064697362757273656d656e74730000000000000000000000000000000060448201526064016101a4565b60015484811461042a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f556e6578706563746564206e657874206465706f73697420696400000000000060448201526064016101a4565b60018054830190556000805b8381101561047757858582818110610450576104506108fc565b610463926040909102013590508361095a565b91508061046f81610972565b915050610436565b50348114610507576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f44697362757273656d656e7420746f74616c20213d20616d6f756e742073656e60448201527f740000000000000000000000000000000000000000000000000000000000000060648201526084016101a4565b60005b83811015610692576000868683818110610526576105266108fc565b9050604002016000013590506000878784818110610546576105466108fc565b905060400201602001602081019061055e91906108bf565b905060008173ffffffffffffffffffffffffffffffffffffffff16836108fc90604051600060405180830381858888f193505050503d80600081146105bf576040519150601f19603f3d011682016040523d82523d6000602084013e6105c4565b606091505b505090508015610624578173ffffffffffffffffffffffffffffffffffffffff16867feaa22fd2d7b875476355b32cf719794faf9d91b66e73bc6375a053cace9caaee8560405161061791815260200190565b60405180910390a3610676565b8173ffffffffffffffffffffffffffffffffffffffff16867f9b478c095979d3d3a7d602ffd9ee1f0843204d853558ae0882c8fcc0a5bc78cf8560405161066d91815260200190565b60405180910390a35b600186019550505050808061068a90610972565b91505061050a565b50505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff16331461071c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016101a4565b73ffffffffffffffffffffffffffffffffffffffff81166107bf576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016101a4565b6107c8816107cb565b50565b6000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60008060006040848603121561085557600080fd5b83359250602084013567ffffffffffffffff8082111561087457600080fd5b818601915086601f83011261088857600080fd5b81358181111561089757600080fd5b8760208260061b85010111156108ac57600080fd5b6020830194508093505050509250925092565b6000602082840312156108d157600080fd5b813573ffffffffffffffffffffffffffffffffffffffff811681146108f557600080fd5b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000821982111561096d5761096d61092b565b500190565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8214156109a4576109a461092b565b506001019056fea26469706673582212209c98cb9cc04bc2bf5e8c0baf23ef87d045122afe6cfbb0e3c31bd854dd7fad7d64736f6c63430008090033",
}
// TeleportrDisburserABI is the input ABI used to generate the binding from.
// Deprecated: Use TeleportrDisburserMetaData.ABI instead.
var TeleportrDisburserABI = TeleportrDisburserMetaData.ABI
// TeleportrDisburserBin is the compiled bytecode used for deploying new contracts.
// Deprecated: Use TeleportrDisburserMetaData.Bin instead.
var TeleportrDisburserBin = TeleportrDisburserMetaData.Bin
// DeployTeleportrDisburser deploys a new Ethereum contract, binding an instance of TeleportrDisburser to it.
func DeployTeleportrDisburser(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *TeleportrDisburser, error) {
parsed, err := TeleportrDisburserMetaData.GetAbi()
if err != nil {
return common.Address{}, nil, nil, err
}
if parsed == nil {
return common.Address{}, nil, nil, errors.New("GetABI returned nil")
}
address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(TeleportrDisburserBin), backend)
if err != nil {
return common.Address{}, nil, nil, err
}
return address, tx, &TeleportrDisburser{TeleportrDisburserCaller: TeleportrDisburserCaller{contract: contract}, TeleportrDisburserTransactor: TeleportrDisburserTransactor{contract: contract}, TeleportrDisburserFilterer: TeleportrDisburserFilterer{contract: contract}}, nil
}
// TeleportrDisburser is an auto generated Go binding around an Ethereum contract.
type TeleportrDisburser struct {
TeleportrDisburserCaller // Read-only binding to the contract
TeleportrDisburserTransactor // Write-only binding to the contract
TeleportrDisburserFilterer // Log filterer for contract events
}
// TeleportrDisburserCaller is an auto generated read-only Go binding around an Ethereum contract.
type TeleportrDisburserCaller struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// TeleportrDisburserTransactor is an auto generated write-only Go binding around an Ethereum contract.
type TeleportrDisburserTransactor struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// TeleportrDisburserFilterer is an auto generated log filtering Go binding around an Ethereum contract events.
type TeleportrDisburserFilterer struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// TeleportrDisburserSession is an auto generated Go binding around an Ethereum contract,
// with pre-set call and transact options.
type TeleportrDisburserSession struct {
Contract *TeleportrDisburser // 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
}
// TeleportrDisburserCallerSession is an auto generated read-only Go binding around an Ethereum contract,
// with pre-set call options.
type TeleportrDisburserCallerSession struct {
Contract *TeleportrDisburserCaller // Generic contract caller binding to set the session for
CallOpts bind.CallOpts // Call options to use throughout this session
}
// TeleportrDisburserTransactorSession is an auto generated write-only Go binding around an Ethereum contract,
// with pre-set transact options.
type TeleportrDisburserTransactorSession struct {
Contract *TeleportrDisburserTransactor // Generic contract transactor binding to set the session for
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
}
// TeleportrDisburserRaw is an auto generated low-level Go binding around an Ethereum contract.
type TeleportrDisburserRaw struct {
Contract *TeleportrDisburser // Generic contract binding to access the raw methods on
}
// TeleportrDisburserCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
type TeleportrDisburserCallerRaw struct {
Contract *TeleportrDisburserCaller // Generic read-only contract binding to access the raw methods on
}
// TeleportrDisburserTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
type TeleportrDisburserTransactorRaw struct {
Contract *TeleportrDisburserTransactor // Generic write-only contract binding to access the raw methods on
}
// NewTeleportrDisburser creates a new instance of TeleportrDisburser, bound to a specific deployed contract.
func NewTeleportrDisburser(address common.Address, backend bind.ContractBackend) (*TeleportrDisburser, error) {
contract, err := bindTeleportrDisburser(address, backend, backend, backend)
if err != nil {
return nil, err
}
return &TeleportrDisburser{TeleportrDisburserCaller: TeleportrDisburserCaller{contract: contract}, TeleportrDisburserTransactor: TeleportrDisburserTransactor{contract: contract}, TeleportrDisburserFilterer: TeleportrDisburserFilterer{contract: contract}}, nil
}
// NewTeleportrDisburserCaller creates a new read-only instance of TeleportrDisburser, bound to a specific deployed contract.
func NewTeleportrDisburserCaller(address common.Address, caller bind.ContractCaller) (*TeleportrDisburserCaller, error) {
contract, err := bindTeleportrDisburser(address, caller, nil, nil)
if err != nil {
return nil, err
}
return &TeleportrDisburserCaller{contract: contract}, nil
}
// NewTeleportrDisburserTransactor creates a new write-only instance of TeleportrDisburser, bound to a specific deployed contract.
func NewTeleportrDisburserTransactor(address common.Address, transactor bind.ContractTransactor) (*TeleportrDisburserTransactor, error) {
contract, err := bindTeleportrDisburser(address, nil, transactor, nil)
if err != nil {
return nil, err
}
return &TeleportrDisburserTransactor{contract: contract}, nil
}
// NewTeleportrDisburserFilterer creates a new log filterer instance of TeleportrDisburser, bound to a specific deployed contract.
func NewTeleportrDisburserFilterer(address common.Address, filterer bind.ContractFilterer) (*TeleportrDisburserFilterer, error) {
contract, err := bindTeleportrDisburser(address, nil, nil, filterer)
if err != nil {
return nil, err
}
return &TeleportrDisburserFilterer{contract: contract}, nil
}
// bindTeleportrDisburser binds a generic wrapper to an already deployed contract.
func bindTeleportrDisburser(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
parsed, err := abi.JSON(strings.NewReader(TeleportrDisburserABI))
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 (_TeleportrDisburser *TeleportrDisburserRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
return _TeleportrDisburser.Contract.TeleportrDisburserCaller.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 (_TeleportrDisburser *TeleportrDisburserRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
return _TeleportrDisburser.Contract.TeleportrDisburserTransactor.contract.Transfer(opts)
}
// Transact invokes the (paid) contract method with params as input values.
func (_TeleportrDisburser *TeleportrDisburserRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
return _TeleportrDisburser.Contract.TeleportrDisburserTransactor.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 (_TeleportrDisburser *TeleportrDisburserCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
return _TeleportrDisburser.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 (_TeleportrDisburser *TeleportrDisburserTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
return _TeleportrDisburser.Contract.contract.Transfer(opts)
}
// Transact invokes the (paid) contract method with params as input values.
func (_TeleportrDisburser *TeleportrDisburserTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
return _TeleportrDisburser.Contract.contract.Transact(opts, method, params...)
}
// Owner is a free data retrieval call binding the contract method 0x8da5cb5b.
//
// Solidity: function owner() view returns(address)
func (_TeleportrDisburser *TeleportrDisburserCaller) Owner(opts *bind.CallOpts) (common.Address, error) {
var out []interface{}
err := _TeleportrDisburser.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 (_TeleportrDisburser *TeleportrDisburserSession) Owner() (common.Address, error) {
return _TeleportrDisburser.Contract.Owner(&_TeleportrDisburser.CallOpts)
}
// Owner is a free data retrieval call binding the contract method 0x8da5cb5b.
//
// Solidity: function owner() view returns(address)
func (_TeleportrDisburser *TeleportrDisburserCallerSession) Owner() (common.Address, error) {
return _TeleportrDisburser.Contract.Owner(&_TeleportrDisburser.CallOpts)
}
// TotalDisbursements is a free data retrieval call binding the contract method 0x25999e7f.
//
// Solidity: function totalDisbursements() view returns(uint256)
func (_TeleportrDisburser *TeleportrDisburserCaller) TotalDisbursements(opts *bind.CallOpts) (*big.Int, error) {
var out []interface{}
err := _TeleportrDisburser.contract.Call(opts, &out, "totalDisbursements")
if err != nil {
return *new(*big.Int), err
}
out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
return out0, err
}
// TotalDisbursements is a free data retrieval call binding the contract method 0x25999e7f.
//
// Solidity: function totalDisbursements() view returns(uint256)
func (_TeleportrDisburser *TeleportrDisburserSession) TotalDisbursements() (*big.Int, error) {
return _TeleportrDisburser.Contract.TotalDisbursements(&_TeleportrDisburser.CallOpts)
}
// TotalDisbursements is a free data retrieval call binding the contract method 0x25999e7f.
//
// Solidity: function totalDisbursements() view returns(uint256)
func (_TeleportrDisburser *TeleportrDisburserCallerSession) TotalDisbursements() (*big.Int, error) {
return _TeleportrDisburser.Contract.TotalDisbursements(&_TeleportrDisburser.CallOpts)
}
// Disburse is a paid mutator transaction binding the contract method 0xad48144d.
//
// Solidity: function disburse(uint256 _nextDepositId, (uint256,address)[] _disbursements) payable returns()
func (_TeleportrDisburser *TeleportrDisburserTransactor) Disburse(opts *bind.TransactOpts, _nextDepositId *big.Int, _disbursements []TeleportrDisburserDisbursement) (*types.Transaction, error) {
return _TeleportrDisburser.contract.Transact(opts, "disburse", _nextDepositId, _disbursements)
}
// Disburse is a paid mutator transaction binding the contract method 0xad48144d.
//
// Solidity: function disburse(uint256 _nextDepositId, (uint256,address)[] _disbursements) payable returns()
func (_TeleportrDisburser *TeleportrDisburserSession) Disburse(_nextDepositId *big.Int, _disbursements []TeleportrDisburserDisbursement) (*types.Transaction, error) {
return _TeleportrDisburser.Contract.Disburse(&_TeleportrDisburser.TransactOpts, _nextDepositId, _disbursements)
}
// Disburse is a paid mutator transaction binding the contract method 0xad48144d.
//
// Solidity: function disburse(uint256 _nextDepositId, (uint256,address)[] _disbursements) payable returns()
func (_TeleportrDisburser *TeleportrDisburserTransactorSession) Disburse(_nextDepositId *big.Int, _disbursements []TeleportrDisburserDisbursement) (*types.Transaction, error) {
return _TeleportrDisburser.Contract.Disburse(&_TeleportrDisburser.TransactOpts, _nextDepositId, _disbursements)
}
// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6.
//
// Solidity: function renounceOwnership() returns()
func (_TeleportrDisburser *TeleportrDisburserTransactor) RenounceOwnership(opts *bind.TransactOpts) (*types.Transaction, error) {
return _TeleportrDisburser.contract.Transact(opts, "renounceOwnership")
}
// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6.
//
// Solidity: function renounceOwnership() returns()
func (_TeleportrDisburser *TeleportrDisburserSession) RenounceOwnership() (*types.Transaction, error) {
return _TeleportrDisburser.Contract.RenounceOwnership(&_TeleportrDisburser.TransactOpts)
}
// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6.
//
// Solidity: function renounceOwnership() returns()
func (_TeleportrDisburser *TeleportrDisburserTransactorSession) RenounceOwnership() (*types.Transaction, error) {
return _TeleportrDisburser.Contract.RenounceOwnership(&_TeleportrDisburser.TransactOpts)
}
// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b.
//
// Solidity: function transferOwnership(address newOwner) returns()
func (_TeleportrDisburser *TeleportrDisburserTransactor) TransferOwnership(opts *bind.TransactOpts, newOwner common.Address) (*types.Transaction, error) {
return _TeleportrDisburser.contract.Transact(opts, "transferOwnership", newOwner)
}
// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b.
//
// Solidity: function transferOwnership(address newOwner) returns()
func (_TeleportrDisburser *TeleportrDisburserSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) {
return _TeleportrDisburser.Contract.TransferOwnership(&_TeleportrDisburser.TransactOpts, newOwner)
}
// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b.
//
// Solidity: function transferOwnership(address newOwner) returns()
func (_TeleportrDisburser *TeleportrDisburserTransactorSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) {
return _TeleportrDisburser.Contract.TransferOwnership(&_TeleportrDisburser.TransactOpts, newOwner)
}
// WithdrawBalance is a paid mutator transaction binding the contract method 0x5fd8c710.
//
// Solidity: function withdrawBalance() returns()
func (_TeleportrDisburser *TeleportrDisburserTransactor) WithdrawBalance(opts *bind.TransactOpts) (*types.Transaction, error) {
return _TeleportrDisburser.contract.Transact(opts, "withdrawBalance")
}
// WithdrawBalance is a paid mutator transaction binding the contract method 0x5fd8c710.
//
// Solidity: function withdrawBalance() returns()
func (_TeleportrDisburser *TeleportrDisburserSession) WithdrawBalance() (*types.Transaction, error) {
return _TeleportrDisburser.Contract.WithdrawBalance(&_TeleportrDisburser.TransactOpts)
}
// WithdrawBalance is a paid mutator transaction binding the contract method 0x5fd8c710.
//
// Solidity: function withdrawBalance() returns()
func (_TeleportrDisburser *TeleportrDisburserTransactorSession) WithdrawBalance() (*types.Transaction, error) {
return _TeleportrDisburser.Contract.WithdrawBalance(&_TeleportrDisburser.TransactOpts)
}
// TeleportrDisburserBalanceWithdrawnIterator is returned from FilterBalanceWithdrawn and is used to iterate over the raw logs and unpacked data for BalanceWithdrawn events raised by the TeleportrDisburser contract.
type TeleportrDisburserBalanceWithdrawnIterator struct {
Event *TeleportrDisburserBalanceWithdrawn // 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 *TeleportrDisburserBalanceWithdrawnIterator) 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(TeleportrDisburserBalanceWithdrawn)
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(TeleportrDisburserBalanceWithdrawn)
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 *TeleportrDisburserBalanceWithdrawnIterator) Error() error {
return it.fail
}
// Close terminates the iteration process, releasing any pending underlying
// resources.
func (it *TeleportrDisburserBalanceWithdrawnIterator) Close() error {
it.sub.Unsubscribe()
return nil
}
// TeleportrDisburserBalanceWithdrawn represents a BalanceWithdrawn event raised by the TeleportrDisburser contract.
type TeleportrDisburserBalanceWithdrawn struct {
Owner common.Address
Balance *big.Int
Raw types.Log // Blockchain specific contextual infos
}
// FilterBalanceWithdrawn is a free log retrieval operation binding the contract event 0xddc398b321237a8d40ac914388309c2f52a08c134e4dc4ce61e32f57cb7d80f1.
//
// Solidity: event BalanceWithdrawn(address indexed owner, uint256 balance)
func (_TeleportrDisburser *TeleportrDisburserFilterer) FilterBalanceWithdrawn(opts *bind.FilterOpts, owner []common.Address) (*TeleportrDisburserBalanceWithdrawnIterator, error) {
var ownerRule []interface{}
for _, ownerItem := range owner {
ownerRule = append(ownerRule, ownerItem)
}
logs, sub, err := _TeleportrDisburser.contract.FilterLogs(opts, "BalanceWithdrawn", ownerRule)
if err != nil {
return nil, err
}
return &TeleportrDisburserBalanceWithdrawnIterator{contract: _TeleportrDisburser.contract, event: "BalanceWithdrawn", logs: logs, sub: sub}, nil
}
// WatchBalanceWithdrawn is a free log subscription operation binding the contract event 0xddc398b321237a8d40ac914388309c2f52a08c134e4dc4ce61e32f57cb7d80f1.
//
// Solidity: event BalanceWithdrawn(address indexed owner, uint256 balance)
func (_TeleportrDisburser *TeleportrDisburserFilterer) WatchBalanceWithdrawn(opts *bind.WatchOpts, sink chan<- *TeleportrDisburserBalanceWithdrawn, owner []common.Address) (event.Subscription, error) {
var ownerRule []interface{}
for _, ownerItem := range owner {
ownerRule = append(ownerRule, ownerItem)
}
logs, sub, err := _TeleportrDisburser.contract.WatchLogs(opts, "BalanceWithdrawn", ownerRule)
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(TeleportrDisburserBalanceWithdrawn)
if err := _TeleportrDisburser.contract.UnpackLog(event, "BalanceWithdrawn", 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
}
// ParseBalanceWithdrawn is a log parse operation binding the contract event 0xddc398b321237a8d40ac914388309c2f52a08c134e4dc4ce61e32f57cb7d80f1.
//
// Solidity: event BalanceWithdrawn(address indexed owner, uint256 balance)
func (_TeleportrDisburser *TeleportrDisburserFilterer) ParseBalanceWithdrawn(log types.Log) (*TeleportrDisburserBalanceWithdrawn, error) {
event := new(TeleportrDisburserBalanceWithdrawn)
if err := _TeleportrDisburser.contract.UnpackLog(event, "BalanceWithdrawn", log); err != nil {
return nil, err
}
event.Raw = log
return event, nil
}
// TeleportrDisburserDisbursementFailedIterator is returned from FilterDisbursementFailed and is used to iterate over the raw logs and unpacked data for DisbursementFailed events raised by the TeleportrDisburser contract.
type TeleportrDisburserDisbursementFailedIterator struct {
Event *TeleportrDisburserDisbursementFailed // 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 *TeleportrDisburserDisbursementFailedIterator) 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(TeleportrDisburserDisbursementFailed)
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(TeleportrDisburserDisbursementFailed)
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 *TeleportrDisburserDisbursementFailedIterator) Error() error {
return it.fail
}
// Close terminates the iteration process, releasing any pending underlying
// resources.
func (it *TeleportrDisburserDisbursementFailedIterator) Close() error {
it.sub.Unsubscribe()
return nil
}
// TeleportrDisburserDisbursementFailed represents a DisbursementFailed event raised by the TeleportrDisburser contract.
type TeleportrDisburserDisbursementFailed struct {
DepositId *big.Int
To common.Address
Amount *big.Int
Raw types.Log // Blockchain specific contextual infos
}
// FilterDisbursementFailed is a free log retrieval operation binding the contract event 0x9b478c095979d3d3a7d602ffd9ee1f0843204d853558ae0882c8fcc0a5bc78cf.
//
// Solidity: event DisbursementFailed(uint256 indexed depositId, address indexed to, uint256 amount)
func (_TeleportrDisburser *TeleportrDisburserFilterer) FilterDisbursementFailed(opts *bind.FilterOpts, depositId []*big.Int, to []common.Address) (*TeleportrDisburserDisbursementFailedIterator, error) {
var depositIdRule []interface{}
for _, depositIdItem := range depositId {
depositIdRule = append(depositIdRule, depositIdItem)
}
var toRule []interface{}
for _, toItem := range to {
toRule = append(toRule, toItem)
}
logs, sub, err := _TeleportrDisburser.contract.FilterLogs(opts, "DisbursementFailed", depositIdRule, toRule)
if err != nil {
return nil, err
}
return &TeleportrDisburserDisbursementFailedIterator{contract: _TeleportrDisburser.contract, event: "DisbursementFailed", logs: logs, sub: sub}, nil
}
// WatchDisbursementFailed is a free log subscription operation binding the contract event 0x9b478c095979d3d3a7d602ffd9ee1f0843204d853558ae0882c8fcc0a5bc78cf.
//
// Solidity: event DisbursementFailed(uint256 indexed depositId, address indexed to, uint256 amount)
func (_TeleportrDisburser *TeleportrDisburserFilterer) WatchDisbursementFailed(opts *bind.WatchOpts, sink chan<- *TeleportrDisburserDisbursementFailed, depositId []*big.Int, to []common.Address) (event.Subscription, error) {
var depositIdRule []interface{}
for _, depositIdItem := range depositId {
depositIdRule = append(depositIdRule, depositIdItem)
}
var toRule []interface{}
for _, toItem := range to {
toRule = append(toRule, toItem)
}
logs, sub, err := _TeleportrDisburser.contract.WatchLogs(opts, "DisbursementFailed", depositIdRule, toRule)
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(TeleportrDisburserDisbursementFailed)
if err := _TeleportrDisburser.contract.UnpackLog(event, "DisbursementFailed", 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
}
// ParseDisbursementFailed is a log parse operation binding the contract event 0x9b478c095979d3d3a7d602ffd9ee1f0843204d853558ae0882c8fcc0a5bc78cf.
//
// Solidity: event DisbursementFailed(uint256 indexed depositId, address indexed to, uint256 amount)
func (_TeleportrDisburser *TeleportrDisburserFilterer) ParseDisbursementFailed(log types.Log) (*TeleportrDisburserDisbursementFailed, error) {
event := new(TeleportrDisburserDisbursementFailed)
if err := _TeleportrDisburser.contract.UnpackLog(event, "DisbursementFailed", log); err != nil {
return nil, err
}
event.Raw = log
return event, nil
}
// TeleportrDisburserDisbursementSuccessIterator is returned from FilterDisbursementSuccess and is used to iterate over the raw logs and unpacked data for DisbursementSuccess events raised by the TeleportrDisburser contract.
type TeleportrDisburserDisbursementSuccessIterator struct {
Event *TeleportrDisburserDisbursementSuccess // 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 *TeleportrDisburserDisbursementSuccessIterator) 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(TeleportrDisburserDisbursementSuccess)
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(TeleportrDisburserDisbursementSuccess)
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 *TeleportrDisburserDisbursementSuccessIterator) Error() error {
return it.fail
}
// Close terminates the iteration process, releasing any pending underlying
// resources.
func (it *TeleportrDisburserDisbursementSuccessIterator) Close() error {
it.sub.Unsubscribe()
return nil
}
// TeleportrDisburserDisbursementSuccess represents a DisbursementSuccess event raised by the TeleportrDisburser contract.
type TeleportrDisburserDisbursementSuccess struct {
DepositId *big.Int
To common.Address
Amount *big.Int
Raw types.Log // Blockchain specific contextual infos
}
// FilterDisbursementSuccess is a free log retrieval operation binding the contract event 0xeaa22fd2d7b875476355b32cf719794faf9d91b66e73bc6375a053cace9caaee.
//
// Solidity: event DisbursementSuccess(uint256 indexed depositId, address indexed to, uint256 amount)
func (_TeleportrDisburser *TeleportrDisburserFilterer) FilterDisbursementSuccess(opts *bind.FilterOpts, depositId []*big.Int, to []common.Address) (*TeleportrDisburserDisbursementSuccessIterator, error) {
var depositIdRule []interface{}
for _, depositIdItem := range depositId {
depositIdRule = append(depositIdRule, depositIdItem)
}
var toRule []interface{}
for _, toItem := range to {
toRule = append(toRule, toItem)
}
logs, sub, err := _TeleportrDisburser.contract.FilterLogs(opts, "DisbursementSuccess", depositIdRule, toRule)
if err != nil {
return nil, err
}
return &TeleportrDisburserDisbursementSuccessIterator{contract: _TeleportrDisburser.contract, event: "DisbursementSuccess", logs: logs, sub: sub}, nil
}
// WatchDisbursementSuccess is a free log subscription operation binding the contract event 0xeaa22fd2d7b875476355b32cf719794faf9d91b66e73bc6375a053cace9caaee.
//
// Solidity: event DisbursementSuccess(uint256 indexed depositId, address indexed to, uint256 amount)
func (_TeleportrDisburser *TeleportrDisburserFilterer) WatchDisbursementSuccess(opts *bind.WatchOpts, sink chan<- *TeleportrDisburserDisbursementSuccess, depositId []*big.Int, to []common.Address) (event.Subscription, error) {
var depositIdRule []interface{}
for _, depositIdItem := range depositId {
depositIdRule = append(depositIdRule, depositIdItem)
}
var toRule []interface{}
for _, toItem := range to {
toRule = append(toRule, toItem)
}
logs, sub, err := _TeleportrDisburser.contract.WatchLogs(opts, "DisbursementSuccess", depositIdRule, toRule)
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(TeleportrDisburserDisbursementSuccess)
if err := _TeleportrDisburser.contract.UnpackLog(event, "DisbursementSuccess", 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
}
// ParseDisbursementSuccess is a log parse operation binding the contract event 0xeaa22fd2d7b875476355b32cf719794faf9d91b66e73bc6375a053cace9caaee.
//
// Solidity: event DisbursementSuccess(uint256 indexed depositId, address indexed to, uint256 amount)
func (_TeleportrDisburser *TeleportrDisburserFilterer) ParseDisbursementSuccess(log types.Log) (*TeleportrDisburserDisbursementSuccess, error) {
event := new(TeleportrDisburserDisbursementSuccess)
if err := _TeleportrDisburser.contract.UnpackLog(event, "DisbursementSuccess", log); err != nil {
return nil, err
}
event.Raw = log
return event, nil
}
// TeleportrDisburserOwnershipTransferredIterator is returned from FilterOwnershipTransferred and is used to iterate over the raw logs and unpacked data for OwnershipTransferred events raised by the TeleportrDisburser contract.
type TeleportrDisburserOwnershipTransferredIterator struct {
Event *TeleportrDisburserOwnershipTransferred // 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 *TeleportrDisburserOwnershipTransferredIterator) 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(TeleportrDisburserOwnershipTransferred)
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(TeleportrDisburserOwnershipTransferred)
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 *TeleportrDisburserOwnershipTransferredIterator) Error() error {
return it.fail
}
// Close terminates the iteration process, releasing any pending underlying
// resources.
func (it *TeleportrDisburserOwnershipTransferredIterator) Close() error {
it.sub.Unsubscribe()
return nil
}
// TeleportrDisburserOwnershipTransferred represents a OwnershipTransferred event raised by the TeleportrDisburser contract.
type TeleportrDisburserOwnershipTransferred 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 (_TeleportrDisburser *TeleportrDisburserFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, previousOwner []common.Address, newOwner []common.Address) (*TeleportrDisburserOwnershipTransferredIterator, 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 := _TeleportrDisburser.contract.FilterLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule)
if err != nil {
return nil, err
}
return &TeleportrDisburserOwnershipTransferredIterator{contract: _TeleportrDisburser.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 (_TeleportrDisburser *TeleportrDisburserFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *TeleportrDisburserOwnershipTransferred, 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 := _TeleportrDisburser.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(TeleportrDisburserOwnershipTransferred)
if err := _TeleportrDisburser.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 (_TeleportrDisburser *TeleportrDisburserFilterer) ParseOwnershipTransferred(log types.Log) (*TeleportrDisburserOwnershipTransferred, error) {
event := new(TeleportrDisburserOwnershipTransferred)
if err := _TeleportrDisburser.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil {
return nil, err
}
event.Raw = log
return event, nil
}
package main
import (
"fmt"
"os"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli"
"github.com/ethereum-optimism/optimism/teleportr/api"
"github.com/ethereum-optimism/optimism/teleportr/flags"
)
var (
GitVersion = ""
GitCommit = ""
GitDate = ""
)
func main() {
// Set up logger with a default INFO level in case we fail to parse flags.
// Otherwise the final critical log won't show what the parsing error was.
log.Root().SetHandler(
log.LvlFilterHandler(
log.LvlInfo,
log.StreamHandler(os.Stdout, log.TerminalFormat(true)),
),
)
app := cli.NewApp()
app.Flags = flags.APIFlags
app.Version = fmt.Sprintf("%s-%s-%s", GitVersion, GitCommit, GitDate)
app.Name = "teleportr-api"
app.Usage = "Teleportr API server"
app.Description = "API serving teleportr data"
app.Action = api.Main(GitVersion)
err := app.Run(os.Args)
if err != nil {
log.Crit("Application failed", "message", err)
}
}
package main
import (
"fmt"
"os"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli"
"github.com/ethereum-optimism/optimism/teleportr"
"github.com/ethereum-optimism/optimism/teleportr/flags"
)
var (
GitVersion = ""
GitCommit = ""
GitDate = ""
)
func main() {
// Set up logger with a default INFO level in case we fail to parse flags.
// Otherwise the final critical log won't show what the parsing error was.
log.Root().SetHandler(
log.LvlFilterHandler(
log.LvlInfo,
log.StreamHandler(os.Stdout, log.TerminalFormat(true)),
),
)
app := cli.NewApp()
app.Flags = flags.Flags
app.Version = fmt.Sprintf("%s-%s-%s", GitVersion, GitCommit, GitDate)
app.Name = "teleportr"
app.Usage = "Teleportr"
app.Description = "Teleportr bridge from L1 to L2"
app.Commands = []cli.Command{
{
Name: "migrate",
Usage: "Migrates teleportr's database",
Action: teleportr.Migrate(),
},
}
app.Action = teleportr.Main(GitVersion)
err := app.Run(os.Args)
if err != nil {
log.Crit("Application failed", "message", err)
}
}
package teleportr
import (
"time"
"github.com/ethereum-optimism/optimism/teleportr/flags"
"github.com/urfave/cli"
)
type Config struct {
/* Required Params */
// BuildEnv identifies the environment this binary is intended for, i.e.
// production, development, etc.
BuildEnv string
// EthNetworkName identifies the intended Ethereum network.
EthNetworkName string
// L1EthRpc is the HTTP provider URL for L1.
L1EthRpc string
// L2EthRpc is the HTTP provider URL for L1.
L2EthRpc string
// DepositAddress is the TeleportrDeposit contract adddress.
DepositAddress string
// DepositDeployBlockNumber is the deployment block number of the
// TeleportrDeposit contract.
DepositDeployBlockNumber uint64
// FilterQueryMaxBlocks is the maximum range of a filter query in blocks.
FilterQueryMaxBlocks uint64
// DisburserAddress is the TeleportrDisburser contract address.
DisburserAddress string
// MaxL2TxSize is the maximum size in bytes of any L2 transactions generated
// for teleportr disbursements.
MaxL2TxSize uint64
// NumDepositConfirmations is the number of confirmations required before a
// deposit is considered confirmed.
NumDepositConfirmations uint64
// PollInterval is the delay between querying L2 for more transaction
// and creating a new batch.
PollInterval time.Duration
// SafeAbortNonceTooLowCount is the number of ErrNonceTooLowObservations
// required to give up on a tx at a particular nonce without receiving
// confirmation.
SafeAbortNonceTooLowCount uint64
// ResubmissionTimeout is time we will wait before resubmitting a
// transaction.
ResubmissionTimeout time.Duration
// PostgresHost is the host of the teleportr postgres instance.
PostgresHost string
// PostgresPort is the port of the teleportr postgres instance.
PostgresPort uint16
// PostgresUser is the username for the teleportr postgres instance.
PostgresUser string
// PostgresPassword is the password for the teleportr postgres instance.
PostgresPassword string
// PostgresDBName is the database name of the teleportr postgres instance.
PostgresDBName string
// PostgresEnableSSL determines whether or not to enable SSL on connections
// to the teleportr postgres instance.
PostgresEnableSSL bool
/* Optional Params */
// LogLevel is the lowest log level that will be output.
LogLevel string
// LogTerminal if true, prints to stdout in terminal format, otherwise
// prints using JSON. If SentryEnable is true this flag is ignored, and logs
// are printed using JSON.
LogTerminal bool
// DisburserPrivKey the private key of the wallet used to submit
// transactions to the TeleportrDisburser contract.
DisburserPrivKey string
// Mnemonic is the HD seed used to derive the wallet private key for
// submitting to the TeleportrDisburser. Must be used in conjunction with
// DisburserHDPath.
Mnemonic string
// DisburserHDPath is the derivation path used to obtain the private key for
// the disburser transactions.
DisburserHDPath string
// MetricsServerEnable if true, will create a metrics client and log to
// Prometheus.
MetricsServerEnable bool
// MetricsHostname is the hostname at which the metrics server is running.
MetricsHostname string
// MetricsPort is the port at which the metrics server is running.
MetricsPort uint64
// DisableHTTP2 disables HTTP2 support.
DisableHTTP2 bool
}
func NewConfig(ctx *cli.Context) (Config, error) {
return Config{
/* Required Flags */
BuildEnv: ctx.GlobalString(flags.BuildEnvFlag.Name),
EthNetworkName: ctx.GlobalString(flags.EthNetworkNameFlag.Name),
L1EthRpc: ctx.GlobalString(flags.L1EthRpcFlag.Name),
L2EthRpc: ctx.GlobalString(flags.L2EthRpcFlag.Name),
DepositAddress: ctx.GlobalString(flags.DepositAddressFlag.Name),
DepositDeployBlockNumber: ctx.GlobalUint64(flags.DepositDeployBlockNumberFlag.Name),
DisburserAddress: ctx.GlobalString(flags.DisburserAddressFlag.Name),
MaxL2TxSize: ctx.GlobalUint64(flags.MaxL2TxSizeFlag.Name),
NumDepositConfirmations: ctx.GlobalUint64(flags.NumDepositConfirmationsFlag.Name),
FilterQueryMaxBlocks: ctx.GlobalUint64(flags.FilterQueryMaxBlocksFlag.Name),
PollInterval: ctx.GlobalDuration(flags.PollIntervalFlag.Name),
SafeAbortNonceTooLowCount: ctx.GlobalUint64(flags.SafeAbortNonceTooLowCountFlag.Name),
ResubmissionTimeout: ctx.GlobalDuration(flags.ResubmissionTimeoutFlag.Name),
PostgresHost: ctx.GlobalString(flags.PostgresHostFlag.Name),
PostgresPort: uint16(ctx.GlobalUint64(flags.PostgresPortFlag.Name)),
PostgresUser: ctx.GlobalString(flags.PostgresUserFlag.Name),
PostgresPassword: ctx.GlobalString(flags.PostgresPasswordFlag.Name),
PostgresDBName: ctx.GlobalString(flags.PostgresDBNameFlag.Name),
PostgresEnableSSL: ctx.GlobalBool(flags.PostgresEnableSSLFlag.Name),
/* Optional flags */
LogLevel: ctx.GlobalString(flags.LogLevelFlag.Name),
LogTerminal: ctx.GlobalBool(flags.LogTerminalFlag.Name),
DisburserPrivKey: ctx.GlobalString(flags.DisburserPrivateKeyFlag.Name),
Mnemonic: ctx.GlobalString(flags.MnemonicFlag.Name),
DisburserHDPath: ctx.GlobalString(flags.DisburserHDPathFlag.Name),
MetricsServerEnable: ctx.GlobalBool(flags.MetricsServerEnableFlag.Name),
MetricsHostname: ctx.GlobalString(flags.MetricsHostnameFlag.Name),
MetricsPort: ctx.GlobalUint64(flags.MetricsPortFlag.Name),
DisableHTTP2: ctx.GlobalBool(flags.HTTP2DisableFlag.Name),
}, nil
}
package db
import (
"database/sql"
"errors"
"fmt"
"math/big"
"strings"
"time"
"github.com/ethereum/go-ethereum/common"
_ "github.com/lib/pq"
)
var (
// ErrZeroTimestamp signals that the caller attempted to insert deposits
// with a timestamp of zero.
ErrZeroTimestamp = errors.New("timestamp is zero")
// ErrUnknownDeposit signals that the target deposit could not be found.
ErrUnknownDeposit = errors.New("unknown deposit")
)
// ConfirmationInfo holds metadata about a tx on either the L1 or L2 chain.
type ConfirmationInfo struct {
TxnHash common.Hash
BlockNumber uint64
BlockTimestamp time.Time
}
// Deposit represents an event emitted from the TeleportrDeposit contract on L1,
// along with additional info about the tx that generated the event.
type Deposit struct {
ID uint64
Address common.Address
Amount *big.Int
ConfirmationInfo
}
type Disbursement struct {
Success bool
ConfirmationInfo
}
// Teleport represents the combination of an L1 deposit and its disbursement on
// L2. Disburment will be nil if the L2 disbursement has not occurred.
type Teleport struct {
Deposit
Disbursement *Disbursement
}
const createDepositsTable = `
CREATE TABLE IF NOT EXISTS deposits (
id INT8 NOT NULL PRIMARY KEY,
txn_hash VARCHAR NOT NULL,
block_number INT8 NOT NULL,
block_timestamp TIMESTAMPTZ NOT NULL,
address VARCHAR NOT NULL,
amount VARCHAR NOT NULL
);
`
const createDepositTxnHashIndex = `
CREATE INDEX ON deposits (txn_hash)
`
const createDepositAddressIndex = `
CREATE INDEX ON deposits (address)
`
const createDisbursementsTable = `
CREATE TABLE IF NOT EXISTS disbursements (
id INT8 NOT NULL PRIMARY KEY REFERENCES deposits(id),
txn_hash VARCHAR NOT NULL,
block_number INT8 NOT NULL,
block_timestamp TIMESTAMPTZ NOT NULL,
success BOOL NOT NULL
);
`
const lastProcessedBlockTable = `
CREATE TABLE IF NOT EXISTS last_processed_block (
id BOOL PRIMARY KEY DEFAULT TRUE,
value INT8 NOT NULL,
CONSTRAINT id CHECK (id)
);
`
const pendingTxTable = `
CREATE TABLE IF NOT EXISTS pending_txs (
txn_hash VARCHAR NOT NULL PRIMARY KEY,
start_id INT8 NOT NULL,
end_id INT8 NOT NULL
);
`
var migrations = []string{
createDepositsTable,
createDepositTxnHashIndex,
createDepositAddressIndex,
createDisbursementsTable,
lastProcessedBlockTable,
pendingTxTable,
}
// Config houses the data required to connect to a Postgres backend.
type Config struct {
// Host is the database hostname.
Host string
// Port is the database port.
Port uint16
// User is the database user to log in as.
User string
// Password is the user's password to authenticate.
Password string
// DBName is the name of the database to connect to.
DBName string
// EnableSSL enables SLL on the connection if set to true.
EnableSSL bool
}
// WithDB returns the connection string with a specific database to connect to.
func (c Config) WithDB() string {
return fmt.Sprintf(
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
c.Host, c.Port, c.User, c.Password, c.DBName, c.sslMode(),
)
}
// WithoutDB returns the connection string without connecting to a specific
// database.
func (c Config) WithoutDB() string {
return fmt.Sprintf(
"host=%s port=%d user=%s password=%s sslmode=%s",
c.Host, c.Port, c.User, c.Password, c.sslMode(),
)
}
// sslMode returns "enabled" if EnableSSL is true, otherwise returns "disabled".
func (c Config) sslMode() string {
if c.EnableSSL {
return "require"
}
return "disable"
}
// Database provides a Go API for accessing Teleportr read/write operations.
type Database struct {
conn *sql.DB
}
// Open creates a new database connection to the configured Postgres backend and
// applies any migrations.
func Open(cfg Config) (*Database, error) {
conn, err := sql.Open("postgres", cfg.WithDB())
if err != nil {
return nil, err
}
return &Database{
conn: conn,
}, nil
}
// Migrate applies all existing migrations to the open database.
func (d *Database) Migrate() error {
for _, migration := range migrations {
_, err := d.conn.Exec(migration)
if err != nil {
return err
}
}
return nil
}
// Close closes the connection to the database.
func (d *Database) Close() error {
return d.conn.Close()
}
const upsertLastProcessedBlock = `
INSERT INTO last_processed_block (value)
VALUES ($1)
ON CONFLICT (id) DO UPDATE
SET value = $1
`
const upsertDepositStatement = `
INSERT INTO deposits (id, txn_hash, block_number, block_timestamp, address, amount)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (id) DO UPDATE
SET (txn_hash, block_number, block_timestamp, address, amount) = ($2, $3, $4, $5, $6)
`
// UpsertDeposits inserts a list of deposits into the database, or updats an
// existing deposit in place if the same ID is found.
func (d *Database) UpsertDeposits(
deposits []Deposit,
lastProcessedBlock uint64,
) error {
// Sanity check deposits.
for _, deposit := range deposits {
if deposit.BlockTimestamp.IsZero() {
return ErrZeroTimestamp
}
}
tx, err := d.conn.Begin()
if err != nil {
return err
}
defer func() {
_ = tx.Rollback()
}()
for _, deposit := range deposits {
_, err = tx.Exec(
upsertDepositStatement,
deposit.ID,
deposit.TxnHash.String(),
deposit.BlockNumber,
deposit.BlockTimestamp,
deposit.Address.String(),
deposit.Amount.String(),
)
if err != nil {
return err
}
}
_, err = tx.Exec(upsertLastProcessedBlock, lastProcessedBlock)
if err != nil {
return err
}
return tx.Commit()
}
const lastProcessedBlockQuery = `
SELECT value FROM last_processed_block
`
func (d *Database) LastProcessedBlock() (*uint64, error) {
row := d.conn.QueryRow(lastProcessedBlockQuery)
var lastProcessedBlock uint64
err := row.Scan(&lastProcessedBlock)
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, err
}
return &lastProcessedBlock, nil
}
const confirmedDepositsQuery = `
SELECT dep.*
FROM deposits AS dep
LEFT JOIN disbursements AS dis ON dep.id = dis.id
WHERE dis.id IS NULL AND dep.block_number + $1 <= $2 + 1
ORDER BY dep.id ASC
`
// ConfirmedDeposits returns the set of all deposits that have sufficient
// confirmation, but do not have a recorded disbursement.
func (d *Database) ConfirmedDeposits(blockNumber, confirmations uint64) ([]Deposit, error) {
rows, err := d.conn.Query(confirmedDepositsQuery, confirmations, blockNumber)
if err != nil {
return nil, err
}
defer rows.Close()
var deposits []Deposit
for rows.Next() {
var deposit Deposit
var txnHashStr string
var addressStr string
var amountStr string
err = rows.Scan(
&deposit.ID,
&txnHashStr,
&deposit.BlockNumber,
&deposit.BlockTimestamp,
&addressStr,
&amountStr,
)
if err != nil {
return nil, err
}
amount, ok := new(big.Int).SetString(amountStr, 10)
if !ok {
return nil, fmt.Errorf("unable to parse amount %v", amount)
}
deposit.TxnHash = common.HexToHash(txnHashStr)
deposit.BlockTimestamp = deposit.BlockTimestamp.Local()
deposit.Amount = amount
deposit.Address = common.HexToAddress(addressStr)
deposits = append(deposits, deposit)
}
if err := rows.Err(); err != nil {
return nil, err
}
return deposits, nil
}
const latestDisbursementIDQuery = `
SELECT id FROM disbursements
ORDER BY id DESC
LIMIT 1
`
// LatestDisbursementID returns the latest deposit id known to the database that
// has a recorded disbursement.
func (d *Database) LatestDisbursementID() (*uint64, error) {
row := d.conn.QueryRow(latestDisbursementIDQuery)
var latestDisbursementID uint64
err := row.Scan(&latestDisbursementID)
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, err
}
return &latestDisbursementID, nil
}
const markDisbursedStatement = `
INSERT INTO disbursements (id, txn_hash, block_number, block_timestamp, success)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (id) DO UPDATE
SET (txn_hash, block_number, block_timestamp, success) = ($2, $3, $4, $5)
`
// UpsertDisbursement inserts a disbursement, or updates an existing record
// in-place if the ID already exists.
func (d *Database) UpsertDisbursement(
id uint64,
txnHash common.Hash,
blockNumber uint64,
blockTimestamp time.Time,
success bool,
) error {
if blockTimestamp.IsZero() {
return ErrZeroTimestamp
}
result, err := d.conn.Exec(
markDisbursedStatement,
id,
txnHash.String(),
blockNumber,
blockTimestamp,
success,
)
if err != nil {
if strings.Contains(err.Error(), "violates foreign key constraint") {
return ErrUnknownDeposit
}
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected != 1 {
return ErrUnknownDeposit
}
return nil
}
const loadTeleportByDepositHashQuery = `
SELECT
dep.id, dep.address, dep.amount, dis.success,
dep.txn_hash, dep.block_number, dep.block_timestamp,
dis.txn_hash, dis.block_number, dis.block_timestamp
FROM deposits AS dep
LEFT JOIN disbursements AS dis
ON dep.id = dis.id
WHERE dep.txn_hash = $1
LIMIT 1
`
func (d *Database) LoadTeleportByDepositHash(
txHash common.Hash,
) (*Teleport, error) {
row := d.conn.QueryRow(loadTeleportByDepositHashQuery, txHash.String())
teleport, err := scanTeleport(row)
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, err
}
return &teleport, nil
}
const loadTeleportsByAddressQuery = `
SELECT
dep.id, dep.address, dep.amount, dis.success,
dep.txn_hash, dep.block_number, dep.block_timestamp,
dis.txn_hash, dis.block_number, dis.block_timestamp
FROM deposits AS dep
LEFT JOIN disbursements AS dis
ON dep.id = dis.id
WHERE dep.address = $1
ORDER BY dep.block_timestamp DESC, dep.id DESC
LIMIT 100
`
func (d *Database) LoadTeleportsByAddress(
addr common.Address,
) ([]Teleport, error) {
rows, err := d.conn.Query(loadTeleportsByAddressQuery, addr.String())
if err != nil {
return nil, err
}
defer rows.Close()
var teleports []Teleport
for rows.Next() {
teleport, err := scanTeleport(rows)
if err != nil {
return nil, err
}
teleports = append(teleports, teleport)
}
if err := rows.Err(); err != nil {
return nil, err
}
return teleports, nil
}
const completedTeleportsQuery = `
SELECT
dep.id, dep.address, dep.amount, dis.success,
dep.txn_hash, dep.block_number, dep.block_timestamp,
dis.txn_hash, dis.block_number, dis.block_timestamp
FROM deposits AS dep, disbursements AS dis
WHERE dep.id = dis.id
ORDER BY id DESC
`
// CompletedTeleports returns the set of all deposits that have also been
// disbursed.
func (d *Database) CompletedTeleports() ([]Teleport, error) {
rows, err := d.conn.Query(completedTeleportsQuery)
if err != nil {
return nil, err
}
defer rows.Close()
var teleports []Teleport
for rows.Next() {
teleport, err := scanTeleport(rows)
if err != nil {
return nil, err
}
teleports = append(teleports, teleport)
}
if err := rows.Err(); err != nil {
return nil, err
}
return teleports, nil
}
type Scanner interface {
Scan(...interface{}) error
}
func scanTeleport(scanner Scanner) (Teleport, error) {
var teleport Teleport
var addressStr string
var amountStr string
var depTxnHashStr string
var disTxnHashStr *string
var disBlockNumber *uint64
var disBlockTimestamp *time.Time
var success *bool
err := scanner.Scan(
&teleport.ID,
&addressStr,
&amountStr,
&success,
&depTxnHashStr,
&teleport.Deposit.BlockNumber,
&teleport.Deposit.BlockTimestamp,
&disTxnHashStr,
&disBlockNumber,
&disBlockTimestamp,
)
if err != nil {
return Teleport{}, err
}
amount, ok := new(big.Int).SetString(amountStr, 10)
if !ok {
return Teleport{}, fmt.Errorf("unable to parse amount %v", amount)
}
teleport.Address = common.HexToAddress(addressStr)
teleport.Amount = amount
teleport.Deposit.TxnHash = common.HexToHash(depTxnHashStr)
teleport.Deposit.BlockTimestamp = teleport.Deposit.BlockTimestamp.Local()
hasDisbursement := success != nil &&
disTxnHashStr != nil &&
disBlockNumber != nil &&
disBlockTimestamp != nil
if hasDisbursement {
teleport.Disbursement = &Disbursement{
ConfirmationInfo: ConfirmationInfo{
TxnHash: common.HexToHash(*disTxnHashStr),
BlockNumber: *disBlockNumber,
BlockTimestamp: disBlockTimestamp.Local(),
},
Success: *success,
}
}
return teleport, nil
}
// PendingTx encapsulates the metadata stored about published disbursement txs.
type PendingTx struct {
// Txhash is the tx hash of the disbursement tx.
TxHash common.Hash
// StartID is the deposit id of the first disbursement, inclusive.
StartID uint64
// EndID is the deposit id fo the last disbursement, exclusive.
EndID uint64
}
const upsertPendingTxStatement = `
INSERT INTO pending_txs (txn_hash, start_id, end_id)
VALUES ($1, $2, $3)
ON CONFLICT (txn_hash) DO UPDATE
SET (start_id, end_id) = ($2, $3)
`
// UpsertPendingTx inserts a disbursement, or updates the entry if the TxHash
// already exists.
func (d *Database) UpsertPendingTx(pendingTx PendingTx) error {
_, err := d.conn.Exec(
upsertPendingTxStatement,
pendingTx.TxHash.String(),
pendingTx.StartID,
pendingTx.EndID,
)
return err
}
const listPendingTxsQuery = `
SELECT txn_hash, start_id, end_id
FROM pending_txs
ORDER BY start_id DESC, end_id DESC, txn_hash ASC
`
// ListPendingTxs returns all pending txs stored in the database.
func (d *Database) ListPendingTxs() ([]PendingTx, error) {
rows, err := d.conn.Query(listPendingTxsQuery)
if err != nil {
return nil, err
}
defer rows.Close()
var pendingTxs []PendingTx
for rows.Next() {
var pendingTx PendingTx
var txHashStr string
err = rows.Scan(
&txHashStr,
&pendingTx.StartID,
&pendingTx.EndID,
)
if err != nil {
return nil, err
}
pendingTx.TxHash = common.HexToHash(txHashStr)
pendingTxs = append(pendingTxs, pendingTx)
}
if err := rows.Err(); err != nil {
return nil, err
}
return pendingTxs, nil
}
const deletePendingTxsStatement = `
DELETE FROM pending_txs
WHERE start_id = $1 AND end_id = $2
`
// DeletePendingTx removes any pending txs with matching start and end ids. This
// allows the caller to remove any logically-conflicting pending txs from the
// database after successfully processing the outcomes.
func (d *Database) DeletePendingTx(startID, endID uint64) error {
_, err := d.conn.Exec(
deletePendingTxsStatement,
startID,
endID,
)
return err
}
package db_test
import (
"database/sql"
"fmt"
"math/big"
"testing"
"time"
"github.com/ethereum-optimism/optimism/teleportr/db"
"github.com/ethereum/go-ethereum/common"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
var (
testTimestamp = time.Unix(time.Now().Unix(), 0)
)
func newDatabase(t *testing.T) *db.Database {
dbName := uuid.NewString()
cfg := db.Config{
Host: "0.0.0.0",
Port: 5432,
User: "postgres",
Password: "password",
DBName: dbName,
}
conn, err := sql.Open("postgres", cfg.WithoutDB())
require.Nil(t, err)
_, err = conn.Exec(fmt.Sprintf("CREATE DATABASE \"%s\";", dbName))
require.Nil(t, err)
err = conn.Close()
require.Nil(t, err)
db, err := db.Open(cfg)
require.Nil(t, err)
err = db.Migrate()
require.Nil(t, err)
return db
}
// TestOpenClose asserts that we are able to open and close the database
// connection.
func TestOpenClose(t *testing.T) {
t.Parallel()
d := newDatabase(t)
err := d.Close()
require.Nil(t, err)
}
// TestUpsertEmptyDeposits empty deposits asserts that it is safe to call
// UpsertDeposits with an empty list.
func TestUpsertEmptyDeposits(t *testing.T) {
t.Parallel()
d := newDatabase(t)
defer d.Close()
err := d.UpsertDeposits(nil, 0)
require.Nil(t, err)
err = d.UpsertDeposits([]db.Deposit{}, 0)
require.Nil(t, err)
}
// TestUpsertDepositWithZeroTimestampFails asserts that trying to insert a
// deposit with a zero-timestamp fails.
func TestUpsertDepositWithZeroTimestampFails(t *testing.T) {
t.Parallel()
d := newDatabase(t)
defer d.Close()
err := d.UpsertDeposits([]db.Deposit{{}}, 0)
require.Equal(t, db.ErrZeroTimestamp, err)
}
// TestUpsertDeposits asserts that UpsertDeposits properly overwrites an
// existing entry with the same ID.
func TestUpsertDeposits(t *testing.T) {
t.Parallel()
d := newDatabase(t)
defer d.Close()
deposit1 := db.Deposit{
ID: 1,
Address: common.HexToAddress("0xaa01"),
Amount: big.NewInt(1),
ConfirmationInfo: db.ConfirmationInfo{
TxnHash: common.HexToHash("0xff01"),
BlockNumber: 1,
BlockTimestamp: testTimestamp,
},
}
err := d.UpsertDeposits([]db.Deposit{deposit1}, 0)
require.Nil(t, err)
deposits, err := d.ConfirmedDeposits(1, 1)
require.Nil(t, err)
require.Equal(t, deposits, []db.Deposit{deposit1})
deposit2 := db.Deposit{
ID: 1,
Address: common.HexToAddress("0xaa02"),
Amount: big.NewInt(2),
ConfirmationInfo: db.ConfirmationInfo{
TxnHash: common.HexToHash("0xff02"),
BlockNumber: 2,
BlockTimestamp: testTimestamp,
},
}
err = d.UpsertDeposits([]db.Deposit{deposit2}, 0)
require.Nil(t, err)
deposits, err = d.ConfirmedDeposits(2, 1)
require.Nil(t, err)
require.Equal(t, deposits, []db.Deposit{deposit2})
}
// TestUpsertDepositsRecordsLastProcessedBlock asserts that calling
// UpsertDeposits properly records the last processed block.
func TestUpsertDepositsRecordsLastProcessedBlock(t *testing.T) {
t.Parallel()
d := newDatabase(t)
defer d.Close()
uint64Ptr := func(x uint64) *uint64 {
return &x
}
// Should be empty initially.
lastProcessedBlock, err := d.LastProcessedBlock()
require.Nil(t, err)
require.Nil(t, lastProcessedBlock)
// Insert nil deposits through block 1.
err = d.UpsertDeposits(nil, 1)
require.Nil(t, err)
// Check that LastProcessedBlock returns 1.
lastProcessedBlock, err = d.LastProcessedBlock()
require.Nil(t, err)
require.Equal(t, uint64Ptr(1), lastProcessedBlock)
// Insert empty deposits through block 2.
err = d.UpsertDeposits([]db.Deposit{}, 2)
require.Nil(t, err)
// Check that LastProcessedBlock returns 2.
lastProcessedBlock, err = d.LastProcessedBlock()
require.Nil(t, err)
require.Equal(t, uint64Ptr(2), lastProcessedBlock)
// Insert real deposit in block 3 with last processed at 4.
deposit := db.Deposit{
ID: 0,
Address: common.HexToAddress("0xaa03"),
Amount: big.NewInt(3),
ConfirmationInfo: db.ConfirmationInfo{
TxnHash: common.HexToHash("0xff03"),
BlockNumber: 3,
BlockTimestamp: testTimestamp,
},
}
err = d.UpsertDeposits([]db.Deposit{deposit}, 4)
require.Nil(t, err)
// Check that LastProcessedBlock returns 2.
lastProcessedBlock, err = d.LastProcessedBlock()
require.Nil(t, err)
require.Equal(t, uint64Ptr(4), lastProcessedBlock)
}
// TestConfirmedDeposits asserts that ConfirmedDeposits properly returns the set
// of deposits that have sufficient confirmation, but do not have a recorded
// disbursement.
func TestConfirmedDeposits(t *testing.T) {
t.Parallel()
d := newDatabase(t)
defer d.Close()
deposits, err := d.ConfirmedDeposits(1e9, 1)
require.Nil(t, err)
require.Equal(t, int(0), len(deposits))
deposit1 := db.Deposit{
ID: 1,
Address: common.HexToAddress("0xaa01"),
Amount: big.NewInt(1),
ConfirmationInfo: db.ConfirmationInfo{
TxnHash: common.HexToHash("0xff01"),
BlockNumber: 1,
BlockTimestamp: testTimestamp,
},
}
deposit2 := db.Deposit{
ID: 2,
Address: common.HexToAddress("0xaa21"),
Amount: big.NewInt(2),
ConfirmationInfo: db.ConfirmationInfo{
TxnHash: common.HexToHash("0xff21"),
BlockNumber: 2,
BlockTimestamp: testTimestamp,
},
}
deposit3 := db.Deposit{
ID: 3,
Address: common.HexToAddress("0xaa22"),
Amount: big.NewInt(2),
ConfirmationInfo: db.ConfirmationInfo{
TxnHash: common.HexToHash("0xff22"),
BlockNumber: 2,
BlockTimestamp: testTimestamp,
},
}
err = d.UpsertDeposits([]db.Deposit{
deposit1, deposit2, deposit3,
}, 0)
require.Nil(t, err)
// First deposit only has 1 conf, should not be found using 2 confs at block
// 1.
deposits, err = d.ConfirmedDeposits(1, 2)
require.Nil(t, err)
require.Equal(t, int(0), len(deposits))
// First deposit should be returned when querying for 1 conf at block 1.
deposits, err = d.ConfirmedDeposits(1, 1)
require.Nil(t, err)
require.Equal(t, []db.Deposit{deposit1}, deposits)
// All deposits should be returned when querying for 1 conf at block 2.
deposits, err = d.ConfirmedDeposits(2, 1)
require.Nil(t, err)
require.Equal(t, []db.Deposit{deposit1, deposit2, deposit3}, deposits)
err = d.UpsertDisbursement(deposit1.ID, common.HexToHash("0xdd01"), 1, testTimestamp, true)
require.Nil(t, err)
deposits, err = d.ConfirmedDeposits(2, 1)
require.Nil(t, err)
require.Equal(t, []db.Deposit{deposit2, deposit3}, deposits)
}
// TestUpsertDisbursement asserts that UpsertDisbursement properly inserts new
// disbursements or overwrites existing ones.
func TestUpsertDisbursement(t *testing.T) {
t.Parallel()
d := newDatabase(t)
defer d.Close()
address := common.HexToAddress("0xaa01")
amount := big.NewInt(1)
depTxnHash := common.HexToHash("0xdd01")
depBlockNumber := uint64(1)
disTxnHash := common.HexToHash("0xee02")
disBlockNumber := uint64(2)
// Calling UpsertDisbursement with the zero timestamp should fail.
err := d.UpsertDisbursement(0, common.HexToHash("0xdd00"), 0, time.Time{}, true)
require.Equal(t, db.ErrZeroTimestamp, err)
// Calling UpsertDisbursement with an unknown id should fail.
err = d.UpsertDisbursement(0, common.HexToHash("0xdd00"), 0, testTimestamp, true)
require.Equal(t, db.ErrUnknownDeposit, err)
// Now, insert a real deposit that we will disburse.
err = d.UpsertDeposits([]db.Deposit{
{
ID: 1,
Address: address,
Amount: amount,
ConfirmationInfo: db.ConfirmationInfo{
TxnHash: depTxnHash,
BlockNumber: depBlockNumber,
BlockTimestamp: testTimestamp,
},
},
}, 0)
require.Nil(t, err)
// Mark the deposit as disbursed with some temporary info.
tempDisTxnHash := common.HexToHash("0xee00")
tempDisBlockNumber := uint64(1)
err = d.UpsertDisbursement(
1, tempDisTxnHash, tempDisBlockNumber, testTimestamp, false,
)
require.Nil(t, err)
expTeleports := []db.Teleport{
{
Deposit: db.Deposit{
ID: 1,
Address: address,
Amount: amount,
ConfirmationInfo: db.ConfirmationInfo{
TxnHash: depTxnHash,
BlockNumber: depBlockNumber,
BlockTimestamp: testTimestamp,
},
},
Disbursement: &db.Disbursement{
Success: false,
ConfirmationInfo: db.ConfirmationInfo{
TxnHash: tempDisTxnHash,
BlockNumber: tempDisBlockNumber,
BlockTimestamp: testTimestamp,
},
},
},
}
// Assert that the deposit shows up in the CompletedTeleports method with
// both the L1 and temp L2 confirmation info.
teleports, err := d.CompletedTeleports()
require.Nil(t, err)
require.Equal(t, expTeleports, teleports)
// Overwrite the disbursement info with the final values.
err = d.UpsertDisbursement(1, disTxnHash, disBlockNumber, testTimestamp, true)
require.Nil(t, err)
expTeleports = []db.Teleport{
{
Deposit: db.Deposit{
ID: 1,
Address: address,
Amount: amount,
ConfirmationInfo: db.ConfirmationInfo{
TxnHash: depTxnHash,
BlockNumber: depBlockNumber,
BlockTimestamp: testTimestamp,
},
},
Disbursement: &db.Disbursement{
Success: true,
ConfirmationInfo: db.ConfirmationInfo{
TxnHash: disTxnHash,
BlockNumber: disBlockNumber,
BlockTimestamp: testTimestamp,
},
},
},
}
// Assert that the deposit now shows up in the CompletedTeleports method
// with both the L1 and L2 confirmation info.
teleports, err = d.CompletedTeleports()
require.Nil(t, err)
require.Equal(t, expTeleports, teleports)
}
// TestUpsertPendingTxs asserts that UpsertPendingTx properly records a pending
// tx, and that it appears in ListPendingTxs on subsequent calls.
func TestUpsertPendingTxs(t *testing.T) {
t.Parallel()
d := newDatabase(t)
defer d.Close()
// Should be empty at first.
pendingTxs, err := d.ListPendingTxs()
require.Nil(t, err)
require.Nil(t, pendingTxs)
// Add first pending tx.
pendingTx1 := db.PendingTx{
TxHash: common.HexToHash("0x11"),
StartID: 0,
EndID: 1,
}
err = d.UpsertPendingTx(pendingTx1)
require.Nil(t, err)
pendingTxs, err = d.ListPendingTxs()
require.Nil(t, err)
require.Equal(t, []db.PendingTx{pendingTx1}, pendingTxs)
// Add second pending tx.
pendingTx2 := db.PendingTx{
TxHash: common.HexToHash("0x22"),
StartID: 0,
EndID: 1,
}
err = d.UpsertPendingTx(pendingTx2)
require.Nil(t, err)
pendingTxs, err = d.ListPendingTxs()
require.Nil(t, err)
require.Equal(t, []db.PendingTx{pendingTx1, pendingTx2}, pendingTxs)
// Readd duplciate pending tx.
err = d.UpsertPendingTx(pendingTx2)
require.Nil(t, err)
pendingTxs, err = d.ListPendingTxs()
require.Nil(t, err)
require.Equal(t, []db.PendingTx{pendingTx1, pendingTx2}, pendingTxs)
// Add third pending tx.
pendingTx3 := db.PendingTx{
TxHash: common.HexToHash("0x33"),
StartID: 1,
EndID: 2,
}
err = d.UpsertPendingTx(pendingTx3)
require.Nil(t, err)
pendingTxs, err = d.ListPendingTxs()
require.Nil(t, err)
require.Equal(t, []db.PendingTx{pendingTx3, pendingTx1, pendingTx2}, pendingTxs)
}
// TestDeletePendingTx asserts that DeletePendingTx properly cleans up the
// pending_txs table when provided with various start/end ids.
func TestDeletePendingTx(t *testing.T) {
t.Parallel()
d := newDatabase(t)
defer d.Close()
pendingTx1 := db.PendingTx{
TxHash: common.HexToHash("0x11"),
StartID: 0,
EndID: 1,
}
pendingTx2 := db.PendingTx{
TxHash: common.HexToHash("0x22"),
StartID: 0,
EndID: 1,
}
pendingTx3 := db.PendingTx{
TxHash: common.HexToHash("0x33"),
StartID: 1,
EndID: 2,
}
err := d.UpsertPendingTx(pendingTx1)
require.Nil(t, err)
err = d.UpsertPendingTx(pendingTx2)
require.Nil(t, err)
err = d.UpsertPendingTx(pendingTx3)
require.Nil(t, err)
pendingTxs, err := d.ListPendingTxs()
require.Nil(t, err)
require.Equal(t, []db.PendingTx{pendingTx3, pendingTx1, pendingTx2}, pendingTxs)
// Delete with indexes that do not match any start/end, no effect.
err = d.DeletePendingTx(3, 4)
require.Nil(t, err)
pendingTxs, err = d.ListPendingTxs()
require.Nil(t, err)
require.Equal(t, []db.PendingTx{pendingTx3, pendingTx1, pendingTx2}, pendingTxs)
// Delete with indexes that matches start but no end, no effect.
err = d.DeletePendingTx(1, 3)
require.Nil(t, err)
pendingTxs, err = d.ListPendingTxs()
require.Nil(t, err)
require.Equal(t, []db.PendingTx{pendingTx3, pendingTx1, pendingTx2}, pendingTxs)
// Delete with indexes that matches end but no start, no effect.
err = d.DeletePendingTx(0, 2)
require.Nil(t, err)
pendingTxs, err = d.ListPendingTxs()
require.Nil(t, err)
require.Equal(t, []db.PendingTx{pendingTx3, pendingTx1, pendingTx2}, pendingTxs)
// Delete with indexes that matches start and end, should remove both.
err = d.DeletePendingTx(0, 1)
require.Nil(t, err)
pendingTxs, err = d.ListPendingTxs()
require.Nil(t, err)
require.Equal(t, []db.PendingTx{pendingTx3}, pendingTxs)
// Delete with indexes that matches start and end, no empty.
err = d.DeletePendingTx(1, 2)
require.Nil(t, err)
pendingTxs, err = d.ListPendingTxs()
require.Nil(t, err)
require.Nil(t, pendingTxs)
}
// TestLoadTeleports asserts that LoadTeleportByDepositHash and
// LoadTeleportsByAddress are able to query for a spcific deposit in various
// stages through the teleport process.
func TestLoadTeleports(t *testing.T) {
t.Parallel()
d := newDatabase(t)
defer d.Close()
address := common.HexToAddress("0x01")
amount := big.NewInt(1000)
depTxnHash := common.HexToHash("0x0d01")
depBlockNumber := uint64(1)
disTxnHash := common.HexToHash("0x0e01")
disBlockNumber := uint64(2)
// Insert deposit.
deposit1 := db.Deposit{
ID: 1,
Address: address,
Amount: amount,
ConfirmationInfo: db.ConfirmationInfo{
TxnHash: depTxnHash,
BlockNumber: depBlockNumber,
BlockTimestamp: testTimestamp,
},
}
err := d.UpsertDeposits([]db.Deposit{deposit1}, 0)
require.Nil(t, err)
// The same, undisbursed teleport should be retruned by hash and address.
expTeleport := db.Teleport{
Deposit: deposit1,
Disbursement: nil,
}
teleport, err := d.LoadTeleportByDepositHash(depTxnHash)
require.Nil(t, err)
require.NotNil(t, teleport)
require.Equal(t, expTeleport, *teleport)
teleports, err := d.LoadTeleportsByAddress(address)
require.Nil(t, err)
require.Equal(t, []db.Teleport{expTeleport}, teleports)
// Insert a disbursement for the above deposit.
err = d.UpsertDisbursement(
1, disTxnHash, disBlockNumber, testTimestamp, true,
)
require.Nil(t, err)
// The now-complete teleport should be returned from both queries.
expTeleport = db.Teleport{
Deposit: deposit1,
Disbursement: &db.Disbursement{
Success: true,
ConfirmationInfo: db.ConfirmationInfo{
TxnHash: disTxnHash,
BlockNumber: disBlockNumber,
BlockTimestamp: testTimestamp,
},
},
}
teleport, err = d.LoadTeleportByDepositHash(depTxnHash)
require.Nil(t, err)
require.NotNil(t, teleport)
require.Equal(t, expTeleport, *teleport)
teleports, err = d.LoadTeleportsByAddress(address)
require.Nil(t, err)
require.Equal(t, []db.Teleport{expTeleport}, teleports)
}
go 1.18
use (
./bss-core
./teleportr
)
package disburser
import (
"context"
"crypto/ecdsa"
"errors"
"math/big"
"strings"
"sync"
"time"
"github.com/cenkalti/backoff"
"github.com/ethereum-optimism/optimism/bss-core/metrics"
"github.com/ethereum-optimism/optimism/bss-core/txmgr"
"github.com/ethereum-optimism/optimism/teleportr/bindings/deposit"
"github.com/ethereum-optimism/optimism/teleportr/bindings/disburse"
"github.com/ethereum-optimism/optimism/teleportr/db"
"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/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
)
// DisbursementSuccessTopic is the topic hash for DisbursementSuccess events.
var DisbursementSuccessTopic = common.HexToHash(
"0xeaa22fd2d7b875476355b32cf719794faf9d91b66e73bc6375a053cace9caaee",
)
// DisbursementFailedTopic is the topic hash for DisbursementFailed events.
var DisbursementFailedTopic = common.HexToHash(
"0x9b478c095979d3d3a7d602ffd9ee1f0843204d853558ae0882c8fcc0a5bc78cf",
)
const MaxDisbursements = 15
type Config struct {
Name string
L1Client *ethclient.Client
L2Client *ethclient.Client
Database *db.Database
MaxTxSize uint64
NumConfirmations uint64
DeployBlockNumber uint64
FilterQueryMaxBlocks uint64
DepositAddr common.Address
DisburserAddr common.Address
ChainID *big.Int
PrivKey *ecdsa.PrivateKey
ChainMetricsEnable bool
}
type Driver struct {
cfg Config
depositContract *deposit.TeleportrDeposit
disburserContract *disburse.TeleportrDisburser
rawDisburserContract *bind.BoundContract
walletAddr common.Address
metrics *Metrics
currentDepositIDs []uint64
chainMetricsEnabled bool
metricsMu sync.Mutex
}
func NewDriver(cfg Config, parentCtx context.Context) (*Driver, error) {
if cfg.NumConfirmations == 0 {
panic("NumConfirmations cannot be zero")
}
if cfg.FilterQueryMaxBlocks == 0 {
panic("FilterQueryMaxBlocks cannot be zero")
}
depositContract, err := deposit.NewTeleportrDeposit(
cfg.DepositAddr, cfg.L1Client,
)
if err != nil {
return nil, err
}
disburserContract, err := disburse.NewTeleportrDisburser(
cfg.DisburserAddr, cfg.L2Client,
)
if err != nil {
return nil, err
}
parsed, err := abi.JSON(strings.NewReader(
disburse.TeleportrDisburserMetaData.ABI,
))
if err != nil {
return nil, err
}
rawDisburserContract := bind.NewBoundContract(
cfg.DisburserAddr, parsed, cfg.L2Client, cfg.L2Client, cfg.L2Client,
)
walletAddr := crypto.PubkeyToAddress(cfg.PrivKey.PublicKey)
metricsInst := NewMetrics(cfg.Name)
d := &Driver{
cfg: cfg,
depositContract: depositContract,
disburserContract: disburserContract,
rawDisburserContract: rawDisburserContract,
walletAddr: walletAddr,
metrics: metricsInst,
chainMetricsEnabled: cfg.ChainMetricsEnable,
}
if d.chainMetricsEnabled {
go d.collectChainMetricsBackground(parentCtx)
}
return d, nil
}
// Name is an identifier used to prefix logs for a particular service.
func (d *Driver) Name() string {
return d.cfg.Name
}
// WalletAddr is the wallet address used to pay for batch transaction fees.
func (d *Driver) WalletAddr() common.Address {
return d.walletAddr
}
// Metrics returns the subservice telemetry object.
func (d *Driver) Metrics() metrics.Metrics {
return d.metrics
}
// ClearPendingTx a publishes a transaction at the next available nonce in order
// to clear any transactions in the mempool left over from a prior running
// instance. When publishing to L2 there is no mempool so the transaction can't
// get stuck, thus the behavior is unimplemented.
func (d *Driver) ClearPendingTx(
ctx context.Context,
txMgr txmgr.TxManager,
l1Client *ethclient.Client,
) error {
return nil
}
// GetBatchBlockRange returns the start and end L2 block heights that need to be
// processed. Note that the end value is *exclusive*, therefore if the returned
// values are identical nothing needs to be processed.
func (d *Driver) GetBatchBlockRange(
ctx context.Context) (*big.Int, *big.Int, error) {
// Update metrics on each iteration.
d.collectChainMetrics(ctx)
// Clear the current deposit IDs from any prior iteration.
d.currentDepositIDs = nil
// Before proceeding, process the outcomes of any transactions we've
// published in the past. This handles both the restart case, as well as
// post processing after a txn is published.
err := d.processPendingTxs(ctx)
if err != nil {
return nil, nil, err
}
// Next, load the last disbursement ID claimed by postgres and the contract.
lastDisbursementID, err := d.latestDisbursementID()
if err != nil {
return nil, nil, err
}
if lastDisbursementID != nil {
d.metrics.PostgresLastDisbursedID.Set(float64(*lastDisbursementID))
}
startID, err := d.disburserContract.TotalDisbursements(
&bind.CallOpts{},
)
if err != nil {
return nil, nil, err
}
startID64 := startID.Uint64()
d.metrics.ContractNextDisbursementID.Set(float64(startID64))
// Do a quick sanity check that that the database and contract are in sync.
d.logDatabaseContractMismatch(lastDisbursementID, startID64)
// Now, proceed to ingest any new deposits by inspect L1 events from the
// deposit contract, using the last processed block in postgres as a lower
// bound.
blockNumber, err := d.cfg.L1Client.BlockNumber(ctx)
if err != nil {
return nil, nil, err
}
lastProcessedBlock, err := d.lastProcessedBlock()
if err != nil {
return nil, nil, err
}
err = d.ingestDeposits(ctx, blockNumber, lastProcessedBlock)
if err != nil {
return nil, nil, err
}
// After successfully ingesting deposits, check to see if there are any
// now-confirmed deposits that we can attempt to disburse.
confirmedDeposits, err := d.loadConfirmedDepositsInRange(
blockNumber, startID64, startID64+MaxDisbursements,
)
if err != nil {
return nil, nil, err
}
if len(confirmedDeposits) == 0 {
return startID, startID, nil
}
// Compute the end fo the range as the last confirmed deposit plus one.
endID64 := confirmedDeposits[len(confirmedDeposits)-1].ID + 1
endID := new(big.Int).SetUint64(endID64)
return startID, endID, nil
}
// CraftBatchTx transforms the L2 blocks between start and end into a batch
// transaction using the given nonce. A dummy gas price is used in the resulting
// transaction to use for size estimation.
//
// NOTE: This method SHOULD NOT publish the resulting transaction.
func (d *Driver) CraftBatchTx(
ctx context.Context,
start, end, nonce *big.Int,
) (*types.Transaction, error) {
name := d.cfg.Name
blockNumber, err := d.cfg.L1Client.BlockNumber(ctx)
if err != nil {
return nil, err
}
confirmedDeposits, err := d.loadConfirmedDepositsInRange(
blockNumber, start.Uint64(), end.Uint64(),
)
if err != nil {
return nil, err
}
var disbursements []disburse.TeleportrDisburserDisbursement
var depositIDs []uint64
value := new(big.Int)
for _, deposit := range confirmedDeposits {
disbursement := disburse.TeleportrDisburserDisbursement{
Amount: deposit.Amount,
Addr: deposit.Address,
}
disbursements = append(disbursements, disbursement)
depositIDs = append(depositIDs, deposit.ID)
value = value.Add(value, deposit.Amount)
}
log.Info(name+" crafting batch tx", "start", start, "end", end,
"nonce", nonce)
d.metrics.NumElementsPerBatch().Observe(float64(len(disbursements)))
log.Info(name+" batch constructed", "num_disbursements", len(disbursements))
gasPrice, err := d.cfg.L2Client.SuggestGasPrice(ctx)
if err != nil {
return nil, err
}
opts, err := bind.NewKeyedTransactorWithChainID(
d.cfg.PrivKey, d.cfg.ChainID,
)
if err != nil {
return nil, err
}
opts.Context = ctx
opts.Nonce = nonce
opts.GasPrice = gasPrice
opts.NoSend = true
opts.Value = value
tx, err := d.disburserContract.Disburse(opts, start, disbursements)
if err != nil {
return nil, err
}
d.currentDepositIDs = depositIDs
return tx, nil
}
// UpdateGasPrice signs an otherwise identical txn to the one provided but with
// updated gas prices sampled from the existing network conditions.
//
// NOTE: Thie method SHOULD NOT publish the resulting transaction.
func (d *Driver) UpdateGasPrice(
ctx context.Context,
tx *types.Transaction,
) (*types.Transaction, error) {
gasPrice, err := d.cfg.L2Client.SuggestGasPrice(ctx)
if err != nil {
return nil, err
}
opts, err := bind.NewKeyedTransactorWithChainID(
d.cfg.PrivKey, d.cfg.ChainID,
)
if err != nil {
return nil, err
}
opts.Context = ctx
opts.Nonce = new(big.Int).SetUint64(tx.Nonce())
opts.GasPrice = gasPrice
opts.Value = tx.Value()
opts.NoSend = true
return d.rawDisburserContract.RawTransact(opts, tx.Data())
}
// SendTransaction injects a signed transaction into the pending pool for
// execution.
func (d *Driver) SendTransaction(
ctx context.Context,
tx *types.Transaction,
) error {
txHash := tx.Hash()
startID := d.currentDepositIDs[0]
endID := d.currentDepositIDs[len(d.currentDepositIDs)-1] + 1
// Record the pending transaction hash so that we can recover if we crash
// after publishing.
err := d.upsertPendingTx(db.PendingTx{
TxHash: txHash,
StartID: startID,
EndID: endID,
})
if err != nil {
return err
}
// This requires special handling - if this request fails,
// then teleportr will halt. Use exponential backoff here to
// handle expected failures (e.g., 503s, 524s, etc.).
return backoff.Retry(func() error {
subCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
err := d.cfg.L2Client.SendTransaction(subCtx, tx)
if err == nil {
return err
}
if !IsRetryableError(err) {
d.metrics.FailedTXSubmissions.WithLabelValues("permanent").Inc()
return backoff.Permanent(err)
}
d.metrics.FailedTXSubmissions.WithLabelValues("recoverable").Inc()
return err
}, DefaultBackoff)
}
// processPendingTxs is a helper method which updates Postgres with the effects
// of a published disbursement tx. This handles both startup recovery as well as
// normal operation after a transaction is published.
func (d *Driver) processPendingTxs(ctx context.Context) error {
pendingTxs, err := d.listPendingTxs()
if err != nil {
return err
}
// Nothing to do. This can happen on first startup, or if GetBatchBlockRange
// was called before shutdown without sending a transaction.
if len(pendingTxs) == 0 {
return nil
}
// Fetch the receipt for the pending transaction that confirmed. In practice
// there should only be one, but if there isn't we will return an error here
// to process any others on subsequent calls.
var receipt *types.Receipt
var pendingTx db.PendingTx
for _, pendingTx = range pendingTxs {
r, err := d.cfg.L2Client.TransactionReceipt(ctx, pendingTx.TxHash)
if err == ethereum.NotFound {
continue
} else if err != nil {
return err
}
// Also skip any reverted transactions.
if r.Status != 1 {
continue
}
receipt = r
break
}
// Backend is reporting not knowing any of the transactions, try again
// later.
if receipt == nil {
return errors.New("unable to find receipt for any pending tx")
}
// Using the block number, load the header so that we can get accurate
// timestamps for the disbursements.
header, err := d.cfg.L2Client.HeaderByNumber(ctx, receipt.BlockNumber)
if err != nil {
return err
}
blockTimestamp := time.Unix(int64(header.Time), 0)
var successfulDisbursements int
var failedDisbursements int
var failedUpserts uint64
for _, event := range receipt.Logs {
// Extract the deposit ID from the second topic if this is a
// success/fail event.
var depositID uint64
var success bool
switch event.Topics[0] {
case DisbursementSuccessTopic:
depositID = new(big.Int).SetBytes(event.Topics[1][:]).Uint64()
success = true
successfulDisbursements++
case DisbursementFailedTopic:
depositID = new(big.Int).SetBytes(event.Topics[1][:]).Uint64()
success = false
failedDisbursements++
default:
continue
}
err = d.cfg.Database.UpsertDisbursement(
depositID,
receipt.TxHash,
receipt.BlockNumber.Uint64(),
blockTimestamp,
success,
)
if err != nil {
failedUpserts++
log.Warn("Unable to mark disbursement success",
"depositId", depositID,
"txHash", receipt.TxHash,
"blockNumber", receipt.BlockNumber,
"blockTimestamp", blockTimestamp,
"err", err)
continue
}
log.Info("Disbursement marked success",
"depositId", depositID,
"txHash", receipt.TxHash,
"blockNumber", receipt.BlockNumber,
"blockTimestamp", blockTimestamp)
}
d.metrics.SuccessfulDisbursements.Add(float64(successfulDisbursements))
d.metrics.FailedDisbursements.Add(float64(failedDisbursements))
d.metrics.FailedDatabaseMethods.With(DBMethodUpsertDisbursement).
Add(float64(failedUpserts))
// We have completed our post-processing once all of the disbursements are
// written without failures.
if failedUpserts > 0 {
return errors.New("failed to upsert all disbursements successfully")
}
// If we upserted all disbursements successfully, remove any pending txs
// with the same start/end id.
err = d.deletePendingTx(pendingTx.StartID, pendingTx.EndID)
if err != nil {
return err
}
// Sanity check that this leaves our pending tx table empty.
pendingTxs, err = d.listPendingTxs()
if err != nil {
return err
}
// If not, return an error so that subsequent calls to GetBatchBlockRange
// will attempt to process other pending txs before continuing.
if len(pendingTxs) != 0 {
return errors.New("pending txs remain in database")
}
return nil
}
// ingestDeposits is a preprocessing step done each time we attempt to compute a
// new block range. This method scans for any missing or recent logs from the
// contract, and upserts any new deposits into the database.
func (d *Driver) ingestDeposits(
ctx context.Context,
blockNumber uint64,
lastProcessedBlockNumber *uint64,
) error {
filterStartBlockNumber := FindFilterStartBlockNumber(
FilterStartBlockNumberParams{
BlockNumber: blockNumber,
NumConfirmations: d.cfg.NumConfirmations,
DeployBlockNumber: d.cfg.DeployBlockNumber,
LastProcessedBlockNumber: lastProcessedBlockNumber,
},
)
// Fetch deposit events in block ranges capped by the FilterQueryMaxBlocks
// parameter.
maxBlocks := d.cfg.FilterQueryMaxBlocks
for start := filterStartBlockNumber; start < blockNumber+1; start += maxBlocks {
var end = start + maxBlocks
if end > blockNumber {
end = blockNumber
}
opts := &bind.FilterOpts{
Start: start,
End: &end,
Context: ctx,
}
events, err := d.depositContract.FilterEtherReceived(opts, nil, nil, nil)
if err != nil {
return err
}
defer events.Close()
var deposits []db.Deposit
for events.Next() {
event := events.Event
header, err := d.cfg.L1Client.HeaderByNumber(
ctx, big.NewInt(int64(event.Raw.BlockNumber)),
)
if err != nil {
return err
}
deposits = append(deposits, db.Deposit{
ID: event.DepositId.Uint64(),
Address: event.Emitter,
Amount: event.Amount,
ConfirmationInfo: db.ConfirmationInfo{
TxnHash: event.Raw.TxHash,
BlockNumber: event.Raw.BlockNumber,
BlockTimestamp: time.Unix(int64(header.Time), 0),
},
})
}
err = events.Error()
if err != nil {
return err
}
err = d.upsertDeposits(deposits, end)
if err != nil {
return err
}
}
return nil
}
// loadConfirmedDeposits retrieves the list of confirmed deposits with IDs
// in the range [startID, endID).
func (d *Driver) loadConfirmedDepositsInRange(
blockNumber uint64,
startID uint64,
endID uint64,
) ([]db.Deposit, error) {
confirmedDeposits, err := d.confirmedDeposits(blockNumber)
if err != nil {
return nil, err
}
// On the off chance that we failed to record a disbursement, filter out any
// which are lower than what the disbursement contract says is the next
// disbursement. Note that it is possible for the last disbursement id to
// match the contract, but still have lingering, incomplete disbursements
// before that.
var filteredDeposits = make([]db.Deposit, 0, len(confirmedDeposits))
var missingDisbursements int
for _, deposit := range confirmedDeposits {
switch {
// If the deposit ID is less than our start ID, this indicates that we
// are missing a disbursement for this deposit even though the contract
// beleves it was disbursed.
case deposit.ID < startID:
log.Warn("Filtering deposit with missing disbursement",
"deposit_id", deposit.ID)
missingDisbursements++
continue
// This is mostly a defensive measure, to ensure that we return the
// exact range that was sanity checked in GetBatchBlockRange, which can
// change as a result of the block number increasing.
case deposit.ID >= endID:
continue
}
filteredDeposits = append(filteredDeposits, deposit)
}
d.metrics.MissingDisbursements.Set(float64(missingDisbursements))
if len(filteredDeposits) == 0 {
return nil, nil
}
// Ensure the next confirmed deposit matches what the contract expects.
if startID != filteredDeposits[0].ID {
panic("confirmed deposits start is not contiguous")
}
// Ensure that the slice has contiguous deposit ids. This is done by
// checking that the final deposit id is equal to:
// start + len(confirmedDeposits).
// The id is the primary key so there cannot be duplicates, and they are
// returned in sorted order.
lastDepositID := filteredDeposits[len(filteredDeposits)-1].ID
if startID+uint64(len(filteredDeposits)) != lastDepositID+1 {
panic("confirmed deposits are not continguous")
}
return filteredDeposits, nil
}
// logDatabaseContractMismatch records any instances of our database
// desynchronizing from the disrburser contract. This method panics in
// irrecoverable cases of desynchronization.
func (d *Driver) logDatabaseContractMismatch(
lastDisbursementID *uint64,
contractNextID uint64,
) {
switch {
// Database indicates we have done a disbursement.
case lastDisbursementID != nil:
switch {
// The last recorded disbursement is behind what the contract believes.
case *lastDisbursementID+1 < contractNextID:
log.Warn("Recorded disbursements behind contract",
"last_disbursement_id", *lastDisbursementID,
"contract_next_id", contractNextID)
d.metrics.DepositIDMismatch.Inc()
// The last recorded disbursement is ahead of what the contract believes.
// This should NEVER happen unless the sequencer blows up and loses
// state. Exit so that the the problem can be surfaced loudly.
case *lastDisbursementID+1 > contractNextID:
log.Error("Recorded disbursements ahead of contract",
"last_disbursement_id", *lastDisbursementID,
"contract_next_id", contractNextID)
panic("Recorded disbursements ahead contract")
// Databse and contract are in sync.
default:
d.metrics.DepositIDMismatch.Set(0.0)
}
// Database indicates we have not done a disbursement, but contract does.
case contractNextID != 0:
// The contract shows that is has disbursed, but we don't have a
// recording of it.
log.Warn("Recorded disbursements behind contract",
"last_disbursement_id", nil,
"contract_next_id", contractNextID)
d.metrics.DepositIDMismatch.Inc()
// Database and contract indicate we have not done a disbursement.
default:
d.metrics.DepositIDMismatch.Set(0.0)
}
}
func (d *Driver) upsertDeposits(deposits []db.Deposit, end uint64) error {
err := d.cfg.Database.UpsertDeposits(deposits, end)
if err != nil {
d.metrics.FailedDatabaseMethods.With(DBMethodUpsertDeposits).Inc()
return err
}
return nil
}
func (d *Driver) confirmedDeposits(blockNumber uint64) ([]db.Deposit, error) {
confirmedDeposits, err := d.cfg.Database.ConfirmedDeposits(
blockNumber, d.cfg.NumConfirmations,
)
if err != nil {
d.metrics.FailedDatabaseMethods.With(DBMethodConfirmedDeposits).Inc()
return nil, err
}
return confirmedDeposits, nil
}
func (d *Driver) lastProcessedBlock() (*uint64, error) {
lastProcessedBlock, err := d.cfg.Database.LastProcessedBlock()
if err != nil {
d.metrics.FailedDatabaseMethods.With(DBMethodLastProcessedBlock).Inc()
return nil, err
}
return lastProcessedBlock, nil
}
func (d *Driver) upsertPendingTx(pendingTx db.PendingTx) error {
err := d.cfg.Database.UpsertPendingTx(pendingTx)
if err != nil {
d.metrics.FailedDatabaseMethods.With(DBMethodUpsertPendingTx).Inc()
return err
}
return nil
}
func (d *Driver) listPendingTxs() ([]db.PendingTx, error) {
pendingTxs, err := d.cfg.Database.ListPendingTxs()
if err != nil {
d.metrics.FailedDatabaseMethods.With(DBMethodListPendingTxs).Inc()
return nil, err
}
return pendingTxs, nil
}
func (d *Driver) latestDisbursementID() (*uint64, error) {
lastDisbursementID, err := d.cfg.Database.LatestDisbursementID()
if err != nil {
d.metrics.FailedDatabaseMethods.With(DBMethodLatestDisbursementID).Inc()
return nil, err
}
return lastDisbursementID, nil
}
func (d *Driver) deletePendingTx(startID, endID uint64) error {
err := d.cfg.Database.DeletePendingTx(startID, endID)
if err != nil {
d.metrics.FailedDatabaseMethods.With(DBMethodDeletePendingTx).Inc()
return err
}
return nil
}
func (d *Driver) collectChainMetricsBackground(ctx context.Context) {
tick := time.NewTicker(time.Minute)
defer tick.Stop()
for {
select {
case <-tick.C:
log.Info("collecting background metrics")
d.collectChainMetrics(ctx)
case <-ctx.Done():
return
}
}
}
func (d *Driver) collectChainMetrics(ctx context.Context) {
if !d.chainMetricsEnabled {
return
}
subCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
disburserBal, err := d.cfg.L2Client.BalanceAt(subCtx, d.cfg.DisburserAddr, nil)
if err != nil {
log.Error("Error getting disburser wallet balance", "err", err)
disburserBal = big.NewInt(0)
}
depositBal, err := d.cfg.L1Client.BalanceAt(subCtx, d.cfg.DepositAddr, nil)
if err != nil {
log.Error("Error getting deposit contract balance", "err", err)
depositBal = big.NewInt(0)
}
nextDepositID, err := d.depositContract.TotalDeposits(&bind.CallOpts{
Context: subCtx,
})
if err != nil {
log.Error("Error getting deposit contract total deposits")
nextDepositID = big.NewInt(0)
}
nextDisbursementID, err := d.disburserContract.TotalDisbursements(
&bind.CallOpts{
Context: subCtx,
},
)
if err != nil {
log.Error("Error getting deposit contract total disbursements")
nextDisbursementID = big.NewInt(0)
}
d.metricsMu.Lock()
d.metrics.DisburserBalance.Set(float64(disburserBal.Uint64()))
d.metrics.DepositContractBalance.Set(float64(depositBal.Uint64()))
d.metrics.ContractNextDepositID.Set(float64(nextDepositID.Uint64()))
d.metrics.ContractNextDisbursementID.Set(float64(nextDisbursementID.Uint64()))
d.metricsMu.Unlock()
}
package disburser
// FilterStartBlockNumberParams holds the arguments passed to
// FindFilterStartBlockNumber.
type FilterStartBlockNumberParams struct {
// BlockNumber the current block height of the chain.
BlockNumber uint64
// NumConfirmations is the number of confirmations required to consider a
// block final.
NumConfirmations uint64
// DeployBlockNumber is the deployment height of the Deposit contract.
DeployBlockNumber uint64
// LastProcessedBlockNumber is the height of the last processed block.
//
// NOTE: This will be nil on the first invocation, before blocks have been
// ingested.
LastProcessedBlockNumber *uint64
}
func (p *FilterStartBlockNumberParams) unconfirmed(blockNumber uint64) bool {
return p.BlockNumber+1 < blockNumber+p.NumConfirmations
}
// FindFilterStartBlockNumber returns the block height from which to begin
// filtering logs based on the relative heights of the chain, the contract
// deployment, and the last block that was processed.
func FindFilterStartBlockNumber(params FilterStartBlockNumberParams) uint64 {
// On initilization, always start at the deploy height.
if params.LastProcessedBlockNumber == nil {
return params.DeployBlockNumber
}
// If the deployment height has not exited the confirmation window, we can
// still begin our search from the deployment height.
if params.unconfirmed(params.DeployBlockNumber) {
return params.DeployBlockNumber
}
// Otherwise, start from the block immediately following the last processed
// block. If that height is still hasn't fully confirmed, we'll use the
// height of the last confirmed block.
var filterStartBlockNumber = *params.LastProcessedBlockNumber + 1
if params.unconfirmed(filterStartBlockNumber) {
filterStartBlockNumber = params.BlockNumber + 1 - params.NumConfirmations
}
return filterStartBlockNumber
}
package disburser_test
import (
"testing"
"github.com/ethereum-optimism/optimism/teleportr/drivers/disburser"
"github.com/stretchr/testify/require"
)
func uint64Ptr(x uint64) *uint64 {
return &x
}
type filterStartBlockNumberTestCase struct {
name string
params disburser.FilterStartBlockNumberParams
expStartBlockNumber uint64
}
// TestFindFilterStartBlockNumber exhaustively tests the behavior of
// FindFilterStartBlockNumber and its edge cases.
func TestFindFilterStartBlockNumber(t *testing.T) {
tests := []filterStartBlockNumberTestCase{
// Deploy number should be returned if LastProcessedBlockNumber is nil.
{
name: "init returns deploy block number",
params: disburser.FilterStartBlockNumberParams{
BlockNumber: 10,
NumConfirmations: 5,
DeployBlockNumber: 42,
LastProcessedBlockNumber: nil,
},
expStartBlockNumber: 42,
},
// Deploy number should be returned if the deploy number is still in our
// confirmation window.
{
name: "conf lookback before deploy number",
params: disburser.FilterStartBlockNumberParams{
BlockNumber: 43,
NumConfirmations: 5,
DeployBlockNumber: 42,
LastProcessedBlockNumber: uint64Ptr(43),
},
expStartBlockNumber: 42,
},
// Deploy number should be returned if the deploy number is still in our
// confirmation window.
{
name: "conf lookback before deploy number",
params: disburser.FilterStartBlockNumberParams{
BlockNumber: 43,
NumConfirmations: 44,
DeployBlockNumber: 42,
LastProcessedBlockNumber: uint64Ptr(43),
},
expStartBlockNumber: 42,
},
// If our confirmation window is ahead of the last deposit + 1, expect
// last deposit + 1.
{
name: "conf lookback gt last deposit plus one",
params: disburser.FilterStartBlockNumberParams{
BlockNumber: 100,
NumConfirmations: 5,
DeployBlockNumber: 42,
LastProcessedBlockNumber: uint64Ptr(43),
},
expStartBlockNumber: 44,
},
// If our confirmation window is equal to last deposit + 1, expect last
// deposit + 1.
{
name: "conf lookback eq last deposit plus one",
params: disburser.FilterStartBlockNumberParams{
BlockNumber: 48,
NumConfirmations: 5,
DeployBlockNumber: 42,
LastProcessedBlockNumber: uint64Ptr(43),
},
expStartBlockNumber: 44,
},
// If our confirmation window starts before last deposit + 1, expect
// block number - num confs + 1.
{
name: "conf lookback lt last deposit plus one",
params: disburser.FilterStartBlockNumberParams{
BlockNumber: 47,
NumConfirmations: 5,
DeployBlockNumber: 42,
LastProcessedBlockNumber: uint64Ptr(43),
},
expStartBlockNumber: 43,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
testFindFilterStartBlockNumber(t, test)
})
}
}
func testFindFilterStartBlockNumber(
t *testing.T,
test filterStartBlockNumberTestCase,
) {
startBlockNumber := disburser.FindFilterStartBlockNumber(test.params)
require.Equal(t, test.expStartBlockNumber, startBlockNumber)
}
package disburser
import (
"github.com/ethereum-optimism/optimism/bss-core/metrics"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
const methodLabel = "method"
var (
// DBMethodUpsertDeposits is a label for UpsertDeposits db method.
DBMethodUpsertDeposits = prometheus.Labels{methodLabel: "upsert_deposits"}
// DBMethodConfirmedDeposits is a label for ConfirmedDeposits db method.
DBMethodConfirmedDeposits = prometheus.Labels{methodLabel: "confirmed_deposits"}
// DBMethodLastProcessedBlock is a label for LastProcessedBlock db method.
DBMethodLastProcessedBlock = prometheus.Labels{methodLabel: "last_processed_block"}
// DBMethodUpsertPendingTx is a label for UpsertPendingTx db method.
DBMethodUpsertPendingTx = prometheus.Labels{methodLabel: "upsert_pending_tx"}
// DBMethodListPendingTxs is a label for ListPendingTxs db method.
DBMethodListPendingTxs = prometheus.Labels{methodLabel: "list_pending_txs"}
// DBMethodUpsertDisbursement is a label for UpsertDisbursement db method.
DBMethodUpsertDisbursement = prometheus.Labels{methodLabel: "upsert_disbursement"}
// DBMethodLatestDisbursementID is a label for LatestDisbursementID db method.
DBMethodLatestDisbursementID = prometheus.Labels{methodLabel: "latest_disbursement_id"}
// DBMethodDeletePendingTx is a label for DeletePendingTx db method.
DBMethodDeletePendingTx = prometheus.Labels{methodLabel: "delete_pending_tx"}
)
// Metrics extends the BSS core metrics with additional metrics tracked by the
// sequencer driver.
type Metrics struct {
*metrics.Base
// FailedDatabaseMethods tracks the number of database failures for each
// known database method.
FailedDatabaseMethods *prometheus.CounterVec
// DepositIDMismatch tracks whether or not our database is in sync with the
// disrburser contract. 1 means in sync, 0 means out of sync.
DepositIDMismatch prometheus.Gauge
// MissingDisbursements tracks the number of deposits that are missing
// disbursement below our supposed next deposit id.
MissingDisbursements prometheus.Gauge
// SuccessfulDisbursements tracks the number of disbursements that emit a
// success event from a given tx.
SuccessfulDisbursements prometheus.Counter
// FailedDisbursements tracks the number of disbursements that emit a failed
// event from a given tx.
FailedDisbursements prometheus.Counter
// PostgresLastDisbursedID tracks the latest disbursement id in postgres.
PostgresLastDisbursedID prometheus.Gauge
// ContractNextDisbursementID tracks the next disbursement id expected by
// the disburser contract.
ContractNextDisbursementID prometheus.Gauge
// ContractNextDepositID tracks the next deposit id expected by the deposit
// contract.
ContractNextDepositID prometheus.Gauge
// DisburserBalance tracks Teleportr's disburser account balance.
DisburserBalance prometheus.Gauge
// DepositContractBalance tracks Teleportr's deposit contract balance.
DepositContractBalance prometheus.Gauge
// FailedTXSubmissions tracks failed requests to eth_sendRawTransaction
// during transaction submission.
FailedTXSubmissions *prometheus.CounterVec
}
// NewMetrics initializes a new, extended metrics object.
func NewMetrics(subsystem string) *Metrics {
base := metrics.NewBase(subsystem, "")
return &Metrics{
Base: base,
FailedDatabaseMethods: promauto.NewCounterVec(prometheus.CounterOpts{
Name: "failed_database_operations",
Help: "Tracks the number of database failures",
Subsystem: base.SubsystemName(),
}, []string{methodLabel}),
DepositIDMismatch: promauto.NewGauge(prometheus.GaugeOpts{
Name: "deposit_id_mismatch",
Help: "Set to 1 when the postgres and the disrburser contract " +
"disagree on the next deposit id, and 0 otherwise",
Subsystem: base.SubsystemName(),
}),
MissingDisbursements: promauto.NewGauge(prometheus.GaugeOpts{
Name: "missing_disbursements",
Help: "Number of deposits that are missing disbursements in " +
"postgres below our supposed next deposit id",
Subsystem: base.SubsystemName(),
}),
SuccessfulDisbursements: promauto.NewGauge(prometheus.GaugeOpts{
Name: "successful_disbursements",
Help: "Number of disbursements that emit a success event " +
"from a given tx",
Subsystem: base.SubsystemName(),
}),
FailedDisbursements: promauto.NewGauge(prometheus.GaugeOpts{
Name: "failed_disbursements",
Help: "Number of disbursements that emit a failed event " +
"from a given tx",
Subsystem: base.SubsystemName(),
}),
PostgresLastDisbursedID: promauto.NewGauge(prometheus.GaugeOpts{
Name: "postgres_last_disbursed_id",
Help: "Latest recorded disbursement id in postgres",
Subsystem: base.SubsystemName(),
}),
ContractNextDisbursementID: promauto.NewGauge(prometheus.GaugeOpts{
Name: "contract_next_disbursement_id",
Help: "Next disbursement id expected by the disburser contract",
Subsystem: base.SubsystemName(),
}),
ContractNextDepositID: promauto.NewGauge(prometheus.GaugeOpts{
Name: "contract_next_deposit_id",
Help: "next deposit id expected by the deposit contract",
Subsystem: base.SubsystemName(),
}),
DisburserBalance: promauto.NewGauge(prometheus.GaugeOpts{
Name: "disburser_balance",
Help: "Balance in Wei of Teleportr's disburser wallet",
Subsystem: base.SubsystemName(),
}),
DepositContractBalance: promauto.NewGauge(prometheus.GaugeOpts{
Name: "deposit_contract_balance",
Help: "Balance in Wei of Teleportr's deposit contract",
Subsystem: base.SubsystemName(),
}),
FailedTXSubmissions: promauto.NewCounterVec(prometheus.CounterOpts{
Name: "failed_tx_submissions",
Help: "Number of failed transaction submissions",
Subsystem: base.SubsystemName(),
}, []string{
"type",
}),
}
}
package disburser
import (
"context"
"errors"
"regexp"
"time"
"github.com/ethereum/go-ethereum/rpc"
"github.com/cenkalti/backoff"
)
var retryRegexes = []*regexp.Regexp{
regexp.MustCompile("read: connection reset by peer$"),
}
var DefaultBackoff = &backoff.ExponentialBackOff{
InitialInterval: backoff.DefaultInitialInterval,
RandomizationFactor: backoff.DefaultRandomizationFactor,
Multiplier: backoff.DefaultMultiplier,
MaxInterval: 10 * time.Second,
MaxElapsedTime: time.Minute,
Clock: backoff.SystemClock,
}
func IsRetryableError(err error) bool {
msg := err.Error()
if httpErr, ok := err.(rpc.HTTPError); ok {
if httpErr.StatusCode == 503 || httpErr.StatusCode == 524 || httpErr.StatusCode == 429 {
return true
}
}
if errors.Is(err, context.DeadlineExceeded) {
return true
}
for _, reg := range retryRegexes {
if reg.MatchString(msg) {
return true
}
}
return false
}
package disburser
import (
"context"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/stretchr/testify/require"
)
func TestIsRetryableError(t *testing.T) {
var resCode int32
var res atomic.Value
res.Store([]byte{})
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(int(atomic.LoadInt32(&resCode)))
_, _ = w.Write(res.Load().([]byte))
}))
defer server.Close()
client, err := ethclient.Dial(server.URL)
require.NoError(t, err)
tests := []struct {
code int
retryable bool
}{
{
503,
true,
},
{
524,
true,
},
{
429,
true,
},
{
500,
false,
},
{
200,
false,
},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("http %d", tt.code), func(t *testing.T) {
atomic.StoreInt32(&resCode, int32(tt.code))
_, err := client.BlockNumber(context.Background())
require.Equal(t, tt.retryable, IsRetryableError(err))
})
}
require.True(t, IsRetryableError(context.DeadlineExceeded))
require.True(t, IsRetryableError(errors.New("read: connection reset by peer")))
}
package flags
import (
"fmt"
"strings"
"github.com/urfave/cli"
)
func prefixAPIEnvVar(name string) string {
return fmt.Sprintf("TELEPORTR_API_%s", strings.ToUpper(name))
}
var (
APIHostnameFlag = cli.StringFlag{
Name: "hostname",
Usage: "The hostname of the API server",
Required: true,
EnvVar: prefixAPIEnvVar("HOSTNAME"),
}
APIPortFlag = cli.StringFlag{
Name: "port",
Usage: "The hostname of the API server",
Required: true,
EnvVar: prefixAPIEnvVar("PORT"),
}
DisburserWalletAddressFlag = cli.StringFlag{
Name: "disburser-wallet-address",
Usage: "The address of the disburser wallet",
Required: true,
EnvVar: prefixAPIEnvVar("DISBURSER_WALLET_ADDRESS"),
}
)
var APIFlags = []cli.Flag{
APIHostnameFlag,
APIPortFlag,
DisburserWalletAddressFlag,
DisburserAddressFlag,
L1EthRpcFlag,
L2EthRpcFlag,
DepositAddressFlag,
NumDepositConfirmationsFlag,
PostgresHostFlag,
PostgresPortFlag,
PostgresUserFlag,
PostgresPasswordFlag,
PostgresDBNameFlag,
PostgresEnableSSLFlag,
MetricsServerEnableFlag,
MetricsHostnameFlag,
MetricsPortFlag,
HTTP2DisableFlag,
}
package flags
import "github.com/urfave/cli"
const envVarPrefix = "TELEPORTR_"
func prefixEnvVar(name string) string {
return envVarPrefix + name
}
var (
/* Required Flags */
BuildEnvFlag = cli.StringFlag{
Name: "build-env",
Usage: "Build environment for which the binary is produced, " +
"e.g. production or development",
Required: true,
EnvVar: "BUILD_ENV",
}
EthNetworkNameFlag = cli.StringFlag{
Name: "eth-network-name",
Usage: "Ethereum network name",
Required: true,
EnvVar: "ETH_NETWORK_NAME",
}
L1EthRpcFlag = cli.StringFlag{
Name: "l1-eth-rpc",
Usage: "HTTP provider URL for L1",
Required: true,
EnvVar: "L1_ETH_RPC",
}
L2EthRpcFlag = cli.StringFlag{
Name: "l2-eth-rpc",
Usage: "HTTP provider URL for L2",
Required: true,
EnvVar: "L2_ETH_RPC",
}
DepositAddressFlag = cli.StringFlag{
Name: "deposit-address",
Usage: "Address of the TeleportrDeposit contract",
Required: true,
EnvVar: prefixEnvVar("DEPOSIT_ADDRESS"),
}
DepositDeployBlockNumberFlag = cli.Uint64Flag{
Name: "deposit-deploy-block-number",
Usage: "Deployment block number of the TeleportrDeposit contract",
Required: true,
EnvVar: prefixEnvVar("DEPOSIT_DEPLOY_BLOCK_NUMBER"),
}
DisburserAddressFlag = cli.StringFlag{
Name: "disburser-address",
Usage: "Address of the TeleportrDisburser contract",
Required: true,
EnvVar: prefixEnvVar("DISBURSER_ADDRESS"),
}
MaxL2TxSizeFlag = cli.Uint64Flag{
Name: "max-l2-tx-size",
Usage: "Maximum size in bytes of any L2 transaction that gets " +
"sent for disbursement",
Required: true,
EnvVar: prefixEnvVar("MAX_L2_TX_SIZE"),
}
NumDepositConfirmationsFlag = cli.Uint64Flag{
Name: "num-deposit-confirmations",
Usage: "Number of confirmations before deposits are considered " +
"confirmed",
Required: true,
EnvVar: prefixEnvVar("NUM_DEPOSIT_CONFIRMATIONS"),
}
FilterQueryMaxBlocksFlag = cli.Uint64Flag{
Name: "filter-query-max-blocks",
Usage: "Maximum range of a filter query in blocks",
Required: true,
EnvVar: prefixEnvVar("FILTER_QUERY_MAX_BLOCKS"),
}
PollIntervalFlag = cli.DurationFlag{
Name: "poll-interval",
Usage: "Delay between querying L1 for more transactions and " +
"creating a new disbursement batch",
Required: true,
EnvVar: prefixEnvVar("POLL_INTERVAL"),
}
SafeAbortNonceTooLowCountFlag = cli.Uint64Flag{
Name: "safe-abort-nonce-too-low-count",
Usage: "Number of ErrNonceTooLow observations required to " +
"give up on a tx at a particular nonce without receiving " +
"confirmation",
Required: true,
EnvVar: prefixEnvVar("SAFE_ABORT_NONCE_TOO_LOW_COUNT"),
}
ResubmissionTimeoutFlag = cli.DurationFlag{
Name: "resubmission-timeout",
Usage: "Duration we will wait before resubmitting a " +
"transaction to L2",
Required: true,
EnvVar: prefixEnvVar("RESUBMISSION_TIMEOUT"),
}
PostgresHostFlag = cli.StringFlag{
Name: "postgres-host",
Usage: "Host of the teleportr postgres instance",
Required: true,
EnvVar: prefixEnvVar("POSTGRES_HOST"),
}
PostgresPortFlag = cli.Uint64Flag{
Name: "postgres-port",
Usage: "Port of the teleportr postgres instance",
Required: true,
EnvVar: prefixEnvVar("POSTGRES_PORT"),
}
PostgresUserFlag = cli.StringFlag{
Name: "postgres-user",
Usage: "Username of the teleportr postgres instance",
Required: true,
EnvVar: prefixEnvVar("POSTGRES_USER"),
}
PostgresPasswordFlag = cli.StringFlag{
Name: "postgres-password",
Usage: "Password of the teleportr postgres instance",
Required: true,
EnvVar: prefixEnvVar("POSTGRES_PASSWORD"),
}
PostgresDBNameFlag = cli.StringFlag{
Name: "postgres-db-name",
Usage: "Database name of the teleportr postgres instance",
Required: true,
EnvVar: prefixEnvVar("POSTGRES_DB_NAME"),
}
/* Optional Flags */
PostgresEnableSSLFlag = cli.BoolFlag{
Name: "postgres-enable-ssl",
Usage: "Whether or not to enable SSL on connections to " +
"teleportr postgres instance",
EnvVar: prefixEnvVar("POSTGRES_ENABLE_SSL"),
}
LogLevelFlag = cli.StringFlag{
Name: "log-level",
Usage: "The lowest log level that will be output",
Value: "info",
EnvVar: prefixEnvVar("LOG_LEVEL"),
}
LogTerminalFlag = cli.BoolFlag{
Name: "log-terminal",
Usage: "If true, outputs logs in terminal format, otherwise prints " +
"in JSON format. If SENTRY_ENABLE is set to true, this flag is " +
"ignored and logs are printed using JSON",
EnvVar: prefixEnvVar("LOG_TERMINAL"),
}
DisburserPrivateKeyFlag = cli.StringFlag{
Name: "disburser-private-key",
Usage: "The private key to use for sending to the disburser contract",
EnvVar: prefixEnvVar("DISBURSER_PRIVATE_KEY"),
}
MnemonicFlag = cli.StringFlag{
Name: "mnemonic",
Usage: "The mnemonic used to derive the wallet for the disburser",
EnvVar: prefixEnvVar("MNEMONIC"),
}
DisburserHDPathFlag = cli.StringFlag{
Name: "disburser-hd-path",
Usage: "The HD path used to derive the disburser wallet from the " +
"mnemonic. The mnemonic flag must also be set.",
EnvVar: prefixEnvVar("DISBURSER_HD_PATH"),
}
MetricsServerEnableFlag = cli.BoolFlag{
Name: "metrics-server-enable",
Usage: "Whether or not to run the embedded metrics server",
EnvVar: prefixEnvVar("METRICS_SERVER_ENABLE"),
}
MetricsHostnameFlag = cli.StringFlag{
Name: "metrics-hostname",
Usage: "The hostname of the metrics server",
Value: "127.0.0.1",
EnvVar: prefixEnvVar("METRICS_HOSTNAME"),
}
MetricsPortFlag = cli.Uint64Flag{
Name: "metrics-port",
Usage: "The port of the metrics server",
Value: 7300,
EnvVar: prefixEnvVar("METRICS_PORT"),
}
HTTP2DisableFlag = cli.BoolFlag{
Name: "http2-disable",
Usage: "Whether or not to disable HTTP/2 support.",
EnvVar: prefixEnvVar("HTTP2_DISABLE"),
}
)
var requiredFlags = []cli.Flag{
BuildEnvFlag,
EthNetworkNameFlag,
L1EthRpcFlag,
L2EthRpcFlag,
DepositAddressFlag,
DepositDeployBlockNumberFlag,
DisburserAddressFlag,
MaxL2TxSizeFlag,
NumDepositConfirmationsFlag,
FilterQueryMaxBlocksFlag,
PollIntervalFlag,
SafeAbortNonceTooLowCountFlag,
ResubmissionTimeoutFlag,
PostgresHostFlag,
PostgresPortFlag,
PostgresUserFlag,
PostgresPasswordFlag,
PostgresDBNameFlag,
}
var optionalFlags = []cli.Flag{
LogLevelFlag,
LogTerminalFlag,
PostgresEnableSSLFlag,
DisburserPrivateKeyFlag,
MnemonicFlag,
DisburserHDPathFlag,
MetricsServerEnableFlag,
MetricsHostnameFlag,
MetricsPortFlag,
HTTP2DisableFlag,
}
// Flags contains the list of configuration options available to the binary.
var Flags = append(requiredFlags, optionalFlags...)
package flags
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/urfave/cli"
)
// TestRequiredFlagsSetRequired asserts that all flags deemed required properly
// have the Required field set to true.
func TestRequiredFlagsSetRequired(t *testing.T) {
for _, flag := range requiredFlags {
reqFlag, ok := flag.(cli.RequiredFlag)
require.True(t, ok)
require.True(t, reqFlag.IsRequired())
}
}
// TestOptionalFlagsDontSetRequired asserts that all flags deemed optional set
// the Required field to false.
func TestOptionalFlagsDontSetRequired(t *testing.T) {
for _, flag := range optionalFlags {
reqFlag, ok := flag.(cli.RequiredFlag)
require.True(t, ok)
require.False(t, reqFlag.IsRequired())
}
}
module github.com/ethereum-optimism/optimism/teleportr
go 1.18
replace github.com/ethereum-optimism/optimism/bss-core v0.0.0 => ../bss-core
require (
github.com/ethereum-optimism/optimism/bss-core v0.0.0
github.com/ethereum/go-ethereum v1.10.17
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
github.com/lib/pq v1.10.4
github.com/prometheus/client_golang v1.11.0
github.com/rs/cors v1.8.2
github.com/stretchr/testify v1.7.0
github.com/urfave/cli v1.22.5
)
require (
github.com/VictoriaMetrics/fastcache v1.9.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.1.2 // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deckarep/golang-set v1.8.0 // indirect
github.com/decred/base58 v1.0.3 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
github.com/decred/dcrd/crypto/ripemd160 v1.0.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/decred/dcrd/hdkeychain/v3 v3.0.0 // indirect
github.com/getsentry/sentry-go v0.12.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect
github.com/holiman/bloomfilter/v2 v2.0.3 // indirect
github.com/holiman/uint256 v1.2.0 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mitchellh/pointerstructure v1.2.1 // indirect
github.com/nxadm/tail v1.4.8 // 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_model v0.2.0 // indirect
github.com/prometheus/common v0.30.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/prometheus/tsdb v0.10.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rjeczalik/notify v0.9.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.4.0 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "@eth-optimism/teleportr",
"version": "0.0.11",
"private": true,
"devDependencies": {}
}
package teleportr
import (
"context"
"os"
"os/signal"
"syscall"
"time"
bsscore "github.com/ethereum-optimism/optimism/bss-core"
"github.com/ethereum-optimism/optimism/bss-core/dial"
"github.com/ethereum-optimism/optimism/bss-core/metrics"
"github.com/ethereum-optimism/optimism/bss-core/txmgr"
"github.com/ethereum-optimism/optimism/teleportr/db"
"github.com/ethereum-optimism/optimism/teleportr/drivers/disburser"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli"
)
func Main(gitVersion string) func(ctx *cli.Context) error {
return func(cliCtx *cli.Context) error {
cfg, err := NewConfig(cliCtx)
if err != nil {
return err
}
log.Info("Initializing teleportr")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var logHandler log.Handler
if cfg.LogTerminal {
logHandler = log.StreamHandler(os.Stdout, log.TerminalFormat(true))
} else {
logHandler = log.StreamHandler(os.Stdout, log.JSONFormat())
}
logLevel, err := log.LvlFromString(cfg.LogLevel)
if err != nil {
return err
}
log.Root().SetHandler(log.LvlFilterHandler(logLevel, logHandler))
disburserPrivKey, disburserAddr, err := bsscore.ParseWalletPrivKeyAndContractAddr(
"Teleportr", cfg.Mnemonic, cfg.DisburserHDPath,
cfg.DisburserPrivKey, cfg.DisburserAddress,
)
if err != nil {
return err
}
depositAddr, err := bsscore.ParseAddress(cfg.DepositAddress)
if err != nil {
return err
}
l1Client, err := dial.L1EthClientWithTimeout(ctx, cfg.L1EthRpc, cfg.DisableHTTP2)
if err != nil {
return err
}
defer l1Client.Close()
l2Client, err := dial.L1EthClientWithTimeout(ctx, cfg.L2EthRpc, cfg.DisableHTTP2)
if err != nil {
return err
}
defer l2Client.Close()
database, err := db.Open(db.Config{
Host: cfg.PostgresHost,
Port: uint16(cfg.PostgresPort),
User: cfg.PostgresUser,
Password: cfg.PostgresPassword,
DBName: cfg.PostgresDBName,
EnableSSL: cfg.PostgresEnableSSL,
})
if err != nil {
return err
}
defer database.Close()
if cfg.MetricsServerEnable {
go metrics.RunServer(cfg.MetricsHostname, cfg.MetricsPort)
}
chainID, err := l2Client.ChainID(ctx)
if err != nil {
return err
}
txManagerConfig := txmgr.Config{
ResubmissionTimeout: cfg.ResubmissionTimeout,
ReceiptQueryInterval: time.Second,
NumConfirmations: 1, // L2 insta confs
SafeAbortNonceTooLowCount: cfg.SafeAbortNonceTooLowCount,
}
teleportrDriver, err := disburser.NewDriver(disburser.Config{
Name: "Teleportr",
L1Client: l1Client,
L2Client: l2Client,
Database: database,
MaxTxSize: cfg.MaxL2TxSize,
NumConfirmations: cfg.NumDepositConfirmations,
DeployBlockNumber: cfg.DepositDeployBlockNumber,
FilterQueryMaxBlocks: cfg.FilterQueryMaxBlocks,
DepositAddr: depositAddr,
DisburserAddr: disburserAddr,
ChainID: chainID,
PrivKey: disburserPrivKey,
ChainMetricsEnable: cfg.MetricsServerEnable,
}, ctx)
if err != nil {
return err
}
teleportrService := bsscore.NewService(bsscore.ServiceConfig{
Context: ctx,
Driver: teleportrDriver,
PollInterval: cfg.PollInterval,
ClearPendingTx: false,
L1Client: l2Client,
TxManagerConfig: txManagerConfig,
})
services := []*bsscore.Service{teleportrService}
teleportr, err := bsscore.NewBatchSubmitter(ctx, cancel, services)
if err != nil {
return err
}
log.Info("Starting teleportr")
err = teleportr.Start()
if err != nil {
return err
}
defer teleportr.Stop()
log.Info("Teleportr started")
interruptChannel := make(chan os.Signal, 1)
signal.Notify(interruptChannel, []os.Signal{
os.Interrupt,
os.Kill,
syscall.SIGTERM,
syscall.SIGQUIT,
}...)
<-interruptChannel
return nil
}
}
func Migrate() func(ctx *cli.Context) error {
return func(cliCtx *cli.Context) error {
cfg, err := NewConfig(cliCtx)
if err != nil {
return err
}
log.Info("Initializing teleportr")
database, err := db.Open(db.Config{
Host: cfg.PostgresHost,
Port: uint16(cfg.PostgresPort),
User: cfg.PostgresUser,
Password: cfg.PostgresPassword,
DBName: cfg.PostgresDBName,
EnableSSL: cfg.PostgresEnableSSL,
})
if err != nil {
return err
}
log.Info("Migrating database")
if err := database.Migrate(); err != nil {
return err
}
log.Info("Done")
return nil
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment