Giter Site home page Giter Site logo

gnosis / gp-v2-contracts Goto Github PK

View Code? Open in Web Editor NEW
160.0 14.0 53.0 4.74 MB

Smart contracts for the Gnosis Protocol v2

License: GNU Lesser General Public License v3.0

Solidity 18.08% TypeScript 80.99% JavaScript 0.15% Shell 0.73% Dockerfile 0.05%
hacktoberfest

gp-v2-contracts's Introduction

โš ๏ธ The team that worked on this project has spun out of Gnosis and continues development on a forked repo (01-04-2022) which is available here https://github.com/cowprotocol/contracts

Gnosis Protocol V2

This repository contains the Solidity smart contract code for the Gnosis Protocol version 2. For more documentation on how the protocol works on a smart contract level, see the documentation pages.

Getting Started

Building the Project

yarn
yarn build

Running Tests

yarn test

The tests can be run in "debug mode" as follows:

DEBUG=* yarn test

Gas Reporter

Gas consumption can be reported by setting the REPORT_GAS flag when running tests as

REPORT_GAS=1 yarn test

Benchmarking

This repository additionally includes tools for gas benchmarking and tracing.

In order to run a gas benchmark on a whole bunch of settlement scenarios:

yarn bench

These gas benchmarks can be compared against any other git reference and will default to the merge-base if omitted:

yarn bench:compare [<ref>]

In order to get a detailed trace of a settlement to identify how much gas is being spent where:

yarn bench:trace

Deployment

Contracts deployment (including contract verification) is run automatically with GitHub Actions. The deployment process is triggered manually. Maintainers of this repository can deploy a new version of the contract in the "Actions" tab, "Deploy GPv2 contracts", "Run workflow". The target branch can be selected before running. A successful workflow results in a new PR asking to merge the deployment artifacts into the main branch.

Contracts can also be deployed and verified manually as follows.

Deploying Contracts

Choose the network and gas price in wei for the deployment. After replacing these values, run:

NETWORK='rinkeby'
GAS_PRICE_WEI='1000000000'
yarn deploy --network $NETWORK --gasprice $GAS_PRICE_WEI

New files containing details of this deployment will be created in the deployment folder. These files should be committed to this repository.

Verify Deployed Contracts

Etherscan

For verifying all deployed contracts:

export ETHERSCAN_API_KEY=<Your Key>
yarn verify:etherscan --network $NETWORK

Tenderly

For verifying all deployed contracts:

yarn verify:tenderly --network $NETWORK

For a single contract, named GPv2Contract and located at address 0xFeDbc87123caF3925145e1bD1Be844c03b36722f in the example:

npx hardhat tenderly:verify --network $NETWORK GPv2Contract=0xFeDbc87123caF3925145e1bD1Be844c03b36722f

Deployed Contract Addresses

This package additionally contains a networks.json file at the root with the address of each deployed contract as well the hash of the Ethereum transaction used to create the contract.

Test coverage Coverage Status

Test coverage can be checked with the command

yarn coverage

A summary of coverage results are printed out to console. More detailed information is presented in the generated file coverage/index.html.

Solver Authentication

This repo contains scripts to manage the list of authenticated solvers in all networks the contract has been deployed.

The scripts are called with:

yarn solvers command [arg ...]

Here is a list of available commands. The commands flagged with [*] require the private key of the authentication contract owner to be available to the script, for example by exporting it with export PK=<private key>.

  1. add $ADDRESS [*]. Adds the address to the list of registered solvers.
  2. remove $ADDRESS [*]. Removes the address from the list of registered solvers.
  3. check $ADDRESS. Checks if the given address is in the list of registered solvers.

For example, adding the address 0x0000000000000000000000000000000000000042 to the solver list:

export PK=<private key>
yarn solvers add 0x0000000000000000000000000000000000000042

Fee Withdrawals

Script to withdraw all balances of the Settlement contract. Allows to specify what minimum value the contract must have for a token to be considered (breadcrumbs might not be worth the gas costs) and how much remaining value should be left in the contract (e.g. to feed token buffers).

If no token list is passed in all traded token balances will be fetched from chain (can take a long time...)

export PK=<private key>
yarn hardhat withdraw --receiver 0x6C2999B6B1fAD608ECEA71B926D68Ee6c62BeEf8 --min-value 10000 --leftover 500 0x038a68ff68c393373ec894015816e33ad41bd564 0x913d8adf7ce6986a8cbfee5a54725d9eea4f0729

Decoding Settlement CallData

This project exposes some handy scripts for parsing settlement calldata into human readable format.

The decode script can be used in two ways:

  1. By specifying the transaction hash of an existing settlement transaction --txhash 0x...
npx hardhat decode --txhash 0xc12e5bc2ef9c116932301495738d555ea1d658977dacd6c7989a6d77125a17d2 --network mainnet
  1. When no txhash is specified, by reading the calldata from stdin (< calldata.txt). If stdin is a terminal, the user is prompted to paste the calldata into the terminal.
> npx hardhat decode --network mainnet
# Paste in the calldata to decode

Note that you will be expected to have your INFURA_KEY exported to your environment variables.

gp-v2-contracts's People

Contributors

alfetopito avatar bh2smith avatar cgewecke avatar dependabot-preview[bot] avatar dependabot[bot] avatar fedgiac avatar fleupold avatar github-actions[bot] avatar josojo avatar nlordell avatar transmissions11 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gp-v2-contracts's Issues

Increment nonce per settled batch

The nonce should be incremented for each batch. Preferably, it should be done right at the start (after computing the pair address so that the global nonce for a pair can be accessed) to protect against re-entrancy attacks.

Smart contract interactions with guaranteed limit price

Limit prices for smart contract orders (#122) can't be guaraneed with checks in the settlement contract. To ensure fairness, we want to give the option to a smart contract order of knowing the exact settlement price in the batch without the need of trusting the solvers.
We should check if a specific kind of function is called in a smart contract order (for example settle(uint256,uint256,bytes)) and have the settlement contract call the function including the right settlement price without involving the solver.
Determining the specific format and data of the "special" function is part of the task.

Access Control Mechanism for Transfering Fees

Fees should only be transferred out by authorized parties. Although, as @fedgiac pointed out, we can possibly do this with settle function, so we may not even need to add this (or we can just use the same access mechanism that is used by the settle function).

Investigate the scalability for current matching system

Write e2e tests for different order sizes and test how many orders can be settled for how much gas. This insight will be valuable to think about further use-cases like IDOs with semi-trusted operators.

One particularity is that solidity only allows stack-deepness of ?18? and it should be investigated whether this cases issues with our recursive implementation.

Test cases: integer overflow

Test behavior of settlement function when integer overflow occurs in various places of the settlement function.

Add `eth_signTypedData` Support

This would allow wallets that support eth_signTypedData to display order information in full to the user at signing time, so they know exactly what they are signing instead of just a bunch of bytes.

Implement transfering fees to operator

From a quick glance, it doesn't look like the operator is receiving fees. We could also be smart about it and have the fees be paid in the surplus token, so we trade even less with Uniswap.

This issue also captures the work of determining what exact fee mechanism we would like to use.

Emit logs

The settlement contract could potentially emit trade and clearing price logs.

Logs that can be included:

  • Trade
  • Interaction
  • Settlement
  • Order Cancellation

Make sure orders are sorted

In order to very quickly check, whether an order violates the uniform clearing price, orders are assumed to be sorted by limit price. We need to also check it in the smart contract

Settlement contract integration tests.

Currently, only unit tests are setup. Once they are setup, test cases such as this should be part of the integration test suite and not unit test suite.

https://github.com/gnosis/oba-contracts/blob/a769ffffc8bb6559f6333284d48a9eebc7185967/test/GPv2Settlement.test.ts#L66-L98

The integration tests should:

  • Deploy OZ ERC20 tokens
  • Deploy Uniswap factory and pair
  • Settle a batch with and without Uniswap
  • Assert side effects:
    • Nonce is incremented
    • Uniswap reserves are updated (or not when not trading against Uniswap).
    • Orders have correct new balances
    • Contract has >= 0 balances for both tokens (fees)
    • ๐ŸŽ‰ and ๐Ÿค‘

Update to `Hardhat`

buidler has been re-branded to hardhat, we should update our repo to use the new npm module (as the buidler one is deprecated).

Replace compile time constants.

There are many instances of constants that can be pre-computed but aren't done automatically by the compiler that should replaced with their values for deployment:

  • EIP712Domain type hash (GPv2Settlment constructor).
  • Order type hash (GPv2Encoding constant, with local variable for compatibility with inline assembly).

Order deletion on expiration

We plan to invalidate an order after its execution by the solver by storing an "executed" flag in memory.
Orders with an expiration date in the past don't need to have the "used" flag set, as they can't be replayed anymore. There should be a way to use these expired orders as a form of "gas token" that can be released on request.

Use assembly calldata support for decoding orders.

This would be a MAJOR gas saver over using abi.decode as we do now but would require updating to Solidity v0.7.5.

I ran this example (which is just the calldata reading part of the order decoding and already had very promising results:

Decode.sol
// SPDX-License-Identifier: LGPL-3.0-or-newer
pragma solidity ^0.7.0;
pragma abicoder v2;

contract Decode {
    struct Order {
        uint8 sellTokenIndex;
        uint8 buyTokenIndex;
        uint256 sellAmount;
        uint256 buyAmount;
        uint32 validTo;
        uint32 nonce;
        uint256 tip;
        uint8 flags;
        uint256 executedAmount;
        uint8 v;
        bytes32 r;
        bytes32 s;
    }
    
    function test() external view returns (uint256 gasAbi, uint256 gasAsm) {
        (gasAbi, gasAsm) = this.testData(hex"ff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacb");
    }
    
    function testData(bytes calldata data) external view returns (uint256 gasAbi, uint256 gasAsm) {
        Order memory orderAbi;
        (orderAbi, gasAbi) = testAbi(data);
        
        Order memory orderAsm;
        (orderAsm, gasAsm) = testAsm(data);
        
        require(orderAbi.sellTokenIndex == orderAsm.sellTokenIndex, "sellTokenIndex");
        require(orderAbi.buyTokenIndex == orderAsm.buyTokenIndex, "buyTokenIndex");
        require(orderAbi.sellAmount == orderAsm.sellAmount, "sellAmount");
        require(orderAbi.buyAmount == orderAsm.buyAmount, "buyAmount");
        require(orderAbi.validTo == orderAsm.validTo, "validTo");
        require(orderAbi.nonce == orderAsm.nonce, "nonce");
        require(orderAbi.tip == orderAsm.tip, "tip");
        require(orderAbi.flags == orderAsm.flags, "flags");
        require(orderAbi.executedAmount == orderAsm.executedAmount, "executedAmount");
        require(orderAbi.v == orderAsm.v, "v");
        require(orderAbi.r == orderAsm.r, "r");
        require(orderAbi.s == orderAsm.s, "s");
    }
    
    function testAbi(bytes calldata data) private view returns (Order memory order, uint256 gas_) {
        gas_ = gasleft();
        
        require(data.length == 204);
        
        order.sellTokenIndex = uint8(data[0]);
        order.buyTokenIndex = uint8(data[1]);
        order.sellAmount = abi.decode(data[2:], (uint256));
        order.buyAmount = abi.decode(data[34:], (uint256));
        order.validTo = uint32(
            abi.decode(data[66:], (uint256)) >> (256 - 32)
        );
        order.nonce = uint32(
            abi.decode(data[70:], (uint256)) >> (256 - 32)
        );
        order.tip = abi.decode(data[74:], (uint256));
        order.flags = uint8(data[106]);
        order.executedAmount = abi.decode(data[107:], (uint256));
        order.v = uint8(data[139]);
        order.r = abi.decode(data[140:], (bytes32));
        order.s = abi.decode(data[172:], (bytes32));
        
        gas_ = gas_ - gasleft();
    }
    
    function testAsm(bytes calldata data) private view returns (Order memory order, uint256 gas_) {
        gas_ = gasleft();
        
        require(data.length == 204);
        assembly {
            mstore(order, shr(248, calldataload(data.offset)))
            mstore(add(order, 32), shr(248, calldataload(add(data.offset, 1))))
            mstore(add(order, 64), calldataload(add(data.offset, 2)))
            mstore(add(order, 96), calldataload(add(data.offset, 34)))
            mstore(add(order, 128), shr(224, calldataload(add(data.offset, 66))))
            mstore(add(order, 160), shr(224, calldataload(add(data.offset, 70))))
            mstore(add(order, 192), calldataload(add(data.offset, 74)))
            mstore(add(order, 224), shr(248, calldataload(add(data.offset, 106))))
            mstore(add(order, 256), calldataload(add(data.offset, 107)))
            mstore(add(order, 288), shr(248, calldataload(add(data.offset, 139))))
            mstore(add(order, 320), calldataload(add(data.offset, 140)))
            mstore(add(order, 352), calldataload(add(data.offset, 172)))
        }
        
        gas_ = gas_ - gasleft();
    }
}

Reading from calldata went from:

  • without solc optimizer: from 3984 gas/order using abi.decode to 350 gas/order using assembly { calldataload }
  • with solc optimizer: from 2106 gas/order using abi.decode to 342 gas/order using assembly { calldataload }

The same order can be matched multiple times

Two related issues on how the nonce of an order is handled.

The same order can be included multiple times in the list of orders matched in the batch, meaning that any order is in practice an unlimited order. This is because there is no check on whether the same nonce is reused in a batch. See
parseOrderBytes.

Similarly, if two signed orders with different, valid nonces are included for the same user, then both orders can be executed so that the order with the highest nonce is still valid in the next batch. It's enough to execute the order with the highest nonce first and then the one with the lowest. This is because the early, higher nonce is later overwritten by the later, lower nonce in markSettledOrders.

Separate Withdrawal Contract

In order to safely guard access to user balances, EOAs should approve a withdrawal contract which is responsible for withdrawing sell amounts for verified orders.

The withdrawal contract should be:

  • Created in the Settlement contract constructor.
  • Only be callable by the Settlement contract.

Enforce gas limits when calling external contracts

All calls to external contracts (like transfer, retrieving balances, calls to Uniswap) should be enforced to use a limited amount of gas.
Orders that reach this threshold should be discarded.
We could also introduce a fee that the users pay if their orders are discarded.

Setup gas benchmarking

This would allow us to see the impact of changes on gas costs of on-chain settlement.

Use Ethereum Signature Prefix

Ethereum transactions and messages (at least with the Ethereum JSON RPC method) use a ``"\x19Ethereum Signed Message:\n" + len(message)` prefix to messages when signing.

This issue captures the work of investigating if we should require this scheme as well for signed orders.

On-chain order cancellation

Implement on-chain order cancellation (incrementing a nonce for example). This is required so users can cancel orders without trusting that the operator will just stop using the signature.

Trade settlement mechanism

The trade settlement mechanism requires:

  1. Transferring user funds from their address into the contract
  2. Transferring contract funds from the contract to the users' addresses
  3. Check that the amount of fees remaining is correct.

Transfer Tips

Tips should be derived from order and included in the transfer from users into settlement contract.

On-chain filtering of orders that don't meet limit price

Between solving and the transaction being executed, the price movement is unpredictable and unavoidable. The smart contract should be able to intelligently filter out orders that do not overlap with the Uniswap exchange rate instead of failing.

Settlement contract order decoding

The settlement contract should decode the orders for processing. As we have decided to use tightly packed order encoding (see #30) this process is non-trivial as there is no Solidity built-in for it.

Verify clearing price.

The settlement contract needs to verify that the settlement price respects the Uniswap AMM price. The Uniswap price to use depends on whether the settlement required trading with the AMM:

  • If it did not, then the Uniswap spot price is used.
  • If it did, then the Uniswap effective price is used.

For either price, we need to verify that the clearing price + OBA fees is better than or equal to the Uniswap price.

More information can be found in the spec.

Use EIP-712 signing hash as order digest.

The idea proposed by @rmeissner is to use the EIP-712 signing hash as the order.digest value. This would make the order digest as seen by the contract unique per domain (therefore also unique per chain and settlement contract address).

This has the added benefit that we would only be wrapping the EIP-712 signing digest instead of having a separate signing scheme (which may be a bit confusing). Additionally, @rmeissner also mentioned that some wallets seem to keccak256 the message before signing, so this proposal would work in that situation as well. Furthermore, this would make this signature scheme more closely match the one in the Gnosis Safe, which is nice for consistency and the fact it is battle-tested.

The main downside is an additional keccak256 operation for recovering only in the eth_sign case.

Use `SafeERC20` .

We should use SafeERC20 library so that we can handle the different ERC20 behaviour that exists out there.

Verify each order's signature

Each order's signature needs to verified in the settlement contract. Currently this is being done in the price calculation contract PreAMMBatcher.sol but should move to the settlement contract.

Determine where to execute gas refunds

There used to be concerns on when exectly to free memory when using a gas token (see for example here). We should determine if this still applies for modern Solidity code and in case free memory at the right time.
This issue is particularly relevant for #124.

Withdrawer contract

The withdrawer contract stores user's approvals away from the settlement contract. This contract should be owned by the settlement contract and only called once at the beginning of settle to recover the funds that will be traded in the batch.

Implement deployment process

Maybe with buidler or truffle, the contracts should be deployable and we should investigate how to share these deployments with other projects.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.