-
Notifications
You must be signed in to change notification settings - Fork 829
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
IBC transfer precompile #1459
IBC transfer precompile #1459
Changes from 3 commits
6f3c513
300140c
3e5b00f
fab8312
4f845e0
86e8e2b
06189c9
5db906c
3c4e47f
a2fd912
cbfc70c
cf9d1ca
8cd477e
ec484c1
e04fa37
9a40d9b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
address constant BANK_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000001008; | ||
|
||
IBC constant BANK_CONTRACT = IBC( | ||
BANK_PRECOMPILE_ADDRESS | ||
); | ||
|
||
interface IBC { | ||
// Transactions | ||
function transfer( | ||
address fromAddress, | ||
address toAddress, | ||
string memory port, | ||
string memory channel, | ||
string memory denom, | ||
uint256 amount | ||
) external returns (bool success); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
[ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. keeping formatted for readability for now (will flatten when code is post-draft) |
||
{ | ||
"inputs": [ | ||
{ | ||
"internalType": "address", | ||
"name": "fromAddress", | ||
"type": "address" | ||
}, | ||
{ | ||
"internalType": "address", | ||
"name": "toAddress", | ||
"type": "address" | ||
}, | ||
{ | ||
"internalType": "string", | ||
"name": "port", | ||
"type": "string" | ||
}, | ||
{ | ||
"internalType": "string", | ||
"name": "channel", | ||
"type": "string" | ||
}, | ||
{ | ||
"internalType": "string", | ||
"name": "denom", | ||
"type": "string" | ||
}, | ||
{ | ||
"internalType": "uint256", | ||
"name": "amount", | ||
"type": "uint256" | ||
} | ||
], | ||
"name": "transfer", | ||
"outputs": [ | ||
{ | ||
"internalType": "bool", | ||
"name": "success", | ||
"type": "bool" | ||
} | ||
], | ||
"stateMutability": "view", | ||
"type": "function" | ||
} | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
package ibc | ||
|
||
import ( | ||
"bytes" | ||
"embed" | ||
"errors" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" | ||
"github.com/ethereum/go-ethereum/accounts/abi" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/core/vm" | ||
pcommon "github.com/sei-protocol/sei-chain/precompiles/common" | ||
"math/big" | ||
) | ||
|
||
const ( | ||
TransferMethod = "transfer" | ||
) | ||
|
||
const ( | ||
IBCAddress = "0x0000000000000000000000000000000000001008" | ||
) | ||
|
||
var _ vm.PrecompiledContract = &Precompile{} | ||
|
||
// Embed abi json file to the executable binary. Needed when importing as dependency. | ||
// | ||
//go:embed abi.json | ||
var f embed.FS | ||
|
||
func GetABI() abi.ABI { | ||
abiBz, err := f.ReadFile("abi.json") | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
newAbi, err := abi.JSON(bytes.NewReader(abiBz)) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return newAbi | ||
} | ||
|
||
type Precompile struct { | ||
pcommon.Precompile | ||
address common.Address | ||
transferKeeper pcommon.TransferKeeper | ||
evmKeeper pcommon.EVMKeeper | ||
|
||
TransferID []byte | ||
} | ||
|
||
func NewPrecompile(transferKeeper pcommon.TransferKeeper, evmKeeper pcommon.EVMKeeper) (*Precompile, error) { | ||
newAbi := GetABI() | ||
|
||
p := &Precompile{ | ||
Precompile: pcommon.Precompile{ABI: newAbi}, | ||
address: common.HexToAddress(IBCAddress), | ||
transferKeeper: transferKeeper, | ||
evmKeeper: evmKeeper, | ||
} | ||
|
||
for name, m := range newAbi.Methods { | ||
switch name { | ||
case TransferMethod: | ||
p.TransferID = m.ID | ||
} | ||
} | ||
Comment on lines
+69
to
+74
Check warning Code scanning / CodeQL Iteration over map Warning
Iteration over map may be a possible source of non-determinism
|
||
|
||
return p, nil | ||
} | ||
|
||
// RequiredGas returns the required bare minimum gas to execute the precompile. | ||
func (p Precompile) RequiredGas(input []byte) uint64 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is IBC gas consumption pretty constant or can vary from call-to-call quite a bit? If the latter we can report gas consumption dynamically similar to https://github.com/sei-protocol/sei-chain/blob/seiv2/precompiles/wasmd/wasmd.go#L111-L135 |
||
methodID := input[:4] | ||
|
||
method, err := p.ABI.MethodById(methodID) | ||
if err != nil { | ||
// This should never happen since this method is going to fail during Run | ||
return 0 | ||
} | ||
|
||
return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) | ||
} | ||
|
||
func (p Precompile) Run(evm *vm.EVM, caller common.Address, input []byte, value *big.Int) (bz []byte, err error) { | ||
ctx, method, args, err := p.Prepare(evm, input) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
switch method.Name { | ||
case TransferMethod: | ||
return p.transfer(ctx, method, args) | ||
} | ||
return | ||
} | ||
func (p Precompile) transfer(ctx sdk.Context, method *abi.Method, args []interface{}) ([]byte, error) { | ||
pcommon.AssertArgsLength(args, 6) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can reference https://github.com/sei-protocol/sei-chain/blob/seiv2/precompiles/bank/bank.go#L116 for example usage of |
||
|
||
senderAddress, err := p.accAddressFromArg(ctx, args[0]) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
receiverAddress, err := p.accAddressFromArg(ctx, args[1]) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
port, ok := args[2].(string) | ||
if !ok { | ||
return nil, errors.New("port is not a string") | ||
} | ||
|
||
channelID, ok := args[3].(string) | ||
if !ok { | ||
return nil, errors.New("channelID is not a string") | ||
} | ||
|
||
denom := args[4].(string) | ||
if denom == "" { | ||
return nil, errors.New("invalid denom") | ||
} | ||
|
||
amount, ok := args[5].(*big.Int) | ||
if !ok { | ||
return nil, errors.New("amount is not a big.Int") | ||
} | ||
|
||
if amount.Cmp(big.NewInt(0)) == 0 { | ||
// short circuit | ||
return method.Outputs.Pack(true) | ||
} | ||
|
||
coin := sdk.Coin{ | ||
Denom: denom, | ||
Amount: sdk.NewIntFromBigInt(amount), | ||
} | ||
|
||
height := clienttypes.Height{} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will to defer to kartik here since it's IBC domain knowledge There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's the timeout height, we can pass that in as a parameter in the transfer method and update the abi |
||
|
||
err = p.transferKeeper.SendTransfer(ctx, port, channelID, coin, senderAddress, receiverAddress.String(), height, 0) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
return method.Outputs.Pack(true) | ||
} | ||
|
||
func (Precompile) IsTransaction(method string) bool { | ||
switch method { | ||
case TransferMethod: | ||
return true | ||
default: | ||
return false | ||
} | ||
} | ||
|
||
func (p Precompile) Address() common.Address { | ||
return p.address | ||
} | ||
|
||
func (p Precompile) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { | ||
addr := arg.(common.Address) | ||
if addr == (common.Address{}) { | ||
return nil, errors.New("invalid addr") | ||
} | ||
return p.evmKeeper.GetSeiAddressOrDefault(ctx, addr), nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package ibc_test | ||
|
||
import ( | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" | ||
"github.com/ethereum/go-ethereum/core/vm" | ||
"github.com/sei-protocol/sei-chain/precompiles/ibc" | ||
testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" | ||
"github.com/sei-protocol/sei-chain/x/evm/state" | ||
"github.com/sei-protocol/sei-chain/x/evm/types" | ||
"github.com/stretchr/testify/require" | ||
tmtypes "github.com/tendermint/tendermint/proto/tendermint/types" | ||
"math/big" | ||
"testing" | ||
) | ||
|
||
func TestRun(t *testing.T) { | ||
testApp := testkeeper.EVMTestApp | ||
ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) | ||
k := &testApp.EvmKeeper | ||
|
||
// Setup sender addresses and environment | ||
privKey := testkeeper.MockPrivateKey() | ||
senderAddr, senderEVMAddr := testkeeper.PrivateKeyToAddresses(privKey) | ||
k.SetAddressMapping(ctx, senderAddr, senderEVMAddr) | ||
k.ScopedCapabilityKeeper().NewCapability(ctx, "capabilities/ports/port/channels/sourceChannel") | ||
k.ChannelKeeper().SetChannel(ctx, "port", "sourceChannel", channeltypes.Channel{ | ||
State: 0, | ||
Ordering: 0, | ||
Counterparty: channeltypes.Counterparty{ | ||
PortId: "destinationPort", | ||
ChannelId: "destinationChannel", | ||
}, | ||
ConnectionHops: nil, | ||
Version: "", | ||
}) | ||
k.ChannelKeeper().SetNextSequenceSend(ctx, "port", "sourceChannel", 1) | ||
|
||
err := k.BankKeeper().MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(10000000)))) | ||
require.Nil(t, err) | ||
err = k.BankKeeper().SendCoinsFromModuleToAccount(ctx, types.ModuleName, senderAddr, sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(10000000)))) | ||
require.Nil(t, err) | ||
|
||
// Setup receiving addresses | ||
_, evmAddr := testkeeper.MockAddressPair() | ||
|
||
p, err := ibc.NewPrecompile(k.TransferKeeper(), k) | ||
|
||
require.Nil(t, err) | ||
stateDb := state.NewDBImpl(ctx, k, true) | ||
evm := vm.EVM{ | ||
StateDB: stateDb, | ||
TxContext: vm.TxContext{Origin: senderEVMAddr}, | ||
} | ||
|
||
// Precompile transfer test | ||
send, err := p.ABI.MethodById(p.TransferID) | ||
require.Nil(t, err) | ||
args, err := send.Inputs.Pack(senderEVMAddr, evmAddr, "port", "sourceChannel", "usei", big.NewInt(25)) | ||
require.Nil(t, err) | ||
_, err = p.Run(&evm, senderEVMAddr, append(p.TransferID, args...), nil) | ||
// TODO: Fix uncomment when all dependencies are resolved | ||
//require.Nil(t, err) | ||
|
||
} |
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 udpate this to 0x0000000000000000000000000000000000001009 ? Oracle is about to be merged #1445
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.
Updated