-
Notifications
You must be signed in to change notification settings - Fork 5
ERC20: enable a subnet's supply to be determined by an ERC20 token in the parent. #313
Conversation
Your PR was set to target |
fe94f85
to
fe6ae46
Compare
src/SubnetActorDiamond.sol
Outdated
revert InvalidERC20Address(); | ||
} | ||
// We require that the ERC20 token exists beforehand. | ||
// The call to balanceOf will revert if the supplied address does not exist, or if it's not an ERC20 contract. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice trick!
@@ -142,6 +147,12 @@ contract GatewayManagerFacet is GatewayActorModifiers, ReentrancyGuard { | |||
// prevent spamming if there's no value to fund. | |||
revert InvalidCrossMsgValue(); | |||
} | |||
// Validate that the supply strategy is native. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fund
description now is outdated
src/gateway/GatewayManagerFacet.sol
Outdated
@@ -142,6 +147,12 @@ contract GatewayManagerFacet is GatewayActorModifiers, ReentrancyGuard { | |||
// prevent spamming if there's no value to fund. | |||
revert InvalidCrossMsgValue(); | |||
} | |||
// Validate that the supply strategy is native. | |||
SupplyStrategy memory supplyStrategy = SubnetActorGetterFacet(subnetId.getActor()).supplyStrategy(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe define a getter like IsSubnetWithNativeSupply
?
@@ -154,6 +165,31 @@ contract GatewayManagerFacet is GatewayActorModifiers, ReentrancyGuard { | |||
LibGateway.commitTopDownMsg(crossMsg); | |||
} | |||
|
|||
function fundWithToken(SubnetID calldata subnetId, FvmAddress calldata to, uint256 amount) external nonReentrant { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we validate the address to
here? Zero address?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so? 0x00..00
is generally used as a burn address and I don't see why we should specifically prevent deposits straight into the 0x00..00
address. I'm not aware of ERC20 code that prevent such transfers in Ethereum. cc @snissn
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0x0 has a "special case-ness" in ethereum and i'm not 100% familiar with all of the mechanics to it. Here's some discussion -- https://forum.openzeppelin.com/t/removing-address-0x0-checks-from-openzeppelin-contracts/2222/2
interestingly it seems to regard 0x0 as the "burn address" and wants to separate semantics around transfer and burn. we may want to consider 0x0 as our burn address and welcoming this standard
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comments in that discussion sum it up pretty well. Various standards have coalesced towards treating the 0x0 address as the creator and burn sink of things. I think blocking 0x0 at this level may hinder legitimate user flows down the line. That said, enforcing the restriction now and then loosening it up is more backwards compatible than the other way around...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe worth considering consensus-shipyard/fendermint#409 has introduced 0x0 for the system address, so at least it works as a source. Hopefully the fact that it doesn't have an actual Ethereum account doesn't interfere with using it as a burn address.
3e930a3
to
ebefde3
Compare
209018f
to
00b1b67
Compare
The bug is that we were not incrementing the top-down nonce when inverting direction.
For tests you mentioned them being incomplete, here are the tests i can think of to add:
|
bool reject; | ||
if (isLCA) { | ||
// We're connecting both networks, so we check them directly. | ||
reject = from.getActor().hasSupplyOfKind(SupplyKind.ERC20); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is wrong, if we assume that multi-level is enabled you may be committing a message locally in a subnet, but you don't have access to the actors of the from
or the to
because you are an intermediate subnet and they have nothing to you with you. You only need to forward the message, but you know nothing about the source or origin subnets. It should be fine for now, but we should add a not or something to flag it and fix it in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@adlrocha yep, this check only seems to catch sibling to sibling xnet comms (where the first hop is to your LCA). I'll adjust this. Thanks for catching!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the bottom-up path or the pivot, I'd love to catch if the arrivingFrom
network was an ERC20 network and reject the message then. However, unless we save the forwarder network in the postbox, we can't do this under propagate
. I don't want to do it in _applyMsg
because that would make a whole batch fail. I'll open an issue for this, as I don't think it's worth refactoring the postbox right now to store both the arrivingFrom
and the CrossMsg
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Else, commit a bottom up message. | ||
LibGateway.commitBottomUpMsg(crossMessage); | ||
// gas-opt: original check: value > 0 | ||
return (shouldBurn = crossMessage.message.value != 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assumes that the message value includes the fee to be paid (which is currently the case, but worth noting in case we change the way it works in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I changed anything here. Do you want to open an issue to track this?
src/lib/SupplySourceHelper.sol
Outdated
function lock(SupplySource memory supplySource, address from, uint256 value) internal { | ||
if (supplySource.kind == SupplyKind.ERC20) { | ||
IERC20 token = IERC20(supplySource.tokenAddress); | ||
token.safeTransferFrom({from: from, to: address(this), value: value}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assumes a previous approve from the user to the gateway, right? Should we document it explicitly somewhere so we don't forget to add it to the docs and is clear for auditors (or anyone else reading the code?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is also how bridges work more generally, but agree we should document.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added soldocs in the IGateway
interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a few comments. Apart from something that I feel may be wrong when handling multi-level propagations (and that shouldn't be blocking) the implementation of the ERC20 side of things LGTM. I see that at least the happy path and some failure cases are covered in the tests, so should be good to go (at least for the audits)
ac35129
to
38374ef
Compare
/* eslint prefer-const: "off" */ | ||
import { deployContractWithDeployer, getTransactionFees } from './util' | ||
import hre, { ethers } from 'hardhat' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The husky prettier hook insists on making these changes 🤷
src/lib/SupplySourceHelper.sol
Outdated
function lock(SupplySource memory supplySource, address from, uint256 value) internal { | ||
if (supplySource.kind == SupplyKind.ERC20) { | ||
IERC20 token = IERC20(supplySource.tokenAddress); | ||
token.safeTransferFrom({from: from, to: address(this), value: value}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added soldocs in the IGateway
interface.
function test_propagation() public { | ||
// TODO: | ||
// 1. Test that propagation is rejected when sender is ERC20. | ||
// 2. Test that propagation is rejected when receiver is ERC20. | ||
// 3. Test that propagation is rejected when an intermediary subnet is ERC20. | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO, will open an issue to track these missing tests.
Aderyn alerted of this: Passing an arbitrary `from` address to `transferFrom` (or `safeTransferFrom`) can lead to loss of funds, because anyone can transfer tokens from the `from` address if an approval is made. This is not a possibility since this parameter is in an internal library, and the only caller passes in msg.sender. But I can't predict how this code will evolve, so it may become a footgun down the line. Removing.
2042ede
to
60af071
Compare
Implemented!
Could you elaborate? |
I'm seeing flaky tests failing at the diamond selector generation step, which is a Python script that calls Forge from within tests via FFI. It's flaky because it appears to fail occasionally, and with a different test each time. I've raised a question in Slack to @dnkolegov to understand why we perform selector generation at test time, when we'll anyway need to do this during deployment or sooner. On surface, it seems like a codegen thing at build time, so curious what other reasons led us to choose this solution. |
For now, I'll go ahead and merge. |
Context
Various IPC users seek to create dedicated crypoeconomies backed by their own token. They'd like to launch their token on another network and inject it into their subnet as native circulating supply, so it can be used (a) to pay for gas, and (b) as the native coin for value transfers.
Contribution
This PR introduces the notion of the "supply source" at the parent subnet. This configuration object determines the kind of supply backing a child subnet. It can be "native" or "ERC20". In the latter case, the token address must be provided. We verify that the address indeed corresponds to an ERC20 token.
We also add a
funcWithToken()
external non-payable function to theGatewayManagerFacet
. This is analogous tofund()
, but for token supply (where the amount must be passed as a parameter).We block message propagation (when the feature flag is enabled) whenever ERC20 networks are involved, as a sender, receiver, or intermediary network.
Non-goals
This PR does NOT introduce the ability to use an ERC20 token as validator stake. We don't expect that feature to be useful at all, since a major benefit of IPC is that subnets draw economic security from the value of the collateral locked, which is denominated in the root coin.
Design doc
https://www.notion.so/pl-strflt/ERC20-Native-coin-conversion-eab9267445b6411d96ec17ef02a6510e?pvs=4
TODO
Unit tests.(preferring integration tests with a real ERC20 token involved)