# Native Aurora ERC20 to NEAR
The following is a proposal about how to build a connector that move native ERC20 tokens in Aurora to NEAR. This proposal assumes that [Aurora Cross Contract Calls](https://github.com/aurora-is-near/aurora-engine/discussions/461) are available. If this connector is done, then moving from Aurora to Ethereum (or any other blockchain) can be done by moving the tokens to NEAR, and then use NEAR <-> Ethereum bride.
## Challenges
- After multiple silos are introduced we should be able to use this connector to move tokens between different silos seamlessly.
- Do we want the same token to have the same address on different silos? This is can be achieved for clones using CREATE2 opcode.
- It should be possible to move tokens from Aurora to NEAR, or Aurora to Ethereum or between silos with one NEAR transaction on NEAR side (potentially requiring one more transaction if it is)
- See [relevant comment](https://github.com/aurora-is-near/aurora-engine/pull/436#issuecomment-1053925103) about expected UX from the product perspective.
## Design
The high level design is similar to [Fungible Token Connector](https://github.com/aurora-is-near/rainbow-token-connector/). There is a `Locker` contract on the native domain, and there is a `Factory` contract on the target domain, that will create one token contract on the target domain per contract on the native domain.
Both the `Locker` and the `Factory` are decentralized and require no operator, so anyone can move any token (with their metadata). Each contract is described with pseudo code below.
Since Aurora domains is just a smart contract on NEAR, it can interact with NEAR contract without requiring any complex bridge, but just the ability to execute cross contract calls to retrieve and verify data on-chain.
- Locker (`locker`): Smart contract on Aurora
- Factory (`factory.aurora`) Smart contract on NEAR
- Original Token (`0xaabb`) Smart contract on Aurora
- Representative Token (`0xaabb.factory.aurora`) Smart contract on NEAR
Here is the expected workflow from scratch:
1. A new (custom) ERC20 token (`0xaabb`) is deployed to Aurora and becomes popular. (Popular enough so users want to use this token in DeFi markets outside of Aurora).
2. Alice (she stands for an arbitrary user with a NEAR wallet) should call `factory.aurora.deploy_token("0xaabb")` and this will automatically deploy and create the new contract `0xaabb.factory.aurora`. This step needs to be executed exactly one time (following times will result in failure). During this step, the metadata of the token is moved automatically.
## Aurora -> NEAR
Bob wants to send 12 `0xaabb` tokens from Aurora to NEAR.
3. Since a transfer call is required first Bob should give an allowance to `locker` contract: `0xaabb.approve("locker", 12)`
4. Bob lock the tokens on Aurora, specifying the target on NEAR, by calling: `locker.lock("0xaabb", 12, target="bob.near")`. In this step an event is emitted, and a record is stored in the state with the information provided : `(token_address, amount, target_account_id)`. The tokens are transffered from Bob address to `locker` address.
5. Bob mints the tokens on NEAR calling `0xaabb.factory.aurora.mint(amount, target_account_id)`.
5.1. In the inside this function calls `locker.claim(token_address, amount, target_account_id)` Notice this is a call from a NEAR smart contract to an Aurora smart contract which is possible. When `claim` is called, it verifies that the event is stored in memory, and in case it is, then it is deleted and returns `true`, otherwise it returns `false`.
5.2. If calling `claim` returns `true` then that exact amount it minted to the target account id. Notice that anyone can call `mint` on behalf of the `target_account_id`.
At this point, Bob already have the tokens on NEAR ecosystem and can exchange them freely using their NEAR wallet.
### Single transfer
Steps 4) and 5) can be done in a single transfer, that can be even signed using an aurora wallet (for example MetaMask). This will be possible after [Aurora Cross Contract Calls](https://github.com/aurora-is-near/aurora-engine/discussions/461) are in place. The opreations looks as follows:
Bob submits an aurora tx that is routed to `async.aurora`.
1. `async.aurora` routes the transaction to `aurora.submit`
2. `locker.lock` is invoked with relevant arguments, and it returns the promises that needs to be executed next.
3. A promise to `0xabb.factory.aurora.mint` is created with relevant arguments.
### UX and Security considerations
It is important that the bundle tx doesn't fail in the middle (i.e either it succeeds or fail without any implication on the state). To achieve this we must properly estimate the amount of gas required per step, and make sure the gas attached is enough to cover the full trip. This requires exposing `gas_used` and `gas_attached` as precompiles in aurora `evm`.
If the transaction fails in the middle it is recoverable, but provides a bad experience or very complex UI which shouldn't be necessary.
## NEAR -> Aurora
Carol received 10 `0xaabb` from Bob on NEAR and wants to send them to Aurora. Let's say its Aurora wallet is `0xca401`.
6. Carol calls `0xaabb.factory.aurora.burn(10, target="0xca401")`. This burns the tokens and triggers a call that unlocks them on Aurora side.
7. A cross contract call is automatically created: `locker.unlock(token="0xaabb", 10, target="0xca401")` that will transfer this tokens to carol account on Aurora
This will work naturally in a single NEAR transaction. Notice this step will require that Carol has a NEAR wallet.
## Aurora -> Ethereum
The main point of this section is to provide a generic interface that allows calling ft_transfer_call automatically after minting on behalf of the user. The mint function on `0xaabb.factory.aurora` should have the following interface:
```rs
fn mint(amount: u256, receiver: ReceiverAccountId)
enum ReceiverAccountId {
Mint {
receiver: AccountId
},
MintCall {
receiver: AccountId,
target: AccountId,
gas: Gas,
args: Bytes,
}
}
```
This will allow minting tokens on NEAR, and automatically locking them on `EthereumLockerOnNEAR`.
Note: It is important to properly verify the amount of gas attached is enough to cover for the whole transaction.
## Contracts
### Locker on Aurora
```python=
class Locker:
AURORA_ASYNC = 'async.aurora'
def __init__(self, near_factory: AccountId):
"""
@near_factory: Account id pointing to the factory on NEAR.
"""
self.near_factory = near_factory
self._internal_nonce = 0
# Map with balance for each user for each token that hasn't been
# claimed on NEAR
self.not_claimed = map(nonce -> (token, amount, receipient))
def lock(self,
token: Addres,
amount: u256,
receipient: ReceipientAccountId) -> Vec<Promise>:
# Transfer the tokens from the user to the locker
self.token.transferFrom(msg.sender, amount, this)
nonce = self._internal_nonce
self._internal_nonce += 1
self.not_claimed[nonce] = (token, amount, receipient)
# Create a promise to call `token.factory.aurora.mint(...)`
promise = PromiseDescription()
return [promise]
def lock_async(self, *args) -> Vec<Promise>:
"""
Same as `lock` but only executes if it was called from `async.aurora`
"""
assert env.predecessor_account_id() == Locker.AURORA_ASYNC
# Make sure the attached gas is enough to cover for the whole transaction
assert verify_gas(args)
return self.lock(*args)
def claim(self, token: Address, nonce: u256) -> Option<(amount, receipient)>:
# This can only be called from the account of the token
token_account_id_on_near = f'{token}.{self.near_factory}'
assert env.predecessor_account_id() == token_account_id_on_near
(expected_token, amount, receipient) = self.not_claimed[nonce]
assert expected_token = token
del self.not_claimed[nonce]
return (amount, recipient)
def unlock(self, token: Address, amount: u256, receipient: Address):
# This can only be called from the account of the token
token_account_id_on_near = f'{token}.{self.near_factory}'
assert env.predecessor_account_id() == token_account_id_on_near
# Unlock the tokens for this receipient
token.transfer(amount, receipient)
enum ReceipientAccountId {
Mint(receipient),
MintCall(receipient, target, gas, arguments)
}
```
### Factory on NEAR
```python=
class Factory:
# Inline bytecode of each token
TokenByteCode = load_from_file!()
def __init__(self, aurora: AccountId, locker: Address)
# Keep track of the locker address and aurora account id
self.aurora = aurora
self.locker = locker
self.deployed_tokens = set()
def deploy_token(self, token: Address):
"""
Deploy a new token contract with the provided Address.
This is trustless and doesn't require any sort of validation.
"""
assert token not in self.deployed_tokens
self.deployed_tokens.add(token)
new Promise(f'{token}.{env.current_account_id()}')
.deploy()
.init(self.aurora, self.locker)
```
### Tokens on NEAR
```python=
class Token(NEP141):
def __init__(self,
token: Address,
aurora: AccountId,
locker: Address):
self.token = token
self.aurora = aurora
self.locker = locker
# Automatic fetch and update metadata
self.update_metadata()
def mint(self, nonce: u256):
(amount, receipient) = self.aurora.call(
self.locker.claim(self.token, nonce)
)
self._mint(receipient.recipient, amount)
# Check if the recipient specifies a transfer call and execute it
if requires_transfer_call(receipient):
self.transfer_call(...)
def burn(self, amount: u256, receipient: Address):
self._burn(env.predecessor_account_id(), amount)
self.aurora.call(
self.locker.unlock(self.token, amount, receipient)
)
def update_metadata(self):
metadata = self.aurora.call(
self.token.get_metadata()
)
self._set_metadata(metadata)
```
In this proposal
## Additional considerations
### Admin keys (pause and upgrade)
These contracts are trustless and decentralised. However, there should be an operator with the ability to upgrade and pause the contracts in case of any emergence. To prevent any malicious intentions, this controlles must be behind a DAO or at least a multisig.
In case there is an emergency on the token contracts, they all should be paused and upgraded at once. For `Token` contract I propose an interface where the binary is uploaded once to the factory, and all tokens can upgrade automatically by calling a single function that fetch the binary, and auto-upgrade.
### Token storage requirements
The Tokens should not impose any storage requirement on the user. Storage requirements usually have affected User Experience while not providing huge benefits. If the contract is running out of balance for storage renting, is still responsibility of the user to provide enough balance to cover the storage.
### Multi Token
We could store all tokens on NEAR in a single contract using [Multi Token Interface (NEP-246)](https://github.com/near/NEPs/pull/245).
### Gas precompiles
We should introduce `gas_used` and `gas_attached` precompiles in aurora-engine.
### Security considerations
This design assumes there is no feasible way to find a collition between different Aurora address which is given for granted. We need to make sure this is still the case after introducing implicit account ids.