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

audit(fees): specification audit #685

Merged
merged 12 commits into from
Jun 15, 2022
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ Ref: https://keepachangelog.com/en/1.0.0/

## Unreleased

### Improvements

- (fees) [\#685](https://github.com/tharsis/evmos/pull/685) Internal Specification audit.

## [v5.0.0] - 2022-06-14

### State Machine Breaking
Expand Down
5 changes: 3 additions & 2 deletions x/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ Here are some production-grade modules that can be used in Evmos applications, a
- [claims](claims/spec/README.md) - Rewards status and claiming process for the mainnet release.
- [epochs](epochs/spec/README.md) - Executes custom state transitions every period (*aka* epoch).
- [erc20](erc20/spec/README.md) - Trustless, on-chain bidirectional internal conversion of tokens between Evmos' EVM and Cosmos runtimes.
- [evm](https://github.com/tharsis/ethermint/blob/main/x/evm/spec/README.md) - Smart Contract deployment and execution on Cosmos
- [feemarket](https://github.com/tharsis/ethermint/blob/main/x/feemarket/spec/README.md) - Fee market implementation based on the EIP1559 specification.
- [evm](https://docs.evmos.org/modules/evm/) - Smart Contract deployment and execution on Cosmos
- [feemarket](https://docs.evmos.org/modules/feemarket/) - Fee market implementation based on the EIP1559 specification.
- [fees](fees/spec/README.md) - Split EVM transaction fees between block proposer and smart contract developers.
- [incentives](incentives/spec/README.md) - Incentivize user interaction with governance-approved smart contracts.
- [inflation](inflation/spec/README.md) - Mint tokens and allocate them to staking rewards, usage incentives and community pool.
- [vesting](vesting/spec/README.md) - Vesting accounts with lockup and clawback capabilities.
40 changes: 17 additions & 23 deletions x/fees/spec/01_concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,37 @@ order: 1

# Concepts

## EOA
## Evmos dApp Store

An Externally Owned Account ([EOA](https://ethereum.org/en/whitepaper/#ethereum-accounts)) is an account controlled by a private key, that can sign transactions.
The Evmos dApp store is a revenue-per-transaction model, which allows developers to get payed for deploying their decentralized application (dApps) on Evmos. Developers generate revenue, every time a user interacts with their dApp in the dApp store, gaining them a steady income. Users can discover new applications in the dApp store and pay for the transaction fees that finance the dApp's revenue. This value-reward exchange of dApp services for transaction fees is implemented by the `x/fees` module.

## Deployer Address
## Registration

The EOA address that deployed the smart contract being registered for fee distribution.
Developers register their application in the dApp store by registering their application's smart contracts. Any contract can be registered by a developer by submitting a signed transaction. The signer of this transaction must match the address of the deployer of the contract in order for the registration to succeed. After the transaction is executed successfully, the developer will start receiving a portion of the transaction fees paid when a user interacts with the registered contract.

## Withdraw Address
## Fee Distribution

The address set by a contract deployer to receive transaction fees for a registered smart contract. If not set, it defaults to the deployer’s address.
As described above, developers will earn a portion of the transaction fee after registering their contracts. To understand how transaction fees are distributed, we look at the following two things in detail:

## Developer
* The transactions eligible are only [EVM transactions](https://docs.evmos.org/modules/evm/) (`MsgEthereumTx`). Cosmos SDK transactions are not eligible at this time.
* The registration of factory contracts (smart contracts that have been deployed by other contracts) requires the identification original contract's deployer. This is done through address derivation.

The entity that has control over the deployer account.
### EVM Transaction Fees

## Registration of a Contract
Users pay transaction fees to pay interact with smart contracts using the EVM. When a transaction is executed, the entire fee amount (`gasLimit * gasPrice`) is sent to the `FeeCollector` module account during the [Cosmos SDK AnteHandler](https://docs.cosmos.network/v0.44/modules/auth/03_antehandlers.html) execution. After the EVM executes the transaction, the user receives a refund of `(gasLimit - gasUsed) * gasPrice`. In result a user pays a total transaction fee of `txFee = gasUsed * gasPrice` for the execution.

Any contract can be registered by a developer by submitting a signed transaction. The signer of this transaction must match the address of the deployer of the contract in order for the registration to succeed. After the transaction is executed successfully, the developer will start receiving a portion of the transaction fees paid when a user interacts with the registered contract.

### Fee Distribution

As described above, developers will earn a portion of the transaction fee after they register their contracts. The transactions eligible are only EVM transactions (`MsgEthereumTx`). Cosmos SDK transactions are not eligible at this time.

#### EVM Transaction Fees

When a transaction is executed, the entire fee amount `gasLimit * gasPrice` is sent to the `FeeCollector` Module Account during the `AnteHandler` execution. After the EVM executes the transaction, the user receives a refund of `(gasLimit - gasUsed) * gasPrice`.

Therefore, the user only pays for the execution: `txFee = gasUsed * gasPrice`. This is the transaction fee distributed between developers and validators, in accordance with the `x/fees` module parameters: `DeveloperShares`, `ValidatorShares`. This distribution is handled through the `PostTxProcessing` [Hook](./05_hooks.md).
This transaction fee is distributed between developers and validators, in accordance with the `x/fees` module parameters: `DeveloperShares`, `ValidatorShares`. This distribution is handled through the EVM's [`PostTxProcessing` Hook](./05_hooks.md).

### Address Derivation

When registering a smart contract, the deployer provides an array of nonces, used to [derive the contract’s address](https://github.com/ethereum/go-ethereum/blob/d8ff53dfb8a516f47db37dbc7fd7ad18a1e8a125/crypto/crypto.go#L107-L111). The smart contract can be directly deployed by the deployer's EOA or created through one or more [factory](https://en.wikipedia.org/wiki/Factory_method_pattern) pattern smart contracts.
dApp developers might use a [factory pattern](https://en.wikipedia.org/wiki/Factory_method_pattern) to implement their application logic through smart contracts. In this case a smart contract can be either deployed by an Externally Owned Account ([EOA](https://ethereum.org/en/whitepaper/#ethereum-accounts): an account controlled by a private key, that can sign transactions) or through another contract.

In both cases, the fee distribution requires the identification a deployer address that is an EOA address, unless a withdrawal address is set by the contract deployer during registration to receive transaction fees for a registered smart contract. If a withdrawal address is not set, it defaults to the deployer’s address.

If `MyContract` is deployed directly by `DeployerEOA`, in a transaction sent with nonce `5`, then the array of nonces is `[5]`.
The identification of the deployer address is done through address derivation. When registering a smart contract, the deployer provides an array of nonces, used to [derive the contract’s address](https://github.com/ethereum/go-ethereum/blob/d8ff53dfb8a516f47db37dbc7fd7ad18a1e8a125/crypto/crypto.go#L107-L111):

If the contract was created by a smart contract, through the `CREATE` opcode, we need to provide all the nonces from the creation path. Let's take the example of `DeployerEOA` deploying a `FactoryA` smart contract with nonce `5`. Then, `DeployerEOA` sends a transaction to `FactoryA` through which a `FactoryB` smart contract is created. Let us assume `FactoryB` is the second contract created by `FactoryA` - the nonce is `2`. Then, `DeployerEOA` sends a transaction to the `FactoryB` contract, through which `MyContract` is created. Let us assume this is the first contract created by `FactoryB` - the nonce is `1`. We now have an address derivation path of `DeployerEOA` -> `FactoryA` -> `FactoryB` -> `MyContract`. To be able to verify that `DeployerEOA` can register `MyContract`, we need to provide the following nonces: `[5, 2, 1]`.
* If `MyContract` is deployed directly by `DeployerEOA`, in a transaction sent with nonce `5`, then the array of nonces is `[5]`.
* If the contract was created by a smart contract, through the `CREATE` opcode, we need to provide all the nonces from the creation path. E.g. if `DeployerEOA` deploys a `FactoryA` smart contract with nonce `5`. Then, `DeployerEOA` sends a transaction to `FactoryA` through which a `FactoryB` smart contract is created. If we assume `FactoryB` is the second contract created by `FactoryA`, then `FactoryA`'s nonce is `2`. Then, `DeployerEOA` sends a transaction to the `FactoryB` contract, through which `MyContract` is created. If this is the first contract created by `FactoryB` - the nonce is `1`. We now have an address derivation path of `DeployerEOA` -> `FactoryA` -> `FactoryB` -> `MyContract`. To be able to verify that `DeployerEOA` can register `MyContract`, we need to provide the following nonces: `[5, 2, 1]`.

::: tip
**Note**: Even if `MyContract` is created from `FactoryB` through a transaction sent by an account different from `DeployerEOA`, only `DeployerEOA` can register `MyContract`.
Expand Down
6 changes: 3 additions & 3 deletions x/fees/spec/02_state.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ The `x/fees` module keeps the following objects in state:

### DeployerAddress

Deployer address for a registered contract.
A `DeployerAddress` is the EOA address for a registered contract.

### WithdrawAddress

Address that will receive transaction fees for a registered contract.
The `WithdrawAddress` is the address that receives transaction fees for a registered contract.

### ContractAddresses

Slice of contract addresses registered by a developer.
`ContractAddresses` defines a slice of all contract addresses registered by a deployer.

## Genesis State

Expand Down
29 changes: 14 additions & 15 deletions x/fees/spec/03_state_transitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,45 @@ order: 3

# State Transitions

## Client-Side

The `x/fees` module allows for three types of state transitions: `RegisterDevFeeInfo`, `UpdateDevFeeInfo` and `CancelDevFeeInfo`. The logic for *distributing transaction fees*, is handled through [Hooks](./05_hooks.md).
The `x/fees` module allows for three types of state transitions: `RegisterDevFeeInfo`, `UpdateDevFeeInfo` and `CancelDevFeeInfo`. The logic for distributing transaction fees is handled through [Hooks](./05_hooks.md).

### Fee Info Registration

A developer registers a contract for receiving transaction fees, defining the contract address, an array of nonces for deriving the contract address from the EOA address deploying the contract and an optional withdraw address for sending the fees.

If the withdraw address is not set, the fees will be sent to the deployer address by default.
A developer registers a contract for receiving transaction fees, defining the contract address, an array of nonces for [address deriviation](01_concepts.md#address-derivation) and an optional withdraw address for receiving fees. If the withdraw address is not set, the fees are sent to the deployer address by default.

1. User submits a `RegisterDevFeeInfo` to register a contract address, along with a withdraw address that they would like to receive the fees to
2. The following checks must pass:
2. Check if the following conditions pass:
1. `x/fees` module is enabled
2. the contract was not previously registered
3. deployer has a valid account (it has done at least one transaction) and is not a smart contract
4. an account corresponding to the contract address exists, with a non-empty bytecode
5. contract address can be derived from the deployer’s address and provided nonces using the `CREATE` operation
3. An instance of the provided fee information is stored
4. All transactions sent to the registered contract occurring after registration will have their fees distributed to the developer, according to the global `DeveloperShares` parameter
3. Store an instance of the provided fee information.

All transactions sent to the registered contract occurring after registration will have their fees distributed to the developer, according to the global `DeveloperShares` parameter.

### Fee Info Update

A developer updates the withdraw address for a registered contract, defining the contract address and the new withdraw address.

1. User submits a `UpdateDevFeeInfo`
2. The following checks must pass:
2. Check if the following conditions pass:
1. `x/fees` module is enabled
2. the contract is registered
3. the signer of the transaction is the same as the contract deployer
3. The fee information is updated with the new withdraw address.
4. The developer receives the fees from transactions occurring after this update, on the new withdraw address
3. Update the fee information with the new withdraw address

After this update, the developer receives the fees on the new withdraw address.

### Fee Info Cancel

A developer cancels receiving fees for a registered contract, defining the contract address.

1. User submits a `CancelDevFeeInfo`
2. The following checks must pass:
2. Check if the following conditions pass:
1. `x/fees` module is enabled
2. the contract is registered
3. the signer of the transaction is the same as the contract deployer
3. The fee information is removed from storage
4. The developer no longer receives fees from transactions sent to this contract
3. Remove fee information from storage

The developer no longer receives fees from transactions sent to this contract.
24 changes: 12 additions & 12 deletions x/fees/spec/04_transactions.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ type MsgRegisterDevFeeInfo struct {

The message content stateless validation fails if:

- Contract address is invalid
- Contract address is zero
- Deployer address is invalid
- Withdraw address is invalid
- Contract hex address is invalid
- Contract hex address is zero
- Deployer bech32 address is invalid
- Withdraw bech32 address is invalid
- Nonces array is empty

### `MsgUpdateDevFeeInfo`
Expand All @@ -51,11 +51,11 @@ type MsgUpdateDevFeeInfo struct {

The message content stateless validation fails if:

- Contract address is invalid
- Contract address is zero
- Deployer address is invalid
- Withdraw address is invalid
- Withdraw address is same as deployer address
- Contract hex address is invalid
- Contract hex address is zero
- Deployer bech32 address is invalid
- Withdraw bech32 address is invalid
- Withdraw bech32 address is same as deployer address

### `MsgCancelDevFeeInfo`

Expand All @@ -72,6 +72,6 @@ type MsgCancelDevFeeInfo struct {

The message content stateless validation fails if:

- Contract address is invalid
- Contract address is zero
- Deployer address is invalid
- Contract hex address is invalid
- Contract hex address is zero
- Deployer bech32 address is invalid
20 changes: 11 additions & 9 deletions x/fees/spec/05_hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,25 @@ order: 5

# Hooks

The fees module implements one transaction hook, from the `x/evm` module.
The fees module implements one transaction hook from the `x/evm` module in order to distribute fees between developers and validators.

## EVM Hook

An [EVM hook](https://evmos.dev/modules/evm/06_hooks.html) executes custom logic after each successful EVM transaction.

All fees paid by a user for transaction execution are sent to the `FeeCollector` Module Account during the `AnteHandler` execution.
An [`PostTxProcessing` EVM hook](https://evmos.dev/modules/evm/06_hooks.html) executes custom logic after each successful EVM transaction. All fees paid by a user for transaction execution are sent to the `FeeCollector` module account during the `AnteHandler` execution before being distributed to developers and validators.

If the `x/fees` module is disabled or the EVM transaction targets an unregistered contract, the EVM hook returns `nil`, without performing any actions. In this case, 100% of the transaction fees remain in the `FeeCollector` module, to be distributed to the block proposer.

If the `x/fees` module is enabled, the EVM hook sends a percentage of the fees paid by the user for a transaction to a registered contract, to the withdraw address set for that contract, or to the contract deployer.
If the `x/fees` module is enabled and a EVM transaction tragets a registered contract, the EVM hook sends a percentage of the transaction fees (paid by the user) to the withdraw address set for that contract, or to the contract deployer.

1. User submits an EVM transaction to a smart contract that has been registered to receive fees and the transaction is finished successfully.
2. The EVM hook’s `PostTxProcessing` method is called on the fees module. It is passed the initial transaction message, that includes the gas price paid by the user and the transaction receipt, which includes the gas used by the transaction. The hook calculates
1. User submits EVM transaction (`MsgEthereumTx`) to a smart contract and transaction is executed successfully
2. Check if
* fees module is enabled
* smart contract is registered to receive fees
3. Calculate developer fees according to the `DeveloperShares` parameter. The initial transaction message includes the gas price paid by the user and the transaction receipt, which includes the gas used by the transaction.

```go
```go
devFees := receipt.GasUsed * msg.GasPrice * params.DeveloperShares
```

and sends these dev fees from the `FeeCollector` (Cosmos SDK `auth` module account) to the registered withdraw address for that contract. The remaining amount in the `FeeCollector` is allocated towards the [SDK Distribution Scheme](https://docs.cosmos.network/main/modules/distribution/03_begin_block.html#the-distribution-scheme). If there is no withdraw address, fees are sent to contract deployer's address.
4. Transfer developer fee from the `FeeCollector` (Cosmos SDK `auth` module account) to the registered withdraw address for that contract. If there is no withdraw address, fees are sent to contract deployer's address.
5. Distribute the remaining amount in the `FeeCollector` to validators according to the [SDK Distribution Scheme](https://docs.cosmos.network/main/modules/distribution/03_begin_block.html#the-distribution-scheme).
21 changes: 11 additions & 10 deletions x/fees/spec/08_clients.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@ evmosd query fees params

### Queries

| Command | Subcommand | Description |
| :------------- | :------------ | :----------------------------- |
| `query` `fees` | `params` | Get fees params |
| `query` `fees` | `fee-info` | Get registered fee info |
| `query` `fees` | `fee-infos` | Get all registered fee infos |
| Command | Subcommand | Description |
| :------------- | :------------------- | :------------------------------------------------ |
| `query` `fees` | `params` | Get fees params |
| `query` `fees` | `fee-info` | Get registered fee info |
| `query` `fees` | `fee-infos` | Get all registered fee infos |
| `query` `fees` | `fee-infos-deployer` | Get all contracts that a deployer has registered |

### Transactions

| Command | Subcommand | Description |
| :---------- | :-------------- | :----------------------------------------- |
| `tx` `fees` | `register-fee` | Register a contract for receiving fees |
| `tx` `fees` | `update-fee` | Update the withdraw address for a contract |
| `tx` `fees` | `cancel-fee` | Remove the fee info for a contract |
| Command | Subcommand | Description |
| :---------- | :------------- | :----------------------------------------- |
| `tx` `fees` | `register-fee` | Register a contract for receiving fees |
| `tx` `fees` | `update-fee` | Update the withdraw address for a contract |
| `tx` `fees` | `cancel-fee` | Remove the fee info for a contract |

## gRPC

Expand Down
Loading