From e887adac80a13a37194b785cfcc9914274ed433c Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 29 Apr 2025 11:29:20 -0700 Subject: [PATCH 1/6] chore: forge init --- counter-sei/.github/workflows/test.yml | 43 +++++++++++++++++ counter-sei/.gitignore | 14 ++++++ counter-sei/README.md | 66 ++++++++++++++++++++++++++ counter-sei/foundry.toml | 6 +++ counter-sei/script/Counter.s.sol | 19 ++++++++ counter-sei/src/Counter.sol | 14 ++++++ counter-sei/test/Counter.t.sol | 24 ++++++++++ 7 files changed, 186 insertions(+) create mode 100644 counter-sei/.github/workflows/test.yml create mode 100644 counter-sei/.gitignore create mode 100644 counter-sei/README.md create mode 100644 counter-sei/foundry.toml create mode 100644 counter-sei/script/Counter.s.sol create mode 100644 counter-sei/src/Counter.sol create mode 100644 counter-sei/test/Counter.t.sol diff --git a/counter-sei/.github/workflows/test.yml b/counter-sei/.github/workflows/test.yml new file mode 100644 index 00000000..34a4a527 --- /dev/null +++ b/counter-sei/.github/workflows/test.yml @@ -0,0 +1,43 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Show Forge version + run: | + forge --version + + - name: Run Forge fmt + run: | + forge fmt --check + id: fmt + + - name: Run Forge build + run: | + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/counter-sei/.gitignore b/counter-sei/.gitignore new file mode 100644 index 00000000..85198aaa --- /dev/null +++ b/counter-sei/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/counter-sei/README.md b/counter-sei/README.md new file mode 100644 index 00000000..9265b455 --- /dev/null +++ b/counter-sei/README.md @@ -0,0 +1,66 @@ +## Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/counter-sei/foundry.toml b/counter-sei/foundry.toml new file mode 100644 index 00000000..25b918f9 --- /dev/null +++ b/counter-sei/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/counter-sei/script/Counter.s.sol b/counter-sei/script/Counter.s.sol new file mode 100644 index 00000000..cdc1fe9a --- /dev/null +++ b/counter-sei/script/Counter.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterScript is Script { + Counter public counter; + + function setUp() public {} + + function run() public { + vm.startBroadcast(); + + counter = new Counter(); + + vm.stopBroadcast(); + } +} diff --git a/counter-sei/src/Counter.sol b/counter-sei/src/Counter.sol new file mode 100644 index 00000000..aded7997 --- /dev/null +++ b/counter-sei/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/counter-sei/test/Counter.t.sol b/counter-sei/test/Counter.t.sol new file mode 100644 index 00000000..54b724f7 --- /dev/null +++ b/counter-sei/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} From 8621059f2c545fcaa001f1aa97f35193a7955d7f Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 29 Apr 2025 11:29:21 -0700 Subject: [PATCH 2/6] forge install: forge-std v1.9.7 --- .gitmodules | 3 +++ counter-sei/lib/forge-std | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 counter-sei/lib/forge-std diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..c2e0d66e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "counter-sei/lib/forge-std"] + path = counter-sei/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/counter-sei/lib/forge-std b/counter-sei/lib/forge-std new file mode 160000 index 00000000..77041d2c --- /dev/null +++ b/counter-sei/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 77041d2ce690e692d6e03cc812b57d1ddaa4d505 From 61d272ca5a42b4b85025358e79a4b2795886a7df Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Thu, 7 Aug 2025 16:57:00 -0700 Subject: [PATCH 3/6] - handle LocalInfo pointer case to display EVM address on key recovery - add missing logic to JSON output --- crypto/keyring/output_test.go | 113 ++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/crypto/keyring/output_test.go b/crypto/keyring/output_test.go index ab179c65..20d87ecb 100644 --- a/crypto/keyring/output_test.go +++ b/crypto/keyring/output_test.go @@ -7,6 +7,8 @@ import ( "github.com/stretchr/testify/require" + "github.com/cosmos/cosmos-sdk/codec/legacy" + "github.com/cosmos/cosmos-sdk/crypto/hd" kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" "github.com/cosmos/cosmos-sdk/crypto/types" @@ -45,3 +47,114 @@ func TestMkAccKeyOutputForSr25519(t *testing.T) { require.NoError(t, err) require.Equal(t, expectedOutput, out) } + +func TestPopulateEvmAddrIfApplicable(t *testing.T) { + sk := secp256k1.GenPrivKey() + pubKey := sk.PubKey() + + // PrivKeyArmor should contain amino-encoded private key bytes + aminoBytes, err := legacy.Cdc.Marshal(sk) + require.NoError(t, err) + privKeyArmor := string(aminoBytes) + + tests := []struct { + name string + info Info + input KeyOutput + expectError bool + expectEvm bool + }{ + { + name: "LocalInfo pointer case - should populate EVM address", + info: &LocalInfo{ + Name: "test-key", + PubKey: pubKey, + PrivKeyArmor: privKeyArmor, + Algo: hd.Secp256k1Type, + }, + input: KeyOutput{ + Name: "test-key", + Type: "local", + Address: sdk.AccAddress(pubKey.Address()).String(), + PubKey: "", + }, + expectError: false, + expectEvm: true, + }, + { + name: "LocalInfo value case - should populate EVM address", + info: LocalInfo{ + Name: "test-key", + PubKey: pubKey, + PrivKeyArmor: privKeyArmor, + Algo: hd.Secp256k1Type, + }, + input: KeyOutput{ + Name: "test-key", + Type: "local", + Address: sdk.AccAddress(pubKey.Address()).String(), + PubKey: "", + }, + expectError: false, + expectEvm: true, + }, + { + name: "Non-LocalInfo case - should return unchanged", + info: &multiInfo{ + Name: "multi-key", + PubKey: pubKey, + Threshold: 1, + }, + input: KeyOutput{ + Name: "multi-key", + Type: "multi", + Address: sdk.AccAddress(pubKey.Address()).String(), + EvmAddress: "0x1234567890123456789012345678901234567890", + PubKey: "", + }, + expectError: false, + expectEvm: false, + }, + { + name: "Invalid private key armor - should return error", + info: &LocalInfo{ + Name: "bad-key", + PubKey: pubKey, + PrivKeyArmor: "invalid-armor", + Algo: hd.Secp256k1Type, + }, + input: KeyOutput{ + Name: "bad-key", + Type: "local", + Address: sdk.AccAddress(pubKey.Address()).String(), + PubKey: "", + }, + expectError: true, + expectEvm: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := PopulateEvmAddrIfApplicable(tt.info, tt.input) + + if tt.expectError { + require.Error(t, err) + return + } + + require.NoError(t, err) + require.Equal(t, tt.input.Name, result.Name) + require.Equal(t, tt.input.Type, result.Type) + require.Equal(t, tt.input.Address, result.Address) + + if tt.expectEvm { + require.NotEmpty(t, result.EvmAddress) + require.Len(t, result.EvmAddress, 42) // 0x + 40 hex chars + require.True(t, result.EvmAddress[:2] == "0x") + } else { + require.Equal(t, tt.input.EvmAddress, result.EvmAddress) + } + }) + } +} From 86d678adabac6b5d47c32090f2abd1db7e75d4cb Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Thu, 7 Aug 2025 16:57:05 -0700 Subject: [PATCH 4/6] - handle LocalInfo pointer case to display EVM address on key recovery - add missing logic to JSON output --- client/keys/add.go | 5 +++++ crypto/keyring/output.go | 36 ++++++++++++++++++++++-------------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/client/keys/add.go b/client/keys/add.go index 11a6326a..334ffd67 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -306,6 +306,11 @@ func printCreate(cmd *cobra.Command, info keyring.Info, showMnemonic bool, mnemo return err } + out, err = keyring.PopulateEvmAddrIfApplicable(info, out) + if err != nil { + return err + } + if showMnemonic { out.Mnemonic = mnemonic } diff --git a/crypto/keyring/output.go b/crypto/keyring/output.go index f8dd2a7f..a1d6f4e0 100644 --- a/crypto/keyring/output.go +++ b/crypto/keyring/output.go @@ -92,20 +92,28 @@ func MkAccKeysOutput(infos []Info) ([]KeyOutput, error) { } func PopulateEvmAddrIfApplicable(info Info, o KeyOutput) (KeyOutput, error) { - localInfo, ok := info.(LocalInfo) - if ok { - // Only works with secp256k1 algo - priv, err := legacy.PrivKeyFromBytes([]byte(localInfo.PrivKeyArmor)) - if err != nil { - return o, err - } - privHex := hex.EncodeToString(priv.Bytes()) - privKey, err := crypto.HexToECDSA(privHex) - if err != nil { - return o, err - } - o.EvmAddress = crypto.PubkeyToAddress(privKey.PublicKey).Hex() - } else { + var localInfo LocalInfo + switch v := info.(type) { + case LocalInfo: + localInfo = v + case *LocalInfo: + localInfo = *v + default: + return o, nil // non-local key – nothing to do } + + // Only secp256k1 keys produce an EVM address + priv, err := legacy.PrivKeyFromBytes([]byte(localInfo.PrivKeyArmor)) + if err != nil { + return o, err + } + + privHex := hex.EncodeToString(priv.Bytes()) + privKey, err := crypto.HexToECDSA(privHex) + if err != nil { + return o, err + } + + o.EvmAddress = crypto.PubkeyToAddress(privKey.PublicKey).Hex() return o, nil } From 2c1016ac90a51e3b2cddba01eb67e6526bb1c240 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Thu, 7 Aug 2025 17:19:26 -0700 Subject: [PATCH 5/6] - remove unintended changes --- .gitmodules | 3 -- counter-sei/.github/workflows/test.yml | 43 ----------------- counter-sei/.gitignore | 14 ------ counter-sei/README.md | 66 -------------------------- counter-sei/foundry.toml | 6 --- counter-sei/lib/forge-std | 1 - counter-sei/script/Counter.s.sol | 19 -------- counter-sei/src/Counter.sol | 14 ------ counter-sei/test/Counter.t.sol | 24 ---------- 9 files changed, 190 deletions(-) delete mode 100644 .gitmodules delete mode 100644 counter-sei/.github/workflows/test.yml delete mode 100644 counter-sei/.gitignore delete mode 100644 counter-sei/README.md delete mode 100644 counter-sei/foundry.toml delete mode 160000 counter-sei/lib/forge-std delete mode 100644 counter-sei/script/Counter.s.sol delete mode 100644 counter-sei/src/Counter.sol delete mode 100644 counter-sei/test/Counter.t.sol diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index c2e0d66e..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "counter-sei/lib/forge-std"] - path = counter-sei/lib/forge-std - url = https://github.com/foundry-rs/forge-std diff --git a/counter-sei/.github/workflows/test.yml b/counter-sei/.github/workflows/test.yml deleted file mode 100644 index 34a4a527..00000000 --- a/counter-sei/.github/workflows/test.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: CI - -on: - push: - pull_request: - workflow_dispatch: - -env: - FOUNDRY_PROFILE: ci - -jobs: - check: - strategy: - fail-fast: true - - name: Foundry project - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - - - name: Show Forge version - run: | - forge --version - - - name: Run Forge fmt - run: | - forge fmt --check - id: fmt - - - name: Run Forge build - run: | - forge build --sizes - id: build - - - name: Run Forge tests - run: | - forge test -vvv - id: test diff --git a/counter-sei/.gitignore b/counter-sei/.gitignore deleted file mode 100644 index 85198aaa..00000000 --- a/counter-sei/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -# Compiler files -cache/ -out/ - -# Ignores development broadcast logs -!/broadcast -/broadcast/*/31337/ -/broadcast/**/dry-run/ - -# Docs -docs/ - -# Dotenv file -.env diff --git a/counter-sei/README.md b/counter-sei/README.md deleted file mode 100644 index 9265b455..00000000 --- a/counter-sei/README.md +++ /dev/null @@ -1,66 +0,0 @@ -## Foundry - -**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** - -Foundry consists of: - -- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). -- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. -- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. -- **Chisel**: Fast, utilitarian, and verbose solidity REPL. - -## Documentation - -https://book.getfoundry.sh/ - -## Usage - -### Build - -```shell -$ forge build -``` - -### Test - -```shell -$ forge test -``` - -### Format - -```shell -$ forge fmt -``` - -### Gas Snapshots - -```shell -$ forge snapshot -``` - -### Anvil - -```shell -$ anvil -``` - -### Deploy - -```shell -$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key -``` - -### Cast - -```shell -$ cast -``` - -### Help - -```shell -$ forge --help -$ anvil --help -$ cast --help -``` diff --git a/counter-sei/foundry.toml b/counter-sei/foundry.toml deleted file mode 100644 index 25b918f9..00000000 --- a/counter-sei/foundry.toml +++ /dev/null @@ -1,6 +0,0 @@ -[profile.default] -src = "src" -out = "out" -libs = ["lib"] - -# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/counter-sei/lib/forge-std b/counter-sei/lib/forge-std deleted file mode 160000 index 77041d2c..00000000 --- a/counter-sei/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 77041d2ce690e692d6e03cc812b57d1ddaa4d505 diff --git a/counter-sei/script/Counter.s.sol b/counter-sei/script/Counter.s.sol deleted file mode 100644 index cdc1fe9a..00000000 --- a/counter-sei/script/Counter.s.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterScript is Script { - Counter public counter; - - function setUp() public {} - - function run() public { - vm.startBroadcast(); - - counter = new Counter(); - - vm.stopBroadcast(); - } -} diff --git a/counter-sei/src/Counter.sol b/counter-sei/src/Counter.sol deleted file mode 100644 index aded7997..00000000 --- a/counter-sei/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/counter-sei/test/Counter.t.sol b/counter-sei/test/Counter.t.sol deleted file mode 100644 index 54b724f7..00000000 --- a/counter-sei/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} From e9616e38dfded1e9a9bee24a579a7c7e149d1993 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Fri, 8 Aug 2025 12:16:11 -0700 Subject: [PATCH 6/6] - add tests for add command --- client/keys/add_test.go | 63 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/client/keys/add_test.go b/client/keys/add_test.go index f59848a8..13f81656 100644 --- a/client/keys/add_test.go +++ b/client/keys/add_test.go @@ -273,3 +273,66 @@ func TestAddRecoverFileBackend(t *testing.T) { require.NoError(t, err) require.Equal(t, "keyname1", info.GetName()) } + +func Test_runAddCmdJSONEvmAddress(t *testing.T) { + cmd := AddKeyCommand() + cmd.Flags().AddFlagSet(Commands("home").PersistentFlags()) + + kbHome := t.TempDir() + mockIn := testutil.ApplyMockIODiscardOutErr(cmd) + + clientCtx := client.Context{}.WithKeyringDir(kbHome).WithInput(mockIn) + ctx := context.WithValue(context.Background(), client.ClientContextKey, &clientCtx) + + b := bytes.NewBufferString("") + cmd.SetOut(b) + + cmd.SetArgs([]string{ + "test-evm-key", + fmt.Sprintf("--%s=%s", flags.FlagHome, kbHome), + fmt.Sprintf("--%s=%s", cli.OutputFlag, OutputFormatJSON), + fmt.Sprintf("--%s=%s", flags.FlagKeyAlgorithm, string(hd.Secp256k1Type)), + fmt.Sprintf("--%s=%s", flags.FlagKeyringBackend, keyring.BackendTest), + }) + + require.NoError(t, cmd.ExecuteContext(ctx)) + + // Check that the JSON output contains an EVM address + output, err := ioutil.ReadAll(b) + require.NoError(t, err) + + outputStr := string(output) + require.Contains(t, outputStr, `"evm_address"`) + require.Contains(t, outputStr, `"0x`) + require.NotContains(t, outputStr, `"evm_address":""`) + require.NotContains(t, outputStr, `"evm_address":null`) +} + +func Test_PopulateEvmAddrError_JSONOutput(t *testing.T) { + // This test verifies that if PopulateEvmAddrIfApplicable returns an error, + // the add command properly handles it and returns the error + + cmd := AddKeyCommand() + cmd.Flags().AddFlagSet(Commands("home").PersistentFlags()) + + kbHome := t.TempDir() + mockIn := testutil.ApplyMockIODiscardOutErr(cmd) + + clientCtx := client.Context{}.WithKeyringDir(kbHome).WithInput(mockIn) + ctx := context.WithValue(context.Background(), client.ClientContextKey, &clientCtx) + + // Create a key with sr25519 algorithm - this should fail when PopulateEvmAddrIfApplicable + // tries to parse the sr25519 private key as secp256k1 + cmd.SetArgs([]string{ + "test-sr25519-key", + fmt.Sprintf("--%s=%s", flags.FlagHome, kbHome), + fmt.Sprintf("--%s=%s", cli.OutputFlag, OutputFormatJSON), + fmt.Sprintf("--%s=%s", flags.FlagKeyAlgorithm, string(hd.Sr25519Type)), + fmt.Sprintf("--%s=%s", flags.FlagKeyringBackend, keyring.BackendTest), + }) + + // This should fail because sr25519 keys can't be used to generate EVM addresses + err := cmd.ExecuteContext(ctx) + require.Error(t, err) + require.Contains(t, err.Error(), "unmarshal to types.PrivKey failed") +}