Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[payment] transactor ping payment vault contract #827

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contracts/bindings/AVSDirectory/binding.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion contracts/bindings/BLSApkRegistry/binding.go

Large diffs are not rendered by default.

213 changes: 5 additions & 208 deletions contracts/bindings/DelegationManager/binding.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion contracts/bindings/EigenDAServiceManager/binding.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion contracts/bindings/EjectionManager/binding.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion contracts/bindings/IndexRegistry/binding.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion contracts/bindings/MockRollup/binding.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion contracts/bindings/OperatorStateRetriever/binding.go

Large diffs are not rendered by default.

1,868 changes: 1,868 additions & 0 deletions contracts/bindings/PaymentVault/binding.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion contracts/bindings/RegistryCoordinator/binding.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion contracts/bindings/StakeRegistry/binding.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion contracts/compile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function create_binding {
forge clean
forge build

contracts="AVSDirectory DelegationManager BitmapUtils OperatorStateRetriever RegistryCoordinator BLSApkRegistry IndexRegistry StakeRegistry BN254 EigenDAServiceManager IEigenDAServiceManager MockRollup EjectionManager"
contracts="PaymentVault AVSDirectory DelegationManager BitmapUtils OperatorStateRetriever RegistryCoordinator BLSApkRegistry IndexRegistry StakeRegistry BN254 EigenDAServiceManager IEigenDAServiceManager MockRollup EjectionManager"
for contract in $contracts; do
create_binding ./ $contract ./bindings
done
Expand Down
52 changes: 52 additions & 0 deletions contracts/src/interfaces/IPaymentVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

interface IPaymentVault {

struct Reservation {
uint64 symbolsPerSecond; // Number of symbols reserved per second
uint64 startTimestamp; // timestamp of epoch where reservation begins
uint64 endTimestamp; // timestamp of epoch where reservation ends
bytes quorumNumbers; // quorum numbers in an ordered bytes array
bytes quorumSplits; // quorum splits in a bytes array that correspond to the quorum numbers
}

/// @notice Emitted when a reservation is created or updated
event ReservationUpdated(address indexed account, Reservation reservation);
/// @notice Emitted when an on-demand payment is created or updated
event OnDemandPaymentUpdated(address indexed account, uint256 onDemandPayment, uint256 totalDeposit);
/// @notice Emitted when minChargeableSize is updated
event MinChargeableSizeUpdated(uint256 previousValue, uint256 newValue);
/// @notice Emitted when globalSymbolsPerSecond is updated
event GlobalSymbolsPerSecondUpdated(uint256 previousValue, uint256 newValue);
/// @notice Emitted when pricePerSymbol is updated
event PricePerSymbolUpdated(uint256 previousValue, uint256 newValue);

/**
* @notice This function is called by EigenDA governance to store reservations
* @param _account is the address to submit the reservation for
* @param _reservation is the Reservation struct containing details of the reservation
*/
function setReservation(
address _account,
Reservation memory _reservation
) external;

/**
* @notice This function is called to deposit funds for on demand payment
* @param _account is the address to deposit the funds for
*/
function depositOnDemand(address _account) external payable;

/// @notice Fetches the current reservation for an account
function getReservation(address _account) external view returns (Reservation memory);

/// @notice Fetches the current reservations for a set of accounts
function getReservations(address[] memory _accounts) external view returns (Reservation[] memory _reservations);

/// @notice Fetches the current total on demand balance of an account
function getOnDemandAmount(address _account) external view returns (uint256);

/// @notice Fetches the current total on demand balances for a set of accounts
function getOnDemandAmounts(address[] memory _accounts) external view returns (uint256[] memory _payments);
}
119 changes: 119 additions & 0 deletions contracts/src/payments/PaymentVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
import {PaymentVaultStorage} from "./PaymentVaultStorage.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
* @title Entrypoint for making reservations and on demand payments for EigenDA.
* @author Layr Labs, Inc.
**/
contract PaymentVault is PaymentVaultStorage, OwnableUpgradeable {

constructor(
uint256 _reservationBinInterval,
uint256 _reservationBinStartTimestamp,
uint256 _priceUpdateCooldown
) PaymentVaultStorage(
_reservationBinInterval,
_reservationBinStartTimestamp,
_priceUpdateCooldown
){
_disableInitializers();
}

function initialize(
address _initialOwner,
uint256 _minChargeableSize,
uint256 _globalSymbolsPerSecond,
uint256 _pricePerSymbol
) public initializer {
transferOwnership(_initialOwner);
minChargeableSize = _minChargeableSize;
globalSymbolsPerSecond = _globalSymbolsPerSecond;
pricePerSymbol = _pricePerSymbol;
}

/**
* @notice This function is called by EigenDA governance to store reservations
* @param _account is the address to submit the reservation for
* @param _reservation is the Reservation struct containing details of the reservation
*/
function setReservation(
address _account,
Reservation memory _reservation
) external onlyOwner {
_checkQuorumSplit(_reservation.quorumNumbers, _reservation.quorumSplits);
reservations[_account] = _reservation;
emit ReservationUpdated(_account, _reservation);
}

/**
* @notice This function is called to deposit funds for on demand payment
* @param _account is the address to deposit the funds for
*/
function depositOnDemand(address _account) external payable {
onDemandPayments[_account] += msg.value;
emit OnDemandPaymentUpdated(_account, msg.value, onDemandPayments[_account]);
}

function setMinChargeableSize(uint256 _minChargeableSize) external onlyOwner {
require(block.timestamp >= lastPriceUpdateTime + priceUpdateCooldown, "price update cooldown not surpassed");
emit MinChargeableSizeUpdated(minChargeableSize, _minChargeableSize);
lastPriceUpdateTime = block.timestamp;
minChargeableSize = _minChargeableSize;
}

function setGlobalSymbolsPerSecond(uint256 _globalSymbolsPerSecond) external onlyOwner {
emit GlobalSymbolsPerSecondUpdated(globalSymbolsPerSecond, _globalSymbolsPerSecond);
globalSymbolsPerSecond = _globalSymbolsPerSecond;
}

function setPricePerSymbol(uint256 _pricePerSymbol) external onlyOwner {
emit PricePerSymbolUpdated(pricePerSymbol, _pricePerSymbol);
pricePerSymbol = _pricePerSymbol;
}

function withdraw(uint256 _amount) external onlyOwner {
(bool success,) = payable(owner()).call{value: _amount}("");
require(success);
}

function withdrawERC20(address _token, uint256 _amount) external onlyOwner {
IERC20(_token).transfer(owner(), _amount);
}

function _checkQuorumSplit(bytes memory _quorumNumbers, bytes memory _quorumSplits) internal pure {
require(_quorumNumbers.length == _quorumSplits.length, "arrays must have the same length");
uint8 total;
for(uint256 i; i < _quorumSplits.length; ++i) total += uint8(_quorumSplits[i]);
require(total == 100, "sum of quorumSplits must be 100");
}

/// @notice Fetches the current reservation for an account
function getReservation(address _account) external view returns (Reservation memory) {
return reservations[_account];
}

/// @notice Fetches the current reservations for a set of accounts
function getReservations(address[] memory _accounts) external view returns (Reservation[] memory _reservations) {
_reservations = new Reservation[](_accounts.length);
for(uint256 i; i < _accounts.length; ++i){
_reservations[i] = reservations[_accounts[i]];
}
}

/// @notice Fetches the current total on demand balance of an account
function getOnDemandAmount(address _account) external view returns (uint256) {
return onDemandPayments[_account];
}

/// @notice Fetches the current total on demand balances for a set of accounts
function getOnDemandAmounts(address[] memory _accounts) external view returns (uint256[] memory _payments) {
_payments = new uint256[](_accounts.length);
for(uint256 i; i < _accounts.length; ++i){
_payments[i] = onDemandPayments[_accounts[i]];
}
}
}
40 changes: 40 additions & 0 deletions contracts/src/payments/PaymentVaultStorage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import {IPaymentVault} from "../interfaces/IPaymentVault.sol";

abstract contract PaymentVaultStorage is IPaymentVault {

/// @notice reservation bin duration
uint256 public immutable reservationBinInterval;
/// @notice start timestamp of reservation bins
uint256 public immutable reservationBinStartTimestamp;
/// @notice cooldown period before the price can be updated again
uint256 public immutable priceUpdateCooldown;

constructor(
uint256 _reservationBinInterval,
uint256 _reservationBinStartTimestamp,
uint256 _priceUpdateCooldown
){
reservationBinInterval = _reservationBinInterval;
reservationBinStartTimestamp = _reservationBinStartTimestamp;
priceUpdateCooldown = _priceUpdateCooldown;
}

/// @notice minimum chargeable size for on-demand payments
uint256 public minChargeableSize;
/// @notice maximum number of symbols to disperse per second network-wide for on-demand payments (applied to only ETH and EIGEN)
uint256 public globalSymbolsPerSecond;
/// @notice price per symbol in wei
uint256 public pricePerSymbol;
/// @notice timestamp of the last price update
uint256 public lastPriceUpdateTime;

/// @notice mapping from user address to current reservation
mapping(address => Reservation) public reservations;
/// @notice mapping from user address to current on-demand payment
mapping(address => uint256) public onDemandPayments;

uint256[44] private __GAP;
}
10 changes: 2 additions & 8 deletions core/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"math/big"

"github.com/Layr-Labs/eigenda/common"
paymentvault "github.com/Layr-Labs/eigenda/contracts/bindings/PaymentVault"
"github.com/Layr-Labs/eigenda/encoding"
"github.com/consensys/gnark-crypto/ecc/bn254"
"github.com/ethereum/go-ethereum/crypto"
Expand Down Expand Up @@ -515,14 +516,7 @@ func (pm *PaymentMetadata) Hash() []byte {

// OperatorInfo contains information about an operator which is stored on the blockchain state,
// corresponding to a particular quorum
type ActiveReservation struct {
SymbolsPerSec uint64 // reserve number of symbols per second
StartTimestamp uint64 // Unix timestamp that's valid for basically eternity
EndTimestamp uint64

QuorumNumbers []uint8 // allowed quorums
QuorumSplit []byte // ordered mapping of quorum number to payment split; on-chain validation should ensure split <= 100
}
type ActiveReservation = paymentvault.IPaymentVaultReservation

type OnDemandPayment struct {
CumulativePayment *big.Int // Total amount deposited by the user
Expand Down
73 changes: 61 additions & 12 deletions core/eth/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
ejectionmg "github.com/Layr-Labs/eigenda/contracts/bindings/EjectionManager"
indexreg "github.com/Layr-Labs/eigenda/contracts/bindings/IIndexRegistry"
opstateretriever "github.com/Layr-Labs/eigenda/contracts/bindings/OperatorStateRetriever"
paymentvault "github.com/Layr-Labs/eigenda/contracts/bindings/PaymentVault"
regcoordinator "github.com/Layr-Labs/eigenda/contracts/bindings/RegistryCoordinator"
stakereg "github.com/Layr-Labs/eigenda/contracts/bindings/StakeRegistry"

Expand Down Expand Up @@ -55,6 +56,7 @@ type ContractBindings struct {
EigenDAServiceManager *eigendasrvmg.ContractEigenDAServiceManager
EjectionManager *ejectionmg.ContractEjectionManager
AVSDirectory *avsdir.ContractAVSDirectory
PaymentVault *paymentvault.ContractPaymentVault
}

type BN254G1Point struct {
Expand Down Expand Up @@ -763,24 +765,71 @@ func (t *Transactor) GetRequiredQuorumNumbers(ctx context.Context, blockNumber u
return requiredQuorums, nil
}

func (t *Transactor) GetActiveReservations(ctx context.Context, blockNumber uint32, accountIDs []string) (map[string]core.ActiveReservation, error) {
// contract is not implemented yet
return map[string]core.ActiveReservation{}, nil
func (t *Transactor) GetActiveReservations(ctx context.Context, accountIDs []string) (map[string]core.ActiveReservation, error) {
// map accountIDs to addresses
accountAddresses := make([]gethcommon.Address, len(accountIDs))
for i, accountID := range accountIDs {
accountAddresses[i] = gethcommon.HexToAddress(accountID)
}

reservations_map := make(map[string]core.ActiveReservation)
reservations, err := t.Bindings.PaymentVault.GetReservations(&bind.CallOpts{
Context: ctx,
}, accountAddresses)
if err != nil {
return nil, err
}

// since reservations are returned in the same order as the accountIDs, we can directly map them
for i, reservation := range reservations {
reservations_map[accountIDs[i]] = reservation
}
return reservations_map, nil
}

func (t *Transactor) GetActiveReservationByAccount(ctx context.Context, blockNumber uint32, accountID string) (core.ActiveReservation, error) {
// contract is not implemented yet
return core.ActiveReservation{}, nil
func (t *Transactor) GetActiveReservationByAccount(ctx context.Context, accountID string) (core.ActiveReservation, error) {
reservation, err := t.Bindings.PaymentVault.GetReservation(&bind.CallOpts{
Context: ctx,
}, gethcommon.HexToAddress(accountID))
if err != nil {
return core.ActiveReservation{}, err
}
return reservation, nil
}

func (t *Transactor) GetOnDemandPayments(ctx context.Context, blockNumber uint32, accountIDs []string) (map[string]core.OnDemandPayment, error) {
// contract is not implemented yet
return map[string]core.OnDemandPayment{}, nil
func (t *Transactor) GetOnDemandPayments(ctx context.Context, accountIDs []string) (map[string]core.OnDemandPayment, error) {
// map accountIDs to addresses
accountAddresses := make([]gethcommon.Address, len(accountIDs))
for i, accountID := range accountIDs {
accountAddresses[i] = gethcommon.HexToAddress(accountID)
}
payments_map := make(map[string]core.OnDemandPayment)
payments, err := t.Bindings.PaymentVault.GetOnDemandAmounts(&bind.CallOpts{
Context: ctx,
}, accountAddresses)
if err != nil {
return nil, err
}

// since payments are returned in the same order as the accountIDs, we can directly map them
for i, payment := range payments {
payments_map[accountIDs[i]] = core.OnDemandPayment{
CumulativePayment: payment,
}
}
return payments_map, nil
}

func (t *Transactor) GetOnDemandPaymentByAccount(ctx context.Context, blockNumber uint32, accountID string) (core.OnDemandPayment, error) {
// contract is not implemented yet
return core.OnDemandPayment{}, nil
func (t *Transactor) GetOnDemandPaymentByAccount(ctx context.Context, accountID string) (core.OnDemandPayment, error) {
onDemandPayment, err := t.Bindings.PaymentVault.GetOnDemandAmount(&bind.CallOpts{
Context: ctx,
}, gethcommon.HexToAddress(accountID))
if err != nil {
return core.OnDemandPayment{}, err
}
return core.OnDemandPayment{
CumulativePayment: onDemandPayment,
}, nil
}

func (t *Transactor) updateContractBindings(blsOperatorStateRetrieverAddr, eigenDAServiceManagerAddr gethcommon.Address) error {
Expand Down
2 changes: 1 addition & 1 deletion core/meterer/meterer.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,5 +286,5 @@ func (m *Meterer) IncrementGlobalBinUsage(ctx context.Context, symbolsCharged ui

// GetReservationBinLimit returns the bin limit for a given reservation
func (m *Meterer) GetReservationBinLimit(reservation *core.ActiveReservation) uint64 {
return reservation.SymbolsPerSec * uint64(m.ReservationWindow)
return reservation.SymbolsPerSecond * uint64(m.ReservationWindow)
}
4 changes: 2 additions & 2 deletions core/meterer/meterer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ func setup(_ *testing.M) {
now := uint64(time.Now().Unix())
accountID1 = crypto.PubkeyToAddress(privateKey1.PublicKey).Hex()
accountID2 = crypto.PubkeyToAddress(privateKey2.PublicKey).Hex()
account1Reservations = core.ActiveReservation{SymbolsPerSec: 100, StartTimestamp: now + 1200, EndTimestamp: now + 1800, QuorumSplit: []byte{50, 50}, QuorumNumbers: []uint8{0, 1}}
account2Reservations = core.ActiveReservation{SymbolsPerSec: 200, StartTimestamp: now - 120, EndTimestamp: now + 180, QuorumSplit: []byte{30, 70}, QuorumNumbers: []uint8{0, 1}}
account1Reservations = core.ActiveReservation{SymbolsPerSecond: 100, StartTimestamp: now + 1200, EndTimestamp: now + 1800, QuorumSplits: []byte{50, 50}, QuorumNumbers: []uint8{0, 1}}
account2Reservations = core.ActiveReservation{SymbolsPerSecond: 200, StartTimestamp: now - 120, EndTimestamp: now + 180, QuorumSplits: []byte{30, 70}, QuorumNumbers: []uint8{0, 1}}
account1OnDemandPayments = core.OnDemandPayment{CumulativePayment: big.NewInt(3864)}
account2OnDemandPayments = core.OnDemandPayment{CumulativePayment: big.NewInt(2000)}

Expand Down
Loading
Loading