Skip to content

Commit

Permalink
refactor(evm): embeds (#1984)
Browse files Browse the repository at this point in the history
* chore: remove redundant JSON unmarshalling

* chore: remove HardhatOutput

* chore: remove byte array pointer

* refactor(evm): remove SmartContractFixture

* refactor: privatize embed functions

* refactor: move contract files into subdirectory and recompile

* chore: add TestERC20.sol

* test: move TestERC20 contract to embeds test

* refactor: remove embeds FixtureType

* fix: embed load function

* refactor: remove HexString

* chore: remove parseCompiledJson

* test(evm): use TestERC20.sol in integration tests

* Update CHANGELOG.md

* fix: small import merge conflict

---------

Co-authored-by: Unique Divine <[email protected]>
Co-authored-by: Unique-Divine <[email protected]>
  • Loading branch information
3 people authored Aug 6, 2024
1 parent 7dd9dd4 commit d71d67d
Show file tree
Hide file tree
Showing 28 changed files with 8,333 additions and 303 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [#1973](https://github.com/NibiruChain/nibiru/pull/1973) - chore(appconst): Add chain IDs ending in "3" to the "knownEthChainIDMap". This makes it possible to use devnet 3 and testnet 3.
- [#1976](https://github.com/NibiruChain/nibiru/pull/1976) - refactor(evm): unique chain ids for all networks
- [#1977](https://github.com/NibiruChain/nibiru/pull/1977) - fix(localnet): rolled back change of evm validator address with cosmos derivation path
- [#1979](https://github.com/NibiruChain/nibiru/pull/1979) -refactor(db): use pebbledb as the default db in integration tests
- [#1979](https://github.com/NibiruChain/nibiru/pull/1979) - refactor(db): use pebbledb as the default db in integration tests
- [#1981](https://github.com/NibiruChain/nibiru/pull/1981) - fix(evm): remove isCheckTx() short circuit on `AnteDecVerifyEthAcc`
- [#1982](https://github.com/NibiruChain/nibiru/pull/1982) - feat(evm): add GlobalMinGasPrices
- [#1983](https://github.com/NibiruChain/nibiru/pull/1983) - chore(evm): remove ExtensionOptionsWeb3Tx and ExtensionOptionDynamicFeeTx
- [#1984](https://github.com/NibiruChain/nibiru/pull/1984) - refactor(evm): embeds
- [#1985](https://github.com/NibiruChain/nibiru/pull/1985) - feat(evm)!: Use atto denomination for the wei units in the EVM so that NIBI is "ether" to clients. Only micronibi (unibi) amounts can be transferred. All clients follow the constraint equation, 1 ether == 1 NIBI == 10^6 unibi == 10^18 wei.
- [#1986](https://github.com/NibiruChain/nibiru/pull/1986) - feat(evm): Combine both account queries into "/eth.evm.v1.Query/EthAccount", accepting both nibi-prefixed Bech32 addresses and Ethereum-type hexadecimal addresses as input.

Expand Down
4 changes: 1 addition & 3 deletions eth/rpc/rpcapi/eth_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,7 @@ func (s *TestSuite) SetupSuite() {

s.network = network
s.ethClient = network.Validators[0].JSONRPCClient

s.contractData, err = embeds.SmartContract_TestERC20.Load()
s.Require().NoError(err)
s.contractData = embeds.SmartContract_TestERC20

testAccPrivateKey, _ := crypto.GenerateKey()
s.fundedAccPrivateKey = testAccPrivateKey
Expand Down
6 changes: 3 additions & 3 deletions x/evm/embeds/.gitignore
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
node_modules
.env

contracts
ignition
bun.lockb
package-lock.json

# Hardhat files
/cache
/artifacts
artifacts/@openzeppelin
artifacts/build-info
*.dbg.json

# TypeChain files
/typechain
Expand Down
13 changes: 3 additions & 10 deletions x/evm/embeds/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
# Sample Hardhat Project

This project demonstrates a basic Hardhat use case. It comes with a sample contract, a test for that contract, and a Hardhat Ignition module that deploys that contract.

Try running some of the following tasks:
# Nibiru Contract Embeds

```shell
npx hardhat help
npx hardhat test
REPORT_GAS=true npx hardhat test
npx hardhat node
npx hardhat ignition deploy ./ignition/modules/Lock.js
npm install
npx hardhat compile
```

Large diffs are not rendered by default.

File renamed without changes.
286 changes: 286 additions & 0 deletions x/evm/embeds/artifacts/contracts/TestERC20.sol/TestERC20.json

Large diffs are not rendered by default.

42 changes: 0 additions & 42 deletions x/evm/embeds/compile.sh

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;
pragma solidity >=0.8.19;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
Expand Down
File renamed without changes.
16 changes: 16 additions & 0 deletions x/evm/embeds/contracts/TestERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// contracts/TestERC20.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract TestERC20 is ERC20 {

// Define the supply of TestERC20: 1,000,000
uint256 constant initialSupply = 1000000 * (10**18);

// Constructor will be called on contract creation
constructor() ERC20("TestERC20", "FOO") {
_mint(msg.sender, initialSupply);
}
}
197 changes: 42 additions & 155 deletions x/evm/embeds/embeds.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,192 +9,79 @@ import (
// program (smart contracts).
_ "embed"
"encoding/json"
"fmt"
"os"
"path"
"path/filepath"
"runtime"
"strings"

gethabi "github.com/ethereum/go-ethereum/accounts/abi"
gethcommon "github.com/ethereum/go-ethereum/common"
)

var (
// Contract_ERC20Minter: The default ERC20 contract deployed during the
// creation of a `FunToken` mapping from a bank coin.
Contract_ERC20Minter CompiledEvmContract

//go:embed ERC20MinterCompiled.json
//go:embed artifacts/contracts/ERC20Minter.sol/ERC20Minter.json
erc20MinterContractJSON []byte

// Contract_Funtoken: Precompile contract interface for
// "IFunToken.sol". This precompile enables transfers of ERC20 tokens
// to non-EVM accounts. Only the ABI is used.
Contract_Funtoken CompiledEvmContract
//go:embed IFunTokenCompiled.json
//go:embed artifacts/contracts/IFunToken.sol/IFunToken.json
funtokenContractJSON []byte
//go:embed artifacts/contracts/TestERC20.sol/TestERC20.json
testErc20Json []byte
)

func init() {
Contract_ERC20Minter = SmartContract_ERC20Minter.MustLoad()
Contract_Funtoken = SmartContract_FunToken.MustLoad()
}

var (
SmartContract_TestERC20 = SmartContractFixture{
Name: "TestERC20.sol",
FixtureType: FixtueType_Test,
// Contract_ERC20Minter: The default ERC20 contract deployed during the
// creation of a `FunToken` mapping from a bank coin.
SmartContract_ERC20Minter = CompiledEvmContract{
Name: "ERC20Minter.sol",
EmbedJSON: erc20MinterContractJSON,
}

SmartContract_ERC20Minter = SmartContractFixture{
Name: "ERC20Minter.sol",
FixtureType: FixtueType_Prod,
EmbedJSON: &erc20MinterContractJSON,
}
SmartContract_FunToken = SmartContractFixture{
Name: "FunToken.sol",
FixtureType: FixtueType_Prod,
EmbedJSON: &funtokenContractJSON,
// SmartContract_Funtoken: Precompile contract interface for
// "IFunToken.sol". This precompile enables transfers of ERC20 tokens
// to non-EVM accounts. Only the ABI is used.
SmartContract_FunToken = CompiledEvmContract{
Name: "FunToken.sol",
EmbedJSON: funtokenContractJSON,
}
)

// CompiledEvmContract: EVM contract that can be deployed into the EVM state and
// used as a valid precompile.
type CompiledEvmContract struct {
ABI gethabi.ABI `json:"abi"`
Bytecode []byte `json:"bytecode"`
}

type SmartContractFixture struct {
Name string
FixtureType ContractFixtureType
EmbedJSON *[]byte
}

// ContractFixtureType: Enum type for embedded smart contracts. This type
// expresses whether a contract is used in production or only for testing.
type ContractFixtureType string

const (
FixtueType_Prod = "prod"
FixtueType_Test = "test"
SmartContract_TestERC20 = CompiledEvmContract{
Name: "TestERC20.sol",
EmbedJSON: testErc20Json,
}
)

// HardhatOutput: Expected format for smart contract test fixtures.
type HardhatOutput struct {
ABI json.RawMessage `json:"abi"`
Bytecode HexString `json:"bytecode"`
func init() {
SmartContract_ERC20Minter.MustLoad()
SmartContract_FunToken.MustLoad()
SmartContract_TestERC20.MustLoad()
}

// HexString: Hexadecimal-encoded string
type HexString string
type CompiledEvmContract struct {
Name string
EmbedJSON []byte

func (h HexString) Bytes() []byte {
return gethcommon.Hex2Bytes(
strings.TrimPrefix(string(h), "0x"),
)
}
func (h HexString) String() string { return string(h) }
func (h HexString) FromBytes(bz []byte) HexString {
return HexString(gethcommon.Bytes2Hex(bz))
// filled in post-load
ABI *gethabi.ABI `json:"abi"`
Bytecode []byte `json:"bytecode"`
}

func NewHardhatOutputFromJson(
jsonBz []byte,
) (out HardhatOutput, err error) {
rawJsonBz := make(map[string]json.RawMessage)
err = json.Unmarshal(jsonBz, &rawJsonBz)
if err != nil {
return
func (sc *CompiledEvmContract) MustLoad() {
if sc.EmbedJSON == nil {
panic("missing compiled contract embed")
}
var rawBytecodeBz HexString
err = json.Unmarshal(rawJsonBz["bytecode"], &rawBytecodeBz)
if err != nil {
return
}

return HardhatOutput{
ABI: rawJsonBz["abi"],
Bytecode: rawBytecodeBz,
}, err
}

func (jsonObj HardhatOutput) EvmContract() (out CompiledEvmContract, err error) {
newAbi := new(gethabi.ABI)
err = newAbi.UnmarshalJSON(jsonObj.ABI)
rawJsonBz := make(map[string]json.RawMessage)
err := json.Unmarshal(sc.EmbedJSON, &rawJsonBz)
if err != nil {
return
panic(err)
}

return CompiledEvmContract{
ABI: *newAbi,
Bytecode: jsonObj.Bytecode.Bytes(),
}, err
}

func (sc SmartContractFixture) MustLoad() (out CompiledEvmContract) {
out, err := sc.Load()
abi := new(gethabi.ABI)
err = abi.UnmarshalJSON(rawJsonBz["abi"])
if err != nil {
panic(err)
}
return out
}

func (sc SmartContractFixture) Load() (out CompiledEvmContract, err error) {
var jsonBz []byte

// Locate the contracts directory.
switch sc.FixtureType {
case FixtueType_Prod:
if sc.EmbedJSON == nil {
return out, fmt.Errorf("missing compiled contract embed")
}
jsonBz = *sc.EmbedJSON
case FixtueType_Test:
contractsDirPath, err := pathToE2EContracts()
if err != nil {
return out, err
}
baseName := strings.TrimSuffix(sc.Name, ".sol")
compiledPath := fmt.Sprintf("%s/%sCompiled.json", contractsDirPath, baseName)

jsonBz, err = os.ReadFile(compiledPath)
if err != nil {
return out, err
}
default:
panic(fmt.Errorf("unexpected case type \"%s\"", sc.FixtureType))
}

compiledJson, err := NewHardhatOutputFromJson(jsonBz)
var bytecodeStr string
err = json.Unmarshal(rawJsonBz["bytecode"], &bytecodeStr)
if err != nil {
return
}

return compiledJson.EvmContract()
}

// pathToE2EContracts: Returns the absolute path to the E2E test contract
// directory located at path, "NibiruChain/nibiru/e2e/evm/contracts".
func pathToE2EContracts() (thePath string, err error) {
dirEvmTest, _ := GetPackageDir()
dirOfRepo := path.Dir(path.Dir(path.Dir(dirEvmTest)))
dirEvmE2e := path.Join(dirOfRepo, "e2e/evm")
if path.Base(dirEvmE2e) != "evm" {
return thePath, fmt.Errorf("failed to locate the e2e/evm directory")
panic(err)
}
return dirEvmE2e + "/contracts", nil
}

// GetPackageDir: Returns the absolute path of the Golang package that
// calls this function.
func GetPackageDir() (string, error) {
// Get the import path of the current package
_, filename, _, _ := runtime.Caller(0)
pkgDir := path.Dir(filename)
pkgPath := path.Join(path.Base(pkgDir), "..")

// Get the directory path of the package
return filepath.Abs(pkgPath)
sc.Bytecode = gethcommon.FromHex(bytecodeStr)
sc.ABI = abi
}
18 changes: 7 additions & 11 deletions x/evm/embeds/embeds_test.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
package embeds_test

import (
_ "embed"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/NibiruChain/nibiru/x/evm/embeds"
)

func TestLoadContracts(t *testing.T) {
for _, tc := range []embeds.SmartContractFixture{
embeds.SmartContract_TestERC20,
embeds.SmartContract_ERC20Minter,
embeds.SmartContract_FunToken,
} {
t.Run(tc.Name, func(t *testing.T) {
_, err := tc.Load()
assert.NoError(t, err)
})
}
require.NotPanics(t, func() {
embeds.SmartContract_ERC20Minter.MustLoad()
embeds.SmartContract_FunToken.MustLoad()
embeds.SmartContract_TestERC20.MustLoad()
})
}
2 changes: 1 addition & 1 deletion x/evm/embeds/hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ require("@nomicfoundation/hardhat-toolbox");

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.19",
solidity: "0.8.24",
};
Loading

0 comments on commit d71d67d

Please sign in to comment.