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: allow erc20 metadata onetime update #104

Merged
merged 10 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion x/bank/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func TestQueryDenomMetadata(t *testing.T) {
require.Equal(t, []*types.DenomUnit{
{
Denom: bondDenom,
Exponent: 0,
Exponent: 18,
},
}, metadata.DenomUnits)
}
2 changes: 1 addition & 1 deletion x/evm/contracts/counter/Counter.go

Large diffs are not rendered by default.

60 changes: 56 additions & 4 deletions x/evm/contracts/erc20/ERC20.go

Large diffs are not rendered by default.

26 changes: 25 additions & 1 deletion x/evm/contracts/erc20/ERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ contract ERC20 is IERC20, Ownable, ERC20Registry, ERC165, ERC20ACL {

mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;

// ERC20 Metadata
string public name;
string public symbol;
uint8 public decimals;
bool public metadataSealed;

uint256 public totalSupply;

/**
Expand All @@ -34,10 +38,11 @@ contract ERC20 is IERC20, Ownable, ERC20Registry, ERC165, ERC20ACL {
}

// for custom erc20s, you should add `register_erc20` modifier to the constructor
constructor(string memory _name, string memory _symbol, uint8 _decimals) {
constructor(string memory _name, string memory _symbol, uint8 _decimals, bool _metadataSealed) {
name = _name;
symbol = _symbol;
decimals = _decimals;
metadataSealed = _metadataSealed;
}
beer-1 marked this conversation as resolved.
Show resolved Hide resolved

function _transfer(
Expand Down Expand Up @@ -127,4 +132,23 @@ contract ERC20 is IERC20, Ownable, ERC20Registry, ERC165, ERC20ACL {
function sudoBurn(address from, uint256 amount) external onlyChain {
_burn(from, amount);
}

//
// ERC20 Metadata onetime setters only for authority(gov)
//

function updateMetadata(
string memory _name,
string memory _symbol,
uint8 _decimals
) external onlyAuthority {
require(!metadataSealed, "ERC20: metadata sealed");

name = _name;
symbol = _symbol;
decimals = _decimals;

// seal the metadata to prevent further updates
metadataSealed = true;
}
beer-1 marked this conversation as resolved.
Show resolved Hide resolved
}
2 changes: 1 addition & 1 deletion x/evm/contracts/erc20_acl/ERC20ACL.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions x/evm/contracts/erc20_acl/ERC20ACL.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ contract ERC20ACL {
_;
}

modifier onlyAuthority() {
require(
COSMOS_CONTRACT.is_authority_address(msg.sender),
"ERC20: caller is not the authority"
);

_;
}

// check if the sender is a module address
modifier burnable(address from) {
require(
Expand Down
2 changes: 1 addition & 1 deletion x/evm/contracts/erc20_factory/ERC20Factory.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion x/evm/contracts/erc20_factory/ERC20Factory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ contract ERC20Factory is ERC20Registry {
string memory symbol,
uint8 decimals
) external returns (address) {
ERC20 erc20 = new ERC20(name, symbol, decimals);
ERC20 erc20 = new ERC20(name, symbol, decimals, msg.sender != CHAIN_ADDRESS);

// register the ERC20 contract with the ERC20 registry
ERC20_REGISTRY_CONTRACT.register_erc20_from_factory(address(erc20));
Expand Down
2 changes: 1 addition & 1 deletion x/evm/contracts/erc20_wrapper/ERC20Wrapper.go

Large diffs are not rendered by default.

33 changes: 32 additions & 1 deletion x/evm/contracts/i_cosmos/ICosmos.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions x/evm/contracts/i_cosmos/ICosmos.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ interface ICosmos {
address account
) external view returns (bool module);

// check if an address is a authority address
function is_authority_address(
address account
) external view returns (bool authority);

// convert an EVM address to a Cosmos address
function to_cosmos_address(
address evm_address
Expand Down
2 changes: 1 addition & 1 deletion x/evm/contracts/initia_erc20/InitiaERC20.go

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions x/evm/keeper/erc20.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,13 @@ func (k ERC20Keeper) GetMetadata(ctx context.Context, denom string) (banktypes.M
Denom: denom,
Exponent: 0,
},
{
Denom: symbol,
Exponent: uint32(decimals),
},
}
if denom == symbol {
denomUnits = denomUnits[1:]
}

base := denom
Expand Down
59 changes: 57 additions & 2 deletions x/evm/keeper/erc20_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ import (
"github.com/initia-labs/minievm/x/evm/types"
)

func deployERC20(t *testing.T, ctx sdk.Context, input TestKeepers, caller common.Address, denom string) common.Address {
func deployERC20(t *testing.T, ctx sdk.Context, input TestKeepers, caller common.Address, symbol string) common.Address {
abi, err := erc20_factory.Erc20FactoryMetaData.GetAbi()
require.NoError(t, err)

inputBz, err := abi.Pack("createERC20", denom, denom, uint8(6))
inputBz, err := abi.Pack("createERC20", symbol, symbol, uint8(6))
require.NoError(t, err)

factoryAddr, err := input.EVMKeeper.GetERC20FactoryAddr(ctx)
Expand Down Expand Up @@ -130,6 +130,20 @@ func transferFromERC20(t *testing.T, ctx sdk.Context, input TestKeepers, caller,
}
}

func updateMetadataERC20(t *testing.T, ctx sdk.Context, input TestKeepers, caller common.Address, denom, name, symbol string, decimals uint8) error {
abi, err := erc20.Erc20MetaData.GetAbi()
require.NoError(t, err)

inputBz, err := abi.Pack("updateMetadata", name, symbol, decimals)
require.NoError(t, err)

erc20ContractAddr, err := types.DenomToContractAddr(ctx, &input.EVMKeeper, denom)
require.NoError(t, err)

_, _, err = input.EVMKeeper.EVMCall(ctx, caller, erc20ContractAddr, inputBz, nil, nil)
return err
}

func Test_BalanceOf(t *testing.T) {
ctx, input := createDefaultTestInput(t)

Expand Down Expand Up @@ -503,3 +517,44 @@ func Test_Approve(t *testing.T) {
// should fail to transferFrom more than approved
transferFromERC20(t, ctx, input, evmAddr2, evmAddr, evmAddr2, sdk.NewCoin(fooDenom, math.NewInt(50)), true)
}

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

evmAddr := common.BytesToAddress(addr.Bytes())
authorityAddr, err := input.AccountKeeper.AddressCodec().StringToBytes(input.EVMKeeper.GetAuthority())
require.NoError(t, err)
authorityEVMAddr := common.BytesToAddress(authorityAddr)

erc20Keeper, err := keeper.NewERC20Keeper(&input.EVMKeeper)
require.NoError(t, err)

// deploy erc20 contract
fooContractAddr := deployERC20(t, ctx, input, evmAddr, "foo")
fooDenom, err := types.ContractAddrToDenom(ctx, &input.EVMKeeper, fooContractAddr)
require.NoError(t, err)
require.Equal(t, "evm/"+fooContractAddr.Hex()[2:], fooDenom)

// update metadata should fail because deployer is not 0x1
err = updateMetadataERC20(t, ctx, input, authorityEVMAddr, fooDenom, "new name", "new symbol", 18)
require.Error(t, err)

// create erc20 contract with deployer 0x1
fooDenom = "foo"
err = input.EVMKeeper.ERC20Keeper().CreateERC20(ctx, fooDenom, 6)
require.NoError(t, err)

// update metadata
err = updateMetadataERC20(t, ctx, input, authorityEVMAddr, fooDenom, "new name", "new symbol", 18)
require.NoError(t, err)
metadata, err := erc20Keeper.GetMetadata(ctx, fooDenom)
require.NoError(t, err)
require.Equal(t, "new name", metadata.Name)
require.Equal(t, "new symbol", metadata.Symbol)
require.Equal(t, uint32(18), metadata.DenomUnits[1].Exponent)

// update metadata again should fail
err = updateMetadataERC20(t, ctx, input, authorityEVMAddr, fooDenom, "new name", "new symbol", 18)
require.Error(t, err)
}
1 change: 1 addition & 0 deletions x/evm/keeper/precompiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func (k *Keeper) precompiles(rules params.Rules, stateDB types.StateDB) (vm.Prec
k,
k.grpcRouter,
k.queryCosmosWhitelist,
k.authority,
)
if err != nil {
return nil, err
Expand Down
31 changes: 31 additions & 0 deletions x/evm/precompiles/cosmos/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
grpcRouter types.GRPCRouter

queryWhitelist types.QueryCosmosWhitelist

authorityAddr sdk.AccAddress
}

func NewCosmosPrecompile(
Expand All @@ -59,7 +61,13 @@
edk types.ERC20DenomKeeper,
grpcRouter types.GRPCRouter,
queryWhitelist types.QueryCosmosWhitelist,
authority string,
) (*CosmosPrecompile, error) {
authorityAddr, err := sdk.AccAddressFromBech32(authority)
if err != nil {
return nil, err
}

Check warning on line 69 in x/evm/precompiles/cosmos/contract.go

View check run for this annotation

Codecov / codecov/patch

x/evm/precompiles/cosmos/contract.go#L68-L69

Added lines #L68 - L69 were not covered by tests

return &CosmosPrecompile{
ABI: erc20CosmosABI,
cdc: cdc,
Expand All @@ -70,6 +78,7 @@
stateDB: stateDB,
grpcRouter: grpcRouter,
queryWhitelist: queryWhitelist,
authorityAddr: authorityAddr,
}, nil
}

Expand Down Expand Up @@ -174,6 +183,28 @@
if err != nil {
return nil, ctx.GasMeter().GasConsumedToLimit(), types.ErrPrecompileFailed.Wrap(err.Error())
}
case METHOD_IS_AUTHORITY_ADDRESS:
ctx.GasMeter().ConsumeGas(IS_AUTHORITY_ADDRESS_GAS, "is_authority_address")

var isAuthorityAddressArguments IsAuthorityAddressArguments
if err := method.Inputs.Copy(&isAuthorityAddressArguments, args); err != nil {
return nil, ctx.GasMeter().GasConsumedToLimit(), types.ErrPrecompileFailed.Wrap(err.Error())
}

Check warning on line 192 in x/evm/precompiles/cosmos/contract.go

View check run for this annotation

Codecov / codecov/patch

x/evm/precompiles/cosmos/contract.go#L191-L192

Added lines #L191 - L192 were not covered by tests

// convert shorthand account to original address
addr, err := e.originAddress(ctx, isAuthorityAddressArguments.Address.Bytes())
if err != nil {
return nil, ctx.GasMeter().GasConsumedToLimit(), types.ErrPrecompileFailed.Wrap(err.Error())
}

Check warning on line 198 in x/evm/precompiles/cosmos/contract.go

View check run for this annotation

Codecov / codecov/patch

x/evm/precompiles/cosmos/contract.go#L197-L198

Added lines #L197 - L198 were not covered by tests

// check if the address is the authority address
isAuthorityAddr := addr.Equals(e.authorityAddr)

// abi encode the response
resBz, err = method.Outputs.Pack(isAuthorityAddr)
if err != nil {
return nil, ctx.GasMeter().GasConsumedToLimit(), types.ErrPrecompileFailed.Wrap(err.Error())
}

Check warning on line 207 in x/evm/precompiles/cosmos/contract.go

View check run for this annotation

Codecov / codecov/patch

x/evm/precompiles/cosmos/contract.go#L206-L207

Added lines #L206 - L207 were not covered by tests
case METHOD_TO_COSMOS_ADDRESS:
ctx.GasMeter().ConsumeGas(TO_COSMOS_ADDRESS_GAS, "to_cosmos_address")

Expand Down
Loading
Loading