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 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
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
}

Check warning on line 38 in app/upgrade.go

View check run for this annotation

Codecov / codecov/patch

app/upgrade.go#L32-L38

Added lines #L32 - L38 were not covered by tests

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

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

Check warning on line 46 in app/upgrade.go

View check run for this annotation

Codecov / codecov/patch

app/upgrade.go#L41-L46

Added lines #L41 - L46 were not covered by tests

//////////////////////////// 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 @@
return nil, err
}

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

Check warning on line 64 in app/upgrade.go

View check run for this annotation

Codecov / codecov/patch

app/upgrade.go#L64

Added line #L64 was not covered by tests

// 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:]

Check warning on line 69 in app/upgrade.go

View check run for this annotation

Codecov / codecov/patch

app/upgrade.go#L66-L69

Added lines #L66 - L69 were not covered by tests
beer-1 marked this conversation as resolved.
Show resolved Hide resolved

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)
}

Check warning on line 79 in app/upgrade.go

View check run for this annotation

Codecov / codecov/patch

app/upgrade.go#L71-L79

Added lines #L71 - L79 were not covered by tests

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

Check warning on line 84 in app/upgrade.go

View check run for this annotation

Codecov / codecov/patch

app/upgrade.go#L81-L84

Added lines #L81 - L84 were not covered by tests

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
}

Check warning on line 94 in app/upgrade.go

View check run for this annotation

Codecov / codecov/patch

app/upgrade.go#L86-L94

Added lines #L86 - L94 were not covered by tests

// 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
}

Check warning on line 101 in app/upgrade.go

View check run for this annotation

Codecov / codecov/patch

app/upgrade.go#L97-L101

Added lines #L97 - L101 were not covered by tests

return false, nil

Check warning on line 103 in app/upgrade.go

View check run for this annotation

Codecov / codecov/patch

app/upgrade.go#L103

Added line #L103 was not covered by tests
})
if err != nil {
return nil, err
}
Expand All @@ -51,3 +110,9 @@
},
)
}

func uint64ToBytes(v uint64) []byte {
bz := make([]byte, 8)
binary.BigEndian.PutUint64(bz, v)
return bz

Check warning on line 117 in app/upgrade.go

View check run for this annotation

Codecov / codecov/patch

app/upgrade.go#L114-L117

Added lines #L114 - L117 were not covered by tests
}
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
Loading