The Resolv Protocol introduces reversible ERC20 tokens, enabling users to dispute fraudulent transactions and potentially recover lost assets. This document is structured to assist security auditors in reviewing the smart contract setup, architecture, and critical security aspects. If a private key is compromised for a wallet, these wrapped tokens are reversible!
Research Links: https://arxiv.org/pdf/2312.14375v1
https://github.com/kkailiwang/erc20r/blob/master/contracts/ERC20R.sol
https://arxiv.org/pdf/2208.00543#page=16&zoom=100,96,233
- User Sets secondary address
- User Wraps Tokens
- Tokens are stolen
- Files dispute with stake
- Governance collects dispute
- Assign Jurors with a VRF
- Jurors Vote
- Dispute is settled, with possiblity of returning the stake if the dispute is settled in favor of complainant.
- Governance
- The Admins of the contract. They are responsible for adding the other role, removing the roles, launching new tokens, managing assets, updating fees...etc.
- Jurors
- Jurors vote on the case of if it is fraudulent or not. They must wait 36 hours to get enough evidence in and also to allow both parties to submit enough evidence to support their case. On a more human level, although not purulent to the audit, we don't deal with merchant disputes as this gets a little too cloudy for us right now. We also acknowledge the possibility one party is unaware of the pending dispute and thus they don't submit evidence. We are working on air drop systems, and web2 notification systesm to alert uses of transactions, disputes and settlements in the future.
- Cosigners - Allow for unwrapping and setting / updating secondary addresses
- The concern if a private key is compromised a hacker could simply unwrap tokens and then steal them. Being as such, in our web based interface a user should set up a password. If a user wishes to unwrap tokens, the user would first submit an unwrap request along with which cosigner they wish to use. (Later expansions include partnerships with wallet providers and more reboot MFA on the web2 level) The user would input their password into a web2 web interface. The web2 client would authenticate that password, then forward the request to the cosigner which would then approve the unwrap token request.
- Similarly if a private key is compromised a hacker or thief could just update the secondary address. As a result cosigner is needed for this too.
- Cosigners at first would be our own backend / web2 platform, but then would expand to other wallet providers / platforms.
- Anyone can unwrap, almost at anytime (after 48 hours) but not everywhere. They would to submit a request on a platform that as a cosigner.
- Payment authenticators - Approve Updates of Free Tiers vs Paid (Paid tiers would be paid through web2, then a web2 infrastructure updates the address on chain)
- Relayers - Relay Juror Votes
Contract | Description |
---|---|
pToken.sol | Implements the wrapped reversible ERC20 token logic. |
pEth.sol | Specialized contract for wrapped ETH with reversible functionality. |
pTokenFactory.sol | Factory contract to deploy new pToken instances. |
pEthFactory.sol | Factory contract for creating instances of pEth . |
JurorHandler.sol | Manages juror assignment, voting, and evidence submission for disputes. |
ResolvGovernance.sol | Handles governance-related operations, including role assignments and token launches. This also acts as a collective for all the disputes and stakes for the disputes. Additionally this contract also acts as the treasury for the wrapped tokens. |
- Purpose: Implements the wrapped reversible ERC20 token.
- Core Features:
- Standard ERC20 functionality with additional methods for freezing and unwrapping.
- Tracks a
secondaryAddress
to enable token recovery in case of private key compromise. - Integrates with the
JurorHandler
andResolvGovernance
for dispute management. - Transfers After 48 hours are considered settled and cannot be disputed
- Users must put up a stake when they file a dispute
- Disputed transactions must be above a certain amount.
- Unwrapping tokens
- Contract is
upgradeable
- Key Functions:
wrap()
fileDispute()
: Files a dispute of a transaction. Tokens are tracked down then frozen. Later jurors decide this case.unwrap()
: Allows unwrapping of tokens, pending approval from a cosigner.
- Purpose: Specializes the
pToken
contract for wrapped ETH with the same reversible features. - Contract is
upgradeable
- Key Functions:
wrap()
fileDispute()
: Files a dispute of a transaction. Tokens are tracked down then frozen. Later jurors decide this case.unwrap()
: Allows unwrapping of tokens, pending approval from a cosigner.
- Purpose: Deploys instances of
pToken
contracts.
- Purpose: Factory for deploying
pEth
instances. - Key Features:
- Mirrors
pTokenFactory
but specifically for ETH-based tokens.
- Mirrors
- Purpose: Manages the lifecycle of disputes, juror voting, and evidence submission.
- Core Functionalities:
- Assigns jurors to cases using a Chainlink VRF
ResolvGovernance
collects disptues from token contracts then sends disputes to jurors.- Tracks dispute statuses (
Active
,Pending
,Resolved
,Rejected
). - Processes votes and finalizes disputes.
-
- Contract is
upgradeable
- Contract is
- Key Security Points:
- Implements a 36-hour delay for evidence submission to ensure fair outcomes.
- Purpose: Handles governance operations, including role management, dispute overrides, and token launches.
- Contract is
upgradeable
General Idea, Resolvrs (Admins) cast the majority of updates via 3/5s vote.
- Most of the admin updates are done in a 2 step process. Firstly Resolvr can create a proposal, then other Resolvrs can vote on it.
- The exception to this rule adding new rules. Such as onboarding a new Juror only requires 2 Resolvrs to add this juror.
- Resolvrs can also remove jurors, remove cosigners.....etc. They are the admins responsible for managing other roles.
- Resolvrs are also responsible for deploying new tokens to the ecosystem.
- Resolvrs are also responsible for upkeeping the governance contract with various functions to update resources such as VRF subscriptionIds, price oracles...etc
- To reduce size and future proof these contracts as much as possible, there
genericCall
function as well as a delegate call function. - Finally
ResolvGovernace
also acts as the treasury. Some of our revenue model includes restaking some of base asset. Resolvrs can vote to withdraw an asset to another wallet as to restake it and receive yield.
-
When a dispute is filed the tokens work "Heat Seeking Missle" concept.
- Being that tokens are liquid, the freeze concept looks for the closest and most recent transactions to the initial disputed transfer. This looks at "depth levels" of transactions. If transactions are the same depth then, the timestamp is used to find the closest dispute.
- Disputes of transactions must be within 48 hours.
- All tokens must go somewhere, unless they are unwrapped, however they cannot be unwraped within 48 hours so all tokens must go somewhere.
- The 'heat seeking' concept favors tokens closer to the disputed transaction rather than tokens later in the chain.
- Scenario
- User 1 sends tokens to user 2.
- User 2 sends tokens to user 3.
- User 3 sends tokens to user 4.
- If the first transaction is disputed, the freeze mechanism first looks at user2 to check if they enough tokens to cover the disputed amount. We freeze what we can, then move on to user 3. Same thing until enough tokens are frozen to match the disputed amount.
- Scenario
-
The
pending
in the dispute struct indicates that the dispute is pending on another dispute.- Scenario: (We'll assume no extra tokens in this scenario)
- User 1 sends 100 tokens to User 2 : Tx1
- User 2 sends 100 tokens to User 3 : Tx2
- User 3 sends 100 tokens to user 4 : Tx3
- Final balances is User1:0 Tokens, User2: 0 Tokens, User3:0 Tokens, User4: 100 Tokens.
- Tx1 is disputed. Then Tx3 is disputed. Being that tx3 was a downstream result of tx1. The second dispute (tx3) dispute is marked as pending since User3 had stolen tokens form the first transaction. Tx3 is marked as pending. And this case is not assigned to any jurors yet.
- If Tx1 Transaction is reversed then Tx2 should not be able to be disputed. Additionally, tx3 should be marked as inactive.
- If Tx1 is marked as rejected. Then Tx2 should still able able to be dispute. Additionally, tx3 should be marked as active and not longer pending.
-
Additionally every dispute has filed must have also a stake put up. This is to prevent DOS attacks on the jurors and to prevent the system from being overloaded. Consquently if a dispute is reversed, the user gets their stake back. If it is rejected user does not receive their stake back.
- Reusing the example of above.
- User 1 sends 100 tokens to User 2 : Tx1
- User 2 sends 100 tokens to User 3 : Tx2
- User 3 sends 100 tokens to user 4 : Tx3
- Final balances is User1:0 Tokens, User2: 0 Tokens, User3:0 Tokens, User4: 100 Tokens.
- Tx1 is disputed. Then Tx3 is disputed. Being that tx3 was a downstream result of tx1. The second dispute (tx3) dispute is marked as pending since User3 had stolen tokens form the first transaction. Tx3 is marked as pending. And this case is not assigned to any jurors yet.
- If Tx1 is found is a valid hack, thus reversed. User 3's dispute would also be found as a hack and thus both user 1 and user 3 should receive their stake back.
- However if tx1 is found as not a valid hack, thus rejected, user 1 does not get their stake back and
As we thought out potential vectors of attack more and more we ran into a simple issue. The private key being leaked, meant that additional security measures had to be put in place.
- Implications
SettleDispute
Tokens and the stake are sent back to the secondary account and who ever initiated the dispute respectively.FileDispute
can be called from the primary or secondary account.UnwrapTokens
This is now a 2 step process. First step is request an unwrap amount. Then additional web2 security features would check if the user is actually the owner of the tokens. (Password, MFA...etc) then the user is able to prove their identity, this would kick off a web2 to web3 call for the cosigner to approve the upwrap request.- SetSecondaryAddresses, similar to above. The security concern is that a hacker compromises private keys, sets their secondary address then steals tokens. To prevent this it is a two step process. User makes a request, web2 validation then cosigner confirms the request.
- Why Multiple Cosigners?
- As we scale we plan to integrate on to other platforms and wallet providers. One small wallet provider may user 1 cosigner, where as a larger wallet provider may use 2-3 different cosigner providers. Additional more sophisticated web2 based methods would monitor for potential cosigner leaks, remove them and update the cosigners as needed.
-
All contracts are upgradable
-
All Contracts are as future proofed as possible
-
pTokens / pEth are ERC20 PermitUpgradable
-
Base Launch to start
-
Concerns
- Gas efficiency / DOS attacks
- Reentrancy attacks
- Lost of Funds
- Dispute Mismatches / Locked or Permafrozen Assets.
- Access Control
- We spent a good chunk of time unit testing, however much less time fork testing. There is a slight concern about this.
-
Gas efficiency / Contract Size Tips are appreciated! :)
-
Note on events
- They will be implemented soon. You may see some notes about events in the contracts.