Skip to content

Commit 639ac2d

Browse files
authored
Merge branch 'master' into state_candidate_with_identity
2 parents 2d6bdf5 + 903fb4e commit 639ac2d

File tree

10 files changed

+1000
-1
lines changed

10 files changed

+1000
-1
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Variables
2+
SOLC := solc
3+
POLL_SOL_FILE := poll.sol
4+
POLL_ABI_FILE := poll.abi.json
5+
TEMP_DIR := .temp
6+
7+
# Default target
8+
all: $(POLL_ABI_FILE)
9+
10+
# Generate ABI file
11+
$(POLL_ABI_FILE): $(POLL_SOL_FILE)
12+
@echo "Generating ABI from $(POLL_SOL_FILE)..."
13+
@mkdir -p $(TEMP_DIR)
14+
@$(SOLC) --abi $(POLL_SOL_FILE) -o $(TEMP_DIR) --overwrite
15+
@if [ -f "$(TEMP_DIR)/IPollProtocolContract.abi" ]; then \
16+
jq '.' "$(TEMP_DIR)/IPollProtocolContract.abi" > $(POLL_ABI_FILE); \
17+
echo "ABI generated and formatted successfully: $(POLL_ABI_FILE)"; \
18+
else \
19+
echo "Error: Poll contract ABI file not generated"; \
20+
exit 1; \
21+
fi
22+
@rm -rf $(TEMP_DIR)
23+
24+
# Clean generated files
25+
clean:
26+
@echo "Cleaning generated files..."
27+
@rm -f $(POLL_ABI_FILE)
28+
@rm -rf $(TEMP_DIR)
29+
30+
.PHONY: all clean

action/protocol/poll/ethabi/abi.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package ethabi
2+
3+
import (
4+
_ "embed"
5+
"strings"
6+
7+
"github.com/cockroachdb/errors"
8+
"github.com/ethereum/go-ethereum/accounts/abi"
9+
"github.com/ethereum/go-ethereum/crypto"
10+
11+
"github.com/iotexproject/iotex-core/v2/action/protocol"
12+
)
13+
14+
var (
15+
//go:embed poll.abi.json
16+
pollABIJSON string
17+
pollABI = initPollABI(pollABIJSON)
18+
pollCandidatesMethod, pollCandidatesByEpochMethod *abi.Method
19+
pollBlockProducersMethod, pollBlockProducersByEpochMethod *abi.Method
20+
pollActiveBlockProducersMethod, pollActiveBlockProducersByEpochMethod *abi.Method
21+
pollProbationListMethod, pollProbationListByEpochMethod *abi.Method
22+
)
23+
24+
func initPollABI(abiJSON string) *abi.ABI {
25+
a, err := abi.JSON(strings.NewReader(abiJSON))
26+
if err != nil {
27+
panic(err)
28+
}
29+
pollCandidatesMethod, err = a.MethodById(signatureToMethodID("CandidatesByEpoch()"))
30+
if err != nil {
31+
panic(err)
32+
}
33+
pollCandidatesByEpochMethod, err = a.MethodById(signatureToMethodID("CandidatesByEpoch(uint256)"))
34+
if err != nil {
35+
panic(err)
36+
}
37+
pollBlockProducersMethod, err = a.MethodById(signatureToMethodID("BlockProducersByEpoch()"))
38+
if err != nil {
39+
panic(err)
40+
}
41+
pollBlockProducersByEpochMethod, err = a.MethodById(signatureToMethodID("BlockProducersByEpoch(uint256)"))
42+
if err != nil {
43+
panic(err)
44+
}
45+
pollActiveBlockProducersMethod, err = a.MethodById(signatureToMethodID("ActiveBlockProducersByEpoch()"))
46+
if err != nil {
47+
panic(err)
48+
}
49+
pollActiveBlockProducersByEpochMethod, err = a.MethodById(signatureToMethodID("ActiveBlockProducersByEpoch(uint256)"))
50+
if err != nil {
51+
panic(err)
52+
}
53+
pollProbationListMethod, err = a.MethodById(signatureToMethodID("ProbationListByEpoch()"))
54+
if err != nil {
55+
panic(err)
56+
}
57+
pollProbationListByEpochMethod, err = a.MethodById(signatureToMethodID("ProbationListByEpoch(uint256)"))
58+
if err != nil {
59+
panic(err)
60+
}
61+
return &a
62+
}
63+
64+
func signatureToMethodID(sig string) []byte {
65+
return crypto.Keccak256([]byte(sig))[:4]
66+
}
67+
68+
func BuildReadStateRequest(data []byte) (protocol.StateContext, error) {
69+
if len(data) < 4 {
70+
return nil, errors.Errorf("invalid call data length: %d", len(data))
71+
}
72+
method, err := pollABI.MethodById(data[:4])
73+
if err != nil {
74+
return nil, errors.Wrapf(err, "failed to get method by id %x", data[:4])
75+
}
76+
switch method.RawName {
77+
case pollCandidatesMethod.RawName, pollCandidatesByEpochMethod.RawName:
78+
return newCandidateListStateContext(data[4:], method, "CandidatesByEpoch")
79+
case pollBlockProducersMethod.RawName, pollBlockProducersByEpochMethod.RawName:
80+
return newCandidateListStateContext(data[4:], method, "BlockProducersByEpoch")
81+
case pollActiveBlockProducersMethod.RawName, pollActiveBlockProducersByEpochMethod.RawName:
82+
return newCandidateListStateContext(data[4:], method, "ActiveBlockProducersByEpoch")
83+
case pollProbationListMethod.RawName, pollProbationListByEpochMethod.RawName:
84+
return newProbationListStateContext(data[4:], method)
85+
default:
86+
}
87+
return nil, nil
88+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package ethabi
2+
3+
import (
4+
"encoding/hex"
5+
"math/big"
6+
"testing"
7+
8+
"github.com/ethereum/go-ethereum/accounts/abi"
9+
"github.com/iotexproject/iotex-proto/golang/iotexapi"
10+
"github.com/iotexproject/iotex-proto/golang/iotextypes"
11+
"github.com/stretchr/testify/require"
12+
13+
"github.com/iotexproject/iotex-core/v2/action/protocol"
14+
"github.com/iotexproject/iotex-core/v2/action/protocol/vote"
15+
"github.com/iotexproject/iotex-core/v2/state"
16+
"github.com/iotexproject/iotex-core/v2/test/identityset"
17+
)
18+
19+
func TestCandidateABI(t *testing.T) {
20+
r := require.New(t)
21+
22+
check := func(calldata []byte, method *abi.Method, expectParam *protocol.Parameters) {
23+
sc, err := BuildReadStateRequest(append(method.ID, calldata...))
24+
r.NoError(err)
25+
r.EqualValues(expectParam.MethodName, sc.Parameters().MethodName)
26+
r.ElementsMatch(expectParam.Arguments, sc.Parameters().Arguments)
27+
height := uint64(12345)
28+
cands := state.CandidateList{
29+
{
30+
Address: identityset.Address(1).String(),
31+
Votes: big.NewInt(100),
32+
RewardAddress: identityset.Address(2).String(),
33+
BLSPubKey: []byte("bls-pub-key-1"),
34+
},
35+
{
36+
Address: identityset.Address(3).String(),
37+
Votes: big.NewInt(200),
38+
RewardAddress: identityset.Address(4).String(),
39+
BLSPubKey: []byte("bls-pub-key-2"),
40+
},
41+
}
42+
candData, err := cands.Serialize()
43+
r.NoError(err)
44+
expect, err := ConvertCandidateListFromState(cands)
45+
r.NoError(err)
46+
out, err := sc.EncodeToEth(&iotexapi.ReadStateResponse{
47+
Data: candData,
48+
BlockIdentifier: &iotextypes.BlockIdentifier{
49+
Height: height,
50+
},
51+
})
52+
r.NoError(err)
53+
54+
var getResult struct {
55+
Candidates []Candidate
56+
Height *big.Int
57+
}
58+
data, err := hex.DecodeString(out)
59+
r.NoError(err)
60+
err = pollABI.UnpackIntoInterface(&getResult, method.Name, data)
61+
r.NoError(err)
62+
63+
r.Equal(height, getResult.Height.Uint64())
64+
r.ElementsMatch(expect, getResult.Candidates)
65+
}
66+
67+
t.Run("Candidates", func(t *testing.T) {
68+
data, err := pollCandidatesMethod.Inputs.Pack()
69+
r.NoError(err)
70+
check(data, pollCandidatesMethod, &protocol.Parameters{MethodName: []byte("CandidatesByEpoch"), Arguments: nil})
71+
})
72+
t.Run("CandidatesByEpoch", func(t *testing.T) {
73+
epoch := big.NewInt(100)
74+
data, err := pollCandidatesByEpochMethod.Inputs.Pack(epoch)
75+
r.NoError(err)
76+
args := [][]byte{[]byte(epoch.Text(10))}
77+
check(data, pollCandidatesByEpochMethod, &protocol.Parameters{MethodName: []byte("CandidatesByEpoch"), Arguments: args})
78+
})
79+
t.Run("BlockProducers", func(t *testing.T) {
80+
data, err := pollBlockProducersMethod.Inputs.Pack()
81+
r.NoError(err)
82+
check(data, pollBlockProducersMethod, &protocol.Parameters{MethodName: []byte("BlockProducersByEpoch"), Arguments: nil})
83+
})
84+
t.Run("BlockProducersByEpoch", func(t *testing.T) {
85+
data, err := pollBlockProducersMethod.Inputs.Pack()
86+
r.NoError(err)
87+
check(data, pollBlockProducersMethod, &protocol.Parameters{MethodName: []byte("BlockProducersByEpoch"), Arguments: nil})
88+
})
89+
t.Run("ActiveBlockProducers", func(t *testing.T) {
90+
data, err := pollActiveBlockProducersMethod.Inputs.Pack()
91+
r.NoError(err)
92+
check(data, pollActiveBlockProducersMethod, &protocol.Parameters{MethodName: []byte("ActiveBlockProducersByEpoch"), Arguments: nil})
93+
})
94+
t.Run("ActiveBlockProducersByEpoch", func(t *testing.T) {
95+
epoch := big.NewInt(100)
96+
data, err := pollActiveBlockProducersByEpochMethod.Inputs.Pack(epoch)
97+
r.NoError(err)
98+
args := [][]byte{[]byte(epoch.Text(10))}
99+
check(data, pollActiveBlockProducersByEpochMethod, &protocol.Parameters{MethodName: []byte("ActiveBlockProducersByEpoch"), Arguments: args})
100+
})
101+
}
102+
103+
func TestProbationABI(t *testing.T) {
104+
r := require.New(t)
105+
106+
check := func(calldata []byte, method *abi.Method, expectParam *protocol.Parameters) {
107+
sc, err := BuildReadStateRequest(append(method.ID, calldata...))
108+
r.NoError(err)
109+
r.EqualValues(expectParam.MethodName, sc.Parameters().MethodName)
110+
r.ElementsMatch(expectParam.Arguments, sc.Parameters().Arguments)
111+
height := uint64(12345)
112+
pl := vote.ProbationList{
113+
ProbationInfo: map[string]uint32{
114+
identityset.Address(1).String(): 3,
115+
identityset.Address(2).String(): 5,
116+
},
117+
IntensityRate: 10,
118+
}
119+
plData, err := pl.Serialize()
120+
r.NoError(err)
121+
expect, err := ConvertProbationListFromState(&pl)
122+
r.NoError(err)
123+
out, err := sc.EncodeToEth(&iotexapi.ReadStateResponse{
124+
Data: plData,
125+
BlockIdentifier: &iotextypes.BlockIdentifier{
126+
Height: height,
127+
},
128+
})
129+
r.NoError(err)
130+
131+
var getResult struct {
132+
Probation ProbationList `json:"probation"`
133+
Height *big.Int `json:"height"`
134+
}
135+
data, err := hex.DecodeString(out)
136+
r.NoError(err)
137+
err = pollABI.UnpackIntoInterface(&getResult, method.Name, data)
138+
r.NoError(err)
139+
140+
r.Equal(height, getResult.Height.Uint64())
141+
r.EqualValues(expect.IntensityRate, getResult.Probation.IntensityRate)
142+
r.ElementsMatch(expect.ProbationInfo, getResult.Probation.ProbationInfo)
143+
}
144+
145+
t.Run("ProbationList", func(t *testing.T) {
146+
data, err := pollProbationListMethod.Inputs.Pack()
147+
r.NoError(err)
148+
check(data, pollProbationListMethod, &protocol.Parameters{MethodName: []byte("ProbationListByEpoch"), Arguments: nil})
149+
})
150+
t.Run("ProbationListByEpoch", func(t *testing.T) {
151+
epoch := big.NewInt(100)
152+
data, err := pollProbationListByEpochMethod.Inputs.Pack(epoch)
153+
r.NoError(err)
154+
args := [][]byte{[]byte(epoch.Text(10))}
155+
check(data, pollProbationListByEpochMethod, &protocol.Parameters{MethodName: []byte("ProbationListByEpoch"), Arguments: args})
156+
})
157+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package ethabi
2+
3+
import (
4+
"encoding/hex"
5+
"math/big"
6+
7+
"github.com/cockroachdb/errors"
8+
"github.com/ethereum/go-ethereum/accounts/abi"
9+
"github.com/ethereum/go-ethereum/common"
10+
"github.com/iotexproject/iotex-address/address"
11+
"github.com/iotexproject/iotex-core/v2/action/protocol"
12+
"github.com/iotexproject/iotex-core/v2/state"
13+
"github.com/iotexproject/iotex-proto/golang/iotexapi"
14+
)
15+
16+
// Candidate represents a candidate in the Ethereum ABI format
17+
type Candidate struct {
18+
ID common.Address `abi:"id"`
19+
Name string `abi:"name"`
20+
OperatorAddress common.Address `abi:"operatorAddress"`
21+
RewardAddress common.Address `abi:"rewardAddress"`
22+
BlsPubKey []byte `abi:"blsPubKey"`
23+
Votes *big.Int `abi:"votes"`
24+
}
25+
26+
// NewCandidateFromState converts a state.Candidate to ethabi.Candidate
27+
func NewCandidateFromState(stateCandidate *state.Candidate) (*Candidate, error) {
28+
// Convert operator address
29+
operatorAddr, err := address.FromString(stateCandidate.Address)
30+
if err != nil {
31+
return nil, errors.Wrapf(err, "invalid operator address %s", stateCandidate.Address)
32+
}
33+
34+
// Convert reward address
35+
rewardAddr, err := address.FromString(stateCandidate.RewardAddress)
36+
if err != nil {
37+
return nil, errors.Wrapf(err, "invalid reward address %s", stateCandidate.RewardAddress)
38+
}
39+
40+
return &Candidate{
41+
Name: string(stateCandidate.CanName),
42+
OperatorAddress: common.BytesToAddress(operatorAddr.Bytes()),
43+
RewardAddress: common.BytesToAddress(rewardAddr.Bytes()),
44+
BlsPubKey: stateCandidate.BLSPubKey,
45+
Votes: stateCandidate.Votes,
46+
}, nil
47+
}
48+
49+
// ConvertCandidateListFromState converts a state.CandidateList to []Candidate
50+
func ConvertCandidateListFromState(stateCandidates state.CandidateList) ([]Candidate, error) {
51+
ethCandidates := make([]Candidate, len(stateCandidates))
52+
for i, stateCandidate := range stateCandidates {
53+
ethCandidate, err := NewCandidateFromState(stateCandidate)
54+
if err != nil {
55+
return nil, errors.Wrapf(err, "failed to convert candidate at index %d", i)
56+
}
57+
ethCandidates[i] = *ethCandidate
58+
}
59+
return ethCandidates, nil
60+
}
61+
62+
type CandidateListStateContext struct {
63+
*protocol.BaseStateContext
64+
}
65+
66+
func newCandidateListStateContext(data []byte, methodABI *abi.Method, name string) (*CandidateListStateContext, error) {
67+
var args [][]byte
68+
paramsMap := map[string]any{}
69+
if err := methodABI.Inputs.UnpackIntoMap(paramsMap, data); err != nil {
70+
return nil, err
71+
}
72+
epoch, ok := paramsMap["epoch"]
73+
if ok {
74+
epochBigInt, ok := epoch.(*big.Int)
75+
if !ok {
76+
return nil, errors.New("failed to cast epoch to *big.Int")
77+
}
78+
args = append(args, []byte(epochBigInt.Text(10)))
79+
}
80+
return &CandidateListStateContext{
81+
&protocol.BaseStateContext{
82+
Parameter: &protocol.Parameters{
83+
MethodName: []byte(name),
84+
Arguments: args,
85+
},
86+
Method: methodABI,
87+
},
88+
}, nil
89+
}
90+
91+
// EncodeToEth encode proto to eth
92+
func (r *CandidateListStateContext) EncodeToEth(resp *iotexapi.ReadStateResponse) (string, error) {
93+
var cands state.CandidateList
94+
if err := cands.Deserialize(resp.Data); err != nil {
95+
return "", err
96+
}
97+
98+
// Convert state.CandidateList to []Candidate for ABI encoding
99+
ethCandidates, err := ConvertCandidateListFromState(cands)
100+
if err != nil {
101+
return "", errors.Wrap(err, "failed to convert candidate list")
102+
}
103+
104+
data, err := r.Method.Outputs.Pack(ethCandidates, new(big.Int).SetUint64(resp.BlockIdentifier.Height))
105+
if err != nil {
106+
return "", errors.Wrap(err, "failed to pack candidate list")
107+
}
108+
109+
return hex.EncodeToString(data), nil
110+
}

0 commit comments

Comments
 (0)