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

Add transfer precompile design doc #51

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

karlb
Copy link

@karlb karlb commented Jul 23, 2024

Description

Bring Celo's transfer precompile to OP-stack to allow Token Duality (native token via ERC20) without requiring an op-geth fork.

Additional context

protocol/transfer-precompile.md Outdated Show resolved Hide resolved
protocol/transfer-precompile.md Outdated Show resolved Hide resolved
protocol/transfer-precompile.md Outdated Show resolved Hide resolved
protocol/transfer-precompile.md Outdated Show resolved Hide resolved
protocol/transfer-precompile.md Outdated Show resolved Hide resolved
@karlb karlb marked this pull request as ready for review July 24, 2024 13:15

The precompile can fail for the following reasons:
* `TransferPrecompileAllowedCaller` is not configured for the blockchain
* It is not called from the contract at the `TransferPrecompileAllowedCaller` address
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the TransferPrecompileAllowedCaller address an arbitrary enshrined smart contract? In the context of the OP Stack, this could be a predeploy. Why can't anybody call this contract and instead of msg.sender being the first arg, the native EVM can look at the CALLER (which is msg.sender)?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok given the geth code and your comment:

Removing the from parameter from the precompile is not an option because
msg.sender can't be accessed from inside the precompile.
There, the caller is set to the contract doing TRANSFER.call.

I understand this design decision more. It would be a larger diff to send through the evm context into the precompile

Copy link
Author

Choose a reason for hiding this comment

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

I understand this design decision more. It would be a larger diff to send through the evm context into the precompile

Yes, and it would also reduce the chances of the precompile being supported by third party tooling (e.g. foundry's anvil).

```solidity
address constant TRANSFER = address(0x0000000000000000000000000000000000000101);

function _transfer(address to, uint256 value) internal {
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the design space of possible TransferPrecompileAllowedCaller contracts? In practice would it mostly look like the pseudocode here?

Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like the allowed caller should just be a predeploy that is literally this code, perhaps optimized and its part of the design, the way that i read the design right now makes it sound like the allowed caller can be configured to different contracts

Copy link
Author

Choose a reason for hiding this comment

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

I would expect the contract to implement the all of ERC20 in a way that passes through the native balance. So in addition to the transfer, you would have other ERC20 functions like

  function decimals() external view returns (uint8) {
    return 18;
  }

  function balanceOf(address _owner) public view returns (uint256) {
    return _owner.balance;
  }

and potentially other interfaces (it could be a proxy for upgradibility if you consider that safe enough, other token interfaces, etc). I assume it would be hard to find one contract that fits everyone here, and it would need to be configurable in every case to get the name and symbol right.

Having a predeploy with only the a transfer function that can be called from the full ERC20 contract would not solve anything AFAICS. That function would still need to receive the from since msg.sender can't be used for that due to the additional call. And it would still need to check that the calling contract is the privileged contract.

Add one new blockchain parameter `TransferPrecompileAllowedCaller` and a new precompile.
The parameter defines the address from which the precompile must be called.

Precompile name: `transfer`
Copy link
Contributor

Choose a reason for hiding this comment

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

Are there any security concerns here? I feel like i've seen bugs based on having a dual interface into balance

Copy link
Author

Choose a reason for hiding this comment

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

One more interface is certainly one more potential attack vector, especially when it provides access to everyone's balances. Fortunately, the interface is quite simple and low on interaction with other features. I don't know of any security issues we had with this feature since the Celo mainnet launch. The biggest danger I see is the privileged contract getting compromised.

of limited use (e.g. can't be used to pay gas) and wrapping/unwrapping is
annoying and incurs additional gas charges.

This can be solved by having a contract that provides an ERC-20 interface
Copy link
Contributor

Choose a reason for hiding this comment

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

So the main value prop here is that the unwrap/wrap steps are saved. In a world with smart contract wallets, users can do multicalls and save needing to send multiple transactions, but this is still cheaper as there is no wrap/unwrap step necessary

Copy link
Author

Choose a reason for hiding this comment

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

Yes, but there is plenty of benefit before everyone uses smart contract wallets and every app has implemented perfect wrap/unwrap handling (Should tokens stay wrapped after the action to save gas? Should existing wrapped tokens be used? Even if they are not enough so that you have to wrap anyway? Do you want to bulk wrap to save gas on the next actions? Should the wallet UI show wrapped tokens separately from unwrapped tokens?).

There is also the argument that it makes the app developers lives easier since they don't have to implement all these wrap/unwrap cases. But since most apps are deployed across many chains, most devs will have to do that anyway.

@tynes
Copy link
Contributor

tynes commented Jul 31, 2024

I think this could make sense given the chain operator could turn on/off the smart contract that is authorized to interact with the predeploy. There is a non zero chance that an existing application has a bug due to this, so chains that want to be more conservative in adopting this would be able to not use it. Then chains that do want this feature could turn it on

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