Skip to content

Commit

Permalink
Merge pull request #3410 from dessaya/emit-transfer-events
Browse files Browse the repository at this point in the history
evm: emit Transfer events on L1 deposits
  • Loading branch information
jorgemmsilva authored May 28, 2024
2 parents eca19c7 + a606d80 commit b62c52f
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 35 deletions.
4 changes: 2 additions & 2 deletions packages/vm/core/evm/emulator/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ func NewStateDB(ctx Context) *StateDB {

// NewStateDBFromKVStore Creates a StateDB without any context. Handle with care. Functions requiring the context will crash.
// It is currently only used for the ERC721 registration using a KVStore which doesn't justify a new class.
func NewStateDBFromKVStore(store kv.KVStore) *StateDB {
func NewStateDBFromKVStore(emulatorState kv.KVStore) *StateDB {
return &StateDB{
ctx: nil,
kv: StateDBSubrealm(store),
kv: StateDBSubrealm(emulatorState),
snapshots: make(map[int][]*types.Log),
}
}
Expand Down
6 changes: 3 additions & 3 deletions packages/vm/core/evm/evmimpl/external.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ func Nonce(evmPartition kv.KVStoreReader, addr common.Address) uint64 {
return emulator.GetNonce(stateDBStore, addr)
}

func RegisterERC721NFTCollectionByNFTId(store kv.KVStore, nft *isc.NFT) {
func RegisterERC721NFTCollectionByNFTId(evmState kv.KVStore, nft *isc.NFT) {
metadata, err := isc.IRC27NFTMetadataFromBytes(nft.Metadata)
if err != nil {
panic(errEVMCanNotDecodeERC27Metadata)
}

addr := iscmagic.ERC721NFTCollectionAddress(nft.ID)
state := emulator.NewStateDBFromKVStore(emulator.StateDBSubrealm(store))
state := emulator.NewStateDBFromKVStore(evm.EmulatorStateSubrealm(evmState))

if state.Exist(addr) {
panic(errEVMAccountAlreadyExists)
Expand All @@ -38,5 +38,5 @@ func RegisterERC721NFTCollectionByNFTId(store kv.KVStore, nft *isc.NFT) {
state.SetState(addr, k, v)
}

addToPrivileged(store, addr)
addToPrivileged(evmState, addr)
}
51 changes: 49 additions & 2 deletions packages/vm/core/evm/evmimpl/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ package evmimpl

import (
"encoding/hex"
"math/big"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/samber/lo"

iotago "github.com/iotaledger/iota.go/v3"
Expand Down Expand Up @@ -465,10 +468,38 @@ func newL1Deposit(ctx isc.Sandbox) dict.Dict {
},
)

// create a fake receipt
logs := make([]*types.Log, 0)
for _, nt := range assets.NativeTokens {
if nt.Amount.Sign() == 0 {
continue
}
// emit a Transfer event from the ERC20NativeTokens / ERC20ExternalNativeTokens contract
erc20Address, ok := findERC20NativeTokenContractAddress(ctx, nt.ID)
if !ok {
continue
}
logs = append(logs, makeTransferEvent(erc20Address, addr, nt.Amount))
}
for _, nftID := range assets.NFTs {
// if the NFT belongs to a collection, emit a Transfer event from the corresponding ERC721NFTCollection contract
if nft := ctx.GetNFTData(nftID); nft != nil {
if collectionNFTAddress, ok := nft.Issuer.(*iotago.NFTAddress); ok {
collectionID := collectionNFTAddress.NFTID()
erc721CollectionContractAddress := iscmagic.ERC721NFTCollectionAddress(collectionID)
stateDB := emulator.NewStateDB(newEmulatorContext(ctx))
if stateDB.Exist(erc721CollectionContractAddress) {
logs = append(logs, makeTransferEvent(erc721CollectionContractAddress, addr, iscmagic.WrapNFTID(nftID).TokenID()))
continue
}
}
}
// otherwise, emit a Transfer event from the ERC721NFTs contract
logs = append(logs, makeTransferEvent(iscmagic.ERC721NFTsAddress, addr, iscmagic.WrapNFTID(nftID).TokenID()))
}

receipt := &types.Receipt{
Type: types.LegacyTxType,
Logs: make([]*types.Log, 0),
Logs: logs,
Status: types.ReceiptStatusSuccessful,
}
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
Expand All @@ -481,3 +512,19 @@ func newL1Deposit(ctx isc.Sandbox) dict.Dict {

return nil
}

var transferEventTopic = crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)"))

func makeTransferEvent(contractAddress, to common.Address, uint256Data *big.Int) *types.Log {
var addrTopic common.Hash
copy(addrTopic[len(addrTopic)-len(to):], to[:])
return &types.Log{
Address: contractAddress,
Topics: []common.Hash{
transferEventTopic, // event topic
{}, // indexed `from` address
addrTopic, // indexed `to` address
},
Data: lo.Must((abi.Arguments{{Type: lo.Must(abi.NewType("uint256", "", nil))}}).Pack(uint256Data)),
}
}
20 changes: 18 additions & 2 deletions packages/vm/core/evm/evmimpl/iscmagic_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/iotaledger/wasp/packages/kv"
"github.com/iotaledger/wasp/packages/vm/core/errors/coreerrors"
"github.com/iotaledger/wasp/packages/vm/core/evm"
"github.com/iotaledger/wasp/packages/vm/core/evm/emulator"
"github.com/iotaledger/wasp/packages/vm/core/evm/iscmagic"
)

Expand Down Expand Up @@ -45,8 +46,8 @@ func isCallerPrivileged(ctx isc.SandboxBase, addr common.Address) bool {
return state.Has(keyPrivileged(addr))
}

func addToPrivileged(s kv.KVStore, addr common.Address) {
state := evm.ISCMagicSubrealm(s)
func addToPrivileged(evmState kv.KVStore, addr common.Address) {
state := evm.ISCMagicSubrealm(evmState)
state.Set(keyPrivileged(addr), []byte{1})
}

Expand Down Expand Up @@ -137,3 +138,18 @@ func getERC20ExternalNativeTokensAddress(ctx isc.SandboxBase, nativeTokenID iota
copy(ret[:], b)
return ret, true
}

// findERC20NativeTokenContractAddress returns the address of an
// ERC20NativeTokens or ERC20ExternalNativeTokens contract.
func findERC20NativeTokenContractAddress(ctx isc.Sandbox, nativeTokenID iotago.NativeTokenID) (common.Address, bool) {
addr, ok := getERC20ExternalNativeTokensAddress(ctx, nativeTokenID)
if ok {
return addr, true
}
addr = iscmagic.ERC20NativeTokensAddress(nativeTokenID.FoundrySerialNumber())
stateDB := emulator.NewStateDB(newEmulatorContext(ctx))
if stateDB.Exist(addr) {
return addr, true
}
return common.Address{}, false
}
109 changes: 85 additions & 24 deletions packages/vm/core/evm/evmtest/evm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,22 @@ func TestERC721NFTs(t *testing.T) {
require.NoError(t, err)
env.Chain.MustDepositNFT(nft, ethAgentID, env.Chain.OriginatorPrivateKey)

// there must be a Transfer event emitted from the ERC721NFTs contract
{
blockTxs := env.latestEVMTxs()
require.Len(t, blockTxs, 1)
tx := blockTxs[0]
receipt := env.evmChain.TransactionReceipt(tx.Hash())
require.Len(t, receipt.Logs, 1)
checkTransferEvent(
t,
receipt.Logs[0],
iscmagic.ERC721NFTsAddress,
ethAddr,
iscmagic.WrapNFTID(nft.ID).TokenID(),
)
}

{
var n *big.Int
erc721.callView("balanceOf", []any{ethAddr}, &n)
Expand Down Expand Up @@ -1011,17 +1027,40 @@ func TestERC721NFTCollection(t *testing.T) {
require.True(t, env.solo.HasL1NFT(collectionOwnerAddr, &nft.ID))
}

// deposit all nfts on L2
nfts := func() []*isc.NFT {
var nfts []*isc.NFT
// deposit the collection NFT in the owner's L2 account
collectionNFT, _ := lo.Find(allNFTs, func(nft *isc.NFT) bool { return nft.ID == collection.ID })
env.Chain.MustDepositNFT(collectionNFT, isc.NewAgentID(collectionOwnerAddr), collectionOwner)

err = env.registerERC721NFTCollection(collectionOwner, collection.ID)
require.NoError(t, err)

// should not allow to register again
err = env.registerERC721NFTCollection(collectionOwner, collection.ID)
require.ErrorContains(t, err, "already exists")

// deposit the two nfts of the collection on ethAddr's L2 account
nfts := func() (nfts []*isc.NFT) {
for _, nft := range allNFTs {
if nft.ID == collection.ID {
// the collection NFT in the owner's account
env.Chain.MustDepositNFT(nft, isc.NewAgentID(collectionOwnerAddr), collectionOwner)
} else {
// others in ethAgentID's account
env.Chain.MustDepositNFT(nft, ethAgentID, collectionOwner)
nfts = append(nfts, nft)
continue
}
env.Chain.MustDepositNFT(nft, ethAgentID, collectionOwner)
nfts = append(nfts, nft)

// there must be a Transfer event emitted from the ERC721NFTCollection contract
{
blockTxs := env.latestEVMTxs()
require.Len(t, blockTxs, 1)
tx := blockTxs[0]
receipt := env.evmChain.TransactionReceipt(tx.Hash())
require.Len(t, receipt.Logs, 1)
checkTransferEvent(
t,
receipt.Logs[0],
iscmagic.ERC721NFTCollectionAddress(collection.ID),
ethAddr,
iscmagic.WrapNFTID(nft.ID).TokenID(),
)
}
}
return nfts
Expand All @@ -1036,13 +1075,6 @@ func TestERC721NFTCollection(t *testing.T) {
})
require.True(t, ok)

err = env.registerERC721NFTCollection(collectionOwner, collection.ID)
require.NoError(t, err)

// should not allow to register again
err = env.registerERC721NFTCollection(collectionOwner, collection.ID)
require.ErrorContains(t, err, "already exists")

erc721 := env.ERC721NFTCollection(ethKey, collection.ID)

{
Expand Down Expand Up @@ -1339,6 +1371,24 @@ func TestERC20BaseTokens(t *testing.T) {
}
}

func checkTransferEvent(
t *testing.T,
log *types.Log,
contractAddress, to common.Address,
uint256Data *big.Int,
) {
require.Equal(t, contractAddress, log.Address)

require.Len(t, log.Topics, 3)
require.Equal(t, crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)")), log.Topics[0])
var addrTopic common.Hash
copy(addrTopic[len(addrTopic)-len(to):], to[:])
require.Equal(t, addrTopic, log.Topics[2])

data := lo.Must((abi.Arguments{{Type: lo.Must(abi.NewType("uint256", "", nil))}}).Unpack(log.Data))[0].(*big.Int)
require.Zero(t, uint256Data.Cmp(data))
}

func TestERC20NativeTokens(t *testing.T) {
env := InitEVM(t, false)

Expand All @@ -1359,7 +1409,6 @@ func TestERC20NativeTokens(t *testing.T) {
WithTokenSymbol(tokenTickerSymbol).
WithTokenDecimals(tokenDecimals).
CreateFoundry()

require.NoError(t, err)
err = env.Chain.MintTokens(foundrySN, supply, foundryOwner)
require.NoError(t, err)
Expand All @@ -1376,6 +1425,22 @@ func TestERC20NativeTokens(t *testing.T) {
}), ethAgentID, foundryOwner)
require.NoError(t, err)

// there must be a Transfer event emitted from the ERC20NativeTokens contract
{
blockTxs := env.latestEVMTxs()
require.Len(t, blockTxs, 1)
tx := blockTxs[0]
receipt := env.evmChain.TransactionReceipt(tx.Hash())
require.Len(t, receipt.Logs, 1)
checkTransferEvent(
t,
receipt.Logs[0],
iscmagic.ERC20NativeTokensAddress(foundrySN),
ethAddr,
supply,
)
}

{
sandbox := env.ISCMagicSandbox(ethKey)
var addr common.Address
Expand Down Expand Up @@ -2465,9 +2530,7 @@ func TestL1DepositEVM(t *testing.T) {
require.NoError(t, err)

// previous block must only have 1 tx, that corresponds to the deposit to ethAddr
block, err := env.Chain.EVM().BlockByNumber(big.NewInt(int64(env.getBlockNumber())))
require.NoError(t, err)
blockTxs := block.Transactions()
blockTxs := env.latestEVMTxs()
require.Len(t, blockTxs, 1)
tx := blockTxs[0]
require.True(t, tx.GasPrice().Cmp(util.Big0) == 1)
Expand All @@ -2484,7 +2547,7 @@ func TestL1DepositEVM(t *testing.T) {

// blockIndex
blockIndex := rr.ReadUint32()
require.Equal(t, block.Number().Uint64(), uint64(blockIndex))
require.Equal(t, env.evmChain.BlockNumber().Uint64(), uint64(blockIndex))
reqIndex := rr.ReadUint16()
require.Zero(t, reqIndex)
n, err := buf.Read([]byte{})
Expand Down Expand Up @@ -2513,9 +2576,7 @@ func TestL1DepositEVM(t *testing.T) {
)
require.NoError(t, err)

block2, err := env.Chain.EVM().BlockByNumber(big.NewInt(int64(env.getBlockNumber())))
require.NoError(t, err)
blockTxs2 := block2.Transactions()
blockTxs2 := env.latestEVMTxs()
require.Len(t, blockTxs2, 1)
tx2 := blockTxs2[0]
require.NotEqual(t, tx.Hash(), tx2.Hash())
Expand Down
11 changes: 9 additions & 2 deletions packages/vm/core/evm/evmtest/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"

iotago "github.com/iotaledger/iota.go/v3"
Expand Down Expand Up @@ -178,14 +179,14 @@ func (e *SoloChainEnv) ERC20BaseTokens(defaultSender *ecdsa.PrivateKey) *IscCont
}

func (e *SoloChainEnv) ERC20NativeTokens(defaultSender *ecdsa.PrivateKey, foundrySN uint32) *IscContractInstance {
erc20BaseABI, err := abi.JSON(strings.NewReader(iscmagic.ERC20NativeTokensABI))
ntABI, err := abi.JSON(strings.NewReader(iscmagic.ERC20NativeTokensABI))
require.NoError(e.t, err)
return &IscContractInstance{
EVMContractInstance: &EVMContractInstance{
chain: e,
defaultSender: defaultSender,
address: iscmagic.ERC20NativeTokensAddress(foundrySN),
abi: erc20BaseABI,
abi: ntABI,
},
}
}
Expand Down Expand Up @@ -291,3 +292,9 @@ func (e *SoloChainEnv) registerERC721NFTCollection(collectionOwner *cryptolib.Ke
}).WithMaxAffordableGasBudget(), collectionOwner)
return err
}

func (e *SoloChainEnv) latestEVMTxs() types.Transactions {
block, err := e.Chain.EVM().BlockByNumber(nil)
require.NoError(e.t, err)
return block.Transactions()
}

0 comments on commit b62c52f

Please sign in to comment.