Git Product home page Git Product logo

v2-periphery's Introduction

Sablier V2 Periphery Github Actions Coverage Foundry Discord

This repository contains the peripheral smart contracts of the Sablier V2 Protocol. For lower-level logic, see the sablier-labs/v2-core repository.

In-depth documentation is available at docs.sablier.com.

Install

Node.js

This is the recommended approach.

Install Sablier V2 Periphery using your favorite package manager, e.g., with Bun:

bun add @sablier/v2-periphery

Then, if you are using Foundry, add these to your remappings.txt file:

@sablier/v2-core/=node_modules/@sablier/v2-core/
@sablier/v2-periphery/=node_modules/@sablier/v2-periphery/
@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/

Git Submodules

This installation method is not recommended, but it is available for those who prefer it.

First, install the submodule using Forge:

forge install sablier-labs/v2-periphery

Second, you need to install the project's dependencies:

forge install --no-commit sablier-labs/v2-core@release OpenZeppelin/[email protected]

Finally, add these to your remappings.txt file:

@sablier/v2-core/=lib/v2-core/
@sablier/v2-periphery/=lib/v2-periphery/
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/

Security

The codebase has undergone rigorous audits by leading security experts from Cantina, as well as independent auditors. For a comprehensive list of all audits conducted, please click here.

For any security-related concerns, please refer to the SECURITY policy. This repository is subject to a bug bounty program per the terms outlined in the aforementioned policy.

Contributing

Feel free to dive in! Open an issue, start a discussion or submit a PR. For any informal concerns or feedback, please join our Discord server.

For guidance on how to create PRs, see the CONTRIBUTING guide.

License

Sablier V2 Periphery is licensed under GPL v3 or later, except for most of the files in test/, which remain unlicensed (as indicated in their SPDX headers).

v2-periphery's People

Contributors

andreivladbrg avatar iaroslavmazur avatar paulrberg avatar razgraf avatar smol-ninja 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

Watchers

 avatar  avatar

v2-periphery's Issues

Accept standard ERC-20 approvals in "ProxyTarget"

Problem

One of the biggest pain points of using Permit2 with Safe is that all signatories need to sign the permit in a round-robin process.

We had a call with Utopia today to discuss a potential integration, and they said that this is one of the biggest issues with our current periphery setup.

Solution

Allow the periphery to be used via standard ERC-20 approvals.

Proposal

We either:

  1. Extend the current ProxyTarget to accept standard ERC-20 approvals
  2. Turn the current ProxyTarget into an abstract with shared logic, and build two other ProxyTargetPermit2 and ProxyTargetERC20 to implement the Permit2 and ERC-20 variants.

"batchCancelMultiple" test failing with precompiled V2 core

The CI workflows in #31 have failed because of the test_BatchCancelMultiple test.

I did a little bit of investigation, and the issue is related to V2 Core, specifically the precompiled version of it. Switching to normal deployments makes the tests pass again.

Write a script that filters the contract artifacts for deployment to the npm registry

Running FOUNDRY_PROFILE=optimized forge build generates contract artifacts in the optimized-out directory, many of which should not be part of the Node.js package that gets deployed on the npm registry.

We should write a script that filters Forge's output and keeps only those contracts that are not needed in production (e.g. not the scripts).

Figure out a way to prevent direct calls to the plugin

Problem

As pointed out by @andreivladbrg in this comment, and as further explained by me in this reply, there is a low-risk security problem in our current proxy plugin implementation.

The problem is that anyone can call the onStreamCanceled implementation on the plugin, like this:

uint128 senderAmount = 10e18;
uint128 recipientAmount = 0;
ISablierV2LockupSender(address(proxy)).onStreamCanceled(lockupContract, streamId, recipient, senderAmount, recipientAmount);

And thus, anyone can withdraw any amount of ERC-20 assets from the proxy contract to the proxy owner:

https://github.com/sablierhq/v2-periphery/blob/e62b768/src/SablierV2ProxyPlugin.sol#L53

This is a low-risk issue because, generally, there is no incentive to trigger random ERC-20 transfers on behalf of other accounts, but there may be cases when this is an undesirable state of affairs:

  1. Adversarial contexts. The proxy contract may perform a flash loan, and a competing flash borrower who sees their tx in the mempool could block it by withdrawing the assets to their EOA.
  2. Griefing: deliberately blocking someone's proxy contract transactions for no particular benefit.
  3. Tax implications. Withdrawing assets from the proxy contract might be interpreted as a taxable event.

Potential Solution

The lockup address is a user-provided function parameter:

https://github.com/sablierhq/v2-periphery/blob/e62b768026d5df52a3df340cd0df108bf455629e/src/SablierV2ProxyPlugin.sol#L37

This makes it difficult to mitigate the problem. The only solution I can think of now is to add an allowlist of Sablier contract addresses in SablierV2ProxyPlugin and cross-check lockup against it. However, implementing this would also require us to inherit from Adminable and write an admin-gated function for listing new contracts, i.e., addSablierContract, which would be demanding.

Parameterize `permit2` address

As the recent saga with the Permit2 signatures in the front-end has shown, it would be a good idea to delete the hard-coded Permit2 address, and make it a user-provided parameter.

Faulty Permit2 remappings due to lack of context-aware remappings in Foundry

In the latest PR we added re-export types. However, this does not work for permit2 due to the remappings we declared:

permit2/=lib/permit2/src/
permit2-test/=lib/permit2/test/

When attempting to import the permit2 types from v2-periphery, the following error will appears:

image

The solution to this issue involves changing the remappings to:

@permit2/=lib/permit2/src/
@permit2-test/=lib/permit2/test/

Try catch for external call

We are currently using the try/catch statement to call the create functions from the core contracts.

This is a problem because we are transferring a total amount of assets (user --> periphery) before making the external call

https://github.com/sablierhq/v2-periphery/blob/180d5dc194c30cc95db9578a18b9db3b9598300c/src/BatchStream.sol#L94-L95

If a stream creation fails(create multiple will not revert) the amount that should have been transferred (periphery --> core) will remain in our periphery contract.

Simplify mappings in airstream factory

The nested mappings here are overkill:

mapping(
address admin
=> mapping(
IERC20 asset
=> mapping(
bytes32 merkleRoot => mapping(uint40 expiration => ISablierV2AirstreamLockupDynamic airstream)
)
)
) private _lockupDynamicAirstreams;
mapping(
address admin
=> mapping(
IERC20 asset
=> mapping(
bytes32 merkleRoot => mapping(uint40 expiration => ISablierV2AirstreamLockupLinear airstream)
)
)
) private _lockupLinearAirstreams;

  • It's bad UX to have to provide so many inputs to query a user's campaigns
  • There's no benefit in splitting the campaigns by type, i.e., LockupLinear and LockupDynamic. Users are interested in all of their airstream campaigns; the payment type is a low-level detail.
  • There's no point in storing the campaign details (expiration, Merkle tree) in the factory. What @razgraf suggested on the call was to emit these data in an event, not store them.
  • Should there be a need to obtain the campaign's details (expiration, Merkle tree) those can be queried from the subgraph or the Airstream contract itself.
  • Should there be a need for advanced filtering, the subgraph can help with that.

Given the feedback above, the solution is to rewrite the mappings like this:

mapping(address user => ISablierV2Airstream[] contracts) internal _airstreams;

Let's please use internal over private. It would make testing easier.

Further corollaries:

  • There's no point in blocking users from creating similar campaigns, i.e., we can remove the CampaignAlreadyDeployed error and the associated checks in the createAirstream functions. The same Merkle tree can be used for multiple airdrops.
  • We can remove the getAirstreamLockupDynamic and getAirstreamLockupLinear getters, and replace them with a new getter getAirstreams, which returns all of the user's airstream campaigns.

Provide end-to-end deployment scripts

As of recently, the v2-periphery makes use of a SablierV2Archive registry contract to track Sablier deployment.
For a more seamless deployment experience we should attach deployment scrips to v2-periphery that can handle both v2-core instantiation and instance registration in the archive contract.

Chain log with all protocol deployments

Problem

While working on the proxy plugin, we stumbled upon the issue described in #36.

Thinking about a solution, I have discovered another problem - that we offer zero on-chain support to integrators in terms of the ability to fetch the Sablier V2 contract addresses in another smart contract.

Solution

To address this problem, as well as fix #36, we should build something similar to Maker's ChainLog contract. The documentation for Maker's contract can be found here.

Documentation nitpicks

  • Add "Notes" in cancel to explain that assets are automatically refunded
  • Explain in the proxy plugin NatSpec that the Core hooks are what make this design possible
  • Remove the requirements here (they are superfluous)

Rename `SablierV2ChainLog`

We need to find a new name for the SablierV2ChainLog contract:

https://github.com/sablierhq/v2-periphery/blob/main/src/SablierV2ChainLog.sol

Because this is the same name as used by Maker's dss-change-log, which is a versioned chain log system. Whereas our ChainLog is an unversioned "dump" of all Sablier addresses (see my comment here).

Given that someday we may want to implement a similar versioned log, it would be prudent to avoid a potential name collision by renaming the current ChainLog to something else.

Support Withdraw Multiple at target level

In the current form we can suport the withdraw action for both the sender and the recipient, but the withdrawMultiple functionality can be implemented just for recipients. If we add withdrawMultiple in the target we can implement withdrawMultiple for senders as well.

Reuse GitHub workflows across repositories

Find a way to re-use GitHub workflows between this repository (V2 Periphery) and other similar repositories such as V2 Core.

For example, we could write a reusable workflow for the lint and the build jobs.

Add "--verify" flag to all deployment scripts

Once we make this repo public, we will want to pass the --verify flag to all deployment scripts under .github/workflows so that their source code gets verified automatically on Etherscan.

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.