diff --git a/abi/constructor.go b/abi/constructor.go index a0568c79..43b9f3a8 100644 --- a/abi/constructor.go +++ b/abi/constructor.go @@ -2,7 +2,6 @@ package abi import ( "fmt" - "github.com/unpackdev/solgo/ir" ) diff --git a/abi/contract.go b/abi/contract.go index 04176b36..ddb95307 100644 --- a/abi/contract.go +++ b/abi/contract.go @@ -5,6 +5,7 @@ import ( ast_pb "github.com/unpackdev/protos/dist/go/ast" "github.com/unpackdev/solgo/ast" "github.com/unpackdev/solgo/ir" + "strings" ) // Contract represents a collection of Ethereum contract methods. @@ -128,10 +129,18 @@ func (b *Builder) buildMethodIO(method MethodIO, typeDescr *ast.TypeDescription) method.Inputs = append(method.Inputs, inputList...) method.Outputs = append(method.Outputs, outputList...) case "contract": - method.Type = "address" + if strings.ContainsAny(typeDescr.GetString(), "[]") { + method.Type = "address[]" + } else { + method.Type = "address" + } method.InternalType = typeDescr.GetString() case "enum": - method.Type = "uint8" + if strings.ContainsAny(typeDescr.GetString(), "[]") { + method.Type = "uint8[]" + } else { + method.Type = "uint8" + } method.InternalType = typeDescr.GetString() case "struct": return b.resolver.ResolveStructType(typeDescr) diff --git a/accounts/account.go b/accounts/account.go index a7f2d2b3..1bf44aad 100644 --- a/accounts/account.go +++ b/accounts/account.go @@ -28,7 +28,7 @@ const ( // It embeds ClientPool for network interactions and KeyStore for account management. // It also includes fields for account details, network information, and additional tags. type Account struct { - client *clients.Client `json:"-" yaml:"-"` // Client for Ethereum client interactions + client *clients.Client *keystore.KeyStore `json:"-" yaml:"-"` // KeyStore for managing account keys Address common.Address `json:"address" yaml:"address"` // Ethereum address of the account Type utils.AccountType `json:"type" yaml:"type"` // Account type @@ -79,7 +79,7 @@ func (a *Account) GetClient() *clients.Client { // It does not affect the real balance on the Ethereum network. func (a *Account) SetAccountBalance(ctx context.Context, amount *big.Int) error { amountHex := common.Bytes2Hex(amount.Bytes()) - return a.client.GetRpcClient().Call(nil, "anvil_setBalance", a.GetAddress(), amountHex) + return a.client.GetRpcClient().CallContext(ctx, nil, "anvil_setBalance", a.GetAddress(), amountHex) } // Balance retrieves the account's balance from the Ethereum network at a specified block number. @@ -236,6 +236,8 @@ func LoadAccount(path string) (*Account, error) { if err != nil { return nil, err } + account.Address = account.KeystoreAccount.Address + account.Type = utils.KeystoreAccountType return &account, nil } diff --git a/accounts/options.go b/accounts/options.go index 3c04ff0a..1afa70f4 100644 --- a/accounts/options.go +++ b/accounts/options.go @@ -6,9 +6,9 @@ import "github.com/unpackdev/solgo/utils" type Options struct { // KeystorePath specifies the file system path to the directory where the keystore files are stored. // The keystore is used to securely store the private keys of Ethereum accounts. - KeystorePath string `json:"keystore_path" yaml:"keystore_path"` + KeystorePath string `json:"keystore_path" yaml:"keystorePath"` // SupportedNetworks lists the Ethereum based networks that the account manager will interact with. // Each network has a corresponding keystore and set of account configurations. - SupportedNetworks []utils.Network `json:"supported_networks" yaml:"supported_networks"` + SupportedNetworks []utils.Network `json:"supported_networks" yaml:"networks"` } diff --git a/ast/helpers.go b/ast/helpers.go index 40996e19..a8dc5e32 100644 --- a/ast/helpers.go +++ b/ast/helpers.go @@ -126,8 +126,30 @@ func normalizeTypeDescription(typeName string) (string, string) { } } +// builtInTypePrefixes is a set of known built-in type prefixes. +var builtInTypePrefixes = []string{ + "uint", + "int", + "bool", + "bytes", + "string", + "address", + "addresspayable", + "tuple", +} + +// isBuiltInType checks if a type name is a built-in type by its prefix. +func isBuiltInType(typeName string) bool { + for _, prefix := range builtInTypePrefixes { + if strings.HasPrefix(typeName, prefix) { + return true + } + } + return false +} + // normalizeTypeDescriptionWithStatus normalizes type names and generates corresponding type identifiers. -// Returns true if normalization occured. +// Returns true if normalization occurred. func normalizeTypeDescriptionWithStatus(typeName string) (string, string, bool) { isArray := strings.Contains(typeName, "[") && strings.Contains(typeName, "]") isSlice := strings.HasSuffix(typeName, "[]") @@ -137,16 +159,25 @@ func normalizeTypeDescriptionWithStatus(typeName string) (string, string, bool) case isArray: numberPart := typeName[strings.Index(typeName, "[")+1 : strings.Index(typeName, "]")] typePart := typeName[:strings.Index(typeName, "[")] + if !isBuiltInType(typePart) { + return typeName, fmt.Sprintf("t_%s", typeName), false + } normalizedTypePart := normalizeTypeName(typePart) return normalizedTypePart + "[" + numberPart + "]", fmt.Sprintf("t_%s_array", normalizedTypePart), true case isSlice: typePart := typeName[:len(typeName)-2] + if !isBuiltInType(typePart) { + return typeName, fmt.Sprintf("t_%s", typeName), false + } normalizedTypePart := normalizeTypeName(typePart) return normalizedTypePart + "[]", fmt.Sprintf("t_%s_slice", normalizedTypePart), true case isPrefixSlice: typePart := typeName[2:] + if !isBuiltInType(typePart) { + return typeName, fmt.Sprintf("t_%s", typeName), false + } normalizedTypePart := normalizeTypeName(typePart) return "[]" + normalizedTypePart, fmt.Sprintf("t_%s_slice", normalizedTypePart), true diff --git a/ast/parameter.go b/ast/parameter.go index 6b431160..1c94c6d6 100644 --- a/ast/parameter.go +++ b/ast/parameter.go @@ -2,7 +2,6 @@ package ast import ( "github.com/goccy/go-json" - ast_pb "github.com/unpackdev/protos/dist/go/ast" "github.com/unpackdev/solgo/parser" ) diff --git a/ast/reference.go b/ast/reference.go index e6c2ed60..b0669464 100644 --- a/ast/reference.go +++ b/ast/reference.go @@ -52,7 +52,17 @@ func (r *Resolver) GetUnprocessedCount() int { // ResolveByNode attempts to resolve a node by its name and returns the resolved Node and its TypeDescription. // If the node cannot be found, it is added to the UnprocessedNodes map for future resolution. func (r *Resolver) ResolveByNode(node Node[NodeType], name string) (int64, *TypeDescription) { - rNode, rNodeType := r.resolveByNode(name, node) + isSlice := strings.HasSuffix(name, "[]") + isPrefixSlice := strings.HasPrefix(name, "[]") + cleanedName := name + + if isSlice { + cleanedName = strings.TrimSuffix(name, "[]") + } else if isPrefixSlice { + cleanedName = strings.TrimPrefix(name, "[]") + } + + rNode, rNodeType := r.resolveByNode(cleanedName, node) // Node could not be found in this moment, we are going to see if we can discover it in the // future at the end of whole parsing process. @@ -62,6 +72,12 @@ func (r *Resolver) ResolveByNode(node Node[NodeType], name string) (int64, *Type Name: name, Node: node, } + } else { + if isSlice && !strings.Contains(rNodeType.TypeString, "[]") { + rNodeType.TypeString = rNodeType.TypeString + "[]" + } else if isPrefixSlice && !strings.Contains(rNodeType.TypeString, "[]") { + rNodeType.TypeString = "[]" + rNodeType.TypeString + } } return rNode, rNodeType diff --git a/ast/type_name.go b/ast/type_name.go index 197b06b0..cb06167c 100644 --- a/ast/type_name.go +++ b/ast/type_name.go @@ -62,8 +62,10 @@ func (t *TypeName) WithParentNode(p Node[NodeType]) { // SetReferenceDescriptor sets the reference descriptions of the TypeName node. func (t *TypeName) SetReferenceDescriptor(refId int64, refDesc *TypeDescription) bool { - t.ReferencedDeclaration = refId - t.TypeDescription = refDesc + if t.TypeDescription == nil { + t.ReferencedDeclaration = refId + t.TypeDescription = refDesc + } // Lets update the parent node as well in case that type description is not set... /* parentNodeId := t.GetSrc().GetParentIndex() @@ -370,7 +372,6 @@ func (t *TypeName) parseTypeName(unit *SourceUnit[Node[ast_pb.SourceUnit]], pare Length: int64(pathCtx.GetStop().GetStop() - pathCtx.GetStart().GetStart() + 1), ParentIndex: t.GetId(), }, - NodeType: ast_pb.NodeType_IDENTIFIER_PATH, } @@ -386,7 +387,7 @@ func (t *TypeName) parseTypeName(unit *SourceUnit[Node[ast_pb.SourceUnit]], pare } if found { - t.TypeDescription = &TypeDescription{ + t.PathNode.TypeDescription = &TypeDescription{ TypeIdentifier: normalizedTypeIdentifier, TypeString: normalizedTypeName, } @@ -400,6 +401,13 @@ func (t *TypeName) parseTypeName(unit *SourceUnit[Node[ast_pb.SourceUnit]], pare } } + // Alright lets now figure out main type description as it can be different such as + // PathNode vs PathNode[] + if refId, refTypeDescription := t.GetResolver().ResolveByNode(t, t.Name); refTypeDescription != nil { + t.ReferencedDeclaration = refId + t.TypeDescription = refTypeDescription + } + } else if ctx.TypeName() != nil { t.generateTypeName(unit, ctx.TypeName(), t, t) } else { @@ -438,7 +446,6 @@ func (t *TypeName) parseTypeName(unit *SourceUnit[Node[ast_pb.SourceUnit]], pare t.TypeDescription = refTypeDescription } } - } } @@ -515,17 +522,39 @@ func (t *TypeName) parseIdentifierPath(unit *SourceUnit[Node[ast_pb.SourceUnit]] } if found { - t.TypeDescription = &TypeDescription{ + t.PathNode.TypeDescription = &TypeDescription{ TypeIdentifier: normalizedTypeIdentifier, TypeString: normalizedTypeName, } } else { - if refId, refTypeDescription := t.GetResolver().ResolveByNode(t, identifierCtx.GetText()); refTypeDescription != nil { + if refId, refTypeDescription := t.GetResolver().ResolveByNode(t.PathNode, identifierCtx.GetText()); refTypeDescription != nil { t.PathNode.ReferencedDeclaration = refId + t.PathNode.TypeDescription = refTypeDescription + } + } + + bNormalizedTypeName, bNormalizedTypeIdentifier, bFound := normalizeTypeDescriptionWithStatus( + identifierCtx.GetText(), + ) + + // Alright lets now figure out main type description as it can be different such as + // PathNode vs PathNode[] + if bFound { + t.TypeDescription = &TypeDescription{ + TypeIdentifier: bNormalizedTypeIdentifier, + TypeString: bNormalizedTypeName, + } + } else { + if refId, refTypeDescription := t.GetResolver().ResolveByNode(t, t.Name); refTypeDescription != nil { t.ReferencedDeclaration = refId t.TypeDescription = refTypeDescription } } + + /* if t.Id == 1787 { + fmt.Println("HERE I AM") + utils.DumpNodeWithExit(t) + }*/ } } @@ -705,6 +734,7 @@ func (t *TypeName) generateTypeName(sourceUnit *SourceUnit[Node[ast_pb.SourceUni } else if specificCtx.IdentifierPath() != nil { typeName.NodeType = ast_pb.NodeType_USER_DEFINED_PATH_NAME t.parseIdentifierPath(sourceUnit, parentNode.GetId(), specificCtx.IdentifierPath().(*parser.IdentifierPathContext)) + } else { normalizedTypeName, normalizedTypeIdentifier := normalizeTypeDescription( @@ -777,6 +807,7 @@ func (t *TypeName) Parse(unit *SourceUnit[Node[ast_pb.SourceUnit]], fnNode Node[ case *antlr.TerminalNodeImpl: continue default: + expression := NewExpression(t.ASTBuilder) if expr := expression.ParseInterface(unit, fnNode, t.GetId(), ctx.Expression()); expr != nil { t.Expression = expr diff --git a/bindings/token.go b/bindings/token.go index ca66ca48..3219d048 100644 --- a/bindings/token.go +++ b/bindings/token.go @@ -42,8 +42,8 @@ func NewToken(ctx context.Context, network utils.Network, manager *Manager, opts // Now lets register all the bindings with the manager for _, opt := range opts { - for _, network := range opt.Networks { - if _, err := manager.RegisterBinding(network, opt.NetworkID, opt.Type, opt.Address, opt.ABI); err != nil { + for _, oNetwork := range opt.Networks { + if _, err := manager.RegisterBinding(oNetwork, opt.NetworkID, opt.Type, opt.Address, opt.ABI); err != nil { return nil, err } } diff --git a/bindings/uniswap_v2.go b/bindings/uniswap_v2.go new file mode 100644 index 00000000..8b5d587e --- /dev/null +++ b/bindings/uniswap_v2.go @@ -0,0 +1,173 @@ +package bindings + +import ( + "context" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/unpackdev/solgo/utils" + "math/big" +) + +const ( + UniswapV2Factory BindingType = "UniswapV2Factory" + UniswapV2Pair BindingType = "UniswapV2Pair" + UniswapV2Router BindingType = "UniswapV2Router" +) + +type UniswapV2Reserves struct { + Reserve0 *big.Int + Reserve1 *big.Int + BlockTimestampLast uint32 +} + +// UniswapV2 encapsulates data and functions for interacting with a blockchain Uniswap V2 contracts. +type UniswapV2 struct { + *Manager + network utils.Network + ctx context.Context + opts []*BindOptions +} + +func NewUniswapV2(ctx context.Context, network utils.Network, manager *Manager, opts []*BindOptions) (*UniswapV2, error) { + if opts == nil { + opts = DefaultUniswapV2BindOptions() + } + + for _, opt := range opts { + if err := opt.Validate(); err != nil { + return nil, err + } + } + + for _, opt := range opts { + for _, oNetwork := range opt.Networks { + if _, err := manager.RegisterBinding(oNetwork, opt.NetworkID, opt.Type, opt.Address, opt.ABI); err != nil { + return nil, err + } + } + } + + return &UniswapV2{ + network: network, + Manager: manager, + ctx: ctx, + opts: opts, + }, nil +} + +func (u *UniswapV2) GetAddress(bindingType BindingType) (common.Address, error) { + for _, opt := range u.opts { + if opt.Type == bindingType { + return opt.Address, nil + } + } + + return common.Address{}, fmt.Errorf("binding not found for type %s", bindingType) +} + +func (u *UniswapV2) WETH(ctx context.Context) (common.Address, error) { + result, err := u.Manager.CallContractMethod(ctx, u.network, UniswapV2Router, utils.ZeroAddress, "WETH") + if err != nil { + return common.Address{}, fmt.Errorf("failed to get WETH address: %w", err) + } + + return result.(common.Address), nil +} + +func (u *UniswapV2) GetPair(ctx context.Context, tokenA, tokenB common.Address) (common.Address, error) { + result, err := u.Manager.CallContractMethod(ctx, u.network, UniswapV2Factory, utils.ZeroAddress, "getPair", tokenA, tokenB) + if err != nil { + return common.Address{}, fmt.Errorf("failed to get pair: %w", err) + } + + pairAddress, ok := result.(common.Address) + if !ok { + return common.Address{}, fmt.Errorf("failed to assert result as common.Address - pair address") + } + + return pairAddress, nil +} + +func (u *UniswapV2) GetToken0(ctx context.Context, pairAddr common.Address) (common.Address, error) { + result, err := u.Manager.CallContractMethod(ctx, u.network, UniswapV2Pair, pairAddr, "token0") + if err != nil { + return common.Address{}, fmt.Errorf("failed to get token0 of a pair: %w", err) + } + + pairAddress, ok := result.(common.Address) + if !ok { + return common.Address{}, fmt.Errorf("failed to assert result as common.Address - token0 address") + } + + return pairAddress, nil +} + +func (u *UniswapV2) GetToken1(ctx context.Context, pairAddr common.Address) (common.Address, error) { + result, err := u.Manager.CallContractMethod(ctx, u.network, UniswapV2Pair, pairAddr, "token1") + if err != nil { + return common.Address{}, fmt.Errorf("failed to get token1 of a pair: %w", err) + } + + pairAddress, ok := result.(common.Address) + if !ok { + return common.Address{}, fmt.Errorf("failed to assert result as common.Address - token1 address") + } + + return pairAddress, nil +} + +func (u *UniswapV2) GetReserves(ctx context.Context, pairAddr common.Address) (*UniswapV2Reserves, error) { + result, err := u.Manager.CallContractMethodUnpackMap(ctx, u.network, UniswapV2Pair, pairAddr, "getReserves") + if err != nil { + return nil, fmt.Errorf("failed to get pair reserves: %w", err) + } + + return &UniswapV2Reserves{ + Reserve0: result["_reserve0"].(*big.Int), + Reserve1: result["_reserve1"].(*big.Int), + BlockTimestampLast: result["_blockTimestampLast"].(uint32), + }, nil +} + +func (u *UniswapV2) GetAmountOut(ctx context.Context, amountIn *big.Int, reserveIn *big.Int, reserveOut *big.Int) (*big.Int, error) { + result, err := u.Manager.CallContractMethod(ctx, u.network, UniswapV2Router, utils.ZeroAddress, "getAmountOut", amountIn, reserveIn, reserveOut) + if err != nil { + return nil, fmt.Errorf("failed to get amounts out: %w", err) + } + + amountOut, ok := result.(*big.Int) + if !ok { + return nil, fmt.Errorf("failed to assert amount result as []*big.Int: %v", result) + } + + return amountOut, nil +} + +func DefaultUniswapV2BindOptions() []*BindOptions { + return []*BindOptions{ + { + Networks: []utils.Network{utils.Ethereum, utils.AnvilNetwork}, + NetworkID: utils.EthereumNetworkID, + Name: "UniswapV2: Router", + Type: UniswapV2Router, + Address: common.HexToAddress("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"), + ABI: `[{"inputs":[{"internalType":"address","name":"_factory","type":"address"},{"internalType":"address","name":"_WETH","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"WETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"amountADesired","type":"uint256"},{"internalType":"uint256","name":"amountBDesired","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidity","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountTokenDesired","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidityETH","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"reserveIn","type":"uint256"},{"internalType":"uint256","name":"reserveOut","type":"uint256"}],"name":"getAmountIn","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"reserveIn","type":"uint256"},{"internalType":"uint256","name":"reserveOut","type":"uint256"}],"name":"getAmountOut","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"}],"name":"getAmountsIn","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"}],"name":"getAmountsOut","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"reserveA","type":"uint256"},{"internalType":"uint256","name":"reserveB","type":"uint256"}],"name":"quote","outputs":[{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidity","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityETH","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityETHSupportingFeeOnTransferTokens","outputs":[{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityETHWithPermit","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityETHWithPermitSupportingFeeOnTransferTokens","outputs":[{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityWithPermit","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapETHForExactTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactETHForTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactETHForTokensSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForETH","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForETHSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForTokensSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapTokensForExactETH","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapTokensForExactTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]`, + }, + { + Networks: []utils.Network{utils.Ethereum, utils.AnvilNetwork}, + NetworkID: utils.EthereumNetworkID, + Name: "UniswapV2: Factory Contract", + Type: UniswapV2Factory, + Address: common.HexToAddress("0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"), + ABI: `[{"inputs":[{"internalType":"address","name":"_feeToSetter","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token0","type":"address"},{"indexed":true,"internalType":"address","name":"token1","type":"address"},{"indexed":false,"internalType":"address","name":"pair","type":"address"},{"indexed":false,"internalType":"uint256","name":"","type":"uint256"}],"name":"PairCreated","type":"event"},{"constant":true,"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"allPairs","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"allPairsLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"}],"name":"createPair","outputs":[{"internalType":"address","name":"pair","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"feeTo","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"feeToSetter","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"getPair","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_feeTo","type":"address"}],"name":"setFeeTo","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_feeToSetter","type":"address"}],"name":"setFeeToSetter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]`, + }, + { + Networks: []utils.Network{utils.Ethereum, utils.AnvilNetwork}, + NetworkID: utils.EthereumNetworkID, + Name: "UniswapV2: Pair", + Type: UniswapV2Pair, + Address: common.HexToAddress("0x3356c9a8f40f8e9c1d192a4347a76d18243fabc5"), + ABI: `[{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0In","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1In","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount0Out","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1Out","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Swap","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint112","name":"reserve0","type":"uint112"},{"indexed":false,"internalType":"uint112","name":"reserve1","type":"uint112"}],"name":"Sync","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"constant":true,"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MINIMUM_LIQUIDITY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"burn","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getReserves","outputs":[{"internalType":"uint112","name":"_reserve0","type":"uint112"},{"internalType":"uint112","name":"_reserve1","type":"uint112"},{"internalType":"uint32","name":"_blockTimestampLast","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_token0","type":"address"},{"internalType":"address","name":"_token1","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"kLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"liquidity","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"price0CumulativeLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"price1CumulativeLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"skim","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"amount0Out","type":"uint256"},{"internalType":"uint256","name":"amount1Out","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"swap","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"sync","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"token0","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"token1","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]`, + }, + } +} diff --git a/bytecode/log.go b/bytecode/log.go index 63134665..eaa0fd29 100644 --- a/bytecode/log.go +++ b/bytecode/log.go @@ -56,19 +56,23 @@ func DecodeLogFromAbi(log *types.Log, abiData []byte) (*Log, error) { return nil, fmt.Errorf("failed to unpack inputs into map: %s", err) } - decodedTopics := make([]Topic, len(log.Topics)) - for i, topic := range log.Topics { - if i == 0 { - continue + // Identify and decode indexed inputs + indexedInputs := make([]abi.Argument, 0) + for _, input := range event.Inputs { + if input.Indexed { + indexedInputs = append(indexedInputs, input) } + } - decodedTopic, err := decodeTopic(topic, event.Inputs[i-1]) + decodedTopics := make([]Topic, len(indexedInputs)) + for i, indexedInput := range indexedInputs { + decodedTopic, err := decodeTopic(log.Topics[i+1], indexedInput) if err != nil { return nil, fmt.Errorf("failed to decode topic: %s", err) } decodedTopics[i] = Topic{ - Name: event.Inputs[i-1].Name, + Name: indexedInput.Name, Value: decodedTopic, } } @@ -87,7 +91,7 @@ func DecodeLogFromAbi(log *types.Log, abiData []byte) (*Log, error) { Name: event.Name, Type: utils.LogEventType(strings.ToLower(event.Name)), Data: data, - Topics: decodedTopics[1:], // Exclude the first topic (event signature) + Topics: decodedTopics, // Exclude the first topic (event signature) } return toReturn, nil diff --git a/bytecode/transaction.go b/bytecode/transaction.go index c64624db..538277b6 100644 --- a/bytecode/transaction.go +++ b/bytecode/transaction.go @@ -63,7 +63,12 @@ func DecodeTransactionFromAbi(data []byte, abiData []byte) (*Transaction, error) Signature: method.String(), Name: method.Name, Method: method, - Type: utils.TransactionMethodType(strings.ToLower(method.Name)), + Type: func() utils.TransactionMethodType { + if len(method.Name) > 0 { + return utils.TransactionMethodType(strings.ToLower(method.Name)) + } + return utils.UnknownTransactionMethodType + }(), Inputs: inputsMap, }, nil } diff --git a/contracts/constructor.go b/contracts/constructor.go index 325cdc9f..5e05db5e 100644 --- a/contracts/constructor.go +++ b/contracts/constructor.go @@ -47,6 +47,7 @@ func (c *Contract) DiscoverConstructor(ctx context.Context) error { return fmt.Errorf("constructor data index out of range") } + fmt.Println(string(cAbi)) constructor, err := bytecode.DecodeConstructorFromAbi(adjustedData[constructorDataIndex:], constructorAbi) if err != nil { if !strings.Contains(err.Error(), "would go over slice boundary") { diff --git a/ir/variables.go b/ir/variables.go index 314e18b8..c5cd5b2b 100644 --- a/ir/variables.go +++ b/ir/variables.go @@ -1,6 +1,7 @@ package ir import ( + "github.com/unpackdev/solgo/utils" "strings" ast_pb "github.com/unpackdev/protos/dist/go/ast" @@ -127,6 +128,9 @@ func (b *Builder) processStateVariables(unit *ast.StateVariableDeclaration) *Sta // It could be that the name of the type name node is not set, but the type description string is. if variableNode.Type == "" { + if variableNode.TypeDescription == nil { + utils.DumpNodeWithExit(variableNode) + } variableNode.Type = variableNode.TypeDescription.TypeString } diff --git a/utils/networks.go b/utils/networks.go index 4c19610f..f41a1b51 100644 --- a/utils/networks.go +++ b/utils/networks.go @@ -83,6 +83,10 @@ func (n Network) String() string { return string(n) } +func (n Network) GetNetworkID() NetworkID { + return GetNetworkID(n) +} + func (n NetworkID) ToBig() *big.Int { return new(big.Int).SetUint64(uint64(n)) } @@ -131,6 +135,25 @@ func GetNetworkFromID(id NetworkID) (Network, error) { } } +func GetNetworkFromInt(id uint64) (Network, error) { + switch id { + case EthereumNetworkID.Uint64(): + return Ethereum, nil + case BscNetworkID.Uint64(): + return Bsc, nil + case PolygonNetworkID.Uint64(): + return Polygon, nil + case AvalancheNetworkID.Uint64(): + return Avalanche, nil + case ArbitrumNetworkID.Uint64(): + return Arbitrum, nil + case OptimismNetworkID.Uint64(): + return Optimism, nil + default: + return "", fmt.Errorf("unknown network ID '%d' provided", id) + } +} + func GetNetworkFromString(network string) (Network, error) { switch network { case "ethereum": diff --git a/utils/types.go b/utils/types.go index 8cd6aaa3..82da1c88 100644 --- a/utils/types.go +++ b/utils/types.go @@ -123,16 +123,21 @@ const ( UnknownTransactionMethodType TransactionMethodType = "unknown" ContractCreationType TransactionMethodType = "contract_creation" + ApproveMethodType TransactionMethodType = "approve" TransferMethodType TransactionMethodType = "transfer" + TransferFromMethodType TransactionMethodType = "transferfrom" + DepositMethodType TransactionMethodType = "deposit" NoSignatureMethodType TransactionMethodType = "no_signature" UnknownLogEventType LogEventType = "unknown" SwapLogEventType LogEventType = "swap" + TransferFromLogEventType LogEventType = "transferfrom" TransferLogEventType LogEventType = "transfer" DepositLogEventType LogEventType = "deposit" WithdrawLogEventType LogEventType = "withdraw" MintLogEventType LogEventType = "mint" BurnLogEventType LogEventType = "burn" + PairCreatedEventType LogEventType = "paircreated" NoSimulator SimulatorType = "no_simulator" AnvilSimulator SimulatorType = "anvil" diff --git a/utils/wei.go b/utils/wei.go new file mode 100644 index 00000000..2e71420e --- /dev/null +++ b/utils/wei.go @@ -0,0 +1,35 @@ +package utils + +import ( + "github.com/unpackdev/solgo/utils/entities" + "math/big" +) + +var Ether = big.NewInt(1e18) +var GWei = big.NewInt(1e9) + +// FromWei converts a balance in wei to Ether. +func FromWei(wei *big.Int, token *entities.Token) *entities.CurrencyAmount { + if wei == nil || token == nil { + return nil + } + return entities.FromRawAmount(token, wei) +} + +func ToOne(token *entities.Token) *entities.CurrencyAmount { + if token == nil { + return nil + } + + divisor := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(token.Decimals())), nil) + return entities.FromRawAmount(token, divisor) +} + +func ToMany(amount *big.Int, token *entities.Token) *entities.CurrencyAmount { + if amount == nil || token == nil { + return nil + } + + divisor := new(big.Int).Mul(amount, ToOne(token).Quotient()) + return entities.FromRawAmount(token, divisor) +}