Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/precompiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package app

import (
"fmt"
"github.com/xrplevm/node/v6/precompiles/erc20factory"

govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper"

Expand Down Expand Up @@ -83,6 +84,11 @@ func NewAvailableStaticPrecompiles(
panic(fmt.Errorf("failed to instantiate gov precompile: %w", err))
}

erc20factoryPrecompile, err := erc20factory.NewPrecompile(authzKeeper, erc20Keeper, bankKeeper)
if err != nil {
panic(fmt.Errorf("failed to instantiate erc20factory precompile: %w", err))
}

// Stateless precompiles
precompiles[bech32Precompile.Address()] = bech32Precompile
precompiles[p256Precompile.Address()] = p256Precompile
Expand All @@ -93,5 +99,6 @@ func NewAvailableStaticPrecompiles(
precompiles[ibcTransferPrecompile.Address()] = ibcTransferPrecompile
precompiles[bankPrecompile.Address()] = bankPrecompile
precompiles[govPrecompile.Address()] = govPrecompile
precompiles[erc20factoryPrecompile.Address()] = erc20factoryPrecompile
return precompiles
}
1 change: 1 addition & 0 deletions local-node.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ jq '.consensus.params["block"]["max_gas"]="10500000"' "$GENESIS" >"$TMP_GENESIS"
jq '.app_state["crisis"]["constant_fee"]["denom"]="token"' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
jq '.app_state["evm"]["params"]["evm_denom"]="token"' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
jq '.app_state["evm"]["params"]["allow_unprotected_txs"]=true' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
jq '.app_state["evm"]["params"]["active_static_precompiles"]+=["0x0000000000000000000000000000000000000900"]' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
jq '.app_state["gov"]["params"]["min_deposit"][0]["denom"]="token"' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
jq '.app_state["gov"]["params"]["min_deposit"][0]["amount"]="1"' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
jq '.app_state["gov"]["params"]["voting_period"]="10s"' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
Expand Down
31 changes: 31 additions & 0 deletions precompiles/erc20factory/ERC20FactoryI.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.8.17;

/// @dev The ERC20 Factory contract's address.
address constant ERC20_FACTORY_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000900;

/// @dev The ERC20 Factory contract's instance.
ERC20FactoryI constant ERC20_FACTORY_CONTRACT = ERC20FactoryI(ERC20_FACTORY_PRECOMPILE_ADDRESS);

interface ERC20FactoryI {
/// @dev Defines a method for creating an ERC20 token.
/// @param tokenPairType Token Pair type
/// @param salt Salt used for deployment
/// @param name The name of the token.
/// @param symbol The symbol of the token.
/// @param decimals the decimals of the token.
/// @return tokenAddress The ERC20 token address.
function create(
uint8 tokenPairType,
bytes32 salt,
string memory name,
string memory symbol,
uint8 decimals
) external returns (address tokenAddress);

/// @dev Calculates the deterministic address for a new token.
/// @param tokenPairType Token Pair type
/// @param salt Salt used for deployment
/// @return tokenAddress The calculated ERC20 token address.
function calculateAddress(uint8 tokenPairType, bytes32 salt) external view returns (address tokenAddress);
}
74 changes: 74 additions & 0 deletions precompiles/erc20factory/abi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "ERC20FactoryI",
"sourceName": "solidity/precompiles/erc20factory/ERC20FactoryI.sol",
"abi": [
{
"inputs": [
{
"internalType": "uint8",
"name": "tokenPairType",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"name": "calculateAddress",
"outputs": [
{
"internalType": "address",
"name": "tokenAddress",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint8",
"name": "tokenPairType",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "uint8",
"name": "decimals",
"type": "uint8"
}
],
"name": "create",
"outputs": [
{
"internalType": "address",
"name": "tokenAddress",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
],
"bytecode": "0x",
"deployedBytecode": "0x",
"linkReferences": {},
"deployedLinkReferences": {}
}
139 changes: 139 additions & 0 deletions precompiles/erc20factory/erc20factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package erc20factory

import (
storetypes "cosmossdk.io/store/types"
"embed"
"fmt"
authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
"github.com/ethereum/go-ethereum/common"
cmn "github.com/evmos/evmos/v20/precompiles/common"
erc20keeper "github.com/evmos/evmos/v20/x/erc20/keeper"
"github.com/evmos/evmos/v20/x/evm/core/vm"
)

const (
Erc20FactoryAddress = "0x0000000000000000000000000000000000000900"
// GasCreate defines the gas required to create a new ERC20 Token Pair calculated from a ERC20 deploy transaction
GasCreate = 3_000_000
// GasCalculateAddress defines the gas required to calculate the address of a new ERC20 Token Pair
GasCalculateAddress = 3_000
)

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

// Precompile defines the precompiled contract for Bech32 encoding.
type Precompile struct {
cmn.Precompile
erc20Keeper erc20keeper.Keeper
bankKeeper bankkeeper.Keeper
}

// NewPrecompile creates a new bech32 Precompile instance as a
// PrecompiledContract interface.
func NewPrecompile(authzKeeper authzkeeper.Keeper, erc20Keeper erc20keeper.Keeper, bankKeeper bankkeeper.Keeper) (*Precompile, error) {
newABI, err := cmn.LoadABI(f, "abi.json")
if err != nil {
return nil, err
}

p := &Precompile{
Precompile: cmn.Precompile{
ABI: newABI,
AuthzKeeper: authzKeeper,
KvGasConfig: storetypes.KVGasConfig(),
TransientKVGasConfig: storetypes.TransientGasConfig(),
ApprovalExpiration: cmn.DefaultExpirationDuration, // should be configurable in the future.
},
erc20Keeper: erc20Keeper,
bankKeeper: bankKeeper,
}

// SetAddress defines the address of the distribution compile contract.
p.SetAddress(common.HexToAddress(Erc20FactoryAddress))
return p, nil
}

// Address defines the address of the bech32 precompiled contract.
func (Precompile) Address() common.Address {
return common.HexToAddress(Erc20FactoryAddress)
}

// RequiredGas calculates the contract gas use.
func (p Precompile) RequiredGas(input []byte) uint64 {
// NOTE: This check avoid panicking when trying to decode the method ID
if len(input) < 4 {
return 0
}

methodID := input[:4]
method, err := p.MethodById(methodID)
if err != nil {
return 0
}

switch method.Name {
// ERC-20 transactions
case CreateMethod:
return GasCreate
case CalculateAddressMethod:
return GasCalculateAddress
default:
return 0
}
}

// Run executes the precompiled contract bech32 methods defined in the ABI.
func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) {
ctx, stateDB, snapshot, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction)
if err != nil {
return nil, err
}
// This handles any out of gas errors that may occur during the execution of a precompile query.
// It avoids panics and returns the out of gas error so the EVM can continue gracefully.
defer cmn.HandleGasError(ctx, contract, initialGas, &err)()

switch method.Name {
// Bank queries
case CreateMethod:
bz, err = p.Create(ctx, method, contract.Caller(), args)
case CalculateAddressMethod:
bz, err = p.CalculateAddress(method, contract.Caller(), args)
default:
return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name)
}

if err != nil {
return nil, err
}

cost := ctx.GasMeter().GasConsumed() - initialGas

if !contract.UseGas(cost) {
return nil, vm.ErrOutOfGas
}

if err := p.AddJournalEntries(stateDB, snapshot); err != nil {
return nil, err
}

return bz, nil
}

// IsTransaction checks if the given method name corresponds to a transaction or query.
//
// Available ERC20 Factory transactions are:
// - Create
func (Precompile) IsTransaction(methodName string) bool {
switch methodName {
case CreateMethod:
return true
default:
return false
}
}
71 changes: 71 additions & 0 deletions precompiles/erc20factory/erc20factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package erc20factory_test

import (
"github.com/ethereum/go-ethereum/common"
"github.com/xrplevm/node/v6/precompiles/erc20factory"
)

func (s *PrecompileTestSuite) TestIsTransaction() {
s.SetupTest()

// Constants
s.Require().Equal(s.precompile.Address().String(), erc20factory.Erc20FactoryAddress)

// Queries
s.Require().False(s.precompile.IsTransaction(erc20factory.CalculateAddressMethod))

// Transactions
s.Require().True(s.precompile.IsTransaction(erc20factory.CreateMethod))
}

func (s *PrecompileTestSuite) TestRequiredGas() {
s.SetupTest()
salt := common.HexToHash("0x4f5b6f778b28c4d67a9c12345678901234567890123456789012345678901234")

testcases := []struct {
name string
malleate func() []byte
expGas uint64
}{
{
name: erc20factory.CreateMethod,
malleate: func() []byte {
bz, err := s.precompile.ABI.Pack(erc20factory.CreateMethod, uint8(0), salt, "Test token", "TT", uint8(3))
s.Require().NoError(err, "expected no error packing ABI")
return bz
},
expGas: erc20factory.GasCreate,
},
{
name: erc20factory.CalculateAddressMethod,
malleate: func() []byte {
bz, err := s.precompile.ABI.Pack(erc20factory.CalculateAddressMethod, uint8(0), salt)
s.Require().NoError(err, "expected no error packing ABI")
return bz
},
expGas: erc20factory.GasCalculateAddress,
},
{
name: "invalid method",
malleate: func() []byte {
return []byte("invalid method")
},
expGas: 0,
},
{
name: "input bytes too short",
malleate: func() []byte {
return []byte{0x00, 0x00, 0x00}
},
expGas: 0,
},
}

for _, tc := range testcases {
s.Run(tc.name, func() {
input := tc.malleate()

s.Require().Equal(tc.expGas, s.precompile.RequiredGas(input))
})
}
}
Loading
Loading