Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion ethartifact/ethartifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,54 @@ type RawArtifact struct {
DeployedBytecode string `json:"deployedBytecode"`
}

type FoundryRawArtifact struct {
ABI json.RawMessage `json:"abi"`
Bytecode struct {
Object string `json:"object"`
} `json:"bytecode"`
DeployedBytecode struct {
Object string `json:"object"`
} `json:"deployedBytecode"`
Metadata struct {
Settings struct {
CompilationTarget map[string]string `json:"compilationTarget"`
} `json:"settings"`
} `json:"metadata"`
}

func ParseFoundryArtifactFile(path string) (FoundryRawArtifact, error) {
filedata, err := os.ReadFile(path)
if err != nil {
return FoundryRawArtifact{}, err
}

var artifact FoundryRawArtifact
err = json.Unmarshal(filedata, &artifact)
if err != nil {
return FoundryRawArtifact{}, err
}

return artifact, nil
}

func (f FoundryRawArtifact) ToRawArtifact() (RawArtifact, error) {
// Contract name is the only value in the compilation target map
if len(f.Metadata.Settings.CompilationTarget) != 1 {
return RawArtifact{}, fmt.Errorf("expected exactly one compilation target, got %d", len(f.Metadata.Settings.CompilationTarget))
}
var contractName string
for _, v := range f.Metadata.Settings.CompilationTarget {
contractName = v
}

return RawArtifact{
ContractName: contractName,
ABI: f.ABI,
Bytecode: f.Bytecode.Object,
DeployedBytecode: f.DeployedBytecode.Object,
}, nil
}

func ParseArtifactFile(path string) (RawArtifact, error) {
filedata, err := os.ReadFile(path)
if err != nil {
Expand All @@ -79,7 +127,13 @@ func ParseArtifactFile(path string) (RawArtifact, error) {
var artifact RawArtifact
err = json.Unmarshal(filedata, &artifact)
if err != nil {
return RawArtifact{}, err
// Try parsing as foundry artifact
foundryArtifact, foundryErr := ParseFoundryArtifactFile(path)
if foundryErr != nil {
// Return the original error
return RawArtifact{}, err
}
return foundryArtifact.ToRawArtifact()
}

return artifact, nil
Expand Down
83 changes: 83 additions & 0 deletions ethartifact/ethartifact_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package ethartifact

import (
"os"
"strings"
"testing"
)

func TestParseArtifactFile_Hardhat(t *testing.T) {
hardhatPath := "testdata/hardhat_artifact.json"
if _, err := os.Stat(hardhatPath); err != nil {
t.Skipf("Hardhat artifact file not found: %v", err)
}

artifact, err := ParseArtifactFile(hardhatPath)
if err != nil {
t.Fatalf("ParseArtifactFile failed for Hardhat artifact: %v", err)
}

// Check contract name
if artifact.ContractName != "ERC20" {
t.Errorf("Expected contract name 'ERC20', got '%s'", artifact.ContractName)
}

// Check ABI is not empty
if len(artifact.ABI) == 0 {
t.Error("Expected ABI to be parsed, but it's empty")
}

// Check bytecode is not empty and has 0x prefix
if len(artifact.Bytecode) == 0 {
t.Error("Expected bytecode to be parsed, but it's empty")
}
if !strings.HasPrefix(artifact.Bytecode, "0x") {
t.Error("Expected bytecode to start with '0x'")
}

// Check deployed bytecode is not empty and has 0x prefix
if len(artifact.DeployedBytecode) == 0 {
t.Error("Expected deployed bytecode to be parsed, but it's empty")
}
if !strings.HasPrefix(artifact.DeployedBytecode, "0x") {
t.Error("Expected deployed bytecode to start with '0x'")
}
}

func TestParseArtifactFile_Foundry(t *testing.T) {
foundryPath := "testdata/foundry_artifact.json"
if _, err := os.Stat(foundryPath); err != nil {
t.Skipf("Foundry artifact file not found: %v", err)
}

artifact, err := ParseArtifactFile(foundryPath)
if err != nil {
t.Fatalf("ParseArtifactFile failed for Foundry artifact: %v", err)
}

// Check contract name
if artifact.ContractName != "ValueForwarder" {
t.Errorf("Expected contract name 'ValueForwarder', got '%s'", artifact.ContractName)
}

// Check ABI is not empty
if len(artifact.ABI) == 0 {
t.Error("Expected ABI to be parsed, but it's empty")
}

// Check bytecode is not empty and has 0x prefix
if len(artifact.Bytecode) == 0 {
t.Error("Expected bytecode to be parsed, but it's empty")
}
if !strings.HasPrefix(artifact.Bytecode, "0x") {
t.Error("Expected bytecode to start with '0x'")
}

// Check deployed bytecode is not empty and has 0x prefix
if len(artifact.DeployedBytecode) == 0 {
t.Error("Expected deployed bytecode to be parsed, but it's empty")
}
if !strings.HasPrefix(artifact.DeployedBytecode, "0x") {
t.Error("Expected deployed bytecode to start with '0x'")
}
}
76 changes: 76 additions & 0 deletions ethartifact/testdata/foundry_artifact.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{
"abi": [
{
"type": "function",
"name": "forwardValue",
"inputs": [
{ "name": "to", "type": "address", "internalType": "address" },
{ "name": "value", "type": "uint256", "internalType": "uint256" }
],
"outputs": [],
"stateMutability": "payable"
}
],
"bytecode": {
"object": "0x6080806040523460155761014f908161001b8239f35b600080fdfe6080604052600436101561001257600080fd5b60003560e01c6398f850f11461002757600080fd5b6040366003190112610114576004356001600160a01b038116810361011457600080808093602435905af13d1561010f573d67ffffffffffffffff81116100f95760405190601f8101601f19908116603f0116820167ffffffffffffffff8111838210176100f9576040528152600060203d92013e5b156100a457005b60405162461bcd60e51b815260206004820152602760248201527f56616c7565466f727761726465723a204661696c656420746f20666f72776172604482015266642076616c756560c81b6064820152608490fd5b634e487b7160e01b600052604160045260246000fd5b61009d565b600080fdfea26469706673582212202706a11f313e042ccf1cd42b40b5f01308364a2290592fe4e14dce33175da1bf64736f6c634300081c0033",
"sourceMap": "151:216:42:-:0;;;;;;;;;;;;;;;;;",
"linkReferences": {}
},
"deployedBytecode": {
"object": "0x6080604052600436101561001257600080fd5b60003560e01c6398f850f11461002757600080fd5b6040366003190112610114576004356001600160a01b038116810361011457600080808093602435905af13d1561010f573d67ffffffffffffffff81116100f95760405190601f8101601f19908116603f0116820167ffffffffffffffff8111838210176100f9576040528152600060203d92013e5b156100a457005b60405162461bcd60e51b815260206004820152602760248201527f56616c7565466f727761726465723a204661696c656420746f20666f72776172604482015266642076616c756560c81b6064820152608490fd5b634e487b7160e01b600052604160045260246000fd5b61009d565b600080fdfea26469706673582212202706a11f313e042ccf1cd42b40b5f01308364a2290592fe4e14dce33175da1bf64736f6c634300081c0033",
"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;;;;;;;;;;;;;;;;;;;;;;;;;",
"linkReferences": {}
},
"methodIdentifiers": { "forwardValue(address,uint256)": "98f850f1" },
"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}",
"metadata": {
"compiler": { "version": "0.8.28+commit.7893614a" },
"language": "Solidity",
"output": {
"abi": [
{
"inputs": [
{ "internalType": "address", "name": "to", "type": "address" },
{ "internalType": "uint256", "name": "value", "type": "uint256" }
],
"stateMutability": "payable",
"type": "function",
"name": "forwardValue"
}
],
"devdoc": { "kind": "dev", "methods": {}, "version": 1 },
"userdoc": { "kind": "user", "methods": {}, "version": 1 }
},
"settings": {
"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/"
],
"optimizer": { "enabled": true, "runs": 200 },
"metadata": { "bytecodeHash": "ipfs" },
"compilationTarget": {
"test/mocks/ValueForwarder.sol": "ValueForwarder"
},
"evmVersion": "paris",
"libraries": {},
"viaIR": true
},
"sources": {
"test/mocks/ValueForwarder.sol": {
"keccak256": "0x2c86b5ca1001b169892de7cd3b069c5d29223e54a8701350cae39502cef5d8a3",
"urls": [
"bzz-raw://c880a96377be176d3385a44653ed62e1678b88da9141582d8fbb5f0a6165bfe2",
"dweb:/ipfs/QmXtDzW2wewKnPp56ntZtCM7AatTpi5M3YWZPvMAmWM15E"
],
"license": "Apache-2.0"
}
},
"version": 1
},
"id": 42
}

Loading
Loading