Skip to content

Commit

Permalink
feat(evm-precompile):Emit EVM events created to reflect the ABCI even…
Browse files Browse the repository at this point in the history
…ts that occur outside the EVM to make sure that block explorers and indexers can find indexed ABCI event information.
  • Loading branch information
Unique-Divine committed Dec 14, 2024
1 parent 790b062 commit 0711426
Show file tree
Hide file tree
Showing 19 changed files with 353 additions and 36 deletions.
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
lts/jod
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ only on the "bankkeeper.BaseKeeper"'s gas consumption.
Remove unnecessary argument in the `VerifyFee` function, which returns the token
payment required based on the effective fee from the tx data. Improve
documentation.
- [#2125](https://github.com/NibiruChain/nibiru/pull/2125) - feat(evm-precompile):Emit EVM events created to reflect the ABCI events that occur outside the EVM to make sure that block explorers and indexers can find indexed ABCI event information.

#### Nibiru EVM | Before Audit 2 - 2024-12-06

Expand Down
10 changes: 5 additions & 5 deletions contrib/bashlib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,26 @@ export COLOR_BRIGHT_WHITE="\033[97m"

# log_debug: Simple wrapper for `echo` with a DEBUG prefix.
log_debug() {
echo "${COLOR_CYAN}DEBUG${COLOR_RESET}" "$@"
echo -e "${COLOR_CYAN}DEBUG${COLOR_RESET}" "$@"
}

# log_error: ERROR messages in red, output to stderr.
log_error() {
echo "${COLOR_RED}ERROR:${COLOR_RESET}" "$@" >&2
echo -e "${COLOR_RED}ERROR:${COLOR_RESET}" "$@" >&2
}

log_success() {
echo "${COLOR_GREEN}✅ SUCCESS:${COLOR_RESET}" "$@"
echo -e "${COLOR_GREEN}✅ SUCCESS:${COLOR_RESET}" "$@"
}

# log_warning: WARNING messages represent non-critical issues that might not
# require immediate action but should be noted as points of concern or failure.
log_warning() {
echo "${COLOR_YELLOW}WARNING${COLOR_RESET}" "$@" >&2
echo -e "${COLOR_YELLOW}WARNING${COLOR_RESET}" "$@" >&2
}

log_info() {
echo "${COLOR_MAGENTA}INFO${COLOR_RESET}" "$@"
echo -e "${COLOR_MAGENTA}INFO${COLOR_RESET}" "$@"
}

# —————————————————————————————————————————————————
Expand Down
24 changes: 18 additions & 6 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,29 @@ build:
clean-cache:
go clean -cache -testcache -modcache

alias b := build

# Generate protobuf code (Golang) for Nibiru
# Generate protobuf-based types in Golang
proto-gen:
#!/usr/bin/env bash
make proto-gen
alias proto := proto-gen
# Generate Solidity artifacts for x/evm/embeds
gen-embeds:
#!/usr/bin/env bash
source contrib/bashlib.sh
embeds_dir="x/evm/embeds"
log_info "Begin to compile Solidity in $embeds_dir"
which_ok npm
log_info "Using system node version: $(npm exec -- node -v)"

cd "$embeds_dir" || (log_error "path $embeds_dir not found" && exit)
npx hardhat compile
log_success "Compiled Solidity in $embeds_dir"

alias gen-proto := proto-gen

# Build protobuf types (Rust)
proto-rs:
# Generate protobuf-based types in Rust
gen-proto-rs:
bash proto/buf.gen.rs.sh

lint:
Expand Down
19 changes: 19 additions & 0 deletions x/evm/embeds/artifacts/contracts/IFunToken.sol/IFunToken.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@
"contractName": "IFunToken",
"sourceName": "contracts/IFunToken.sol",
"abi": [
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "string",
"name": "eventType",
"type": "string"
},
{
"indexed": false,
"internalType": "bytes",
"name": "attrs",
"type": "bytes"
}
],
"name": "AbciEvent",
"type": "event"
},
{
"inputs": [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "INibiruEvm",
"sourceName": "contracts/NibiruEvmUtils.sol",
"abi": [
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "string",
"name": "eventType",
"type": "string"
},
{
"indexed": false,
"internalType": "bytes",
"name": "attrs",
"type": "bytes"
}
],
"name": "AbciEvent",
"type": "event"
}
],
"bytecode": "0x",
"deployedBytecode": "0x",
"linkReferences": {},
"deployedLinkReferences": {}
}

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

25 changes: 22 additions & 3 deletions x/evm/embeds/artifacts/contracts/Wasm.sol/IWasm.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@
"contractName": "IWasm",
"sourceName": "contracts/Wasm.sol",
"abi": [
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "string",
"name": "eventType",
"type": "string"
},
{
"indexed": false,
"internalType": "bytes",
"name": "attrs",
"type": "bytes"
}
],
"name": "AbciEvent",
"type": "event"
},
{
"inputs": [
{
Expand All @@ -28,7 +47,7 @@
"type": "uint256"
}
],
"internalType": "struct IWasm.BankCoin[]",
"internalType": "struct INibiruEvm.BankCoin[]",
"name": "funds",
"type": "tuple[]"
}
Expand Down Expand Up @@ -71,7 +90,7 @@
"type": "uint256"
}
],
"internalType": "struct IWasm.BankCoin[]",
"internalType": "struct INibiruEvm.BankCoin[]",
"name": "funds",
"type": "tuple[]"
}
Expand Down Expand Up @@ -127,7 +146,7 @@
"type": "uint256"
}
],
"internalType": "struct IWasm.BankCoin[]",
"internalType": "struct INibiruEvm.BankCoin[]",
"name": "funds",
"type": "tuple[]"
}
Expand Down
4 changes: 3 additions & 1 deletion x/evm/embeds/contracts/IFunToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ pragma solidity >=0.8.19;
address constant FUNTOKEN_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000800;
IFunToken constant FUNTOKEN_PRECOMPILE = IFunToken(FUNTOKEN_PRECOMPILE_ADDRESS);

import "./NibiruEvmUtils.sol";

/// @dev Implements the functionality for sending ERC20 tokens and bank
/// coins to various Nibiru accounts using either the Nibiru Bech32 address
/// using the "FunToken" mapping between the ERC20 and bank.
interface IFunToken {
interface IFunToken is INibiruEvm {
/// @dev sendToBank sends ERC20 tokens as coins to a Nibiru base account
/// @param erc20 - the address of the ERC20 token contract
/// @param amount - the amount of tokens to send
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,21 @@ pragma solidity >=0.8.19;

/// @notice Interface defining the AbciEvent for interoperability between
/// Ethereum and the ABCI (Application Blockchain Interface).
interface IAbciEventEmitter {
interface INibiruEvm {
struct BankCoin {
string denom;
uint256 amount;
}

/// @notice Event emitted to in precompiled contracts to relay information
/// from the ABCI to the EVM logs and indexers. Consumers of this event should
/// decode the `attrs` parameter based on the `eventType` context.
///
/// @param eventType The type of the event, used for categorization and indexing.
/// @param eventType An identifier type of the event, used for indexing.
/// Event types indexable with CometBFT indexer are in snake case like
/// "pending_ethereum_tx" or "message", while protobuf typed events use the
/// proto message name as their event type (e.g.
/// "eth.evm.v1.EventEthereumTx").
/// @param attrs Arbitrary data payload associated with the event, typically
/// encoding state changes or metadata.
event AbciEvent(string indexed eventType, bytes attrs);
Expand Down
15 changes: 5 additions & 10 deletions x/evm/embeds/contracts/Wasm.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,9 @@ address constant WASM_PRECOMPILE_ADDRESS = 0x00000000000000000000000000000000000

IWasm constant WASM_PRECOMPILE = IWasm(WASM_PRECOMPILE_ADDRESS);

import "./AbciEvent.sol";

interface IWasm is IAbciEventEmitter {
struct BankCoin {
string denom;
uint256 amount;
}
import "./NibiruEvmUtils.sol";

interface IWasm is INibiruEvm {
/// @notice Invoke a contract's "ExecuteMsg", which corresponds to
/// "wasm/types/MsgExecuteContract". This enables arbitrary smart contract
/// execution using the Wasm VM from the EVM.
Expand All @@ -25,13 +20,13 @@ interface IWasm is IAbciEventEmitter {
function execute(
string memory contractAddr,
bytes memory msgArgs,
BankCoin[] memory funds
INibiruEvm.BankCoin[] memory funds
) external payable returns (bytes memory response);

struct WasmExecuteMsg {
string contractAddr;
bytes msgArgs;
BankCoin[] funds;
INibiruEvm.BankCoin[] funds;
}

/// @notice Identical to "execute", except for multiple contract calls.
Expand Down Expand Up @@ -69,6 +64,6 @@ interface IWasm is IAbciEventEmitter {
uint64 codeID,
bytes memory msgArgs,
string memory label,
BankCoin[] memory funds
INibiruEvm.BankCoin[] memory funds
) external payable returns (string memory contractAddr, bytes memory data);
}
13 changes: 13 additions & 0 deletions x/evm/precompile/funtoken.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ func (p precompileFunToken) Run(
// Gracefully handles "out of gas"
defer HandleOutOfGasPanic(&err)()

abciEventsStartIdx := len(startResult.CacheCtx.EventManager().Events())

method := startResult.Method
switch PrecompileMethod(method.Name) {
case FunTokenMethod_sendToBank:
Expand All @@ -80,6 +82,17 @@ func (p precompileFunToken) Run(
return nil, err
}

// Emit extra events for the EVM if this is a transaction
// https://github.com/NibiruChain/nibiru/issues/2121
if !readonly {
EmitEventAbciEvents(
startResult.CacheCtx,
startResult.StateDB,
startResult.CacheCtx.EventManager().Events()[abciEventsStartIdx:],
p.Address(),
)
}

// Gas consumed by a local gas meter
contract.UseGas(startResult.CacheCtx.GasMeter().GasConsumed())
return bz, err
Expand Down
1 change: 1 addition & 0 deletions x/evm/precompile/funtoken_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

// TestSuite: Runs all the tests in the suite.
func TestSuite(t *testing.T) {
suite.Run(t, new(UtilsSuite))
suite.Run(t, new(FuntokenSuite))
suite.Run(t, new(WasmSuite))
}
Expand Down
87 changes: 87 additions & 0 deletions x/evm/precompile/nibiru_evm_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package precompile

import (
"bytes"
"fmt"

abci "github.com/cometbft/cometbft/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
gethcommon "github.com/ethereum/go-ethereum/common"
gethcore "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"

"github.com/NibiruChain/nibiru/v2/x/common/set"
"github.com/NibiruChain/nibiru/v2/x/evm/embeds"
"github.com/NibiruChain/nibiru/v2/x/evm/statedb"
)

const EvmEventAbciEvent = "AbciEvent"

// EmitEventAbciEvents adds a sequence of ABCI events to the EVM state DB so that
// they can be emitted at the end of the "EthereumTx". These events are indexed
// by their ABCI event type and help communicate non-EVM events in Ethereum-based
// block explorers and indexers by saving the event attributes in JSON form.
func EmitEventAbciEvents(
ctx sdk.Context,
db *statedb.StateDB,
abciEvents []sdk.Event,
emittingAddr gethcommon.Address,
) {
blockNumber := uint64(ctx.BlockHeight())
event := embeds.SmartContract_Wasm.ABI.Events[EvmEventAbciEvent]
for _, abciEvent := range abciEvents {
topics := make([]gethcommon.Hash, 2)
// Why 2? Because 2 = event ID + number of indexed event fields
topics[0] = event.ID

// eventType is the first (and only) indexed event
topics[1] = EventTopicFromString(abciEvent.Type)

attrsBz := AttrsToJSON(abciEvent.Attributes)
db.AddLog(&gethcore.Log{
Address: emittingAddr,
Topics: topics,
Data: attrsBz,
BlockNumber: blockNumber,
})
}
}

// AttrsToJSON creates a deterministic JSON encoding for the
func AttrsToJSON(attrs []abci.EventAttribute) []byte {
if len(attrs) == 0 {
return []byte("")
}
keysSeen := set.New[string]()

// Create JSON object from the key-value tuples
var buf bytes.Buffer
buf.WriteByte('{')
for i, attr := range attrs {
// Keys must be unique to guarantee valid JSON object
if keysSeen.Has(attr.Key) {
continue
}
keysSeen.Add(attr.Key)

if i > 0 {
buf.WriteByte(',')
}

// Quote key and value
_, _ = fmt.Fprintf(&buf, `"%s":"%s"`, attr.Key, attr.Value)
}
buf.WriteByte('}')

return buf.Bytes()
}

// EventTopicFromBytes creates an [abi.Event]
func EventTopicFromBytes(bz []byte) (topic gethcommon.Hash) {
hash := crypto.Keccak256Hash(bz)
copy(topic[:], hash[:])
return topic
}
func EventTopicFromString(str string) (topic gethcommon.Hash) {
return EventTopicFromBytes([]byte(str))
}
Loading

0 comments on commit 0711426

Please sign in to comment.