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

feat: Permissioned Across-v2 bond token #226

Merged
merged 21 commits into from
Feb 7, 2023
Merged

feat: Permissioned Across-v2 bond token #226

merged 21 commits into from
Feb 7, 2023

Conversation

pxrl
Copy link
Contributor

@pxrl pxrl commented Feb 1, 2023

This PR creates a new bond token, to be used as the collateral required by the HubPool on the following functions:

  • proposeRootBundle()
  • disputeRootBundle()

The new bond token (symbol "ABT") hosts an internal set of approved proposer addresses in order to impose constraints on attempted inbound ABT transfers to the HubPool. Permissioned addresses are unconstrained in their transfer of ABT, whereas all other addresses must not be the proposer of a root bundle in order to transfer ABT to the HubPool. This permits any ABT holder to submit a dispute of an existing root bundle proposal, whilst preventing non-approved addresses from proposing.

ABT inherits from the WETH9 contract with minimal modifications necessary to override the transferFrom() function.

Fixes ACX-699 ACX-700

This PR creates a new bond token, to be used as the collateral required
by the HubPool on the following functions:
 - proposeRootBundle()
 - disputeRootBundle()

The new bond token (symbol "ABT") hosts an internal set of approved
proposer addresses in order to impose constraints on attempted inbound
ABT transfers to the HubPool. Permissioned addresses are unconstrained
in their transfer of ABT, whereas all other addresses must _not_ be the
proposer of a root bundle in order to transfer ABT to the HubPool. This
permits any ABT holder to submit a dispute of an existing root bundle
proposal, whilst preventing non-approved addresses from proposing.

ABT inherits from the WETH9 contract with minimal modifications
necessary to override the transferFrom() function.
@pxrl pxrl requested review from nicholaspai and mrice32 February 1, 2023 15:38
@linear
Copy link

linear bot commented Feb 1, 2023

import "./HubPoolInterface.sol";
import "./WETH9.sol";

interface _HubPool is HubPoolInterface {
Copy link
Member

Choose a reason for hiding this comment

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

Prefer sticking to existing naming convention and not prefixing this with a _. Maybe name HubPoolExtendedInterface or something, or even IHubPool

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point - I'll find something better 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Implemented: 91c1780

* @param _hubPool Address of the target HubPool contract.
*/
constructor(_HubPool _hubPool) {
name = "Across v2 Bond Token";
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
name = "Across v2 Bond Token";
name = "Across Bond Token";

Not sure V2 is necessary. Do you think we should name it Proposer Bond instead of just Bond?

Copy link
Contributor Author

@pxrl pxrl Feb 1, 2023

Choose a reason for hiding this comment

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

Preface: Naming things is hard :)

My thinking with the "v2" naming is that this token has a finite lifespan - it will only be in use as long as the current (Across v2) HubPool is deployed (nb. the HubPool address is immutable, per current implementation). That's why I preferred to include v2 in the name.

I'd prefer not to include "Proposer" in the name, since this token is also needed to dispute.

Copy link
Member

Choose a reason for hiding this comment

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

I generally don't like Versions in token names, look at UMA Voting Token V1 for example

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"v2" removed: ee0aedb

I do think it's a different scenario to UMA v1, but I can live with either :)

*/
constructor(_HubPool _hubPool) {
name = "Across v2 Bond Token";
symbol = "ABT";
Copy link
Member

Choose a reason for hiding this comment

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

nit: can we come up with a catchier symbol? :P

Copy link
Contributor Author

@pxrl pxrl Feb 1, 2023

Choose a reason for hiding this comment

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

"AMT" => "Across Moon Token" ?

(Slightly) more seriously:
APRT => Across Proposer Responsibility Token
APPT => Across Permissioned Proposer Token
APDT => Across Permisionless Dispute Token
AHST => Across HubPool Security Token

IDK 🤷

Copy link
Member

Choose a reason for hiding this comment

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

Will think on this more haha

uint256 amt
) public override returns (bool) {
if (dst == address(hubPool)) {
require(proposers[src] || hubPool.rootBundleProposal().proposer != src, "Transfer not permitted");
Copy link
Member

Choose a reason for hiding this comment

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

Does this mean proposer's can't dispute themselves? I don't think we should block such behavior

Copy link
Contributor Author

@pxrl pxrl Feb 1, 2023

Choose a reason for hiding this comment

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

This check only requires that one of the conditions is true, so approved proposers can always self-dispute without impediment (and, all other users can dispute as well). There's a test that verifies that - see test/BondToken.e2e.ts line 72.

The statement hubPool.rootBundleProposal().proposer != src does seem counter-intuitive at first. It's there based on the observation that when a non-approved proposer is attempting to propose, their address is recorded as rootBundleProposal.proposer.

Copy link
Contributor

@mrice32 mrice32 Feb 1, 2023

Choose a reason for hiding this comment

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

This is extremely counterintuitive, but it's really clever. This is the sort of logic that I think Certora would be really good for verifying. We would probably want to verify that any address with tokens can always send a dispute.

I'm not super worried about this edge case, but this logic does seem to mean that if I proposed and then I was removed from the whitelist, I would then be no longer able to dispute myself.

Just out of curiousity, why did you not use the the check we use in the HubPool, that unclaimedPoolRebalanceLeafCount != 0 to detect disputes vs proposals. See the noActiveRequests modifier.

EDIT: That last idea doesn't work. I didn't quite get the nuance you picked up on, that the variables are written before the transfer. I think the way you've done it or checking the timestamp is probably the only way I could see this working. Very clever.

Copy link
Contributor Author

@pxrl pxrl Feb 1, 2023

Choose a reason for hiding this comment

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

I'm not super worried about this edge case, but this logic does seem to mean that if I proposed and then I was removed from the whitelist, I would then be no longer able to dispute myself.

That was also my intuition, but then I tried to implement a test for it and found the opposite - it works unexpectedly. This is similarly because the pending root bundle is deleted before the bond token transferFrom() function is called, so the disputer is no longer the proposer, even though both the proposal and dispute were submitted by the same address. It's a happy accident, I guess.

For reference, see the test at line 78 in test/BondToken.e2e.ts.

Just out of curiousity, why did you not use the the check we use in the HubPool, that unclaimedPoolRebalanceLeafCount != 0 to detect disputes vs proposals. See the noActiveRequests modifier.

That was my first attempt as well, but it doesn't work (edit: As mentioned in your update 👍). At the time of evaluation, the proposed root bundle is the version submitted by the proposer, so it seems reliably non-zero in case of both proposals and disputes.

Copy link
Contributor Author

@pxrl pxrl Feb 1, 2023

Choose a reason for hiding this comment

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

This is extremely counterintuitive

I've added a dev comment to the BondToken.safeTransferFrom() implementation to call this out more explicitly. See 5215b60.

Copy link
Member

Choose a reason for hiding this comment

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

I had the same question as Matt but see your point now!

Copy link
Contributor

Choose a reason for hiding this comment

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

Wow, it keeps getting weirder @pxrl. Really would love some validation on this!

contracts/WETH9.sol Outdated Show resolved Hide resolved
Copy link
Member

@nicholaspai nicholaspai left a comment

Choose a reason for hiding this comment

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

Did a once over and left some comments

contracts/BondToken.sol Outdated Show resolved Hide resolved
mrice32
mrice32 previously approved these changes Feb 1, 2023
Copy link
Contributor

@mrice32 mrice32 left a comment

Choose a reason for hiding this comment

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

This is a really clever implementation. Nice work, @pxrl!

uint256 amt
) public override returns (bool) {
if (dst == address(hubPool)) {
require(proposers[src] || hubPool.rootBundleProposal().proposer != src, "Transfer not permitted");
Copy link
Contributor

@mrice32 mrice32 Feb 1, 2023

Choose a reason for hiding this comment

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

This is extremely counterintuitive, but it's really clever. This is the sort of logic that I think Certora would be really good for verifying. We would probably want to verify that any address with tokens can always send a dispute.

I'm not super worried about this edge case, but this logic does seem to mean that if I proposed and then I was removed from the whitelist, I would then be no longer able to dispute myself.

Just out of curiousity, why did you not use the the check we use in the HubPool, that unclaimedPoolRebalanceLeafCount != 0 to detect disputes vs proposals. See the noActiveRequests modifier.

EDIT: That last idea doesn't work. I didn't quite get the nuance you picked up on, that the variables are written before the transfer. I think the way you've done it or checking the timestamp is probably the only way I could see this working. Very clever.

return transferFrom(msg.sender, dst, wad);
}

function transferFrom(
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: can we add a comment that this is the only difference between this contract and the real WETH9?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added here: edbee53.


pragma solidity ^0.8.0;

contract WETH9 {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: we probably don't want two contracts in the repo called WETH9 -- this causes issues in hardhat I think. Any reason we can't just delete the interface or rename it to WETH9Interface?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point - I'd totally missed the existing interface. I've split this out - see #227.

pxrl added 3 commits February 1, 2023 23:40
Make way a copy of the WETH9 implementation to be imported.
Import the full contract so that it can be tweaked and used as the base
contract for the Across Bond Token (name subject to change).

The import is made as-is, with no changes. Follow-up commits will apply
modifications as required.
 - Fix SPDX license identifier: AGPL-3.0-only => GPL-3.0-or-later
 - Reinstate WETH9 license header for compliance with GPLv3 sections 4.
   and 5:

     https://www.gnu.org/licenses/gpl-3.0.html

  - Relocate the pragma.
@pxrl pxrl changed the base branch from master to pxrl/weth9-shuffle February 1, 2023 22:45
@pxrl pxrl dismissed mrice32’s stale review February 1, 2023 22:45

The base branch was changed.

Base automatically changed from pxrl/weth9-shuffle to master February 2, 2023 21:34
@pxrl pxrl requested a review from mrice32 February 6, 2023 23:42
Copy link
Contributor

@mrice32 mrice32 left a comment

Choose a reason for hiding this comment

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

LGTM!

uint256 amt
) public override returns (bool) {
if (dst == address(hubPool)) {
require(proposers[src] || hubPool.rootBundleProposal().proposer != src, "Transfer not permitted");
Copy link
Contributor

Choose a reason for hiding this comment

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

Wow, it keeps getting weirder @pxrl. Really would love some validation on this!

@pxrl pxrl merged commit b320ba7 into master Feb 7, 2023
@pxrl pxrl deleted the pxrl/bondtoken branch February 7, 2023 09:11
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.

3 participants