Skip to content

Commit 24eab68

Browse files
committed
Support parsing foundry artifacts
1 parent b2e1971 commit 24eab68

File tree

4 files changed

+457
-1
lines changed

4 files changed

+457
-1
lines changed

ethartifact/ethartifact.go

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,54 @@ type RawArtifact struct {
7070
DeployedBytecode string `json:"deployedBytecode"`
7171
}
7272

73+
type FoundryRawArtifact struct {
74+
ABI json.RawMessage `json:"abi"`
75+
Bytecode struct {
76+
Object string `json:"object"`
77+
} `json:"bytecode"`
78+
DeployedBytecode struct {
79+
Object string `json:"object"`
80+
} `json:"deployedBytecode"`
81+
Metadata struct {
82+
Settings struct {
83+
CompilationTarget map[string]string `json:"compilationTarget"`
84+
} `json:"settings"`
85+
} `json:"metadata"`
86+
}
87+
88+
func ParseFoundryArtifactFile(path string) (FoundryRawArtifact, error) {
89+
filedata, err := os.ReadFile(path)
90+
if err != nil {
91+
return FoundryRawArtifact{}, err
92+
}
93+
94+
var artifact FoundryRawArtifact
95+
err = json.Unmarshal(filedata, &artifact)
96+
if err != nil {
97+
return FoundryRawArtifact{}, err
98+
}
99+
100+
return artifact, nil
101+
}
102+
103+
func convertFoundryArtifactToRawArtifact(artifact FoundryRawArtifact) (RawArtifact, error) {
104+
// Contract name is the only value in the compilation target map
105+
if len(artifact.Metadata.Settings.CompilationTarget) != 1 {
106+
return RawArtifact{}, fmt.Errorf("expected exactly one compilation target, got %d", len(artifact.Metadata.Settings.CompilationTarget))
107+
}
108+
var contractName string
109+
for _, v := range artifact.Metadata.Settings.CompilationTarget {
110+
contractName = v
111+
}
112+
113+
return RawArtifact{
114+
ContractName: contractName,
115+
ABI: artifact.ABI,
116+
Bytecode: artifact.Bytecode.Object,
117+
DeployedBytecode: artifact.DeployedBytecode.Object,
118+
}, nil
119+
}
120+
73121
func ParseArtifactFile(path string) (RawArtifact, error) {
74122
filedata, err := os.ReadFile(path)
75123
if err != nil {
@@ -79,7 +127,13 @@ func ParseArtifactFile(path string) (RawArtifact, error) {
79127
var artifact RawArtifact
80128
err = json.Unmarshal(filedata, &artifact)
81129
if err != nil {
82-
return RawArtifact{}, err
130+
// Try parsing as foundry artifact
131+
artifact, foundryErr := ParseFoundryArtifactFile(path)
132+
if foundryErr != nil {
133+
// Return the original error
134+
return RawArtifact{}, err
135+
}
136+
return convertFoundryArtifactToRawArtifact(artifact)
83137
}
84138

85139
return artifact, nil

ethartifact/ethartifact_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package ethartifact
2+
3+
import (
4+
"os"
5+
"strings"
6+
"testing"
7+
)
8+
9+
func TestParseArtifactFile_Hardhat(t *testing.T) {
10+
hardhatPath := "testdata/hardhat_artifact.json"
11+
if _, err := os.Stat(hardhatPath); err != nil {
12+
t.Skipf("Hardhat artifact file not found: %v", err)
13+
}
14+
15+
artifact, err := ParseArtifactFile(hardhatPath)
16+
if err != nil {
17+
t.Fatalf("ParseArtifactFile failed for Hardhat artifact: %v", err)
18+
}
19+
20+
// Check contract name
21+
if artifact.ContractName != "ERC20" {
22+
t.Errorf("Expected contract name 'ERC20', got '%s'", artifact.ContractName)
23+
}
24+
25+
// Check ABI is not empty
26+
if len(artifact.ABI) == 0 {
27+
t.Error("Expected ABI to be parsed, but it's empty")
28+
}
29+
30+
// Check bytecode is not empty and has 0x prefix
31+
if len(artifact.Bytecode) == 0 {
32+
t.Error("Expected bytecode to be parsed, but it's empty")
33+
}
34+
if !strings.HasPrefix(artifact.Bytecode, "0x") {
35+
t.Error("Expected bytecode to start with '0x'")
36+
}
37+
38+
// Check deployed bytecode is not empty and has 0x prefix
39+
if len(artifact.DeployedBytecode) == 0 {
40+
t.Error("Expected deployed bytecode to be parsed, but it's empty")
41+
}
42+
if !strings.HasPrefix(artifact.DeployedBytecode, "0x") {
43+
t.Error("Expected deployed bytecode to start with '0x'")
44+
}
45+
}
46+
47+
func TestParseArtifactFile_Foundry(t *testing.T) {
48+
foundryPath := "testdata/foundry_artifact.json"
49+
if _, err := os.Stat(foundryPath); err != nil {
50+
t.Skipf("Foundry artifact file not found: %v", err)
51+
}
52+
53+
artifact, err := ParseArtifactFile(foundryPath)
54+
if err != nil {
55+
t.Fatalf("ParseArtifactFile failed for Foundry artifact: %v", err)
56+
}
57+
58+
// Check contract name
59+
if artifact.ContractName != "ValueForwarder" {
60+
t.Errorf("Expected contract name 'ValueForwarder', got '%s'", artifact.ContractName)
61+
}
62+
63+
// Check ABI is not empty
64+
if len(artifact.ABI) == 0 {
65+
t.Error("Expected ABI to be parsed, but it's empty")
66+
}
67+
68+
// Check bytecode is not empty and has 0x prefix
69+
if len(artifact.Bytecode) == 0 {
70+
t.Error("Expected bytecode to be parsed, but it's empty")
71+
}
72+
if !strings.HasPrefix(artifact.Bytecode, "0x") {
73+
t.Error("Expected bytecode to start with '0x'")
74+
}
75+
76+
// Check deployed bytecode is not empty and has 0x prefix
77+
if len(artifact.DeployedBytecode) == 0 {
78+
t.Error("Expected deployed bytecode to be parsed, but it's empty")
79+
}
80+
if !strings.HasPrefix(artifact.DeployedBytecode, "0x") {
81+
t.Error("Expected deployed bytecode to start with '0x'")
82+
}
83+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
{
2+
"abi": [
3+
{
4+
"type": "function",
5+
"name": "forwardValue",
6+
"inputs": [
7+
{ "name": "to", "type": "address", "internalType": "address" },
8+
{ "name": "value", "type": "uint256", "internalType": "uint256" }
9+
],
10+
"outputs": [],
11+
"stateMutability": "payable"
12+
}
13+
],
14+
"bytecode": {
15+
"object": "0x6080806040523460155761014f908161001b8239f35b600080fdfe6080604052600436101561001257600080fd5b60003560e01c6398f850f11461002757600080fd5b6040366003190112610114576004356001600160a01b038116810361011457600080808093602435905af13d1561010f573d67ffffffffffffffff81116100f95760405190601f8101601f19908116603f0116820167ffffffffffffffff8111838210176100f9576040528152600060203d92013e5b156100a457005b60405162461bcd60e51b815260206004820152602760248201527f56616c7565466f727761726465723a204661696c656420746f20666f72776172604482015266642076616c756560c81b6064820152608490fd5b634e487b7160e01b600052604160045260246000fd5b61009d565b600080fdfea26469706673582212202706a11f313e042ccf1cd42b40b5f01308364a2290592fe4e14dce33175da1bf64736f6c634300081c0033",
16+
"sourceMap": "151:216:42:-:0;;;;;;;;;;;;;;;;;",
17+
"linkReferences": {}
18+
},
19+
"deployedBytecode": {
20+
"object": "0x6080604052600436101561001257600080fd5b60003560e01c6398f850f11461002757600080fd5b6040366003190112610114576004356001600160a01b038116810361011457600080808093602435905af13d1561010f573d67ffffffffffffffff81116100f95760405190601f8101601f19908116603f0116820167ffffffffffffffff8111838210176100f9576040528152600060203d92013e5b156100a457005b60405162461bcd60e51b815260206004820152602760248201527f56616c7565466f727761726465723a204661696c656420746f20666f72776172604482015266642076616c756560c81b6064820152608490fd5b634e487b7160e01b600052604160045260246000fd5b61009d565b600080fdfea26469706673582212202706a11f313e042ccf1cd42b40b5f01308364a2290592fe4e14dce33175da1bf64736f6c634300081c0033",
21+
"sourceMap": "151:216:42:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;151:216:42;;;;;;-1:-1:-1;;;;;151:216:42;;;;;;;;;;;;;270:25;;;151:216;;;;;;;;;;;;;;;;-1:-1:-1;;151:216:42;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;151:216:42;;;;;;;;;;;;;;;;;-1:-1:-1;;;151:216:42;;;;;;;;;;;;;;;;;;;;;;;;;",
22+
"linkReferences": {}
23+
},
24+
"methodIdentifiers": { "forwardValue(address,uint256)": "98f850f1" },
25+
"rawMetadata": "{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"forwardValue\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"}],\"devdoc\":{\"author\":\"Michael Standen\",\"kind\":\"dev\",\"methods\":{},\"title\":\"ValueForwarder\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"Forwarder for value\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"test/mocks/ValueForwarder.sol\":\"ValueForwarder\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/\",\":erc2470-libs/=lib/erc2470-libs/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\"],\"viaIR\":true},\"sources\":{\"test/mocks/ValueForwarder.sol\":{\"keccak256\":\"0x2c86b5ca1001b169892de7cd3b069c5d29223e54a8701350cae39502cef5d8a3\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://c880a96377be176d3385a44653ed62e1678b88da9141582d8fbb5f0a6165bfe2\",\"dweb:/ipfs/QmXtDzW2wewKnPp56ntZtCM7AatTpi5M3YWZPvMAmWM15E\"]}},\"version\":1}",
26+
"metadata": {
27+
"compiler": { "version": "0.8.28+commit.7893614a" },
28+
"language": "Solidity",
29+
"output": {
30+
"abi": [
31+
{
32+
"inputs": [
33+
{ "internalType": "address", "name": "to", "type": "address" },
34+
{ "internalType": "uint256", "name": "value", "type": "uint256" }
35+
],
36+
"stateMutability": "payable",
37+
"type": "function",
38+
"name": "forwardValue"
39+
}
40+
],
41+
"devdoc": { "kind": "dev", "methods": {}, "version": 1 },
42+
"userdoc": { "kind": "user", "methods": {}, "version": 1 }
43+
},
44+
"settings": {
45+
"remappings": [
46+
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
47+
"erc2470-libs/=lib/erc2470-libs/",
48+
"erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
49+
"forge-std/=lib/forge-std/src/",
50+
"halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/",
51+
"openzeppelin-contracts/=lib/openzeppelin-contracts/"
52+
],
53+
"optimizer": { "enabled": true, "runs": 200 },
54+
"metadata": { "bytecodeHash": "ipfs" },
55+
"compilationTarget": {
56+
"test/mocks/ValueForwarder.sol": "ValueForwarder"
57+
},
58+
"evmVersion": "paris",
59+
"libraries": {},
60+
"viaIR": true
61+
},
62+
"sources": {
63+
"test/mocks/ValueForwarder.sol": {
64+
"keccak256": "0x2c86b5ca1001b169892de7cd3b069c5d29223e54a8701350cae39502cef5d8a3",
65+
"urls": [
66+
"bzz-raw://c880a96377be176d3385a44653ed62e1678b88da9141582d8fbb5f0a6165bfe2",
67+
"dweb:/ipfs/QmXtDzW2wewKnPp56ntZtCM7AatTpi5M3YWZPvMAmWM15E"
68+
],
69+
"license": "Apache-2.0"
70+
}
71+
},
72+
"version": 1
73+
},
74+
"id": 42
75+
}
76+

0 commit comments

Comments
 (0)