Skip to content

Commit

Permalink
Updated with more GIPs to expand test sample
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew-Clews committed Sep 13, 2024
1 parent 2f21f6e commit 29b8021
Show file tree
Hide file tree
Showing 8 changed files with 853 additions and 0 deletions.
49 changes: 49 additions & 0 deletions GIP-Test/00017-allow-batching-calls-staking-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
GIP: "0017"
Title: Allow batching calls in Staking contract
Authors: Ariel Barmat <[email protected]>
Created: 2021-09-25
Stage: Candidate
Implementations: https://github.com/graphprotocol/contracts/pull/475
---

# Abstract

Expose a way to batch multiple calls into a single transaction. It provides great flexibility for indexer agents to combine multiple functions in different ways. It also reduce the gas cost by saving the initial gas, and in some cases, accessing a "used" slot by the other bundled transactions.

# Specification

A new contract called MultiCall is introduced, inspired by the one used by Uniswap. The payable keyword was removed from the `multicall()` as the protocol does not deal with ETH. Additionally, it is insecure in some instances if the contract relies on msg.value.

The Staking contract inherits from MultiCall that expose a public `multicall(bytes[] calldata data)` function that receives an array of payloads to send to the contract itself. This allows to batch ANY publicly callable contract function.

By using a multicall a user can batch an arbitrary number of operations into a single transaction. The indexer agent can combine close, open, claim transactions in any way it sees more effective.

# Implementation

See [@graphprotocol/contracts#475](https://github.com/graphprotocol/contracts/pull/475)

# Backwards Compatibility

The proposal introduces some breaking changes to save bytecode.

The following functions are removed as they can be constructed using a
multicall:

- closeAllocationMany()
- closeAndAllocate()
- claimMany()

# Validation

### Audits

The implementation was audited by Consensys Diligence.

### Testnet

The implementation has not yet been deployed to Testnet.

# Copyright Waiver

Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
89 changes: 89 additions & 0 deletions GIP-Test/0005-gas-costing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
GIP: "0005"
Title: Gas costing for handlers
Authors: Leonardo Yvens<[email protected]> and Zac Burns <[email protected]>
Created: 2021-05-04
Stage: Candidate
Implementations: https://github.com/graphprotocol/graph-node/pull/2414
---

## Abstract

This GIP proposes that the computational cost of a handler be measured in units of gas and limited to a maximum cost. If a handler attempts to consume more gas than the maximum, the subgraph will fail with a deterministic error. This is necessary so that it is possible to prove that a subgraph cannot be indexed past the block which triggers such a handler.

## Motivation

Subgraph handlers that run for too long are an issue for operators, since they excessively consume resources, and also a issue for protocol security since they make the subgraph impossible to sync in a reasonable time, if it can be synced at all. Graph Node currently implements a timeout for handlers, which is useful to prevent excessive resource consumption, however timeouts are not deterministic. For protocol security, it is necessary to deterministically measure the cost of a handler. This GIP proposes gas costing for handlers, similar to the gas costing for transactions that exists in blockchain protocols.

## Detailed Specification

Gas is defined as the unit of execution cost. As a reference, one unit of gas should correspond to 0.1ns of execution time, making for 10 billion gas in a second. Close correlation with execution time is not a primary goal of gas costing, at least not in this first iteration. Still gas needs to have some correlation to execution time in order to be useful.

The maximum gas cost for a handler should be sufficient for any reasonable subgraph, and be limited by the cost a Fisherman or Arbitrator would be willing to incur in order to check an attestation. The proposed limit is 1000 seconds of execution time, which corresponds to 10 trillion gas.

The costs incurred by executing a handler can be categorized as either WASM execution costs or host function execution costs. WASM execution costs must be measured by injecting a callback at the block, see the documentation for this technique in the [pwasm-utils crate](https://docs.rs/pwasm-utils/0.17.1/pwasm_utils/fn.inject_gas_counter.html). For the gas costing of individual WASM instructions, refer to the implementation in [this commit](https://github.com/graphprotocol/graph-node/blob/30720a8f1fb074b71fe76729d4e0dc4ba3c3e955/runtime/wasm/src/gas_rules.rs).

The cost of most host functions is calculated as a base cost added to a cost per byte of input. To
account for host functions that have non-linear complexity, the input bytes are first combined
through a complexity function and then multiplied by the cost per byte. To calculate the size of the
input bytes, a `gas_size_of` function exists for each data type that is an input to a host
function, see implementations of `GasSizeOf` in the reference implementation for details. Note that
`gas_size_of` is an approximation of the true in-memory size of the data, the actual size may vary
between platforms and implementations but still this approximation should be sufficient for gas
costing purposes.

#### Constants


##### Table 1:

| Constant | Value (in gas) | Description |
| ---------------------- | ------------------------------------ | ------------------------------------------------------------ |
| GAS_PER_SECOND | 10_000_000_000 | Relates time and gas units. |
| MAX_GAS_PER_HANDLER | 3600 * GAS_PER_SECOND | The limit is one hour worth of gas. |
| HOST_EXPORT_GAS | 10_000 | Cost of the callback that measures gas. |
| DEFAULT_BASE_COST | 100_000 | Base cost of most host functions. |
| DEFAULT_GAS_PER_BYTE | 1_000 | Equivalent to 10MB/s, so 36GB in the 1 hour limit. |
| BIG_MATH_GAS_PER_BYTE | 100 | Equivalent to 100MB/s. |
| ETHEREUM_CALL | 25_000_000_000 | Gas cost of a contract call. Allows for 400 contract calls. |
| CREATE_DATA_SOURCE | MAX_GAS_PER_HANDLER / 100_000 | Cost of `dataSource.create`. |
| LOG_GAS | MAX_GAS_PER_HANDLER / 100_000 | Base cost of `log.log`. |
| STORE_SET_BASE_COST | MAX_GAS_PER_HANDLER / 250_000 | Base cost of `store.set`. |
| STORE_SET_GAS_PER_BYTE | MAX_GAS_PER_HANDLER / 1_000_000_000 | Allows 1GB of entity data for `store.set`. |
| STORE_GET_BASE_COST | MAX_GAS_PER_HANDLER / 10_000_000 | Base cost of `store.get`. |
| STORE_GET_GAS_PER_BYTE | MAX_GAS_PER_HANDLER / 1_000_000_000 | Allows 10GB of entity data for `store.get`. |

#### Costs for host functions

This section specifies the costs attributed to each host function.

Most host functions have linear complexity on a single parameter `N` and are costed as:

````
DEFAULT_BASE_COST + gas_size_of(N)*DEFAULT_GAS_PER_BYTE
````

Host functions that differ from this are listed in the table below:

| Host function | Cost |
| --------------------------- | ------------------------------------------------------------ |
| abort | DEFAULT_BASE_COST |
| store.set(key, data) | STORE_SET_BASE_COST + STORE_SET_GAS_PER_BYTE * (gas_size_of(key) + gas_size_of(data)) |
| store.remove(key) | STORE_SET_BASE_COST + STORE_SET_GAS_PER_BYTE * gas_size_of(key) |
| store.get(key) -> data | STORE_GET_BASE_COST + STORE_GET_GAS_PER_BYTE * (gas_size_of(key) + gas_size_of(data)) |
| ethereum.call | ETHEREUM_CALL |
| dataSource.create | CREATE_DATA_SOURCE |
| log.log(m) | LOG_GAS + DEFAULT_GAS_PER_BYTE * gas_size_of(m) |
| bigInt.plus(x, y) | DEFAULT_BASE_COST + BIG_MATH_GAS_PER_BYTE * max(gas_size_of(x), gas_size_of(y)) |
| bigInt.minus(x, y) | DEFAULT_BASE_COST + BIG_MATH_GAS_PER_BYTE * max(gas_size_of(x), gas_size_of(y)) |
| bigInt.times(x, y) | DEFAULT_BASE_COST + BIG_MATH_GAS_PER_BYTE * gas_size_of(x) * gas_size_of(y) |
| bigInt.divided_by(x, y) | DEFAULT_BASE_COST + BIG_MATH_GAS_PER_BYTE * gas_size_of(x) * gas_size_of(y) |
| bigInt.mod(x, y) | DEFAULT_BASE_COST + BIG_MATH_GAS_PER_BYTE * gas_size_of(x) * gas_size_of(y) |
| bigInt.pow(x, exp) | DEFAULT_BASE_COST + BIG_MATH_GAS_PER_BYTE * gas_size_of(x) ^ exp |
| bigInt.bit_or(x, y) | DEFAULT_BASE_COST + BIG_MATH_GAS_PER_BYTE * max(gas_size_of(x), gas_size_of(y)) |
| bigInt.bit_and(x, y) | DEFAULT_BASE_COST + BIG_MATH_GAS_PER_BYTE * min(gas_size_of(x), gas_size_of(y)) |
| bigDecimal.plus(x, y) | DEFAULT_BASE_COST + BIG_MATH_GAS_PER_BYTE * (gas_size_of(x) + gas_size_of(y)) |
| bigDecimal.minus(x, y) | DEFAULT_BASE_COST + BIG_MATH_GAS_PER_BYTE * (gas_size_of(x) + gas_size_of(y)) |
| bigDecimal.times(x, y) | DEFAULT_BASE_COST + BIG_MATH_GAS_PER_BYTE * gas_size_of(x) * gas_size_of(y) |
| bigDecimal.divided_by(x, y) | DEFAULT_BASE_COST + BIG_MATH_GAS_PER_BYTE * gas_size_of(x) * gas_size_of(y) |
| bigDecimal.equals(x, y) | DEFAULT_BASE_COST + BIG_MATH_GAS_PER_BYTE * min(gas_size_of(x), gas_size_of(y)) |
104 changes: 104 additions & 0 deletions GIP-Test/0006-gip-withdraw-helper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
GIP: "0006"
Title: Withdraw helper contract to support collecting funds from Vector channels.
Authors: Ariel Barmat <[email protected]>
Created: 2021-05-21
Stage: Candidate
Implementations: https://github.com/graphprotocol/contracts/pull/454
---

# Abstract

Consumers in the Graph Network set up state channels for sending query fees to Indexers using Vector. [Vector](https://github.com/connext/vector) is an ultra-simple, flexible state channel protocol implementation. This proposal introduces a contract required to encode custom logic in the process of withdrawing query fees from Vector state channels. The **GRTWithdrawHelper** contract receives query fees from a Vector state channel, then it forwards them to the **Staking** contract where they are distributed according to the protocol economics.

# Motivation

Vector state channels are contracts that hold funds used as collateral for consumer-to-indexer payments. When an Indexer closes an allocation, both parties sign a withdrawal commitment to transfer the query fees accumulated for the allocation back to the network. Vector, by default, supports plain transfers from the channel to any destination defined in the withdrawal commitment. In the case of the Graph Network, all funds should be collected by calling the collect() function in the Staking contract. For the purpose of calling custom logic when transferring from channels, Vector supports using a **WithdrawHelper** contract. In this proposal, we describe the implementation of such a contract for connecting a Vector withdrawal transfer to The Graph smart contracts.

# Detailed Specification

We created a contract called **GRTWithdrawHelper** that extends the base **WithdrawHelper** provided by the Vector framework. This contract has a function `execute(WithdrawData _wd, uint256 _actualAmount) external` that is called in the same transaction when the indexer withdraw funds from the channel. The execute function evaluates the validity of the `WithdrawData` struct, and then approve and transfer the received funds to the **Staking** contract by calling `collect()`.

### Transaction Flow

```sequence
Channel->GRTWithdrawHelper: transfer(_actualAmount)
Channel->GRTWithdrawHelper: execute(WithdrawData _wd, uint256 _actualAmount)
GRTWithdrawHelper->Staking: approve(_actualAmount)
GRTWithdrawHelper->Staking: collect(_actualAmount, allocationID)
Staking->GRTWithdrawHelper: transferFrom(_actualAmount)
```

## Features

- The **GRTWithdrawHelper** is deployed with the token address that will be used for transfers, in our case it is the GRT token address. It can't be changed at later stage but this can easily be solved deploying a new contract if needed.
- The contract expects to have available tokens in balance when `execute(WithdrawData calldata wd, uint256 actualAmount)` is called by the Channel. For that to happen the Channel needs to transfer funds to the **GRTWithdrawHelper** before execute is called.
- On execute, the **GRTWithdrawHelper** will approve the funds to send to the Staking contract and then execute `collect()` passing the proper allocationID and amount. The Staking contract will then pull the tokens.
- If the call to `collect()` fails, the funds are returned to a return address. This is important for Vector accounting of funds.

## Requirements

- Set the **GRTWithdrawHelper** contract as **AssetHolder** in the **Staking** contract. This way we allow the network contracts to accept funds from this contract.
- The cooperative withdrawal commitments must have the following data (indicated with arrows):

```
struct WithdrawData {
address channelAddress;
address assetId; // -> GRT token address
address payable recipient; // -> GRTWithdrawHelper contract address
uint256 amount;
uint256 nonce;
address callTo; // -> GRTWithdrawHelper contract address
bytes callData; // -> CollectData struct defined below
}
```

```
struct CollectData {
address staking; // -> Staking contract address
address allocationID; // -> AllocationID to send collected funds
address returnAddress; // -> Address where to return funds in case of errors
}
```

The parties must agree and sign the WithdrawalCommitment with that information to achieve proper execution of the withdrawals.

## Exceptions

The following conditions will result in reverts of the execution:

- `assetId` in `WithdrawData` not matching the token address configured in the **GRTWithdrawHelper**.
- **GRTWithdrawHelper** not having enough balance to transfer the **Staking** contract as stated in `actualAmount`.
- Setting an invalid staking contract address.

Note: To avoid unexpected conditions in the Vector off-chain logic, it is encouraged that the parties verify the WithdrawData does not revert by doing a call/estimateGas.

## Handled Exceptions

These reverts are handled and will make the contract to return the funds to the return address.

- Doing a withdrawal for a non-existing `allocationID` as defined in `CollectData` when calling `collect()`.

## Assumptions

- This contract does not know if a `WithdrawalCommitment` is correct, it just accepts a transfer and `WithdrawData` that defines a proper destination.

- A properly formatted `WithdrawData` can be crafted to send outside funds to the **Staking** contract. In that case, the person will get those funds distributed with Curators + Delegators + taxed a protocol percentage. There is no rational economic incentive to do so.

# Backwards Compatibility

The contract in this proposal is new and peripheral to the rest of the network contracts. No breaking change is introduced. The Graph Council will need to set the **GRTWithdrawHelper** as an **AssetHolder** in the Staking contract.

# Validation

## Audits

An audit of the changes described in this document was performed by ChainSafe (see [`assets/gip-0006/TheGraph-GRTWithdrawHelper-Audit.pdf`](../assets/gip-0006/TheGraph-GRTWithdrawHelper-Audit.pdf)).

## Testnet

The implementation was deployed and tested on Rinkeby testnet. https://rinkeby.etherscan.io/address/0xe5fa88135c992a385aaa1c65a0c1b8ff3fde1fd4

# Copyright Waiver

Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
Loading

0 comments on commit 29b8021

Please sign in to comment.