Skip to content

Commit

Permalink
Support HIP-336 Allowance APIs (#3252)
Browse files Browse the repository at this point in the history
* Add an allowance design document
* Add crypto_allowance, nft_allowance and token_allowance & corresponding history tables
* Refactor history tables to use new History interface

Signed-off-by: Steven Sheehy <steven.sheehy@hedera.com>
Signed-off-by: Matheus DallRosa <matheus.dallrosa@swirlds.com>
  • Loading branch information
steven-sheehy authored and matheus-dallrosa committed Feb 21, 2022
1 parent b3b616b commit 867392d
Show file tree
Hide file tree
Showing 63 changed files with 2,337 additions and 252 deletions.
244 changes: 244 additions & 0 deletions docs/design/allowances.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
# HIP-336 Approval and Allowance

## Purpose

[HIP-336](https://hips.hedera.com/hip/hip-336) describes new APIs to approve and exercise allowances to a delegate
account. An allowance grants a spender the right to transfer a predetermined maximum limit of the payer's hbars or
tokens to another account of the spender's choice.

## Goals

* Enhance the database schema to store an account's allowances
* Store the historical state of allowances
* Enhance the REST API to show an account's crypto and token allowances

## Non-Goals

* Store the live state of allowances adjusted for each crypto transfer
* Enhance gRPC APIs with allowance information
* Enhance Web3 APIs with allowance information

## Architecture

### Database

#### Crypto Allowance

```sql
create table if not exists crypto_allowance
(
amount bigint not null,
owner bigint not null,
payer_account_id bigint not null,
spender bigint not null,
timestamp_range int8range not null,
primary key (owner, spender)
);
```

```sql
create table if not exists crypto_allowance_history
(
like crypto_allowance including defaults,
primary key (owner, spender, timestamp_range)
);
```

#### NFT Allowance

```sql
create table if not exists nft_allowance
(
approved_for_all boolean not null,
owner bigint not null,
payer_account_id bigint not null,
serial_numbers bigint[] not null,
spender bigint not null,
timestamp_range int8range not null,
token_id bigint not null,
primary key (owner, spender, token_id)
);
```

```sql
create table if not exists nft_allowance_history
(
like nft_allowance including defaults,
primary key (owner, spender, token_id, timestamp_range)
);
```

#### Token Allowance

```sql
create table if not exists token_allowance
(
amount bigint not null,
owner bigint not null,
payer_account_id bigint not null,
spender bigint not null,
timestamp_range int8range not null,
token_id bigint not null,
primary key (owner, spender, token_id)
);
```

```sql
create table if not exists token_allowance_history
(
like token_allowance including defaults,
primary key (owner, spender, token_id, timestamp_range)
);
```

### REST API

#### Crypto Allowances

`/api/v1/accounts/{accountId}/allowances/crypto`

```json
{
"allowances": [
{
"amount": 10,
"owner": "0.0.1000",
"payer_account_id": "0.0.1000",
"spender": "0.0.8488",
"timestamp": {
"from": "1633466229.96874612",
"to": "1633466568.31556926"
}
},
{
"amount": 5,
"owner": "0.0.1000",
"payer_account_id": "0.0.1001",
"spender": "0.0.9857",
"timestamp": {
"from": "1633466229.96874612",
"to": null
}
}
],
"links": {}
}
```

Optional Filters

* `limit`: The maximum amount of items to return.
* `order`: Order by `spender`. Accepts `asc` or `desc` with a default of `asc`.
* `spender`: Filter by the spender account ID. Only need to support `eq` operator and allow multiple.

#### NFT Allowances

`/api/v1/accounts/{accountId}/allowances/nfts`

```json
{
"allowances": [
{
"approved_for_all": false,
"owner": "0.0.1000",
"payer_account_id": "0.0.1000",
"serial_numbers": [
1,
2,
3
],
"spender": "0.0.8488",
"token_id": "0.0.1032",
"timestamp": {
"from": "1633466229.96874612",
"to": "1633466568.31556926"
}
},
{
"approved_for_all": true,
"owner": "0.0.1000",
"payer_account_id": "0.0.1000",
"serial_numbers": [],
"spender": "0.0.9857",
"token_id": "0.0.1032",
"timestamp": {
"from": "1633466229.96874612",
"to": null
}
}
],
"links": {}
}
```

Optional Filters

* `limit`: The maximum amount of items to return.
* `order`: Order by `spender` and `token_id`. Accepts `asc` or `desc` with a default of `asc`.
* `spender`: Filter by the spender account ID. Only need to support `eq` operator and allow multiple.
* `token.id`: Filter by the token ID. Only need to support `eq` operator and allow multiple.

#### Token Allowances

`/api/v1/accounts/{accountId}/allowances/tokens`

```json
{
"allowances": [
{
"amount": 10,
"owner": "0.0.1000",
"payer_account_id": "0.0.1000",
"spender": "0.0.8488",
"token_id": "0.0.1032",
"timestamp": {
"from": "1633466229.96874612",
"to": "1633466568.31556926"
}
},
{
"amount": 5,
"owner": "0.0.1000",
"payer_account_id": "0.0.1000",
"spender": "0.0.9857",
"token_id": "0.0.1032",
"timestamp": {
"from": "1633466229.96874612",
"to": null
}
}
],
"links": {}
}
```

Optional Filters

* `limit`: The maximum amount of items to return.
* `order`: Order by `spender` and `token_id`. Accepts `asc` or `desc` with a default of `asc`.
* `spender`: Filter by the spender account ID. Only need to support `eq` operator and allow multiple.
* `token.id`: Filter by the token ID. Only need to support `eq` operator and allow multiple.

#### Transactions APIs

Update all APIs that show transfers to return `is_approval` in its response. Including `/api/v1/accounts/:id` and all
the transactions REST APIs.

## Non-Functional Requirements

* Ingest new transaction types at the same rate as consensus nodes

## Open Questions

1) How will we do REST API pagination using multiple columns?

## Answered Questions

1) How will we handle adjust allowance for serial numbers?

The full list of allowed serials that result from the transaction will be provided in the record.

2) What happens if client populates both `approvedForAll` and `serialNumbers`?

It is an error if they populate `approvedForAll=true` and a non-empty `serialNumbers`. It is allowed, but not
required, to populate `approvedForAll=false` when providing a non-empty `serialNumbers`.
26 changes: 12 additions & 14 deletions docs/design/smart-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,9 @@ create table if not exists contract
realm bigint not null,
shard bigint not null,
timestamp_range int8range not null,
type entity_type default 'CONTRACT' not null
type entity_type default 'CONTRACT' not null,
primary key (id)
);

alter table if exists contract
add primary key (id);
```

#### Contract History
Expand Down Expand Up @@ -159,7 +157,7 @@ create table if not exists contract_state_change
);
```

## Importer
### Importer

- Add a `Contract` domain object with fields that match the schema.
- Add a `ContractAccess` domain object with fields that match the schema.
Expand All @@ -179,9 +177,9 @@ create table if not exists contract_state_change
the `Contract` domain object.
- Remove logic specific to contracts in `EntityRecordItemListener`.

## REST API
### REST API

### List Contracts
#### List Contracts

`GET /api/v1/contracts`

Expand Down Expand Up @@ -221,7 +219,7 @@ Optional filters
- `limit`
- `order`

### Get Contract
#### Get Contract

`GET /api/v1/contracts/{id}`

Expand Down Expand Up @@ -254,7 +252,7 @@ Optional filters
- `timestamp` Return the historical state of the contract. Supports all the operators but returns the latest version of
the contract within that time range.

### List Contract Results
#### List Contract Results

`GET /api/v1/contracts/{id}/results`

Expand Down Expand Up @@ -294,7 +292,7 @@ Optional filters
- `timestamp`
- `from`

### Get Contract Result
#### Get Contract Result

`GET /api/v1/contracts/{id}/results/{timestamp}` & `GET /api/v1/contracts/results/{transactionId}`

Expand Down Expand Up @@ -382,7 +380,7 @@ Optional filters
> _Note:_ `/api/v1/contracts/results/{transactionId}` will have to extract the correlating contractId and timestamp to
> retrieve the correct contract_result row
### Get Contract Logs
#### Get Contract Logs

`GET /api/v1/contracts/{id}/results/logs`

Expand Down Expand Up @@ -439,7 +437,7 @@ Optional filters
> _Note3:_ This API will only return logs for the given contract id. It will not return logs
> generated by a child or parent contract.
## JSON-RPC
### JSON-RPC

On the Ethereum network, all client nodes implement
the [Ethereum JSON-RPC Specification](https://playground.open-rpc.org/?schemaUrl=https://raw.githubusercontent.com/ethereum/eth1.0-apis/assembled-spec/openrpc.json)
Expand All @@ -453,7 +451,7 @@ The Mirror Node should implement a subset of the standard calls used to:
- Support existing Ethereum developers who may call the JSON-RPC endpoints directly.
- Encompass Hedera EVM translation logic that can be wrapped by potential Web3 modules.

### Setup
#### Setup

- Create a new Maven module `hedera-mirror-web3`
- Create a new Maven module `hedera-mirror-common` that encompasses all domain POJOs and repositories
Expand All @@ -468,7 +466,7 @@ The Mirror Node should implement a subset of the standard calls used to:
Existing domain classes can be utilized from the `hedera-mirror-common` dependencies. Applicable CRUD repositories can
be created using Spring based on `hedera-mirror-common` domains to extract information from the database.

### JSON-RPC Service
#### Service

- `Web3Service` interface that describes the supported rpc methods
- Implement `Web3Service` for each of the supported RPC methods. Methods query the appropriate tables and return data in
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.hedera.mirror.common.domain;

/*-
* ‌
* Hedera Mirror Node
* ​
* Copyright (C) 2019 - 2022 Hedera Hashgraph, LLC
* ​
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ‍
*/

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.collect.Range;

public interface History {

Range<Long> getTimestampRange();

void setTimestampRange(Range<Long> timestampRange);

@JsonIgnore
default Long getTimestampLower() {
var timestampRange = getTimestampRange();
return timestampRange != null && timestampRange.hasLowerBound() ? timestampRange.lowerEndpoint() : null;
}

default void setTimestampLower(long timestampLower) {
setTimestampRange(Range.atLeast(timestampLower));
}

@JsonIgnore
default Long getTimestampUpper() {
var timestampRange = getTimestampRange();
return timestampRange != null && timestampRange.hasUpperBound() ? timestampRange.upperEndpoint() : null;
}

default void setTimestampUpper(long timestampUpper) {
setTimestampRange(Range.closedOpen(getTimestampLower(), timestampUpper));
}
}
Loading

0 comments on commit 867392d

Please sign in to comment.