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

runtime-sdk: Implement ERC-20 compatibility #2144

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

jberci
Copy link
Contributor

@jberci jberci commented Feb 4, 2025

Implements #1856.

Copy link

netlify bot commented Feb 4, 2025

Deploy Preview for oasisprotocol-oasis-sdk canceled.

Name Link
🔨 Latest commit d9a4e42
🔍 Latest deploy log https://app.netlify.com/sites/oasisprotocol-oasis-sdk/deploys/67bef4614db29e00084d1521

Copy link

codecov bot commented Feb 4, 2025

Codecov Report

Attention: Patch coverage is 57.70492% with 129 lines in your changes missing coverage. Please review.

Project coverage is 57.65%. Comparing base (facdbe1) to head (bfa8638).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
runtime-sdk-macros/src/evm_derive.rs 0.00% 88 Missing ⚠️
runtime-sdk/modules/evm/src/precompile/erc20.rs 84.21% 33 Missing ⚠️
runtime-sdk-macros/src/lib.rs 0.00% 8 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2144      +/-   ##
==========================================
- Coverage   58.88%   57.65%   -1.23%     
==========================================
  Files         144      146       +2     
  Lines       10172    10451     +279     
==========================================
+ Hits         5990     6026      +36     
- Misses       4182     4425     +243     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Member

@ptrus ptrus left a comment

Choose a reason for hiding this comment

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

The approach seems sensible to me 👍 . A couple of suggestions regarding the ERC-20 implementation (skipped reviewing the macros part for now, since rust macro code is so unreadable to me 😅)

const DECIMALS: u8 = 18;
}
Some(BTreeMap::from([(
primitive_types::H160([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
Copy link
Member

@ptrus ptrus Feb 9, 2025

Choose a reason for hiding this comment

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

I know this is just a test but should there be some additional checks that the addresses here cannot be all zeroes in practice (or any other reserved address).

const DECIMALS: u8 = T::DECIMALS;

fn total_supply() -> Result<u128, Error> {
let denom = token::Denomination::from_str(Self::SYMBOL).unwrap();
Copy link
Member

@ptrus ptrus Feb 9, 2025

Choose a reason for hiding this comment

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

Would we ever want to have a different symbol than the denomination name? I think we might want to use symbol "ROSE"/"Native ROSE" (or "TEST" on testnet) and not "NATIVE" for the native denomination mapping?

Ok(T::Accounts::get_total_supplies()?
.get(&denom)
.copied()
.unwrap_or_default())
Copy link
Member

Choose a reason for hiding this comment

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

Should this fail if the denomination doesn't exist?

}

fn mint(to: address::Address, amount: &token::BaseUnits) -> Result<(), Error> {
T::Accounts::mint(to, amount)
Copy link
Member

Choose a reason for hiding this comment

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

Minting should likely not be allowed or even supported. The ERC-20 spec doesn't require this method.

We could implement an ERC20 with ownership support (e.g. https://docs.openzeppelin.com/contracts/2.x/api/token/erc20#ERC20Mintable) but that's probably not needed at this time, since I believe the main usecase currently is exposing the native denomination via ERC20 interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Minting and burning specifically are requirements for this feature. People usually seem to implement these two methods on top of the base ERC-20 spec so I added them, but we can decide which particular interface to follow.

Copy link
Member

Choose a reason for hiding this comment

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

Okay, that makes sense. But I believe that in that case, we need to include a configurable ownership mechanism as well. Since without it, everyone will be able to call this method and mint tokens?

@jberci jberci force-pushed the jberci/feature/erc20 branch from 1c7fc23 to 14d3b9d Compare February 13, 2025 03:57
@jberci jberci force-pushed the jberci/feature/erc20 branch 4 times, most recently from bfa8638 to 3bfe29b Compare February 18, 2025 11:57
@jberci jberci marked this pull request as ready for review February 18, 2025 12:04
Copy link
Member

@ptrus ptrus left a comment

Choose a reason for hiding this comment

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

Updates look good to me. A couple of questions/suggestions about strictly following the ERC-20 spec / Openzeppelins ERC20 token implementation.

) -> Result<bool, Error> {
with_allowance::<Self, _>(owner, spender, |allowance| {
let allowance = allowance.unwrap_or_default();
if amount.amount() <= allowance {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is defined in the official spec, but the OpenZeppelin ERC-20 token contract treats the uint:256::max() allowance value as infinity, and doesn't deduct from it:

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/9586aaf35241daf4b17e4858bf7c86edbb4b7247/contracts/token/ERC20/ERC20.sol#L303-L310

probably doesn't have an effect in practice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's an issue here that amounts in EVM are u256, but we use only u128 for them. I can do the equivalent of infinity but for u128, not sure how wrong that would be.

if amount.amount() <= allowance {
Ok(allowance - amount.amount())
} else {
Err(Error::InsufficientBalance)
Copy link
Member

Choose a reason for hiding this comment

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

Should we follow error conventions as defined in https://eips.ethereum.org/EIPS/eip-6093

Not sure if it matters in practice.

@@ -1201,3 +1207,527 @@ fn test_return_value_limits() {
assert_eq!(result[64], 0xFF, "result should be correct");
assert_eq!(result[1023], 0x42, "result should be correct");
}

#[derive(Default)]
pub struct TestErcToken {}
Copy link
Member

Choose a reason for hiding this comment

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

This is maybe more for a follow-up, potentially it should go even in a separate repo (sapphire-paratime?), but we should consider running some ERC-20 token test cases against our precompile implementation?

e.g. https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/test/token/ERC20

@jberci jberci force-pushed the jberci/feature/erc20 branch from 3bfe29b to d9a4e42 Compare February 26, 2025 11:00
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