# ERC1820 code review ###### tags: erc1820, rengo-labs, casper-network, rust, test, code-review ## Questions 1. **(Raul)** Why `CLType::Key` is [used on creation](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/entry_points.rs#L36) of `entry_points` that [return](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/lib.rs#L88) `Result<(), ApiError>`? In ERC20 package they [use](https://github.com/casper-ecosystem/erc20/blob/d1e0d0b26668e4ca902fe90756be0d28859d447a/erc20/src/entry_points.rs#L91) `CLType::Unit` instead. 2. **CasperLabs** In [ERC20 package upon entry_point creation](https://github.com/casper-ecosystem/erc20/blob/d1e0d0b26668e4ca902fe90756be0d28859d447a/erc20/src/entry_points.rs#L91) used `CLType::Unit` for [entry_points that dont return any](https://github.com/casper-ecosystem/erc20/blob/d1e0d0b26668e4ca902fe90756be0d28859d447a/example/erc20-token/src/main.rs#L54). However I [saw usage](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/entry_points.rs#L36) of `CLType::Key` for the same [non-return function](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/example/implementations/erc1820-registry/src/main.rs#L46). **Question:** What would be a proper choice for `no-return` entry_points, when we coding it? `CLType::Key` or `CLType::Unit` **Answer:** - [A1](https://rengocapital.slack.com/archives/C02B1QEEMHP/p1658927666577949?thread_ts=1658924675.783749&cid=C02B1QEEMHP). `Unit`. The `EntryPoints` structure is not 100% when it comes to the return value. - [A2](https://rengocapital.slack.com/archives/C02B1QEEMHP/p1658927863395809?thread_ts=1658924675.783749&cid=C02B1QEEMHP). https://docs.rs/casper-types/latest/casper_types/struct.EntryPoints.html - [A3](https://rengocapital.slack.com/archives/C02B1QEEMHP/p1658927922860099?thread_ts=1658924675.783749&cid=C02B1QEEMHP). This is the struct that is passed in when you create a new contract version. In this the EntryPoint struct writes a lot of things as you have seen. return type, parameters, and security things. the return type as I recall is not enforced. - [A4](https://rengocapital.slack.com/archives/C02B1QEEMHP/p1658928425277429?thread_ts=1658924675.783749&cid=C02B1QEEMHP). Practically you can put anything there and it will not enforce it. 3. **CasperLabs** In your ERC20 test examples in [test_fixture.rs](https://github.com/casper-ecosystem/erc20/blob/master/example/erc20-tests/src/test_fixture.rs) were implemented: - [query_contract()](https://github.com/casper-ecosystem/erc20/blob/d1e0d0b26668e4ca902fe90756be0d28859d447a/example/erc20-tests/src/test_fixture.rs#L87) to recieve values (read from chain). Looks like imitation of `entry_point` call that `return values`. - [call()](https://github.com/casper-ecosystem/erc20/blob/d1e0d0b26668e4ca902fe90756be0d28859d447a/example/erc20-tests/src/test_fixture.rs#L102) to execute logic of an `entry_point`. And it's used in test for `no-return` entry_points. **Question:** How could be tested an `entry_point` that returns value ([example](https://github.com/casper-ecosystem/erc20/blob/d1e0d0b26668e4ca902fe90756be0d28859d447a/example/erc20-token/src/main.rs#L41)) through `contract calling` and `not querying`? **Entry point example:** Return a `keccak256_bytes_hash` for provided `string_value`. In that example I don't want to store any values on chain. But would like to consume a `string`, compute `hash` and return it to a caller. **Question:** How that logic could be tested using [casper-engine-test-support](https://crates.io/crates/casper-engine-test-support)? **Answer:** - [1](https://rengocapital.slack.com/archives/C02B1QEEMHP/p1658927698117709?thread_ts=1658925847.965779&cid=C02B1QEEMHP). Use another session code that calls into the contract. No other option. - [2](https://rengocapital.slack.com/archives/C02B1QEEMHP/p1658928454586939?thread_ts=1658925847.965779&cid=C02B1QEEMHP). https://github.com/casper-ecosystem/kyc-proxy-contract/blob/master/contract/src/test_contract.rs ## Error codes ### 1820 - 999 - "Not the manager" (managers_registry.rs) - 1000 - "Not the manager" (implementers_registry.rs) - 1001 - "Does not implement the interface" (implementers_registry.rs) ## [lib.rs](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/main/erc1820/src/lib.rs) 1. Implement `interfaceHash(String)` function. That returns `hash`(keccak256 bytes) of provided `string` value. ``` function interfaceHash( string calldata _interfaceName ) external pure returns(bytes32) { return keccak256(abi.encodePacked(_interfaceName)); } ``` 2. Delete unused codelines [here](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/lib.rs#L14) ``` mod address; ``` ## [managers_registry.rs](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/main/erc1820/src/managers_registry.rs) 1. Rename `manager` into `new_manager` [here](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/managers_registry.rs#L17) and [here](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/managers_registry.rs#L29) 2. Rename `previous_manager` into `current_manager` [here](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/managers_registry.rs#L19) and [here](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/managers_registry.rs#L20) 3. Rename `manager` into `current_manager` [here](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/managers_registry.rs#L36) and [here](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/managers_registry.rs#L41) 4. Rename `hash_account` into `hash_string` [here](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/managers_registry.rs#L24) and [here](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/managers_registry.rs#L28) 5. Change `unwrap_or_default()` for `unwrap()` [`here`](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/managers_registry.rs#L33) 7. [`get_manager(account)`](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/managers_registry.rs#L33) Should return `account` if `manager` is not registered. **Currently**: Returns `Caller` ``` pub fn get_manager(manager_uref: URef, account: Key) -> Key { let caller = runtime::get_caller(); let hash_string = to_str(account); let manager: Key = storage::dictionary_get( manager_uref, hash_string.as_str() ).unwrap_or_default().unwrap_or(Key::Account(caller)); manager } ``` Update: ``` pub fn get_manager(manager_uref: URef, account: Key) -> Key { let hash_string = to_str(account); let manager: Key = storage::dictionary_get( manager_uref, hash_string.as_str() ).unwrap_or_default().unwrap_or(account); manager } ``` 7. [`set_manager(manager_uref, account, manager)`](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/managers_registry.rs#L17) Should check if `caller` is a current `manager` of `account`. Throw error `Not the manager` if not. ``` require(getManager(_addr) == msg.sender, "Not the manager"); ``` Change this: ``` let previous_manager = get_manager(manager_uref, account); if previous_manager.ne(&account) { return Err(ApiError::User(999)) } ``` For this: ``` let caller_hash = runtime::get_caller(); let caller_key = Key::Account(caller); let previous_manager = get_manager(manager_uref, account); if previous_manager.ne(caller_key) { return Err(ApiError::User(error_code_not_the_manager)) } ``` 8. Unused codelines [here](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/managers_registry.rs#L6) and [here](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/managers_registry.rs#L8): ``` use casper_contract::unwrap_or_revert::UnwrapOrRevert, use casper_types::account::AccountHash; ``` ## [implementers_registry.rs](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/main/erc1820/src/implementers_registry.rs) 1. Update `use ::{detail};` into `use ::detail;` [here](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/implementers_registry.rs#L6) 2. [`get_implementer(implementer_uref, account, interface_hash)`](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/implementers_registry.rs#L43) Leave option, when user specified `empty` `account` value Consume `Caller` as `account: Key`. ``` /// (If '_addr' is the zero address then 'msg.sender' is assumed.) address addr = _addr == address(0) ? msg.sender : _addr; ``` Change: ``` pub fn get_implementer(implementer_uref: URef, account: Key, interface_hash: Bytes) -> Key { let hash_string = to_str(account, interface_hash); let implementer = storage::dictionary_get( implementer_uref, hash_string.as_str() ).unwrap().unwrap_or(Key::Account(AccountHash::default())); implementer } ``` For: ``` pub fn get_implementer(implementer_uref: URef, _account: Key, interface_hash: Bytes) -> Key { let account: Key; If _account.eq(Key::Account(AccountHash::default())) { let caller = runtime::get_caller(); account = Key::Account(caller); } else { account = _account; } let hash_string = to_str(account, interface_hash); let implementer = storage::dictionary_get( implementer_uref, hash_string.as_str() ).unwrap().unwrap_or(Key::Account(AccountHash::default())); implementer } ``` 3. [`create_or_update_implementer(implementer_uref, account, interface_hash, implementer, manager)`](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/implementers_registry.rs#L17) Leave option, when user specified `empty` `account` value Consume `Caller` as `account: Key`. ``` /// (If '_addr' is the zero address then 'msg.sender' is assumed.) ``` Change: ``` pub fn create_or_update_implementer( implementer_uref: URef, account: Key, interface_hash: Bytes, implementer: Key, manager: Key ) -> Result<(), ApiError> { let hash_string: String; if manager.ne(&account) { return Err(ApiError::User(1000)); } if implementer.eq(&manager) || implementer.eq(&Key::Account(AccountHash::default())) { return Err(ApiError::User(1001)) } hash_string = to_str(account, interface_hash); storage::dictionary_put( implementer_uref, hash_string.as_str(), implementer); Ok(()) } ``` For: ``` pub fn create_or_update_implementer( implementer_uref: URef, account: Key, interface_hash: Bytes, implementer: Key, manager: Key ) -> Result<(), ApiError> { let hash_string: String; let caller_hash = runtime::get_caller(); let caller_key = Key::Account(caller); let account: Key; If _account.eq(Key::Account(AccountHash::default())) { account = caller_key; } else { account = _account; } if manager.ne(&account) { return Err(ApiError::User(1000)); } if implementer.eq(&manager) || implementer.eq(&Key::Account(AccountHash::default())) { return Err(ApiError::User(1001)) } hash_string = to_str(account, interface_hash); storage::dictionary_put( implementer_uref, hash_string.as_str(), implementer); Ok(()) } ``` 4. [`create_or_update_implementer(implementer_uref, account, interface_hash, implementer, manager)`](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/implementers_registry.rs#L17) Should check that `caller` is a `manager` **Currently**: Checks if `account` is a `manager` Change: ``` if manager.ne(&account) { return Err(ApiError::User(1000)); } ``` For: ``` let caller_hash = runtime::get_caller(); let caller_key = Key::Account(caller); if manager.ne(&caller_key) { return Err(ApiError::User(error_code_not_the_manager)); } ``` 5. Need to figure out how to: Check that `implementer` can implement `interface_hash` for `account` **Before that need to:** Verify that `implementer` is not `0` or `empty` or `default` Solidity: ``` if (_implementer != address(0) && _implementer != msg.sender) { require( ERC1820ImplementerInterface(_implementer) .canImplementInterfaceForAddress(_interfaceHash, addr) == ERC1820_ACCEPT_MAGIC, "Does not implement the interface" ); } ``` You actually can register an `implementer` with `address zero`. It means you will erase implementation for `account` **Change:** ``` if implementer.eq(&manager) || implementer.eq(&Key::Account(AccountHash::default())) { return Err(ApiError::User(1001)) } ``` **For:** ``` let caller_hash = runtime::get_caller(); let caller_key = Key::Account(caller); let default_account = Key::Account(AccountHash::default()); /// Verify that `implementer` is not `0` or `empty` or `default` /// And `implementer` is not a `caller` if implementer.ne(default_account) && implementer.ne(caller_key) { /// Check that `implementer` can implement `interface_hash` for `account` /// Figure out how to check interface implementation return Err(ApiError::User(error_code_does_not_implement_the_interface)) } ``` ## [entry_points.rs](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/entry_points.rs) 1. [`interfaceHash()`](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/entry_points.rs#L71) Change `SET_INTERFACE_ENTRY_POINT` for `INTERFACE_HASH_ENTRY_POINT` Update `vec![]` of entry_point arguments: Eliminate: `ACCOUNT_RUNTIME_ARG_NAME`, `I_HASH_RUNTIME_ARG_NAME`, `IMPLEMENTER_RUNTIME_ARG_NAME`. Add: `INTERFACE_NAME_ARG_NAME` 2. [`default()`](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/entry_points.rs#L86) Add `interfaceHash()` function into default `entry_point` list. 3. Change `CLType::Key` for `CLType::Unit` ## [address.rs](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/main/erc1820/src/address.rs) ERC20 `address` implementation could be deleted, since not used. ## [constants.rs](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/main/erc1820/src/constants.rs) 1. [Next](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/constants.rs#L20) `entry_point` constants could be deleted since not used. - `UPDATE_ERC165_CACHE` - `IMPLEMENTERS_ERC165_INTERFACE` - `IMPLEMENTERS_ERC165_INTERFACE_NO_CACHE` 2. Update [constant name](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/constants.rs#L18) From `INTERFACE_HASH` into `INTERFACE_HASH_ENTRY_POINT` 3. Update [constant value](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/constants.rs#L31). To suit rest constant values. From `i_hash` into `interface_hash` ## [lib.rs](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/main/erc1820/src/lib.rs) 1. Unused imports: - `String` [here](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/lib.rs#L21) 2. Rename: - `i_hash` for `interface_hash` on linse [64](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/lib.rs#L64), [70](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/lib.rs#L70), [77](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/lib.rs#L77) and [81](https://github.com/Rengo-Labs/CasperLabs-ERC777/blob/9970a180a20f70bf6b0eefb9bd73cd8193e21e8a/erc1820/src/lib.rs#L81). 3. Update comments. Some comments are copy pasted from different sources (erc20). And not enough details about function logic and checks.