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

docs: Add SphinxEscrow contract spec #1693

Closed
wants to merge 1 commit into from
Closed

Conversation

RPate97
Copy link
Collaborator

@RPate97 RPate97 commented Apr 25, 2024

Purpose

Adds the spec for the SphinxEscrow contract.

@RPate97 RPate97 force-pushed the pate/escrow-spec branch 2 times, most recently from a0c1c57 to ee22fa0 Compare April 25, 2024 04:00
Copy link
Collaborator

@sam-goldman sam-goldman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks solid. Got some questions / comments

@@ -0,0 +1,60 @@
# `SphinxEscrow` Contract Specification
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General question: should this contract be deployed on every chain we support or a select number of chains?


When deploying via the Sphinx DevOps Platform, the user has the option to pay for their deployments using ERC20 tokens on a single network. Before the deployment begins, the user must grant an allowance on an ERC20 token to the `SphinxEscrow` contract that is sufficient to cover the estimated cost of the deployment. The ERC20 token is then locked in the `SphinxEscrow` contract while the deployment is being executed. After the deployment is completed, the Sphinx backend calculates the exact cost of the deployment and transfers the remainder back to the user.

The Sphinx team owns the `SphinxEscrow` contract.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should consider calling this contract something other than SphinxEscrow based on our conversation with the lawyer today

## Function-Level Invariants

#### `constructor(address _owner)`
- Must grant the `DEFAULT_ADMIN_ROLE` to the specified owner address.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we're using OpenZeppelin's AccessControl contract, but I only see the DEFAULT_ADMIN_ROLE used as a permission (in the disburse function). If we're planning on only having a single owner of this contract, we should use Ownable instead of AccessControl. If, instead, we're planning on allowing multiple addresses to withdraw funds, I think we should give them a specified role like we do in the ManagedService contract instead of giving them all the DEFAULT_ADMIN_ROLE. I think only one address should have the DEFAULT_ADMIN_ROLE because this address has permission to add or remove any other roles. This means that if we have multiple addresses with the DEFAULT_ADMIN_ROLE and we leak only one of their private keys, an attacker could remove all owners

Comment on lines +37 to +45
#### `function claim(address _owner, address _erc20, uint256 _amount)`
- Must revert if `_amount` is greater than the allowance of the `SphinxEscrow` contract on `_erc20` for `_owner`.
- Must revert if the `_owner` does not have a balance of `_erc20` that is >= `_amount`.
- A successful call must:
- Transfer the `_amount` of `_erc20` to the `SphinxEscrow` contract.
- Increment the `escrowBalance` of `_erc20` for `_owner` by `_amount`.
- Emit a `Claimed` event in the `SphinxEscrow` contract.

#### `function disburse(address _owner, address _feeRecipient, address _erc20, uint256 _refund, uint256 _cost)`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In both of these functions, the _owner parameter is the user, right? The semantics made me confused because the contract itself is owned by us

Comment on lines +38 to +39
- Must revert if `_amount` is greater than the allowance of the `SphinxEscrow` contract on `_erc20` for `_owner`.
- Must revert if the `_owner` does not have a balance of `_erc20` that is >= `_amount`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fyi these invariants may be enforced by the ERC20 standard itself, so it may be redundant to include these checks in our spec/contract. You don't need to worry about that though - I'll investigate this when I implement this contract

- Emit a `Claimed` event in the `SphinxEscrow` contract.

#### `function disburse(address _owner, address _feeRecipient, address _erc20, uint256 _refund, uint256 _cost)`
- Must revert if the amount of `_erc20` currently in escrow for `_owner` is < `_refund` + `_cost`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Must revert if the amount of _erc20 currently in escrow for _erc20 is < _refund + _cost

This invariant allows for the possibility that only a fraction of the escrowed _erc20 is sent to the _owner or _feeRecipient. For example, if the escrowed _erc20 amount is 20 USDC, and _refund and _cost are both 5, then an error isn't thrown by this function. Is that intentional? I could see this being useful if the user wants to keep a balance of ERC20 for all deployments instead of adding a new allowance for each deployment. However, this use case isn't mentioned in the "Use Case" section, so it's not clear what the expected behavior is

- Must revert if the `_owner` address is `address(0)`.

#### `function claim(address _owner, address _erc20, uint256 _amount)`
- Must revert if `_amount` is greater than the allowance of the `SphinxEscrow` contract on `_erc20` for `_owner`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like anybody can call this function to lock funds in the SphinxEscrow contract. This seems benign, but I'm not sure if it could mess up our backend logic. For example:

  1. Griefer grants the SphinxEscrow contract an allowance
  2. Griefer approves deployment
  3. Griefer frontruns the claim function, causing our call to the claim function to throw an error

Unless there's a use case for keeping this open, I'd probably prefer to make it permissioned

@RPate97 RPate97 closed this Jun 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants