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/nft transfer #7

Merged
merged 13 commits into from
May 2, 2024
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,16 @@ proto-check-breaking:
### Tests
###############################################################################

test: contracts-gen test-unit
test: contracts-gen test-unit test-integration

test-all: contracts-gen test-unit test-race test-cover
test-all: contracts-gen test-unit test-race test-cover test-integration

test-unit:
@VERSION=$(VERSION) go test -mod=readonly -tags='ledger test_ledger_mock' ./...

test-integration:
@VERSION=$(VERSION) go test -mod=readonly -tags='ledger test_ledger_mock' ./integration-tests/...

test-race:
@VERSION=$(VERSION) go test -mod=readonly -race -tags='ledger test_ledger_mock' ./...

Expand Down
60 changes: 55 additions & 5 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ import (
ibchooks "github.com/initia-labs/initia/x/ibc-hooks"
ibchookskeeper "github.com/initia-labs/initia/x/ibc-hooks/keeper"
ibchookstypes "github.com/initia-labs/initia/x/ibc-hooks/types"
ibcnfttransfer "github.com/initia-labs/initia/x/ibc/nft-transfer"
ibcnfttransferkeeper "github.com/initia-labs/initia/x/ibc/nft-transfer/keeper"
ibcnfttransfertypes "github.com/initia-labs/initia/x/ibc/nft-transfer/types"
ibctestingtypes "github.com/initia-labs/initia/x/ibc/testing/types"
icaauth "github.com/initia-labs/initia/x/intertx"
icaauthkeeper "github.com/initia-labs/initia/x/intertx/keeper"
Expand Down Expand Up @@ -213,6 +216,7 @@ type MinitiaApp struct {
ConsensusParamsKeeper *consensusparamkeeper.Keeper
IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly
TransferKeeper *ibctransferkeeper.Keeper
NftTransferKeeper *ibcnfttransferkeeper.Keeper
AuthzKeeper *authzkeeper.Keeper
FeeGrantKeeper *feegrantkeeper.Keeper
ICAHostKeeper *icahostkeeper.Keeper
Expand All @@ -230,6 +234,7 @@ type MinitiaApp struct {
// make scoped keepers public for test purposes
ScopedIBCKeeper capabilitykeeper.ScopedKeeper
ScopedTransferKeeper capabilitykeeper.ScopedKeeper
ScopedNftTransferKeeper capabilitykeeper.ScopedKeeper
ScopedICAHostKeeper capabilitykeeper.ScopedKeeper
ScopedICAControllerKeeper capabilitykeeper.ScopedKeeper
ScopedICAAuthKeeper capabilitykeeper.ScopedKeeper
Expand All @@ -251,7 +256,7 @@ func NewMinitiaApp(
db dbm.DB,
traceStore io.Writer,
loadLatest bool,
moveConfig evmconfig.EVMConfig,
evmConfig evmconfig.EVMConfig,
appOpts servertypes.AppOptions,
baseAppOptions ...func(*baseapp.BaseApp),
) *MinitiaApp {
Expand All @@ -273,6 +278,7 @@ func NewMinitiaApp(
keys := storetypes.NewKVStoreKeys(
authtypes.StoreKey, banktypes.StoreKey, group.StoreKey, consensusparamtypes.StoreKey,
ibcexported.StoreKey, upgradetypes.StoreKey, ibctransfertypes.StoreKey,
ibcnfttransfertypes.StoreKey,
capabilitytypes.StoreKey, authzkeeper.StoreKey, feegrant.StoreKey,
icahosttypes.StoreKey, icacontrollertypes.StoreKey, icaauthtypes.StoreKey,
ibcfeetypes.StoreKey, evmtypes.StoreKey, opchildtypes.StoreKey,
Expand Down Expand Up @@ -319,6 +325,7 @@ func NewMinitiaApp(
// grant capabilities for the ibc and ibc-transfer modules
app.ScopedIBCKeeper = app.CapabilityKeeper.ScopeToModule(ibcexported.ModuleName)
app.ScopedTransferKeeper = app.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName)
app.ScopedNftTransferKeeper = app.CapabilityKeeper.ScopeToModule(ibcnfttransfertypes.ModuleName)
app.ScopedICAHostKeeper = app.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName)
app.ScopedICAControllerKeeper = app.CapabilityKeeper.ScopeToModule(icacontrollertypes.SubModuleName)
app.ScopedICAAuthKeeper = app.CapabilityKeeper.ScopeToModule(icaauthtypes.ModuleName)
Expand All @@ -328,6 +335,7 @@ func NewMinitiaApp(
// add keepers
app.EVMKeeper = &evmkeeper.Keeper{}
erc20Keeper := new(evmkeeper.ERC20Keeper)
erc721Keeper := new(evmkeeper.ERC721Keeper)

accountKeeper := authkeeper.NewAccountKeeper(
appCodec,
Expand Down Expand Up @@ -541,6 +549,45 @@ func NewMinitiaApp(
)
}

////////////////////////////////
// Nft Transfer configuration //
////////////////////////////////

var nftTransferStack porttypes.IBCModule
{
// Create Transfer Keepers
app.NftTransferKeeper = ibcnfttransferkeeper.NewKeeper(
appCodec,
runtime.NewKVStoreService(keys[ibcnfttransfertypes.StoreKey]),
// ics4wrapper: nft transfer -> fee -> channel
app.IBCFeeKeeper,
app.IBCKeeper.ChannelKeeper,
app.IBCKeeper.PortKeeper,
app.AccountKeeper,
erc721Keeper,
app.ScopedNftTransferKeeper,
authorityAddr,
)
nftTransferIBCModule := ibcnfttransfer.NewIBCModule(*app.NftTransferKeeper)

// create move middleware for nft-transfer
hookMiddleware := ibchooks.NewIBCMiddleware(
// receive: evm -> nft-transfer
nftTransferIBCModule,
ibchooks.NewICS4Middleware(
nil, /* ics4wrapper: not used */
ibcevmhooks.NewEVMHooks(appCodec, ac, app.EVMKeeper),
),
app.IBCHooksKeeper,
)

nftTransferStack = ibcfee.NewIBCMiddleware(
// receive: channel -> fee -> evm -> nft transfer
hookMiddleware,
*app.IBCFeeKeeper,
)
}

///////////////////////
// ICA configuration //
///////////////////////
Expand Down Expand Up @@ -597,14 +644,14 @@ func NewMinitiaApp(
ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack).
AddRoute(icahosttypes.SubModuleName, icaHostStack).
AddRoute(icacontrollertypes.SubModuleName, icaControllerStack).
AddRoute(icaauthtypes.ModuleName, icaControllerStack)
AddRoute(icaauthtypes.ModuleName, icaControllerStack).
AddRoute(ibcnfttransfertypes.ModuleName, nftTransferStack)

app.IBCKeeper.SetRouter(ibcRouter)

//////////////////////////////
// EVMKeeper Configuration //
//////////////////////////////
evmConfig := evmconfig.GetConfig(appOpts)

app.EVMKeeper = evmkeeper.NewKeeper(
ac,
Expand All @@ -619,6 +666,7 @@ func NewMinitiaApp(
evmtypes.DefaultQueryCosmosWhitelist(),
)
*erc20Keeper = *app.EVMKeeper.ERC20Keeper().(*evmkeeper.ERC20Keeper)
*erc721Keeper = *app.EVMKeeper.ERC721Keeper().(*evmkeeper.ERC721Keeper)

// x/auction module keeper initialization

Expand Down Expand Up @@ -659,6 +707,7 @@ func NewMinitiaApp(
// ibc modules
ibc.NewAppModule(app.IBCKeeper),
ibctransfer.NewAppModule(*app.TransferKeeper),
ibcnfttransfer.NewAppModule(appCodec, *app.NftTransferKeeper),
ica.NewAppModule(app.ICAControllerKeeper, app.ICAHostKeeper),
icaauth.NewAppModule(appCodec, *app.ICAAuthKeeper),
ibcfee.NewAppModule(*app.IBCFeeKeeper),
Expand Down Expand Up @@ -718,9 +767,10 @@ func NewMinitiaApp(
capabilitytypes.ModuleName, authtypes.ModuleName, evmtypes.ModuleName, banktypes.ModuleName,
opchildtypes.ModuleName, genutiltypes.ModuleName, authz.ModuleName, group.ModuleName,
upgradetypes.ModuleName, feegrant.ModuleName, consensusparamtypes.ModuleName, ibcexported.ModuleName,
ibctransfertypes.ModuleName, icatypes.ModuleName, icaauthtypes.ModuleName,
ibctransfertypes.ModuleName, ibcnfttransfertypes.ModuleName, icatypes.ModuleName, icaauthtypes.ModuleName,
ibcfeetypes.ModuleName, auctiontypes.ModuleName, oracletypes.ModuleName,
packetforwardtypes.ModuleName, ibchookstypes.ModuleName, forwardingtypes.ModuleName,
packetforwardtypes.ModuleName, forwardingtypes.ModuleName,
ibchookstypes.ModuleName,
}

app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...)
Expand Down
75 changes: 38 additions & 37 deletions app/ibc-hooks/ack.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types"
channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types"
"github.com/ethereum/go-ethereum/common/hexutil"
nfttransfertypes "github.com/initia-labs/initia/x/ibc/nft-transfer/types"

ibchooks "github.com/initia-labs/initia/x/ibc-hooks"
evmtypes "github.com/initia-labs/minievm/x/evm/types"
Expand Down Expand Up @@ -54,45 +55,45 @@
return nil
}

// func (h EVMHooks) onAckIcs721Packet(
// ctx sdk.Context,
// im ibchooks.IBCMiddleware,
// packet channeltypes.Packet,
// acknowledgement []byte,
// relayer sdk.AccAddress,
// data nfttransfertypes.NonFungibleTokenPacketData,
// ) error {
// if err := im.App.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer); err != nil {
// return err
// }
func (h EVMHooks) onAckIcs721Packet(
ctx sdk.Context,
im ibchooks.IBCMiddleware,
packet channeltypes.Packet,
acknowledgement []byte,
relayer sdk.AccAddress,
data nfttransfertypes.NonFungibleTokenPacketData,
) error {
if err := im.App.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer); err != nil {
return err
}

Check warning on line 68 in app/ibc-hooks/ack.go

View check run for this annotation

Codecov / codecov/patch

app/ibc-hooks/ack.go#L67-L68

Added lines #L67 - L68 were not covered by tests

// isEVMRouted, hookData, err := validateAndParseMemo(data.GetMemo())
// if !isEVMRouted || hookData.AsyncCallback == nil {
// return nil
// } else if err != nil {
// return err
// }
isEVMRouted, hookData, err := validateAndParseMemo(data.GetMemo())
if !isEVMRouted || hookData.AsyncCallback == nil {
return nil
} else if err != nil {
return err
}

Check warning on line 75 in app/ibc-hooks/ack.go

View check run for this annotation

Codecov / codecov/patch

app/ibc-hooks/ack.go#L74-L75

Added lines #L74 - L75 were not covered by tests

// callback := hookData.AsyncCallback
// if allowed, err := h.checkACL(im, ctx, callback.ContractAddress); err != nil {
// return err
// } else if !allowed {
// return nil
// }
callback := hookData.AsyncCallback
if allowed, err := h.checkACL(im, ctx, callback.ContractAddress); err != nil {
return err

Check warning on line 79 in app/ibc-hooks/ack.go

View check run for this annotation

Codecov / codecov/patch

app/ibc-hooks/ack.go#L79

Added line #L79 was not covered by tests
} else if !allowed {
return nil
}

// inputBz, err := h.asyncCallbackABI.Pack(functionNameAck, callback.Id, !isAckError(acknowledgement))
// if err != nil {
// return err
// }
inputBz, err := h.asyncCallbackABI.Pack(functionNameAck, callback.Id, !isAckError(h.codec, acknowledgement))
if err != nil {
return err
}

Check warning on line 87 in app/ibc-hooks/ack.go

View check run for this annotation

Codecov / codecov/patch

app/ibc-hooks/ack.go#L86-L87

Added lines #L86 - L87 were not covered by tests

// _, err = h.execMsg(ctx, &evmtypes.MsgCall{
// Sender: data.Sender,
// ContractAddr: callback.ContractAddress,
// Input: hexutil.Encode(inputBz),
// })
// if err != nil {
// return err
// }
_, err = h.execMsg(ctx, &evmtypes.MsgCall{
Sender: data.Sender,
ContractAddr: callback.ContractAddress,
Input: hexutil.Encode(inputBz),
})
if err != nil {
return err
}

Check warning on line 96 in app/ibc-hooks/ack.go

View check run for this annotation

Codecov / codecov/patch

app/ibc-hooks/ack.go#L95-L96

Added lines #L95 - L96 were not covered by tests

// return nil
// }
return nil
}
115 changes: 114 additions & 1 deletion app/ibc-hooks/ack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ import (

transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types"
channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/holiman/uint256"
"github.com/initia-labs/minievm/x/evm/contracts/counter"
"github.com/stretchr/testify/require"

nfttransfertypes "github.com/initia-labs/initia/x/ibc/nft-transfer/types"
"github.com/initia-labs/minievm/x/evm/contracts/counter"
)

func Test_onAckIcs20Packet_noMemo(t *testing.T) {
Expand Down Expand Up @@ -116,3 +119,113 @@ func Test_onAckIcs20Packet_memo(t *testing.T) {
require.Equal(t, uint256.NewInt(100).Bytes32(), [32]byte(queryRes))
require.Empty(t, logs)
}

func Test_OnAckPacket_ICS721(t *testing.T) {
ctx, input := createDefaultTestInput(t)
_, _, addr := keyPubAddr()
_, _, addr2 := keyPubAddr()

data := nfttransfertypes.NonFungibleTokenPacketData{
ClassId: "classId",
ClassUri: "classUri",
ClassData: "classData",
TokenIds: []string{"tokenId"},
TokenUris: []string{"tokenUri"},
TokenData: []string{"tokenData"},
Sender: addr.String(),
Receiver: addr2.String(),
Memo: "",
}

dataBz, err := json.Marshal(&data)
require.NoError(t, err)

ackBz, err := json.Marshal(channeltypes.NewResultAcknowledgement([]byte{byte(1)}))
require.NoError(t, err)

err = input.IBCHooksMiddleware.OnAcknowledgementPacket(ctx, channeltypes.Packet{
Data: dataBz,
}, ackBz, addr)
require.NoError(t, err)
}

func Test_onAckPacket_memo_ICS721(t *testing.T) {
ctx, input := createDefaultTestInput(t)
_, _, addr := keyPubAddr()
evmAddr := common.BytesToAddress(addr.Bytes())

codeBz, err := hexutil.Decode(counter.CounterBin)
require.NoError(t, err)

_, contractAddr, err := input.EVMKeeper.EVMCreate(ctx, evmAddr, codeBz)
require.NoError(t, err)

abi, err := counter.CounterMetaData.GetAbi()
require.NoError(t, err)

data := nfttransfertypes.NonFungibleTokenPacketData{
ClassId: "classId",
ClassUri: "classUri",
ClassData: "classData",
TokenIds: []string{"tokenId"},
TokenUris: []string{"tokenUri"},
TokenData: []string{"tokenData"},
Sender: addr.String(),
Receiver: "0x1::Counter::increase",
Memo: fmt.Sprintf(`{
"evm": {
"async_callback": {
"id": 99,
"contract_address": "%s"
}
}
}`, contractAddr.Hex()),
}

dataBz, err := json.Marshal(&data)
require.NoError(t, err)

successAckBz := channeltypes.NewResultAcknowledgement([]byte{byte(1)}).Acknowledgement()
failedAckBz := channeltypes.NewErrorAcknowledgement(errors.New("failed")).Acknowledgement()

// hook should not be called to due to acl
err = input.IBCHooksMiddleware.OnAcknowledgementPacket(ctx, channeltypes.Packet{
Data: dataBz,
}, successAckBz, addr)
require.NoError(t, err)

// check the contract state
queryInputBz, err := abi.Pack("count")
require.NoError(t, err)
queryRes, logs, err := input.EVMKeeper.EVMCall(ctx, evmAddr, contractAddr, queryInputBz)
require.NoError(t, err)
require.Equal(t, uint256.NewInt(0).Bytes32(), [32]byte(queryRes))
require.Empty(t, logs)

// set acl
require.NoError(t, input.IBCHooksKeeper.SetAllowed(ctx, contractAddr[:], true))

// success with success ack
err = input.IBCHooksMiddleware.OnAcknowledgementPacket(ctx, channeltypes.Packet{
Data: dataBz,
}, successAckBz, addr)
require.NoError(t, err)

// check the contract state; increased by 99 if ack is success
queryRes, logs, err = input.EVMKeeper.EVMCall(ctx, evmAddr, contractAddr, queryInputBz)
require.NoError(t, err)
require.Equal(t, uint256.NewInt(99).Bytes32(), [32]byte(queryRes))
require.Empty(t, logs)

// success with failed ack
err = input.IBCHooksMiddleware.OnAcknowledgementPacket(ctx, channeltypes.Packet{
Data: dataBz,
}, failedAckBz, addr)
require.NoError(t, err)

// check the contract state; increased by 1 if ack is failed
queryRes, logs, err = input.EVMKeeper.EVMCall(ctx, evmAddr, contractAddr, queryInputBz)
require.NoError(t, err)
require.Equal(t, uint256.NewInt(100).Bytes32(), [32]byte(queryRes))
require.Empty(t, logs)
}
Loading
Loading