Skip to content

Commit

Permalink
Add address association precompile method (#1767)
Browse files Browse the repository at this point in the history
* Sample of address association precompile method

* working state

* revert name change

* golint

* more lint

* with tests

* typo

* comments

* remove flaky test
  • Loading branch information
mj850 authored Jul 22, 2024
1 parent f8ffd70 commit 7d563ed
Show file tree
Hide file tree
Showing 12 changed files with 474 additions and 99 deletions.
38 changes: 38 additions & 0 deletions contracts/test/EVMPrecompileTest.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,44 @@ describe("EVM Precompile Tester", function () {
admin = await getAdmin();
})

describe("EVM Addr Precompile Tester", function () {
const AddrPrecompileContract = '0x0000000000000000000000000000000000001004';
let addr;

before(async function () {
const signer = accounts[0].signer
const contractABIPath = '../../precompiles/addr/abi.json';
const contractABI = require(contractABIPath);
// Get a contract instance
addr = new ethers.Contract(AddrPrecompileContract, contractABI, signer);
});

it("Assosciates successfully", async function () {
const unassociatedWallet = hre.ethers.Wallet.createRandom();
try {
await addr.getSeiAddr(unassociatedWallet.address);
expect.fail("Expected an error here since we look up an unassociated address");
} catch (error) {
expect(error).to.have.property('message').that.includes('execution reverted');
}

const message = `Please sign this message to link your EVM and Sei addresses. No SEI will be spent as a result of this signature.\n\n`;
const messageLength = Buffer.from(message, 'utf8').length;
const signatureHex = await unassociatedWallet.signMessage(message);

const sig = hre.ethers.Signature.from(signatureHex);

const appendedMessage = `\x19Ethereum Signed Message:\n${messageLength}${message}`;
const associatedAddrs = await addr.associate(`0x${sig.v-27}`, sig.r, sig.s, appendedMessage)
const addrs = await associatedAddrs.wait();
expect(addrs).to.not.be.null;

// Verify that addresses are now associated.
const seiAddr = await addr.getSeiAddr(unassociatedWallet.address);
expect(seiAddr).to.not.be.null;
});
});

describe("EVM Gov Precompile Tester", function () {
const GovPrecompileContract = '0x0000000000000000000000000000000000001006';
let gov;
Expand Down
7 changes: 4 additions & 3 deletions evmrpc/simulate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"math/big"
"time"

"github.com/sei-protocol/sei-chain/utils/helpers"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
Expand All @@ -24,7 +26,6 @@ import (
"github.com/ethereum/go-ethereum/rpc"
"github.com/sei-protocol/sei-chain/utils"
"github.com/sei-protocol/sei-chain/utils/metrics"
"github.com/sei-protocol/sei-chain/x/evm/ante"
"github.com/sei-protocol/sei-chain/x/evm/keeper"
"github.com/sei-protocol/sei-chain/x/evm/state"
"github.com/sei-protocol/sei-chain/x/evm/types"
Expand Down Expand Up @@ -310,7 +311,7 @@ func (b *Backend) StateAtTransaction(ctx context.Context, block *ethtypes.Block,
metrics.IncrementAssociationError("state_at_tx", err)
return nil, vm.BlockContext{}, nil, nil, err
}
if err := ante.NewEVMPreprocessDecorator(b.keeper, b.keeper.AccountKeeper()).AssociateAddresses(statedb.Ctx(), seiAddr, msg.From, nil); err != nil {
if err := helpers.NewAssociationHelper(b.keeper, b.keeper.BankKeeper(), b.keeper.AccountKeeper()).AssociateAddresses(statedb.Ctx(), seiAddr, msg.From, nil); err != nil {
return nil, vm.BlockContext{}, nil, nil, err
}
}
Expand Down Expand Up @@ -349,7 +350,7 @@ func (b *Backend) StateAtBlock(ctx context.Context, block *ethtypes.Block, reexe
metrics.IncrementAssociationError("state_at_block", err)
return nil, emptyRelease, err
}
if err := ante.NewEVMPreprocessDecorator(b.keeper, b.keeper.AccountKeeper()).AssociateAddresses(statedb.Ctx(), seiAddr, msg.From, nil); err != nil {
if err := helpers.NewAssociationHelper(b.keeper, b.keeper.BankKeeper(), b.keeper.AccountKeeper()).AssociateAddresses(statedb.Ctx(), seiAddr, msg.From, nil); err != nil {
return nil, emptyRelease, err
}
}
Expand Down
8 changes: 8 additions & 0 deletions precompiles/addr/Addr.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ IAddr constant ADDR_CONTRACT = IAddr(
);

interface IAddr {
// Transactions
function associate(
string memory v,
string memory r,
string memory s,
string memory customMessage
) external returns (string seiAddr, address evmAddr);

// Queries
function getSeiAddr(address addr) external view returns (string memory response);
function getEvmAddr(string memory addr) external view returns (address response);
Expand Down
2 changes: 1 addition & 1 deletion precompiles/addr/abi.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
[{"inputs":[{"internalType":"string","name":"addr","type":"string"}],"name":"getEvmAddr","outputs":[{"internalType":"address","name":"response","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getSeiAddr","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"}]
[{"inputs":[{"internalType":"string","name":"v","type":"string"},{"internalType":"string","name":"r","type":"string"},{"internalType":"string","name":"s","type":"string"},{"internalType":"string","name":"customMessage","type":"string"}],"name":"associate","outputs":[{"internalType":"string","name":"seiAddr","type":"string"},{"internalType":"address","name":"evmAddr","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getSeiAddr","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"addr","type":"string"}],"name":"getEvmAddr","outputs":[{"internalType":"address","name":"response","type":"address"}],"stateMutability":"view","type":"function"}]
103 changes: 98 additions & 5 deletions precompiles/addr/addr.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package addr

import (
"bytes"
"embed"
"encoding/hex"
"fmt"
"strings"

"math/big"

"github.com/ethereum/go-ethereum/crypto"
"github.com/sei-protocol/sei-chain/utils"
"github.com/sei-protocol/sei-chain/utils/helpers"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
Expand All @@ -18,6 +26,7 @@ import (
const (
GetSeiAddressMethod = "getSeiAddr"
GetEvmAddressMethod = "getEvmAddr"
Associate = "associate"
)

const (
Expand All @@ -30,17 +39,23 @@ const (
var f embed.FS

type PrecompileExecutor struct {
evmKeeper pcommon.EVMKeeper
evmKeeper pcommon.EVMKeeper
bankKeeper pcommon.BankKeeper
accountKeeper pcommon.AccountKeeper

GetSeiAddressID []byte
GetEvmAddressID []byte
AssociateID []byte
}

func NewPrecompile(evmKeeper pcommon.EVMKeeper) (*pcommon.Precompile, error) {
func NewPrecompile(evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper, accountKeeper pcommon.AccountKeeper) (*pcommon.Precompile, error) {

newAbi := pcommon.MustGetABI(f, "abi.json")

p := &PrecompileExecutor{
evmKeeper: evmKeeper,
evmKeeper: evmKeeper,
bankKeeper: bankKeeper,
accountKeeper: accountKeeper,
}

for name, m := range newAbi.Methods {
Expand All @@ -49,6 +64,8 @@ func NewPrecompile(evmKeeper pcommon.EVMKeeper) (*pcommon.Precompile, error) {
p.GetSeiAddressID = m.ID
case GetEvmAddressMethod:
p.GetEvmAddressID = m.ID
case Associate:
p.AssociateID = m.ID
}
}

Expand All @@ -57,6 +74,9 @@ func NewPrecompile(evmKeeper pcommon.EVMKeeper) (*pcommon.Precompile, error) {

// RequiredGas returns the required bare minimum gas to execute the precompile.
func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 {
if bytes.Equal(method.ID, p.AssociateID) {
return 50000
}
return pcommon.DefaultGasCost(input, p.IsTransaction(method.Name))
}

Expand All @@ -66,6 +86,8 @@ func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, _ commo
return p.getSeiAddr(ctx, method, args, value)
case GetEvmAddressMethod:
return p.getEvmAddr(ctx, method, args, value)
case Associate:
return p.associate(ctx, method, args, value)
}
return
}
Expand Down Expand Up @@ -109,6 +131,77 @@ func (p PrecompileExecutor) getEvmAddr(ctx sdk.Context, method *abi.Method, args
return method.Outputs.Pack(evmAddr)
}

func (PrecompileExecutor) IsTransaction(string) bool {
return false
func (p PrecompileExecutor) associate(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) {
if err := pcommon.ValidateNonPayable(value); err != nil {
return nil, err
}

if err := pcommon.ValidateArgsLength(args, 4); err != nil {
return nil, err
}

// v, r and s are components of a signature over the customMessage sent.
// We use the signature to construct the user's pubkey to obtain their addresses.
v := args[0].(string)
r := args[1].(string)
s := args[2].(string)
customMessage := args[3].(string)

rBytes, err := decodeHexString(r)
if err != nil {
return nil, err
}
sBytes, err := decodeHexString(s)
if err != nil {
return nil, err
}
vBytes, err := decodeHexString(v)
if err != nil {
return nil, err
}

vBig := new(big.Int).SetBytes(vBytes)
rBig := new(big.Int).SetBytes(rBytes)
sBig := new(big.Int).SetBytes(sBytes)

// Derive addresses
vBig = new(big.Int).Add(vBig, utils.Big27)

customMessageHash := crypto.Keccak256Hash([]byte(customMessage))
evmAddr, seiAddr, pubkey, err := helpers.GetAddresses(vBig, rBig, sBig, customMessageHash)
if err != nil {
return nil, err
}

// Check that address is not already associated
_, found := p.evmKeeper.GetEVMAddress(ctx, seiAddr)
if found {
return nil, fmt.Errorf("address %s is already associated with evm address %s", seiAddr, evmAddr)
}

// Associate Addresses:
associationHelper := helpers.NewAssociationHelper(p.evmKeeper, p.bankKeeper, p.accountKeeper)
err = associationHelper.AssociateAddresses(ctx, seiAddr, evmAddr, pubkey)
if err != nil {
return nil, err
}

return method.Outputs.Pack(seiAddr.String(), evmAddr)
}

func (PrecompileExecutor) IsTransaction(method string) bool {
switch method {
case Associate:
return true
default:
return false
}
}

func decodeHexString(hexString string) ([]byte, error) {
trimmed := strings.TrimPrefix(hexString, "0x")
if len(trimmed)%2 != 0 {
trimmed = "0" + trimmed
}
return hex.DecodeString(trimmed)
}
Loading

0 comments on commit 7d563ed

Please sign in to comment.