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"
|
ens "github.com/wealdtech/go-ens/v3"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
"github.com/status-im/status-go/eth-node/crypto"
|
||||||
|
@ -30,6 +31,17 @@ func NewVerifier(logger *zap.Logger) *Verifier {
|
||||||
return &Verifier{logger: logger}
|
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 {
|
func (m *Verifier) verifyENSName(ensInfo enstypes.ENSDetails, ethclient *ethclient.Client) enstypes.ENSResponse {
|
||||||
publicKeyStr := ensInfo.PublicKeyString
|
publicKeyStr := ensInfo.PublicKeyString
|
||||||
ensName := ensInfo.Name
|
ensName := ensInfo.Name
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
package enstypes
|
package enstypes
|
||||||
|
|
||||||
import "crypto/ecdsa"
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
type ENSVerifier interface {
|
type ENSVerifier interface {
|
||||||
// CheckBatch verifies that a registered ENS name matches the expected public key
|
// CheckBatch verifies that a registered ENS name matches the expected public key
|
||||||
CheckBatch(ensDetails []ENSDetails, rpcEndpoint, contractAddress string) (map[string]ENSResponse, error)
|
CheckBatch(ensDetails []ENSDetails, rpcEndpoint, contractAddress string) (map[string]ENSResponse, error)
|
||||||
|
ReverseResolve(address common.Address, rpcEndpoint string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ENSDetails struct {
|
type ENSDetails struct {
|
||||||
|
|
|
@ -1632,6 +1632,11 @@ func (m *Manager) checkPermissionToJoin(permissions []*protobuf.CommunityTokenPe
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ownedENSNames, err := m.getOwnedENS(walletAddresses)
|
||||||
|
if err != nil {
|
||||||
|
return response, err
|
||||||
|
}
|
||||||
|
|
||||||
for _, tokenPermission := range permissions {
|
for _, tokenPermission := range permissions {
|
||||||
|
|
||||||
permissionRequirementsMet := true
|
permissionRequirementsMet := true
|
||||||
|
@ -1685,6 +1690,25 @@ func (m *Manager) checkPermissionToJoin(permissions []*protobuf.CommunityTokenPe
|
||||||
if ownedERC20Tokens[tokenRequirement.Symbol].Cmp(big.NewFloat(amount)) != -1 {
|
if ownedERC20Tokens[tokenRequirement.Symbol].Cmp(big.NewFloat(amount)) != -1 {
|
||||||
tokenRequirementMet = true
|
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 {
|
if !tokenRequirementMet {
|
||||||
permissionRequirementsMet = false
|
permissionRequirementsMet = false
|
||||||
|
@ -1815,6 +1839,24 @@ func (m *Manager) getAccumulatedTokenBalances(accounts []gethcommon.Address, tok
|
||||||
return accumulatedBalances, nil
|
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) {
|
func (m *Manager) HandleCommunityRequestToJoinResponse(signer *ecdsa.PublicKey, request *protobuf.CommunityRequestToJoinResponse) (*RequestToJoin, error) {
|
||||||
pkString := common.PubkeyToHex(&m.identity.PublicKey)
|
pkString := common.PubkeyToHex(&m.identity.PublicKey)
|
||||||
|
|
||||||
|
|
|
@ -2211,6 +2211,144 @@ func (s *MessengerCommunitiesSuite) TestDeclineAccess() {
|
||||||
s.Require().Len(requestsToJoin, 0)
|
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() {
|
func (s *MessengerCommunitiesSuite) TestLeaveAndRejoinCommunity() {
|
||||||
community := s.createCommunity()
|
community := s.createCommunity()
|
||||||
s.advertiseCommunityTo(community, s.alice)
|
s.advertiseCommunityTo(community, s.alice)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
enstypes "github.com/status-im/status-go/eth-node/types/ens"
|
enstypes "github.com/status-im/status-go/eth-node/types/ens"
|
||||||
"github.com/status-im/status-go/protocol/common"
|
"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
|
// Verify verifies that a registered ENS name matches the expected public key
|
||||||
func (v *Verifier) verify(rpcEndpoint, contractAddress string) error {
|
func (v *Verifier) verify(rpcEndpoint, contractAddress string) error {
|
||||||
v.logger.Debug("verifying ENS Names", zap.String("endpoint", rpcEndpoint))
|
v.logger.Debug("verifying ENS Names", zap.String("endpoint", rpcEndpoint))
|
||||||
|
|
Loading…
Reference in New Issue