EIP 7702

EIP-7702 是一项以太坊改进提案,旨在通过引入一种新的交易类型,使外部拥有账户(EOA)能够临时像智能合约账户一样运作。

这项提案的目的是简化账户抽象,允许普通的 Web3 钱包能够实现智能钱包的某些功能。

账户模型

evm账户模型大概如下

address => {
   Nonce    uint64
   Balance  *uint256.Int
   Code []byte
}

对于eoa账户,code里无代码,不具备合约功能

使用7702交易,eoa授权合约后, eoa账户下也会有code。跟eoa地址交互,是跟他的合约交互。

比如授权后,他的合约里没提供fallback方法,就无法收bnb

比如授权合约是一个工具类转出token,那其实每个人都可以调用他的方法转出token

具体实现

7702交易

授权合约/取消授权合约,都需要通过7702的交易类型;

7702交易类型,为新增的一个交易类型。

// SetCodeTx implements the EIP-7702 transaction type which temporarily installs
// the code at the signer's address.
type SetCodeTx struct {
   ChainID    *uint256.Int
   Nonce      uint64
   GasTipCap  *uint256.Int // a.k.a. maxPriorityFeePerGas
   GasFeeCap  *uint256.Int // a.k.a. maxFeePerGas
   Gas        uint64
   To         common.Address
   Value      *uint256.Int
   Data       []byte
   AccessList AccessList
   AuthList   []SetCodeAuthorization

   // Signature values
   V *uint256.Int
   R *uint256.Int
   S *uint256.Int
}

// SetCodeAuthorization is an authorization from an account to deploy code at its address.
type SetCodeAuthorization struct {
   ChainID uint256.Int    `json:"chainId" gencodec:"required"`
   Address common.Address `json:"address" gencodec:"required"`
   Nonce   uint64         `json:"nonce" gencodec:"required"`
   V       uint8          `json:"yParity" gencodec:"required"`
   R       uint256.Int    `json:"r" gencodec:"required"`
   S       uint256.Int    `json:"s" gencodec:"required"`
}

用户授权,需要对合约地址和用户nonce, chainid进行签名即可。

EVM

在执行交易前,先查看SetCodeAuthorizations, 设置code到用户地址下,再进行执行交易;

啊这里要注意点,要是applyAuthorization有error,这里是忽略error的

if contractCreation {
   ret, _, st.gasRemaining, vmerr = st.evm.Create(sender, msg.Data, st.gasRemaining, value)
} else {
   // Increment the nonce for the next transaction.
   st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)

   // Apply EIP-7702 authorizations.
   if msg.SetCodeAuthorizations != nil {
      for _, auth := range msg.SetCodeAuthorizations {
         // Note errors are ignored, we simply skip invalid authorizations here.
         st.applyAuthorization(&auth)
      }
   }

   // Perform convenience warming of sender's delegation target. Although the
   // sender is already warmed in Prepare(..), it's possible a delegation to
   // the account was deployed during this transaction. To handle correctly,
   // simply wait until the final state of delegations is determined before
   // performing the resolution and warming.
   if addr, ok := types.ParseDelegation(st.state.GetCode(*msg.To)); ok {
      st.state.AddAddressToAccessList(addr)
   }

   // Execute the transaction's call.
   ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, value)
}

查询某账户是否是有授权

//eth_getCode

返回值如果是 以 0xef010051510973ba7c1cc5a5e48e180c68b2ea4b9ec7df 0xef0100开头,那就是授权合约
合约地址就把前缀去掉就行

要取消,就签一笔address = common.Address{}的授权就行

回到交易

如果是自己签授权/取消授权,因为evm 里msg.from nonce先生效,所以auth里的nonce记得要➕1

最后放一个例子

func TestCccD(t *testing.T) {
   ctx := context.Background()
   client, _ := ethclient.DialContext(ctx, "https://data-seed-prebsc-1-s1.bnbchain.org:8545")
   feeKey, _ := crypto.HexToECDSA("")
   feeAddress := crypto.PubkeyToAddress(feeKey.PublicKey)
   authKey, _ := crypto.HexToECDSA("")
   authAddress := crypto.PubkeyToAddress(authKey.PublicKey)

   authNonce, err := client.NonceAt(context.Background(), authAddress, nil)
   noErr(err)
   feeNonce, err := client.NonceAt(context.Background(), feeAddress, nil)
   noErr(err)

   fmt.Println("fee", feeAddress.String(), feeNonce)
   fmt.Println("auth", authAddress.String(), authNonce)

   chainId := uint256.NewInt(97)
   authList, err := types.SignSetCode(authKey, types.SetCodeAuthorization{
      ChainID: *chainId,
      //Address: common.HexToAddress("0x51510973ba7c1cc5a5e48e180c68b2ea4b9ec7df"),
      Address: common.Address{},
      Nonce:   authNonce,
   })
   //fmt.Println(authList, err)

   //authList.Address = common.HexToAddress("0x51510973ba8c1cc5a5e48e180c68b2ea4b9ec7df")
   //authList.Nonce = 123

   data, err := hex.DecodeString("79f2447e000000000000000000000000ec5dcb5dbf4b114c9d0f65bccab49ec54f6a0867000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000eeae6c472be3ced42094b0f5a4f49380d0cfbccc000000000000000000000000eeae6c472be3ced42094b0f5a4f49380d0cfbccc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000012c000000000000000000000000000000000000000000000000000000000000012c")
   noErr(err)

   tx := &types.SetCodeTx{
      ChainID:    chainId,
      Nonce:      feeNonce,
      GasTipCap:  uint256.NewInt(1e9),
      GasFeeCap:  uint256.NewInt(1e9),
      Gas:        1000000,
      To:         authAddress,
      Value:      nil,
      Data:       data,
      AccessList: nil,
      AuthList:   []types.SetCodeAuthorization{authList},
   }
   //fmt.Println(authList)
   //
   signedTx, err := types.SignTx(types.NewTx(tx), types.NewPragueSigner(big.NewInt(97)), feeKey)
   noErr(err)
   fmt.Println(signedTx.Type())

   //tx := &types.LegacyTx{
   // Nonce:    feeNonce,
   // GasPrice: big.NewInt(1e9),
   // Gas:      100000,
   // To:       &authAddress,
   // Value:    nil,
   // Data:     data,
   //}
   //signedTx, err := types.SignTx(types.NewTx(tx), types.NewEIP155Signer(big.NewInt(97)), feeKey)
   //noErr(err)
   fmt.Println(signedTx.Type())
   err = client.SendTransaction(ctx, signedTx)
   noErr(err)

   fmt.Println(signedTx.Hash())
}