-
Notifications
You must be signed in to change notification settings - Fork 38
EVM Callbacks #150
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 Callbacks #150
Conversation
x/vm/statedb/statedb.go
Outdated
@@ -111,7 +111,7 @@ func (s *StateDB) MultiStoreSnapshot() storetypes.CacheMultiStore { | |||
// the cacheCtx multi store is already a CacheMultiStore | |||
// so we need to pass a copy of the current state of it | |||
cms := s.cacheCtx.MultiStore().(storetypes.CacheMultiStore) | |||
snapshot := cms.Copy() | |||
snapshot := cms.CacheMultiStore() |
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.
Had to change this to fix the SDK upgrade. Not sure the correct way to fix this
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 should fixed on main already
x/ibc/callbacks/keeper/keeper.go
Outdated
|
||
// can only call callback if the receiver is the module address | ||
receiver := sdk.MustAccAddressFromBech32(data.Receiver) | ||
if !receiver.Equals(k.authKeeper.GetModuleAddress(types.ModuleName)) { |
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.
Only continue callback logic on recvPacket
if the receiver of the tokens is the module account address. This is because we set the msg.sender on the contract callback to the module address
x/ibc/callbacks/keeper/keeper.go
Outdated
contractAddr := common.HexToAddress(contractAddress) | ||
|
||
// TODO: Do something with the response | ||
_, err = k.evmKeeper.CallEVMWithData(cachedCtx, sender, &contractAddr, cbData.Calldata, true) |
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.
Here the contract is getting the calldata in the packet regardless of what the acknowledgment is. Question: Should we allow packet senders to provide different calldata for successful and unsuccessful acknowledgements
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 assume people will need different behaviors to either finalize or unwind application logic here, but we should get some more product feedback around exactly what is required since I don't have a clear picture of what that would look like.
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.
What currently sets the calldata used here? Is it the contract that handles the receive packet?
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 all being set by the original packet sender at the start of the packet lifecycle
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 maybe I'm missing something, but if the source calldata is chosen prior to packet creation, how does a contract determine for which packet this source callback is associated with? For example, our entrypoint contract has a usecase where when handling a timed out or failed transfer, it will send the recovered assets to an address that is stored in state with an association to the packet seq no / channel of the transfer. How would our entrypoint contract determine what packet is failing in the evm callbacks case?
return nil | ||
} | ||
|
||
func (k ContractKeeper) IBCOnAcknowledgementPacketCallback( |
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.
Note: Here we can use the packetsenderAddress
safely as msg.sender
in AcknowledgePacket
and TimeoutPacket
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.
To clarify, we can use msg.sender
here because we assume there will be no logic around who is sending the ack or timeout?
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.
Not sure I understand this question
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 don't think there's a question here. It seems like msg.sender
is not a "trustable" field. So we can't actually use this field for anything, which is why it seems prudent to have that field be the hashed value of something.
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.
Here we do not want the packet sender to spoof a msg.sender on the receiving side
But on the source side, we can allow the packet sender to approve contract executions on behalf of himself. So there is no need for a hashed value here imo
x/ibc/callbacks/keeper/keeper.go
Outdated
contractAddr := common.HexToAddress(contractAddress) | ||
|
||
// TODO: Do something with the response | ||
_, err = k.evmKeeper.CallEVMWithData(cachedCtx, sender, &contractAddr, cbData.Calldata, true) |
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.
If we support different success and failure calldata for acknowledgement, we can use the failure calldata here as well.
return nil | ||
} | ||
|
||
func (k ContractKeeper) IBCOnAcknowledgementPacketCallback( |
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.
To clarify, we can use msg.sender
here because we assume there will be no logic around who is sending the ack or timeout?
x/vm/statedb/statedb.go
Outdated
@@ -111,7 +111,7 @@ func (s *StateDB) MultiStoreSnapshot() storetypes.CacheMultiStore { | |||
// the cacheCtx multi store is already a CacheMultiStore | |||
// so we need to pass a copy of the current state of it | |||
cms := s.cacheCtx.MultiStore().(storetypes.CacheMultiStore) | |||
snapshot := cms.Copy() | |||
snapshot := cms.CacheMultiStore() |
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 should fixed on main already
ante/interfaces/cosmos.go
Outdated
UnorderedTransactionsEnabled() bool | ||
RemoveExpiredUnorderedNonces(ctx sdk.Context) error | ||
TryAddUnorderedNonce(ctx sdk.Context, sender []byte, timestamp time.Time) error |
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.
Why do we now depend on unordered transactions? I don't see it being used in this PR.
x/ibc/callbacks/keeper/keeper.go
Outdated
contractAddr := common.HexToAddress(contractAddress) | ||
|
||
// TODO: Do something with the response | ||
_, err = k.evmKeeper.CallEVMWithData(cachedCtx, sender, &contractAddr, cbData.Calldata, true) |
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 assume people will need different behaviors to either finalize or unwind application logic here, but we should get some more product feedback around exactly what is required since I don't have a clear picture of what that would look like.
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.
Self-review to create TODOs and further explanations
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.
All of these files get deleted if you call:
make contracts-all
If you want to add them back, you can just call:
make contracts-compile
evmd/app.go
Outdated
- IBC Transfer | ||
|
||
SendPacket, since it is originating from the application to core IBC: | ||
transferKeeper.SendPacket -> erc20.SendPacket -> channel.SendPacket | ||
transferKeeper.SendPacket -> callbacks.SendPacket -> erc20.SendPacket -> channel.SendPacket |
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 SendPacket flow doesn't happen this way because the ICS4 stack is not wired correctly.
ERC20Keeper must implement ICS4Wrapper
and then we can wire this correctly.
It doesn't have an effect at the moment, but we can make this fix later
*/ | ||
|
||
// create IBC module from top to bottom of stack | ||
var transferStack porttypes.IBCModule | ||
|
||
transferStack = transfer.NewIBCModule(app.TransferKeeper) | ||
maxCallbackGas := uint64(1_000_000) |
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.
Not used in EVM contractKeeper
// SPDX-License-Identifier: LGPL-3.0-only | ||
pragma solidity >=0.8.18; | ||
|
||
interface ICallbacks { |
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.
Here is the ABI interface for source side callbacks. Looking for feedback here
precompiles/callbacks/callbacks.go
Outdated
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 file ended up being unnecessary. It was generated by abigen
. We can remove if desired
return nil | ||
} | ||
|
||
func (k ContractKeeper) IBCOnAcknowledgementPacketCallback( |
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.
Here we do not want the packet sender to spoof a msg.sender on the receiving side
But on the source side, we can allow the packet sender to approve contract executions on behalf of himself. So there is no need for a hashed value here imo
x/ibc/callbacks/keeper/keeper.go
Outdated
receiver := sdk.MustAccAddressFromBech32(data.Receiver) | ||
isolatedAddr := types.GenerateIsolatedAddress(packet.GetDestChannel(), data.Sender) |
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 should be converted into an ETH address but not sure exactly how this should work.
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.
Added: 5b01591
x/ibc/callbacks/keeper/keeper.go
Outdated
ethReceiver := common.BytesToAddress(receiver) | ||
contractAddr := common.HexToAddress(contractAddress) | ||
|
||
// TODO: Approve the ERC20 tokens in the transfer packet for the contract on behalf of the receiver |
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.
Wasn't sure how this should happen in cosmos/evm so I left as a TODO now
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.
Added: 5b01591
{ | ||
"success", | ||
func() {}, | ||
nil, // TODO: This is unexpected. Not sure why this is passing without a valid contract address |
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 don't understand why the EVM didn't throw an error here
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.
If you send a transaction with packed data to an address that has no code, the transaction will still succeed, but the data will be ignored.
Added a check that fails if there's no code at the contract account.
{ | ||
"success", | ||
func() {}, | ||
nil, // TODO: This is unexpected. Not sure why this is passing without a valid contract address |
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 don't understand why this doesn't throw an error in EVM
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.
If you send a transaction with packed data to an address that has no code, the transaction will still succeed, but the data will be ignored.
Added a check that fails if there's no code at the contract account.
x/ibc/callbacks/keeper/keeper.go
Outdated
// before calling the callback. | ||
|
||
// TODO: Do something with the response | ||
_, err = k.evmKeeper.CallEVMWithData(cachedCtx, ethReceiver, &contractAddr, cbData.Calldata, true) |
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.
TODO: Check if EVM reversion matches SDK transaction expectations:
- If the EVM tx reverts, does the EVM state also revert.
- If the SDK transaction fails at a later point after a successful EVM call, does the EVM state revert along with the Cosmos state
|
||
type ContractKeeper struct { | ||
authKeeper types.AccountKeeper | ||
evmKeeper types.EVMKeeper |
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.
TODO: We need to fail if the contract address has no code before calling into EVM since this will fail silently in the EVM side
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.
Added a check that fails if there's no code at the contract account.
Create ERC20 precompiles for tokens that have more than 1 hop
- checks if erc20 exists (should exist because this callback is after the erc20 callback) - sets allowance for ibc'd erc20 for the full transfer amount to the contract on behalf of the isolated address
fails out if the contract address provided has no code. this prevents silent successes if the contract address is an EOA
include erc20 keeper
and fix tests
@@ -111,7 +111,7 @@ func (k Keeper) OnRecvPacket( | |||
// IsNativeFromSourceChain will check if the coin is native from the source chain. | |||
// If the coin denom starts with `factory/` then it is a token factory coin, and we should not convert it | |||
// NOTE: Check https://docs.osmosis.zone/osmosis-core/modules/tokenfactory/ for more information | |||
case !found && strings.HasPrefix(coin.Denom, "ibc/") && ibc.IsBaseDenomFromSourceChain(data.Denom): | |||
case !found && strings.HasPrefix(coin.Denom, "ibc/"): |
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.
@vladjdk @AdityaSripal
Could you clarify why this condition was removed?
If we're removing it intentionally, we should also update the comments in Case 1 to reflect the change.
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.
Let's update the docs here. I don't think it makes sense to only accept single hop tokens, given the fact that Eureka routes through the Hub and recipient chains may be using Eureka.
GasTotalSupply = 2_477 | ||
GasBalanceOf = 2_851 | ||
GasAllowance = 3_246 | ||
GasTotalSupply = 2_480 | ||
GasBalanceOf = 2_870 | ||
GasAllowance = 3_225 |
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.
Why are we using more gas for these now?
GasTransfer = 3_000_000 | ||
GasApprove = 30_956 | ||
GasIncreaseAllowance = 34_605 | ||
GasDecreaseAllowance = 34_519 | ||
// NOTE: These gas values have been derived from tests that have been concluded on a testing branch, which | ||
// is not being merged to the main branch. The reason for this was to not clutter the repository with the | ||
// necessary tests for this use case. | ||
// | ||
// The results can be inspected here: | ||
// https://github.com/evmos/evmos/blob/malte/erc20-gas-tests/precompiles/erc20/plot_gas_values.ipynb | ||
|
||
GasTransfer = 9_000 | ||
GasTransferFrom = 30_500 | ||
GasApprove = 8_100 | ||
GasIncreaseAllowance = 8_580 | ||
GasDecreaseAllowance = 3_620 |
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.
Why are these values changing so much?
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.
Based on this: evmos/evmos#2142
Malte was saying that he missed this in Evmos/OS when he was cherry picking commits
x/ibc/callbacks/keeper/keeper.go
Outdated
cachedCtx = evmante.BuildEvmExecutionCtx(cachedCtx). | ||
WithGasMeter(types2.NewInfiniteGasMeterWithLimit(cbData.CommitGasLimit)) | ||
|
||
receiver := sdk.MustAccAddressFromBech32(data.Receiver) |
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.
Why is it ok to panic here?
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.
Maybe because data.Receiver
has already been validated somewhere before this in the stack?
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.
Not ok - we should error out instead
x/ibc/callbacks/keeper/keeper.go
Outdated
return errorsmod.Wrap(types.ErrInvalidCalldata, "timeout callback data should not contain calldata") | ||
} | ||
|
||
sender := common.BytesToAddress(sdk.MustAccAddressFromBech32(packetSenderAddress)) |
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.
Also panics here?
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.
Same as above
x/ibc/callbacks/keeper/keeper.go
Outdated
var allowance *big.Int | ||
err = erc20.ABI.UnpackIntoInterface(&allowance, "allowance", res.Ret) | ||
if err != nil { | ||
return errorsmod.Wrapf(types.ErrAllowanceFailed, "failed to unpack allowance: %v", err) | ||
} | ||
|
||
if allowance.Cmp(big.NewInt(1)) != 0 { | ||
return errorsmod.Wrapf(types.ErrAllowanceFailed, "failed to set allowance") | ||
} |
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 think this is really confusing to read. It's not clear to me that the allowance
value being returned here is actually a boolean for success and not the actual allowance value being set by the approve
call.
Are we not able to actually unpack to a bool here instead?
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.
Let's unpack a bool and rename it to success. The naming is an artifact of me initially believing that you got an allowance amount back, and then changing the check after I realized it was a success/fail
x/ibc/callbacks/keeper/keeper.go
Outdated
// Check that the contract has received all the tokens. | ||
// NOTE: contracts must implement an IERC20(token).transferFrom(msg.sender, address(this), amount) | ||
// for the total amount, or the callback will fail. | ||
// This check is here to prevent funds from getting stuck in the isolated address, | ||
// since they would become irretrievable. | ||
contractTokenBalance := k.erc20Keeper.BalanceOf(ctx, erc20.ABI, tokenPair.GetERC20Contract(), contractAddr) // here, we can use the original ctx and skip manually adding the gas | ||
if contractTokenBalance.Cmp(amountInt.BigInt()) != 0 { | ||
return errorsmod.Wrapf(erc20types.ErrEVMCall, "contract balance %d does not equal sent amount %d", contractTokenBalance, amountInt.BigInt()) | ||
} |
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.
How does this work if the callback contract already had tokens of this denom before the callback, or if the callback has already forwarded the tokens out to another account? I think you just need to check that the sender account has no tokens in it rather than assuming anything about the state of the callback contract.
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.
Good call - current code would only allow callbacks on a contract one time and would break afterwards
Co-authored-by: Eric Warehime <[email protected]> Co-authored-by: Hyunwoo Lee <[email protected]>
Description
TODO for EVM team:
onRecvPacket
Closes: #XXXX
Author Checklist
All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.
I have...
main
branchReviewers Checklist
All items are required.
Please add a note if the item is not applicable
and please add your handle next to the items reviewed
if you only reviewed selected items.
I have...
Unreleased
section inCHANGELOG.md