Commit 019657db authored by smartcontracts's avatar smartcontracts Committed by GitHub

feat(ctp): move Teleportr contracts to periphery (#2985)

Copies TeleportrDeposit and TeleportrDisburser to the contracts-periphery
package in preparation for an upcoming audit.
Co-authored-by: default avatarmergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
parent 0001b453
---
'@eth-optimism/contracts-periphery': patch
---
Add TeleportrDeposit and TeleportrDisburser to contracts-periphery
......@@ -4,8 +4,8 @@ import { ethers } from 'hardhat'
import { getChainId } from '@eth-optimism/core-utils'
import { predeploys } from '@eth-optimism/contracts'
import Artifact__TestERC721 from '@eth-optimism/contracts-periphery/artifacts/contracts/testing/helpers/TestERC721.sol/TestERC721.json'
import Artifact__L1ERC721Bridge from '@eth-optimism/contracts-periphery/artifacts/contracts/L1/messaging/L1ERC721Bridge.sol/L1ERC721Bridge.json'
import Artifact__L2ERC721Bridge from '@eth-optimism/contracts-periphery/artifacts/contracts/L2/messaging/L2ERC721Bridge.sol/L2ERC721Bridge.json'
import Artifact__L1ERC721Bridge from '@eth-optimism/contracts-periphery/artifacts/contracts/L1/L1ERC721Bridge.sol/L1ERC721Bridge.json'
import Artifact__L2ERC721Bridge from '@eth-optimism/contracts-periphery/artifacts/contracts/L2/L2ERC721Bridge.sol/L2ERC721Bridge.json'
import Artifact__OptimismMintableERC721Factory from '@eth-optimism/contracts-periphery/artifacts/contracts/universal/op-erc721/OptimismMintableERC721Factory.sol/OptimismMintableERC721Factory.json'
import Artifact__OptimismMintableERC721 from '@eth-optimism/contracts-periphery/artifacts/contracts/universal/op-erc721/OptimismMintableERC721.sol/OptimismMintableERC721.json'
......
......@@ -9,7 +9,7 @@ import {
} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { L2ERC721Bridge } from "../../L2/messaging/L2ERC721Bridge.sol";
import { L2ERC721Bridge } from "../L2/L2ERC721Bridge.sol";
import { Semver } from "@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol";
/**
......
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.9;
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;
}
}
......@@ -9,8 +9,8 @@ import {
} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { L1ERC721Bridge } from "../../L1/messaging/L1ERC721Bridge.sol";
import { IOptimismMintableERC721 } from "../../universal/op-erc721/IOptimismMintableERC721.sol";
import { L1ERC721Bridge } from "../L1/L1ERC721Bridge.sol";
import { IOptimismMintableERC721 } from "../universal/op-erc721/IOptimismMintableERC721.sol";
import { Semver } from "@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol";
/**
......
// 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;
}
/**
* @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.9;
contract FailingReceiver {
receive() external payable {
require(false, "FailingReceiver");
}
}
......@@ -10,11 +10,8 @@ import {
} from '@defi-wonderland/smock'
import ICrossDomainMessenger from '@eth-optimism/contracts/artifacts/contracts/libraries/bridge/ICrossDomainMessenger.sol/ICrossDomainMessenger.json'
import { expect } from '../../../setup'
import {
NON_NULL_BYTES32,
NON_ZERO_ADDRESS,
} from '../../../../../contracts/test/helpers'
import { NON_NULL_BYTES32, NON_ZERO_ADDRESS } from '../../helpers'
import { expect } from '../../setup'
const ERR_INVALID_MESSENGER = 'OVM_XCHAIN: messenger contract unauthenticated'
const ERR_INVALID_X_DOMAIN_MSG_SENDER =
......
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)
})
})
})
......@@ -5,11 +5,8 @@ import { smock, FakeContract } from '@defi-wonderland/smock'
import ICrossDomainMessenger from '@eth-optimism/contracts/artifacts/contracts/libraries/bridge/ICrossDomainMessenger.sol/ICrossDomainMessenger.json'
import { toRpcHexString } from '@eth-optimism/core-utils'
import { expect } from '../../../setup'
import {
NON_NULL_BYTES32,
NON_ZERO_ADDRESS,
} from '../../../../../contracts/test/helpers'
import { NON_NULL_BYTES32, NON_ZERO_ADDRESS } from '../../helpers'
import { expect } from '../../setup'
const ERR_ALREADY_INITIALIZED = 'Initializable: contract is already initialized'
const ERR_INVALID_MESSENGER = 'OVM_XCHAIN: messenger contract unauthenticated'
......
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
)
})
})
})
export const NON_NULL_BYTES32 = '0x' + '11'.repeat(32)
export const NON_ZERO_ADDRESS = '0x' + '11'.repeat(20)
export * from './deploy'
export * from './solidity'
export * from './constants'
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