Add permission checks for ENS token permissions
Also add tests for creating and editing token permissions as well as a test for checking ENS ownership.
This commit is contained in:
parent
92b5d831fe
commit
ffc1006953
|
@ -11,6 +11,7 @@ import (
|
|||
ens "github.com/wealdtech/go-ens/v3"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
|
@ -30,6 +31,17 @@ func NewVerifier(logger *zap.Logger) *Verifier {
|
|||
return &Verifier{logger: logger}
|
||||
}
|
||||
|
||||
func (m *Verifier) ReverseResolve(address common.Address, rpcEndpoint string) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), contractQueryTimeout)
|
||||
defer cancel()
|
||||
|
||||
ethClient, err := ethclient.DialContext(ctx, rpcEndpoint)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ens.ReverseResolve(ethClient, address)
|
||||
}
|
||||
|
||||
func (m *Verifier) verifyENSName(ensInfo enstypes.ENSDetails, ethclient *ethclient.Client) enstypes.ENSResponse {
|
||||
publicKeyStr := ensInfo.PublicKeyString
|
||||
ensName := ensInfo.Name
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
package enstypes
|
||||
|
||||
import "crypto/ecdsa"
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
type ENSVerifier interface {
|
||||
// CheckBatch verifies that a registered ENS name matches the expected public key
|
||||
CheckBatch(ensDetails []ENSDetails, rpcEndpoint, contractAddress string) (map[string]ENSResponse, error)
|
||||
ReverseResolve(address common.Address, rpcEndpoint string) (string, error)
|
||||
}
|
||||
|
||||
type ENSDetails struct {
|
||||
|
|
|
@ -1632,6 +1632,11 @@ func (m *Manager) checkPermissionToJoin(permissions []*protobuf.CommunityTokenPe
|
|||
return response, err
|
||||
}
|
||||
|
||||
ownedENSNames, err := m.getOwnedENS(walletAddresses)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
for _, tokenPermission := range permissions {
|
||||
|
||||
permissionRequirementsMet := true
|
||||
|
@ -1685,6 +1690,25 @@ func (m *Manager) checkPermissionToJoin(permissions []*protobuf.CommunityTokenPe
|
|||
if ownedERC20Tokens[tokenRequirement.Symbol].Cmp(big.NewFloat(amount)) != -1 {
|
||||
tokenRequirementMet = true
|
||||
}
|
||||
} else if tokenRequirement.Type == protobuf.CommunityTokenType_ENS {
|
||||
if len(ownedENSNames) == 0 {
|
||||
response.Permissions[tokenPermission.Id].Criteria = append(response.Permissions[tokenPermission.Id].Criteria, false)
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(tokenRequirement.EnsPattern, "*.") {
|
||||
for _, ownedENS := range ownedENSNames {
|
||||
if ownedENS == tokenRequirement.EnsPattern {
|
||||
tokenRequirementMet = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
parentName := tokenRequirement.EnsPattern[2:]
|
||||
for _, ownedENS := range ownedENSNames {
|
||||
if strings.HasSuffix(ownedENS, parentName) {
|
||||
tokenRequirementMet = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !tokenRequirementMet {
|
||||
permissionRequirementsMet = false
|
||||
|
@ -1815,6 +1839,24 @@ func (m *Manager) getAccumulatedTokenBalances(accounts []gethcommon.Address, tok
|
|||
return accumulatedBalances, nil
|
||||
}
|
||||
|
||||
func (m *Manager) getOwnedENS(addresses []gethcommon.Address) ([]string, error) {
|
||||
ownedENS := make([]string, 0)
|
||||
if m.ensVerifier == nil {
|
||||
m.logger.Warn("no ensVerifier configured for communities manager")
|
||||
return ownedENS, nil
|
||||
}
|
||||
for _, address := range addresses {
|
||||
name, err := m.ensVerifier.ReverseResolve(address)
|
||||
if err != nil && err.Error() != "not a resolver" {
|
||||
return ownedENS, err
|
||||
}
|
||||
if name != "" {
|
||||
ownedENS = append(ownedENS, name)
|
||||
}
|
||||
}
|
||||
return ownedENS, nil
|
||||
}
|
||||
|
||||
func (m *Manager) HandleCommunityRequestToJoinResponse(signer *ecdsa.PublicKey, request *protobuf.CommunityRequestToJoinResponse) (*RequestToJoin, error) {
|
||||
pkString := common.PubkeyToHex(&m.identity.PublicKey)
|
||||
|
||||
|
|
|
@ -2211,6 +2211,144 @@ func (s *MessengerCommunitiesSuite) TestDeclineAccess() {
|
|||
s.Require().Len(requestsToJoin, 0)
|
||||
}
|
||||
|
||||
func (s *MessengerCommunitiesSuite) TestCreateTokenPermission() {
|
||||
community := s.createCommunity()
|
||||
|
||||
createTokenPermission := &requests.CreateCommunityTokenPermission{
|
||||
CommunityID: community.ID(),
|
||||
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
|
||||
TokenCriteria: []*protobuf.TokenCriteria{
|
||||
&protobuf.TokenCriteria{
|
||||
Type: protobuf.CommunityTokenType_ERC20,
|
||||
ContractAddresses: map[uint64]string{uint64(1): "0x123"},
|
||||
Symbol: "TEST",
|
||||
Amount: "100",
|
||||
Decimals: uint64(18),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
response, err := s.admin.CreateCommunityTokenPermission(createTokenPermission)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(response)
|
||||
s.Require().Len(response.Communities(), 1)
|
||||
|
||||
tokenPermissions := response.Communities()[0].TokenPermissions()
|
||||
for _, tokenPermission := range tokenPermissions {
|
||||
for _, tc := range tokenPermission.TokenCriteria {
|
||||
s.Require().Equal(tc.Type, protobuf.CommunityTokenType_ERC20)
|
||||
s.Require().Equal(tc.Symbol, "TEST")
|
||||
s.Require().Equal(tc.Amount, "100")
|
||||
s.Require().Equal(tc.Decimals, uint64(18))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MessengerCommunitiesSuite) TestEditTokenPermission() {
|
||||
community := s.createCommunity()
|
||||
|
||||
tokenPermission := &requests.CreateCommunityTokenPermission{
|
||||
CommunityID: community.ID(),
|
||||
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
|
||||
TokenCriteria: []*protobuf.TokenCriteria{
|
||||
&protobuf.TokenCriteria{
|
||||
Type: protobuf.CommunityTokenType_ERC20,
|
||||
ContractAddresses: map[uint64]string{uint64(1): "0x123"},
|
||||
Symbol: "TEST",
|
||||
Amount: "100",
|
||||
Decimals: uint64(18),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
response, err := s.admin.CreateCommunityTokenPermission(tokenPermission)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(response)
|
||||
s.Require().Len(response.Communities(), 1)
|
||||
|
||||
tokenPermissions := response.Communities()[0].TokenPermissions()
|
||||
|
||||
var tokenPermissionID string
|
||||
for id := range tokenPermissions {
|
||||
tokenPermissionID = id
|
||||
}
|
||||
|
||||
tokenPermission.TokenCriteria[0].Symbol = "TESTUpdated"
|
||||
tokenPermission.TokenCriteria[0].Amount = "200"
|
||||
tokenPermission.TokenCriteria[0].Decimals = uint64(20)
|
||||
|
||||
editTokenPermission := &requests.EditCommunityTokenPermission{
|
||||
PermissionID: tokenPermissionID,
|
||||
CreateCommunityTokenPermission: *tokenPermission,
|
||||
}
|
||||
|
||||
response2, err := s.admin.EditCommunityTokenPermission(editTokenPermission)
|
||||
s.Require().NoError(err)
|
||||
// wait for `checkMemberPermissions` to finish
|
||||
time.Sleep(1 * time.Second)
|
||||
s.Require().NotNil(response2)
|
||||
s.Require().Len(response2.Communities(), 1)
|
||||
|
||||
tokenPermissions = response2.Communities()[0].TokenPermissions()
|
||||
for _, tokenPermission := range tokenPermissions {
|
||||
for _, tc := range tokenPermission.TokenCriteria {
|
||||
s.Require().Equal(tc.Type, protobuf.CommunityTokenType_ERC20)
|
||||
s.Require().Equal(tc.Symbol, "TESTUpdated")
|
||||
s.Require().Equal(tc.Amount, "200")
|
||||
s.Require().Equal(tc.Decimals, uint64(20))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MessengerCommunitiesSuite) TestRequestAccessWithENSTokenPermission() {
|
||||
community := s.createCommunity()
|
||||
|
||||
createTokenPermission := &requests.CreateCommunityTokenPermission{
|
||||
CommunityID: community.ID(),
|
||||
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
|
||||
TokenCriteria: []*protobuf.TokenCriteria{
|
||||
&protobuf.TokenCriteria{
|
||||
Type: protobuf.CommunityTokenType_ENS,
|
||||
EnsPattern: "test.stateofus.eth",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
response, err := s.admin.CreateCommunityTokenPermission(createTokenPermission)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(response)
|
||||
s.Require().Len(response.Communities(), 1)
|
||||
|
||||
s.advertiseCommunityTo(community, s.alice)
|
||||
|
||||
requestToJoin := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
|
||||
// We try to join the org
|
||||
response, err = s.alice.RequestToJoinCommunity(requestToJoin)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(response)
|
||||
s.Require().Len(response.RequestsToJoinCommunity, 1)
|
||||
|
||||
requestToJoin1 := response.RequestsToJoinCommunity[0]
|
||||
s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin1.State)
|
||||
|
||||
// Retrieve request to join
|
||||
err = tt.RetryWithBackOff(func() error {
|
||||
response, err = s.admin.RetrieveAll()
|
||||
return err
|
||||
})
|
||||
s.Require().NoError(err)
|
||||
// We don't expect a requestToJoin in the response because due
|
||||
// to missing revealed wallet addresses, the request should've
|
||||
// been declined right away
|
||||
s.Require().Len(response.RequestsToJoinCommunity, 0)
|
||||
|
||||
// Ensure alice is not a member of the community
|
||||
allCommunities, err := s.admin.Communities()
|
||||
if bytes.Equal(allCommunities[0].ID(), community.ID()) {
|
||||
s.Require().False(allCommunities[0].HasMember(&s.alice.identity.PublicKey))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MessengerCommunitiesSuite) TestLeaveAndRejoinCommunity() {
|
||||
community := s.createCommunity()
|
||||
s.advertiseCommunityTo(community, s.alice)
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"go.uber.org/zap"
|
||||
|
||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
enstypes "github.com/status-im/status-go/eth-node/types/ens"
|
||||
"github.com/status-im/status-go/protocol/common"
|
||||
|
@ -129,6 +130,11 @@ func (v *Verifier) publish(records []*VerificationRecord) {
|
|||
|
||||
}
|
||||
|
||||
func (v *Verifier) ReverseResolve(address gethcommon.Address) (string, error) {
|
||||
verifier := v.node.NewENSVerifier(v.logger)
|
||||
return verifier.ReverseResolve(address, v.rpcEndpoint)
|
||||
}
|
||||
|
||||
// Verify verifies that a registered ENS name matches the expected public key
|
||||
func (v *Verifier) verify(rpcEndpoint, contractAddress string) error {
|
||||
v.logger.Debug("verifying ENS Names", zap.String("endpoint", rpcEndpoint))
|
||||
|
|
Loading…
Reference in New Issue