ckETH: a canister-issued Ether twin token on the IC

That makes sense. Could the foundation choose one decentralized stablecoin - perhaps ckDAI - to roll out in Q4 alongside ckETH? I do believe it would be incredibly important to prioritize some type of stablecoin on the IC @Manu

3 Likes

The balance table stores (account, amount) pairs; each account occupies at least 62 bytes (up to 29 bytes for the principal + 1 byte for the principal size + 32 bytes of the subaccount). Hence, using 128bit vs. 256bits saves 1 - (62 + 16) / (62 + 32) ~ 17% of storage, a minor difference given other inefficiencies in the current ledger storage technology.

Even if all the Ethereum in existence fits in 128bits, some other ERC20 tokens we might want to support in the future might not. Using EVM’s native integer width simplifies the code understanding and helps us avoid future surprises.

3 Likes

Do we store the principal repeatedly for each subaccount of the same owner?

Yes, currently, the key in the map is the entire Account (principal + subaccount).
We also plan to migrate the stable structures library to stable storage, where we must keep the same approach because stable structures don’t support nesting by design.
One optimization we could do is to store accounts with default subaccounts separately:

DEFAULT_ACCOUNTS: StableMap<Principal, Amount>;
ACCOUNTS: StableMap<(Principal, Subaccount), Amount>;

We could also use variable-size encoding (leb128) for amounts to support up to u256 without wasting too much space.

2 Likes

We absolutely agree on how useful ckERC20 would be, and in particular a ckStablecoin, and are working hard to get it as quickly as we can. We think it makes sense to start with ckETH, and then do ckERC20 as quickly as we can after that.

Why not build both at the same time? If we would launch them together, it would mainly mean that one simply launches later because its waiting for the other one, which does not make sense to me.

Why start with ckETH? we think starting with ckETH makes sense, because ETH is the native token, and gas has to be paid in ETH also for ckERC20. We think that we may be able to use ckETH for gas fees in ckERC20. Additionally, the approach of passing in a principal via a call argument is possible for ETH but cannot be done for ERC20. We may be able to build on ckETH to link ETH accounts and IC principals, which we can then use in ckERC20.

11 Likes

ckDAI is one option. ckFRAX would be cool too.

Even better imho is ckCHAI, this is the yield bearing version of DAI!

2 Likes

Thanks so much for the detailed response. I understand your rationale and agree. We all appreciate your hard work and know that it is a trying time in the depths of the bear market.

It is sometimes difficult for non-technical folks to understand how long it takes for complex features to be rolled out. I want to stress just how grateful the community is for all your hard work!

7 Likes

@Manu do you think Q4’24 is a good timeline for phase 2 integration?

It’s hard to say something confidently as it’s so far out, but that timeline seems realistic to me.

3 Likes

Hello, are you sure it’s December 2024? Is the integration we’re looking forward to in Q4 2023 going to be delayed by another year?

dfisher asked about the “phase 2” integration, where ICP nodes also act as Ethereum mainnet nodes, and canisters can request ethereum state directly from ICP nodes. This feature will not be done in 2023, but come later.

For ckETH, so the topic of discussion in this thread, we hope that this will launch in 2023. This will build on HTTPS outcalls first (and may later be upgraded to build on the “phase 2” native ethereum integration when that’s available).

1 Like

Not sure if I make it wrong or not,

  • Querying multiple ETH RPC-providers to make sure the result of state-changing operation are all the same. This is good to me as query is idempotent.
  • But how about send out write transactions, e.g. converting ckETH into ETH? What if the ckETH canister failed to construct a native Ethereum transaction sending the user ETH, will we try to construct the same transaction to another ETH RPC-provider? If so, how to make sure the transfer only success once per the transfer result.

Forgive me if the questions are so straightforward, I am expecting we are using Nonce to control this but would love to learn more about your solution.

2 Likes

We made quite some progress with our proof-of-concept ckETH, and @gregory-demay demonstrates the current status in this video. We plan to publish some guide on how to use the current test version soon, so everybody can play around with it.

Happy to hear everybody’s feedback!

14 Likes

Hi @0xkookoo !

  • Querying multiple ETH RPC-providers to make sure the result of state-changing operation are all the same. This is good to me as query is idempotent.

You’re correct that all JSON-RPC methods that we query, excepted of course with eth_sendRawTransaction for sending Ethereum transactions issued by the minter, simply read the state of the Ethereum blockchain, e.g. eth_getLogs to retrieve the events originated from the helper smart contract when a deposit occurred. Note that requiring equality between the various responses from the various contacted JSON-RPC providers only make sense when one is querying finalized data since all JSON-RPC nodes should have the same view on the finalized state. This is not the case when one is querying more recent data, like at latest block height since some nodes might be in advance by a few blocks in comparison to others. There, another another strategy is needed depending on the concrete use-case.

  • But how about send out write transactions, e.g. converting ckETH into ETH? What if the ckETH canister failed to construct a native Ethereum transaction sending the user ETH, will we try to construct the same transaction to another ETH RPC-provider? If so, how to make sure the transfer only success once per the transfer result.

The minter will construct an EIP-1559 transaction, sign it via threshold ECDSA and send it to Ethereum by using the JSON-RPC method eth_sendRawTransaction. The current implementation sends a transaction via HTTP outcall to only one JSON-RPC provider (if it’s offline the next ones will be tried out one-at-a-time in a round-robin fashion). The reasoning being that the sent payload is a signed statement and cannot be altered by any man-in-the-middle. The worst a malicious node provider could do would be some type of DoS by postponing the submission of the transaction to the mempool.

Even though a single JSON-RPC provider is contacted, the call is still replicated by the number of nodes in the subnet (28 in case of pzp6e). This is not a problem because Ethereum guarantees that for a given nonce there is at most one transaction for a given sender. What typically happens is that one of the contacted JSON-RPC node will accept the transaction, while the other contacted nodes will reply with an error along the lines that this transaction is already known (which is expected in our case).

6 Likes

Thanks @gregory-demay , that’s very clear to me.

BTW, could you elaborate more on the special strategy for querying the most recent data?

Hi @0xkookoo

BTW, could you elaborate more on the special strategy for querying the most recent data?

A concrete example is eth_getTransactionCount which for a given Ethereum address and block height returns the number of transactions that was sent by this address for that height. Calling this method with the minter’s address (controlled via threshold ECDSA) at height latest is interesting because it tells us how many transactions from the minter were already mined. Based on that information, sent transactions with higher or equal nonces were not yet mined, which might be a sign that there are stuck and should be resubmitted.

We are going to call this method with several providers say A and B and they may have a different view on which block is the latest one: say for provider A latest is block 0x42ba9a while for provider B latest is block 0x42ba9b. In that example provider A is one block behind provider B. That means that when calling eth_getTransactionCount with Provider A we might get a smaller returned value that when doing the same query with Provider B, because the transactions in block 0x42ba9b are not yet accounted for. How we reconcile those 2 results is highly use-case dependent. In that particular example we actually are going to take the minimum of those 2 values. The reasoning being that resubmitting a transaction is cheap (if we were to resubmit a transaction that was previously mined in 0x42ba9b the the resubmitted transaction will be ignored and we would have only wasted a few cycles) while having a stuck transaction (say because 0x42ba9b was reorganized) is critical because it blocks all future transactions.

1 Like

How hard would it be to do an integration or some form of cross chain bridge with an L2 like base, enabling the IC to obtain USDC and other assets from and to base and Coinbase seamlessly? A direct integration with Ethereum will still involve expensive gas fees to interact with L1. I feel integration a leading L2 would be more beneficial in the short to mid term, especially Base.

What do you think? Perhaps I am missing something.

At what point is this the responsibility of the ecosystem vs the responsibility of the platform developers.

Circle’s CCIP exists.

1 Like

Valid point. This doesn’t have to be Dfinity. Maybe that’s something rocklab or orally can implement since it seems to be along their alley. Perhaps platform devs can use CCIP to connect to L2s to bring stables to IC.

CCIP is permissioned. Circle owns it. Just use BTC until the stables are officially supported