Skip to content

Commit

Permalink
feat: allow erc20 metadata onetime update (#104)
Browse files Browse the repository at this point in the history
allow erc20 metadata to be updatable via gov if it is created from (ibc or opbridge)
  • Loading branch information
beer-1 authored Nov 11, 2024
1 parent cd3abf8 commit 381e983
Show file tree
Hide file tree
Showing 20 changed files with 554 additions and 46 deletions.
9 changes: 7 additions & 2 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ linters:
- varnamelen
- tagliatelle
- wrapcheck
- typecheck
errcheck:
exclude-functions:
- fmt:.*
- io/ioutil:^Read.*
- github.com/spf13/cobra:MarkFlagRequired
- github.com/spf13/viper:BindPFlag
linters-settings:
gocyclo:
min-complexity: 11
errcheck:
ignore: fmt:.*,io/ioutil:^Read.*,github.com/spf13/cobra:MarkFlagRequired,github.com/spf13/viper:BindPFlag
golint:
min-confidence: 1.1
issues:
Expand Down
83 changes: 74 additions & 9 deletions app/upgrade.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,52 @@
package app

import (
"bytes"
"context"
"encoding/binary"
"fmt"
"strings"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"

upgradetypes "cosmossdk.io/x/upgrade/types"
"github.com/cosmos/cosmos-sdk/types/module"

"github.com/initia-labs/minievm/x/evm/contracts/erc20"
"github.com/initia-labs/minievm/x/evm/state"
evmtypes "github.com/initia-labs/minievm/x/evm/types"

opchildtypes "github.com/initia-labs/OPinit/x/opchild/types"
)

const upgradeName = "0.6.4"
const upgradeName = "0.6.5"

// RegisterUpgradeHandlers returns upgrade handlers
func (app *MinitiaApp) RegisterUpgradeHandlers(cfg module.Configurator) {
app.UpgradeKeeper.SetUpgradeHandler(
upgradeName,
func(ctx context.Context, _ upgradetypes.Plan, versionMap module.VersionMap) (module.VersionMap, error) {
//////////////////////////// OPINIT ////////////////////////////////////

// opchild params update
params, err := app.OPChildKeeper.GetParams(ctx)
if err != nil {
return nil, err
}

// set non-zero default values for new params
params.HookMaxGas = opchildtypes.DefaultHookMaxGas

err = app.OPChildKeeper.SetParams(ctx, params)
if err != nil {
return nil, err
}

//////////////////////////// MINIEVM ///////////////////////////////////

// deploy and store erc20 factory contract address
if err := app.EVMKeeper.DeployERC20Factory(ctx); err != nil &&
// ignore contract address collision error (contract already deployed)
Expand All @@ -33,16 +61,47 @@ func (app *MinitiaApp) RegisterUpgradeHandlers(cfg module.Configurator) {
return nil, err
}

// opchild params update
params, err := app.OPChildKeeper.GetParams(ctx)
if err != nil {
return nil, err
}
code := hexutil.MustDecode(erc20.Erc20MetaData.Bin)

// set non-zero default values for new params
params.HookMaxGas = opchildtypes.DefaultHookMaxGas
// runtime code
initCodeOP := common.Hex2Bytes("5ff3fe")
initCodePos := bytes.Index(code, initCodeOP)
code = code[initCodePos+3:]

err = app.OPChildKeeper.SetParams(ctx, params)
// code hash
codeHash := crypto.Keccak256Hash(code).Bytes()

// iterate all erc20 contracts and replace contract code to new version
err = app.EVMKeeper.ERC20s.Walk(ctx, nil, func(contractAddr []byte) (bool, error) {
acc := app.AccountKeeper.GetAccount(ctx, contractAddr)
if acc == nil {
return true, fmt.Errorf("account not found for contract address %s", contractAddr)
}

contractAcc, ok := acc.(*evmtypes.ContractAccount)
if !ok {
return true, fmt.Errorf("account is not a contract account for contract address %s", contractAddr)
}

contractAcc.CodeHash = codeHash
app.AccountKeeper.SetAccount(ctx, contractAcc)

// set code
codeKey := append(contractAddr, append(state.CodeKeyPrefix, codeHash...)...)
err := app.EVMKeeper.VMStore.Set(ctx, codeKey, code)
if err != nil {
return true, err
}

// set code size
codeSizeKey := append(contractAddr, append(state.CodeSizeKeyPrefix, codeHash...)...)
err = app.EVMKeeper.VMStore.Set(ctx, codeSizeKey, uint64ToBytes(uint64(len(code))))
if err != nil {
return true, err
}

return false, nil
})
if err != nil {
return nil, err
}
Expand All @@ -51,3 +110,9 @@ func (app *MinitiaApp) RegisterUpgradeHandlers(cfg module.Configurator) {
},
)
}

func uint64ToBytes(v uint64) []byte {
bz := make([]byte, 8)
binary.BigEndian.PutUint64(bz, v)
return bz
}
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.

196 changes: 192 additions & 4 deletions x/evm/contracts/erc20/ERC20.go

Large diffs are not rendered by default.

67 changes: 59 additions & 8 deletions x/evm/contracts/erc20/ERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,16 @@ 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;
uint256 public totalSupply;

// metadataSealed is set to true after metadata is sealed
bool public metadataSealed;

/**
* @dev See {IERC165-supportsInterface}.
*/
Expand All @@ -34,18 +39,27 @@ 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;
}

function _transfer(
address sender,
address recipient,
uint256 amount
) internal register_erc20_store(recipient) {
require(balanceOf[sender] >= amount, "ERC20: transfer amount exceeds balance");
require(
balanceOf[sender] >= amount,
"ERC20: transfer amount exceeds balance"
);
balanceOf[sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(sender, recipient, amount);
Expand All @@ -61,7 +75,10 @@ contract ERC20 is IERC20, Ownable, ERC20Registry, ERC165, ERC20ACL {
}

function _burn(address from, uint256 amount) internal {
require(balanceOf[from] >= amount, "ERC20: burn amount exceeds balance");
require(
balanceOf[from] >= amount,
"ERC20: burn amount exceeds balance"
);
balanceOf[from] -= amount;
totalSupply -= amount;
emit Transfer(from, address(0), amount);
Expand All @@ -86,7 +103,10 @@ contract ERC20 is IERC20, Ownable, ERC20Registry, ERC165, ERC20ACL {
address recipient,
uint256 amount
) external transferable(recipient) returns (bool) {
require(allowance[sender][msg.sender] >= amount, "ERC20: transfer amount exceeds allowance");
require(
allowance[sender][msg.sender] >= amount,
"ERC20: transfer amount exceeds allowance"
);
allowance[sender][msg.sender] -= amount;
_transfer(sender, recipient, amount);
return true;
Expand All @@ -96,17 +116,18 @@ contract ERC20 is IERC20, Ownable, ERC20Registry, ERC165, ERC20ACL {
_mint(to, amount);
}

function burn(
uint256 amount
) external burnable(msg.sender) {
function burn(uint256 amount) external burnable(msg.sender) {
_burn(msg.sender, amount);
}

function burnFrom(
address from,
uint256 amount
) external burnable(from) returns (bool) {
require(allowance[from][msg.sender] >= amount, "ERC20: burn amount exceeds allowance");
require(
allowance[from][msg.sender] >= amount,
"ERC20: burn amount exceeds allowance"
);
allowance[from][msg.sender] -= amount;
_burn(from, amount);
return true;
Expand All @@ -127,4 +148,34 @@ 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)
//

event MetadataUpdated(string name, string symbol, uint8 decimals);

/// @notice Allows one-time update of token metadata by authority
/// @dev Only callable when metadata is not sealed and by authority
/// @param _name New token name
/// @param _symbol New token symbol
/// @param _decimals New decimal places
function updateMetadata(
string memory _name,
string memory _symbol,
uint8 _decimals
) external onlyAuthority {
require(!metadataSealed, "ERC20: metadata sealed");
require(bytes(_name).length > 0, "ERC20: empty name");
require(bytes(_symbol).length > 0, "ERC20: empty symbol");
require(_decimals <= 18, "ERC20: invalid decimals");

// Update all fields at once to save gas
(name, symbol, decimals) = (_name, _symbol, _decimals);

// seal the metadata to prevent further updates
metadataSealed = true;

emit MetadataUpdated(_name, _symbol, _decimals);
}
}
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
Loading

0 comments on commit 381e983

Please sign in to comment.