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

Feat(examples): FT Refund #13

Merged
merged 15 commits into from
Nov 9, 2023
Merged

Feat(examples): FT Refund #13

merged 15 commits into from
Nov 9, 2023

Conversation

birchmd
Copy link
Member

@birchmd birchmd commented Aug 22, 2023

This example shows how to bridge a NEP-141 token from Aurora to Near to use in a contract there and includes a refund flow where if the token is refunded to the XCC account on Near then it is automatically bridged back to Aurora and credited to the original transaction signer's account.

See the README in the example for more information.

@birchmd birchmd requested a review from mooori August 22, 2023 14:19
@mooori
Copy link
Contributor

mooori commented Aug 23, 2023

cargo test in examples/ft-refund fails with:

Compiling ft-refund v0.1.0 (/home/mo/opensource/aurora-contracts-sdk/examples/ft-refund/near-contract)
error[E0599]: no method named `verbose_call_evm_contract_with` found for reference `&AuroraEngine` in the current scope
   --> integration-tests/src/lib.rs:313:18
    |
313 |                 .verbose_call_evm_contract_with(
    |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: there is a method with a similar name: `call_evm_contract`

For more information about this error, try `rustc --explain E0599`.
error: could not compile `recursive-callbacks-integration-tests` due to previous error

AuroraEngine has a method call_evm_contract_with (missing verbose_) which seems to have the same signature. Are these two methods equivalent apart from different log levels?

@birchmd
Copy link
Member Author

birchmd commented Aug 23, 2023

Thanks @mooori Sorry about that. That was left over from some debugging. Fixed in 6b44501

@sept-en
Copy link
Contributor

sept-en commented Aug 23, 2023

Requested @karim-en and @olga24912 reviews as this seems to be at least partly common with the functionality implemented in Near-One/rainbow-token-connector#225

constructor(string memory _nearAccountId, IERC20 _wNEAR) {
nearAccountId = _nearAccountId;
near = AuroraSdk.initNear(_wNEAR);
wNEAR = _wNEAR;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why we save wNEAR as well and not just use near.wNEAR?

Copy link
Member Author

Choose a reason for hiding this comment

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

The fields of the NEAR struct are not public, so cannot be used outside the SDK itself. Being the maintainers of the SDK that is something we have the power to change, but I'm not sure if we want to. Exposing the fields publicly leaks some of the implementation details and we may want to change them in the future.

);
}

function approveWNEAR() public {
Copy link
Contributor

Choose a reason for hiding this comment

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

If I correctly understood the meaning of this function -- approve will be called by CALLBACK_ROLE and we will allowe this contract to spend the CALLBACK_ROLE's wNEAR. This should be explained in the comments to this function. And who provided wNEAR to the CALLBACK_ROLE?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is the tricky part of this example (and a sort of pitfall in the XCC design). The CALLBACK_ROLE is logically the same as this contract because it is the implicit address of this contract's XCC account (and therefore all transactions from CALLBACK_ROLE actually originated from this contract). So this function is essentially just approving itself to spend its own tokens. The reason its needed is because the addresses are different on the level of the EVM even though they are logically the same entity as I have described above. I tried to explain this in the README file and comments in the integration tests. But I could add a comment here too.

I think it is the maintainer of this contract who is responsible for funding WNEAR to CALLBACK_ROLE. It only requires 1 yoctoNEAR per call, so if a user were to try to maliciously drain the WNEAR by intentionally making failed calls they would need to invest a lot of ETH or NEAR in gas fees. If the maintainer initially funded it with 1 WNEAR then 10^24 refunds would be needed to drain it and that is infeasible.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think a comment referring to the README/docs would be helpful. As mentioned, it's the tricky part and someone who's using this example to learn might not get it right away.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added a comment in 6180a22

1,
REFUND_NEAR_GAS
);
callFtTransfer.transact();
Copy link
Contributor

Choose a reason for hiding this comment

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

What if this function will fail? For example because we don't have 1 yoctowNEAR.

Copy link
Member Author

Choose a reason for hiding this comment

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

If this function fails then the funds would remain in the contract's XCC account. It would be possible to add an owner-only manual recovery as a fallback option.

Copy link
Contributor

@mooori mooori left a comment

Choose a reason for hiding this comment

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

To help myself with the review, I’ve made the diagram below. I’m not quite sure if I understood everything that’s going on correctly. @birchmd could you please have a look and let me know if the diagram is accurate? If yes, adding it to the README perhaps could help other readers too.

drawing_ft_transfer excalidraw

examples/ft-refund/README.md Outdated Show resolved Hide resolved
examples/ft-refund/README.md Outdated Show resolved Hide resolved
examples/ft-refund/near-contract/src/lib.rs Show resolved Hide resolved
);
}

function approveWNEAR() public {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think a comment referring to the README/docs would be helpful. As mentioned, it's the tricky part and someone who's using this example to learn might not get it right away.

Copy link
Contributor

@mooori mooori left a comment

Choose a reason for hiding this comment

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

@birchmd thanks for the clarifications and updates! I’ve proposed some comments to make assumptions and requirements more explicit. If they are needed, I suppose, depends on the expected audience and their familiarity with token bridging and Aurora engine internals. Feel free to modify or discard these comments.

The commit to add the diagram to the README (as discussed offline) is coming up shortly.

examples/ft-refund/solidity-contract/src/FtRefund.sol Outdated Show resolved Hide resolved
examples/ft-refund/solidity-contract/src/FtRefund.sol Outdated Show resolved Hide resolved
Copy link
Contributor

@mooori mooori left a comment

Choose a reason for hiding this comment

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

LGTM. One note though: When commenting out this call

solidity_contract
.ft_transfer_call(
&account,
&test_erc20.address,
test_token_contract.id().to_string(),
1,
)
.await
.unwrap();

the test still passes, as there seem to be no checks of intermediate states (which are quite a lot for this example).

I’m not sure if this is relevant for an example. Anyway, one way to tackle this might be making near-contract keep a small amount if the msg is "refund”, e.g. as a fee. Then the following assert could check the balance is amount - fee to ensure something has happened.

let erc20_balance = engine.erc20_balance_of(&test_erc20, address).await.unwrap();
assert_eq!(erc20_balance, "1".into());

uint128 amount
) public {
token.transferFrom(msg.sender, address(this), amount);
token.withdrawToNear(
Copy link
Contributor

Choose a reason for hiding this comment

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

@birchmd In our fast bridge and silo to silo transfers contracts we added this comment.

// WARNING: The withdrawToNear() method works asynchronously.
// As a result, there is no guarantee that this method will be completed before ft_transfer_call().
// In case of such an error, the user will be able to call the withdraw() method and get his tokens back.
// We expect such an error not to happen as long as transactions are executed in one shard.

Do you agree with these conclusions?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, I do not think that is possible with this example (I'm not sure if the bridge contracts differ from this example significantly). The reason is because the important receipts are going through the same NEP-141 contract and I think the protocol implementation guarantees as FIFO ordering on receipts without data dependencies (at least when talking about cross-shard receipts; local receipts are always processed first).

What happens in this example is that withdrawToNear creates an ft_transfer receipt to the NEP-141 contract (transferring the tokens from Aurora to the user's XCC account) and at the same time the XCC mechanism creates a receipt calling the execute method on the user's XCC account. When the execute receipt resolves it will create a new receipt calling the ft_transfer_call function on the NEP-141 contract. No matter what, this second receipt is created after the first one created by Aurora. Even if the first receipt does not resolve right away (because of congestion on the NEP-141 contract's shard), the FIFO execution will ensure the ft_transfer (i.e. withdrawToNear) executes before ft_transfer_call.

Copy link
Member Author

Choose a reason for hiding this comment

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

Turns out what I thought was true about FIFO ordering was incorrect. The bridge did in fact encounter an error due to receipts being executed out of order. So the warning in the comment is accurate.

Fortunately the issue should come up rarely because there is still 1 block between when the ft_transfer receipt is created and when the ft_transfer_call receipt is. So the only way an error could happen is if there is congestion on the shard containing the NEP-141 token causing the ft_transfer receipt to be delayed by one or more blocks.

Copy link
Member Author

Choose a reason for hiding this comment

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

This issue is addressed at the Engine level in aurora-is-near/aurora-engine#868

@birchmd
Copy link
Member Author

birchmd commented Nov 9, 2023

making near-contract keep a small amount if the msg is "refund”, e.g. as a fee. Then the following assert could check the balance is amount - fee to ensure something has happened.

Thanks for the suggestion @mooori ! Implemented in 10ee691

@birchmd birchmd dismissed olga24912’s stale review November 9, 2023 18:04

Review comments have been addressed

@birchmd birchmd merged commit 76cb2f4 into main Nov 9, 2023
@birchmd birchmd deleted the ft-refund branch November 9, 2023 18:10
github-merge-queue bot pushed a commit to aurora-is-near/aurora-engine that referenced this pull request Nov 20, 2023
## Description

This PR is addressing a [usability issue with
XCC](aurora-is-near/aurora-contracts-sdk#13 (comment))
brought up by @karim-en .

It is somewhat common that XCC requires tokens to be bridged from the
user's Aurora address to their XCC account on Near. Naturally, this
bridging needs to happen before the XCC promise resolves so that the
tokens are available for it to use. However, due to the async nature of
Near we could not guarantee that the bridging would happen before the
XCC call.

In this PR we change how the Engine produces promises so that they are
sequential instead of concurrent (i.e. all the promises produced by the
EVM are connected by the `then` combinator, in the order they were
produced during the EVM execution). This means a contract which calls
the exit precompile and then later calls XCC will have the promises
happen in that same order, as the developer intended.

## Performance / NEAR gas cost considerations

N/A

## Testing

Existing XCC tests
aleksuss pushed a commit to aurora-is-near/aurora-engine that referenced this pull request Nov 28, 2023
## Description

This PR is addressing a [usability issue with
XCC](aurora-is-near/aurora-contracts-sdk#13 (comment))
brought up by @karim-en .

It is somewhat common that XCC requires tokens to be bridged from the
user's Aurora address to their XCC account on Near. Naturally, this
bridging needs to happen before the XCC promise resolves so that the
tokens are available for it to use. However, due to the async nature of
Near we could not guarantee that the bridging would happen before the
XCC call.

In this PR we change how the Engine produces promises so that they are
sequential instead of concurrent (i.e. all the promises produced by the
EVM are connected by the `then` combinator, in the order they were
produced during the EVM execution). This means a contract which calls
the exit precompile and then later calls XCC will have the promises
happen in that same order, as the developer intended.

## Performance / NEAR gas cost considerations

N/A

## Testing

Existing XCC tests
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.

6 participants