-
Notifications
You must be signed in to change notification settings - Fork 82
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
EVM tokens deposit & withdraw #24
Conversation
mrLSD
commented
Apr 7, 2021
- ETH accounts deposit
- ETH accounts withdraw
- Changed transfers logic
- Modified FT logic for EVM and NEAR account balance storage
- Added ETH -> NEAR. NEAR -> ETH transfer
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 is an initial review. The main suggestion is about code organization to make it easier to read and test. I recognize this is potentially a large refactor and maybe not a high enough priority given the timelines. Otherwise, there are some questions for my own understanding.
I haven't looked very closely at the correctness of the logic yet. I will do that in another pass.
let total_supply = self.ft.ft_total_supply_near(); | ||
sdk::return_output(&total_supply.to_string().as_bytes()); | ||
#[cfg(feature = "log")] | ||
sdk::log(format!("Total supply NEAR: {}", total_supply)); |
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.
Seems a little counter-intuitive to put the logging after the return. I would expect a return
to be the last statement and all following code unreachable. But maybe that is not the case with this return_output
API? I guess it just sets some state internal to the Near contract runtime which is used as the return value after the wasm execution finishes?
In any case, I think the code would map closer to normal rust patterns if return_output
acted similar to the return
keyword.
self.total_supply_eth = self | ||
.total_supply_eth | ||
.checked_add(amount) | ||
.expect("ERR_TOTAL_SUPPLY_OVERFLOW"); |
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.
Question: is there a difference between expect
and sdk::panic_utf8
?
If not, then it seems like it would be nice to be consistent in which we use. E.g.
let new_balance = balance.checked_add(amount).expect("ERR_BALANCE_OVERFLOW");
// other check_add calls ...
If there is a difference then I am interested in knowing what the difference is and when it is appropriate to use either.
@@ -156,7 +155,6 @@ mod exports { | |||
} | |||
} | |||
|
|||
#[allow(dead_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.
Hooray!
It would be great if we could eventually remove all of these that are peppered throughout the codebase.
@birchmd FYI the code was developed not only by me for that repo. |
@birchmd For example, if you try to understand the low-level logic of how contracts work through macros in current core contracts, I'm sure you will spend weeks doing this. This is beautiful, but for a low-level implementation it can make the logic very difficult to understand. |
Yes, please don't take this as a personal attack. I am only starting with this PR because it is high priority. If my questions about general patterns are inappropriate here then maybe I should start a discussion thread about them?
I think making the inputs and outputs of a function clear in its type signature makes it easier to see what a function is trying to do at a glance, without necessarily diving into the specifics in the body. But I think the biggest benefit of isolating core logic from And to be clear, I understand if we cannot do that sort of change now because of the time constraints. But if that is the case then I think it would be good to file an issue so that we return to it later.
I agree the macros hide lots of details. I was only asking why those details are important. If they are not important then I think there is value is hiding those details because it means there is less cognitive overhead for future developers looking at this code who may not be familiar with the idiosyncrasy of how rust compiles to wasm. I know all the details myself because I have worked with wasm-based smart contracts in rust before, but that may not be true of all developers who work on this project in the future. |
* EIP712-Withdraw: fixed encoding rules and order. * EIP712-Withdraw: `verify_withdraw_eip712` returns `true` only if the sender address equals to the address of message signer. * EIP712-Withdraw: update tests. * EIP712-Withdraw: refactoring. * ethabit::encode_token_packed: use right-padded encoding for `Address`. * WithdrawFromEthCallArgs: fixed `amount` type conversion.
…ra-engine into evm-deposit-withdraw
* Use references in fungible_token to avoid cloning * cargo fmt
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.
Some questions about the new tests:
// TODO: should be aligned to eth-connector balance management logic | ||
//Engine::set_balance(&address, &basic.balance); |
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.
Looks like outdated comment+code that can be deleted.
} | ||
|
||
#[test] | ||
fn test_deposit_eth_and_near() { |
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 am uncertain about this test. It calls init
twice, so I am unsure what happens to the state in between. I'm also not clear on why we need two different custodian addresses between these calls.
I would expect this test to be similar to the ones above and below; it initializes the contract, deposits some near and eth, then checks the all the total supplies are the right numbers. Is there a complication here I am missing?
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.
We have different Proof and EIP712 messages that related to different custodian_address. Kirill can't generate data for the same custodian address. That's why we call init
again.
} | ||
/* | ||
#[test] | ||
fn test_withdraw_eth() { |
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 this test still be commented out?
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.
@birchmd that method will be removed, as Alex Shevchenko announced. So we don't need that test.
} | ||
|
||
#[test] | ||
fn test_withdraw_near() { |
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.
As part of this test I think we should check the balance of RECIPIENT_ETH_ADDRESS
too.
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.
@birchmd as Alex Shevchenko announced that logic will be changed.
Clippy reports many errors like the following: ``` error: use of `expect` followed by a function call --> src/prover.rs:143:14 | 143 | .expect(str_from_slice(FAILED_PARSE)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| { panic!("{}", str_from_slice(FAILED_PARSE).to_string()) })` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#expect_fun_call ``` This commit corrects all those errors by using a new trait on Option and Result that can take an expect message in bytes.
@birchmd FYI: as we discussed last Friday, we should implement new logic for EVM and nETH tokens. |
@mrLSD I remember Alex Shevchenko presented a design for bridging ERC-20 tokens into the EVM and he said this could work for native Eth as well. It wasn't clear to me what the conclusion was since we already have work on the eth-connector. You're saying we are definitely changing over to the new design? And this means some sections of the logic will undergo significant change in a future iteration? |
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.
Another round of comments:
} | ||
|
||
pub fn init_contract() { | ||
//assert_eq!(sdk::current_account_id(), sdk::predecessor_account_id()); |
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.
Outdated code
@@ -1,6 +1,6 @@ | |||
CARGO = cargo | |||
NEAR = near | |||
FEATURES = contract | |||
FEATURES = contract,integration-test |
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'm not sure what our release pipeline looks like, but if it uses make release
then I think we should not be including the integration-test
feature by default. That feature should only be enabled when compiling the contract for testing.
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.
As I said @artob we should remove it from Make file
EthConnectorContract::new().storage_balance_of() | ||
} | ||
|
||
#[cfg(feature = "integration-test")] |
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 see, so the reason we need this feature is to mock up the prover contract functions the eth-connector uses. I think it would be nicer if we didn't have to define these functions here and instead did something within the tests themselves. I understand this probably adds more complexity to the tests, so we might not want to do it now, but it might be something to think about in a code cleanup 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.
The main question is - how?
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.
Ideally the prover contract stub would be a separate resource only used in the tests and it would be deployed to the simulated environment as part of the init
setup in the tests.
The part I'm not sure on is if we would need to commit the compiled wasm for that stub to the repo, or if it could be compiled as part of the test. I know nearcore has some contracts that are only used for testing, so we can look at what was done there.
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.
Thanks for the idea! I think we will implement it as the next step of testing.
|
||
/// Data that was emitted by the Ethereum Deposited NEAR-token event. | ||
#[derive(Debug, PartialEq)] | ||
pub struct EthDepositedNearEvent { |
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.
It seems like there is a lot of code duplication between EthDepositedNearEvent
and EthDepositedEthEvent
. Is this "accidental duplication" (i.e. two logically separate implementations which happen to look the same now, but may evolve separately, so having the duplication is a feature for future changes)? Or is it true duplication in the sense that EthDepositedNearEvent
and EthDepositedEthEvent
will always need to be changed together?
If it is the latter then I think we could reduce the duplication by either using a type parameter
pub struct EthDepositedEvent<T> {
pub eth_custodian_address: EthAddress,
pub sender: T,
pub recipient: T,
pub amount: U128,
pub fee: U128,
}
or an enum
pub struct EthDepositedEvent {
pub eth_custodian_address: EthAddress,
pub amount: U128,
pub fee: U128,
pub parties: Parties // TODO: better naming
}
pub enum Parties {
Near { sender: AccountId, recipient: AccountId },
Eth { sender: EthAddress, recipient: EthAddress },
}
Personally I think the type parameter is a cleaner approach.
} | ||
|
||
impl EthDepositedNearEvent { | ||
#[allow(dead_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.
Could we remove all the #[allow(dead_code)]
annotations from things that are now being used?
} | ||
|
||
/// Parameters of Etherium event | ||
pub type EthEventParams = Vec<(String, ParamType, bool)>; |
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.
Seems like this should be Vec<EventParam>
. It would avoid the map
call in EthEvent::fetch_log_entry_data
.
.save_contract(); | ||
} | ||
|
||
pub fn deposit_near(&self) { |
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 is maybe a silly question, but how does a user inside the EVM use the Near they have deposited?
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.
Hmm, I guess this is a silly question. I think I completely misunderstood what is supposed to be going on here. The eth
and near
parts of the FungibleToken
are actually different buckets containing the same type of token ($ETH). I was confused because Near has its own token ($NEAR) and so that's what I assumed was being referenced with comments like /// Total supply of the all NEAR token.
If I understand correctly now, the total_supply_eth
represents the amount of $ETH which is owned by addresses within our EVM, and total_supply_near
represents the amount of $ETH owned by accounts on the Near platform. There are functions which allow moving $ETH into/out of EVM addresses from Near accounts. There must also be some way for these Near accounts to send the $ETH back over to Ethereum via the bridge? And I guess the relayer_eth_account
is some way of moving $ETH directly into the Near EVM from Ethereum?
Is there a higher level description of what is supposed to be going on here? I think I've slowly pieced together what the code is doing, but having some documentation would be really helpful in making sure I understand it correctly.
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.
Thanks @mrLSD for the hard work, and @birchmd for the multiple rounds of reviews! I've now merged this to the standalone @mrLSD Please target |
Oh shoot, I was about 10% into my review. No worries :D |