mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 07:37:20 +00:00
370 lines
11 KiB
Go
370 lines
11 KiB
Go
// Copyright 2024 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package bind
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"testing"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"golang.org/x/exp/rand"
|
|
)
|
|
|
|
type linkTestCase struct {
|
|
// map of pattern to unlinked bytecode (for the purposes of tests just contains the patterns of its dependencies)
|
|
libCodes map[string]string
|
|
contractCodes map[string]string
|
|
|
|
overrides map[string]common.Address
|
|
}
|
|
|
|
func copyMetaData(m *MetaData) *MetaData {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
var deps []*MetaData
|
|
if len(m.Deps) > 0 {
|
|
for _, dep := range m.Deps {
|
|
deps = append(deps, copyMetaData(dep))
|
|
}
|
|
}
|
|
return &MetaData{
|
|
Bin: m.Bin,
|
|
ABI: m.ABI,
|
|
Deps: deps,
|
|
ID: m.ID,
|
|
parsedABI: m.parsedABI,
|
|
}
|
|
}
|
|
|
|
func makeLinkTestCase(input map[rune][]rune, overrides map[rune]common.Address) *linkTestCase {
|
|
codes := make(map[string]string)
|
|
libCodes := make(map[string]string)
|
|
contractCodes := make(map[string]string)
|
|
|
|
inputMap := make(map[rune]map[rune]struct{})
|
|
// set of solidity patterns for all contracts that are known to be libraries
|
|
libs := make(map[string]struct{})
|
|
|
|
// map of test contract id (rune) to the solidity library pattern (hash of that rune)
|
|
patternMap := map[rune]string{}
|
|
|
|
for contract, deps := range input {
|
|
inputMap[contract] = make(map[rune]struct{})
|
|
if _, ok := patternMap[contract]; !ok {
|
|
patternMap[contract] = crypto.Keccak256Hash([]byte(string(contract))).String()[2:36]
|
|
}
|
|
|
|
for _, dep := range deps {
|
|
if _, ok := patternMap[dep]; !ok {
|
|
patternMap[dep] = crypto.Keccak256Hash([]byte(string(dep))).String()[2:36]
|
|
}
|
|
codes[patternMap[contract]] = codes[patternMap[contract]] + fmt.Sprintf("__$%s$__", patternMap[dep])
|
|
inputMap[contract][dep] = struct{}{}
|
|
libs[patternMap[dep]] = struct{}{}
|
|
}
|
|
}
|
|
overridesPatterns := make(map[string]common.Address)
|
|
for contractId, overrideAddr := range overrides {
|
|
pattern := crypto.Keccak256Hash([]byte(string(contractId))).String()[2:36]
|
|
overridesPatterns[pattern] = overrideAddr
|
|
}
|
|
|
|
for _, pattern := range patternMap {
|
|
if _, ok := libs[pattern]; ok {
|
|
// if the library didn't depend on others, give it some dummy code to not bork deployment logic down-the-line
|
|
if len(codes[pattern]) == 0 {
|
|
libCodes[pattern] = "ff"
|
|
} else {
|
|
libCodes[pattern] = codes[pattern]
|
|
}
|
|
} else {
|
|
contractCodes[pattern] = codes[pattern]
|
|
}
|
|
}
|
|
|
|
return &linkTestCase{
|
|
libCodes,
|
|
contractCodes,
|
|
overridesPatterns,
|
|
}
|
|
}
|
|
|
|
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
|
|
|
type linkTestCaseInput struct {
|
|
input map[rune][]rune
|
|
overrides map[rune]struct{}
|
|
expectDeployed map[rune]struct{}
|
|
}
|
|
|
|
// linkDeps will return a set of root dependencies and their sub-dependencies connected via the Deps field
|
|
func linkDeps(deps map[string]*MetaData) []*MetaData {
|
|
roots := make(map[string]struct{})
|
|
for pattern := range deps {
|
|
roots[pattern] = struct{}{}
|
|
}
|
|
|
|
connectedDeps := make(map[string]*MetaData)
|
|
for pattern, dep := range deps {
|
|
connectedDeps[pattern] = internalLinkDeps(dep, deps, &roots)
|
|
}
|
|
|
|
var rootMetadatas []*MetaData
|
|
for pattern := range roots {
|
|
dep := connectedDeps[pattern]
|
|
rootMetadatas = append(rootMetadatas, dep)
|
|
}
|
|
return rootMetadatas
|
|
}
|
|
|
|
// internalLinkDeps is the internal recursing logic of linkDeps:
|
|
// It links the contract referred to by MetaData given the depMap (map of solidity
|
|
// link pattern to contract metadata object), deleting contract entries from the
|
|
// roots map if they were referenced as dependencies. It returns a new MetaData
|
|
// object which is the linked version of metadata parameter.
|
|
func internalLinkDeps(metadata *MetaData, depMap map[string]*MetaData, roots *map[string]struct{}) *MetaData {
|
|
linked := copyMetaData(metadata)
|
|
depPatterns := parseLibraryDeps(metadata.Bin)
|
|
for _, pattern := range depPatterns {
|
|
delete(*roots, pattern)
|
|
connectedDep := internalLinkDeps(depMap[pattern], depMap, roots)
|
|
linked.Deps = append(linked.Deps, connectedDep)
|
|
}
|
|
return linked
|
|
}
|
|
|
|
func testLinkCase(tcInput linkTestCaseInput) error {
|
|
var (
|
|
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
|
|
overridesAddrs = make(map[common.Address]struct{})
|
|
overrideAddrs = make(map[rune]common.Address)
|
|
)
|
|
// generate deterministic addresses for the override set.
|
|
rng := rand.New(rand.NewSource(42))
|
|
for contract := range tcInput.overrides {
|
|
var addr common.Address
|
|
rng.Read(addr[:])
|
|
overrideAddrs[contract] = addr
|
|
overridesAddrs[addr] = struct{}{}
|
|
}
|
|
|
|
tc := makeLinkTestCase(tcInput.input, overrideAddrs)
|
|
allContracts := make(map[rune]struct{})
|
|
|
|
for contract, deps := range tcInput.input {
|
|
allContracts[contract] = struct{}{}
|
|
for _, dep := range deps {
|
|
allContracts[dep] = struct{}{}
|
|
}
|
|
}
|
|
|
|
var testAddrNonce uint64
|
|
mockDeploy := func(input []byte, deployer []byte) (common.Address, *types.Transaction, error) {
|
|
contractAddr := crypto.CreateAddress(testAddr, testAddrNonce)
|
|
testAddrNonce++
|
|
|
|
if len(deployer) >= 20 {
|
|
// assert that this contract only references libs that are known to be deployed or in the override set
|
|
for i := 0; i < len(deployer); i += 20 {
|
|
var dep common.Address
|
|
dep.SetBytes(deployer[i : i+20])
|
|
if _, ok := overridesAddrs[dep]; !ok {
|
|
return common.Address{}, nil, fmt.Errorf("reference to dependent contract that has not yet been deployed: %x\n", dep)
|
|
}
|
|
}
|
|
}
|
|
overridesAddrs[contractAddr] = struct{}{}
|
|
// we don't care about the txs themselves for the sake of the linking tests. so we can return nil for them in the mock deployer
|
|
return contractAddr, nil, nil
|
|
}
|
|
|
|
contracts := make(map[string]*MetaData)
|
|
overrides := make(map[string]common.Address)
|
|
|
|
for pattern, bin := range tc.contractCodes {
|
|
contracts[pattern] = &MetaData{ID: pattern, Bin: "0x" + bin}
|
|
}
|
|
for pattern, bin := range tc.libCodes {
|
|
contracts[pattern] = &MetaData{
|
|
Bin: "0x" + bin,
|
|
ID: pattern,
|
|
}
|
|
}
|
|
|
|
contractsList := linkDeps(contracts)
|
|
|
|
for pattern, override := range tc.overrides {
|
|
overrides[pattern] = override
|
|
}
|
|
|
|
deployParams := &DeploymentParams{
|
|
Contracts: contractsList,
|
|
Overrides: overrides,
|
|
}
|
|
res, err := LinkAndDeploy(deployParams, mockDeploy)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(res.Txs) != len(tcInput.expectDeployed) {
|
|
return fmt.Errorf("got %d deployed contracts. expected %d.\n", len(res.Addresses), len(tcInput.expectDeployed))
|
|
}
|
|
for contract := range tcInput.expectDeployed {
|
|
pattern := crypto.Keccak256Hash([]byte(string(contract))).String()[2:36]
|
|
if _, ok := res.Addresses[pattern]; !ok {
|
|
return fmt.Errorf("expected contract %s was not deployed\n", string(contract))
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestContractLinking(t *testing.T) {
|
|
for i, tc := range []linkTestCaseInput{
|
|
// test simple contract without any dependencies or overrides
|
|
{
|
|
map[rune][]rune{
|
|
'a': {}},
|
|
map[rune]struct{}{},
|
|
map[rune]struct{}{
|
|
'a': {}},
|
|
},
|
|
// test deployment of a contract that depends on somes libraries.
|
|
{
|
|
map[rune][]rune{
|
|
'a': {'b', 'c', 'd', 'e'}},
|
|
map[rune]struct{}{},
|
|
map[rune]struct{}{
|
|
'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}},
|
|
},
|
|
// test deployment of a contract that depends on some libraries,
|
|
// one of which has its own library dependencies.
|
|
{
|
|
map[rune][]rune{
|
|
'a': {'b', 'c', 'd', 'e'},
|
|
'e': {'f', 'g', 'h', 'i'}},
|
|
map[rune]struct{}{},
|
|
map[rune]struct{}{
|
|
'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {}, 'i': {}},
|
|
},
|
|
// test single contract only without deps
|
|
{
|
|
map[rune][]rune{
|
|
'a': {}},
|
|
map[rune]struct{}{},
|
|
map[rune]struct{}{
|
|
'a': {},
|
|
},
|
|
},
|
|
// test that libraries at different levels of the tree can share deps,
|
|
// and that these shared deps will only be deployed once.
|
|
{
|
|
map[rune][]rune{
|
|
'a': {'b', 'c', 'd', 'e'},
|
|
'e': {'f', 'g', 'h', 'i', 'm'},
|
|
'i': {'j', 'k', 'l', 'm'}},
|
|
map[rune]struct{}{},
|
|
map[rune]struct{}{
|
|
'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {}, 'i': {}, 'j': {}, 'k': {}, 'l': {}, 'm': {},
|
|
},
|
|
},
|
|
// test two contracts can be deployed which don't share deps
|
|
linkTestCaseInput{
|
|
map[rune][]rune{
|
|
'a': {'b', 'c', 'd', 'e'},
|
|
'f': {'g', 'h', 'i', 'j'}},
|
|
map[rune]struct{}{},
|
|
map[rune]struct{}{
|
|
'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {}, 'i': {}, 'j': {},
|
|
},
|
|
},
|
|
// test two contracts can be deployed which share deps
|
|
linkTestCaseInput{
|
|
map[rune][]rune{
|
|
'a': {'b', 'c', 'd', 'e'},
|
|
'f': {'g', 'c', 'd', 'h'}},
|
|
map[rune]struct{}{},
|
|
map[rune]struct{}{
|
|
'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {},
|
|
},
|
|
},
|
|
// test one contract with overrides for all lib deps
|
|
linkTestCaseInput{
|
|
map[rune][]rune{
|
|
'a': {'b', 'c', 'd', 'e'}},
|
|
map[rune]struct{}{'b': {}, 'c': {}, 'd': {}, 'e': {}},
|
|
map[rune]struct{}{
|
|
'a': {}},
|
|
},
|
|
// test one contract with overrides for some lib deps
|
|
linkTestCaseInput{
|
|
map[rune][]rune{
|
|
'a': {'b', 'c'}},
|
|
map[rune]struct{}{'b': {}, 'c': {}},
|
|
map[rune]struct{}{
|
|
'a': {}},
|
|
},
|
|
// test deployment of a contract with overrides
|
|
linkTestCaseInput{
|
|
map[rune][]rune{
|
|
'a': {}},
|
|
map[rune]struct{}{'a': {}},
|
|
map[rune]struct{}{},
|
|
},
|
|
// two contracts ('a' and 'f') share some dependencies. contract 'a' is marked as an override. expect that any of
|
|
// its depdencies that aren't shared with 'f' are not deployed.
|
|
linkTestCaseInput{map[rune][]rune{
|
|
'a': {'b', 'c', 'd', 'e'},
|
|
'f': {'g', 'c', 'd', 'h'}},
|
|
map[rune]struct{}{'a': {}},
|
|
map[rune]struct{}{
|
|
'f': {}, 'g': {}, 'c': {}, 'd': {}, 'h': {}},
|
|
},
|
|
// test nested libraries that share deps at different levels of the tree... with override.
|
|
// same condition as above test: no sub-dependencies of
|
|
{
|
|
map[rune][]rune{
|
|
'a': {'b', 'c', 'd', 'e'},
|
|
'e': {'f', 'g', 'h', 'i', 'm'},
|
|
'i': {'j', 'k', 'l', 'm'},
|
|
'l': {'n', 'o', 'p'}},
|
|
map[rune]struct{}{
|
|
'i': {},
|
|
},
|
|
map[rune]struct{}{
|
|
'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {}, 'm': {}},
|
|
},
|
|
} {
|
|
if err := testLinkCase(tc); err != nil {
|
|
t.Fatalf("test case %d failed: %v", i, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func parseLibraryDeps(unlinkedCode string) (res []string) {
|
|
reMatchSpecificPattern, err := regexp.Compile(`__\$([a-f0-9]+)\$__`)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
for _, match := range reMatchSpecificPattern.FindAllStringSubmatch(unlinkedCode, -1) {
|
|
res = append(res, match[1])
|
|
}
|
|
return res
|
|
}
|