From 5f5a4f19280628425f5d6e77ca91997401a7cce7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20P=C5=99evr=C3=A1til?= Date: Tue, 11 Jun 2024 10:12:01 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20new=20built-in=20contracts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/wake/Create3.sol | 121 ++++++ contracts/wake/Create3Deployer.sol | 18 + contracts/wake/ERC1967Factory.sol | 418 +++++++++++++++++++ contracts/wake/interfaces/IERC1155.sol | 105 +++++ contracts/wake/interfaces/IERC165.sol | 12 + contracts/wake/interfaces/IERC20.sol | 43 ++ contracts/wake/interfaces/IERC20Metadata.sol | 24 ++ contracts/wake/interfaces/IERC721.sol | 164 ++++++++ wake/cli/init.py | 8 +- wake/compiler/source_unit_name_resolver.py | 16 +- 10 files changed, 924 insertions(+), 5 deletions(-) create mode 100644 contracts/wake/Create3.sol create mode 100644 contracts/wake/Create3Deployer.sol create mode 100644 contracts/wake/ERC1967Factory.sol create mode 100644 contracts/wake/interfaces/IERC1155.sol create mode 100644 contracts/wake/interfaces/IERC165.sol create mode 100644 contracts/wake/interfaces/IERC20.sol create mode 100644 contracts/wake/interfaces/IERC20Metadata.sol create mode 100644 contracts/wake/interfaces/IERC721.sol diff --git a/contracts/wake/Create3.sol b/contracts/wake/Create3.sol new file mode 100644 index 000000000..a7e54bc53 --- /dev/null +++ b/contracts/wake/Create3.sol @@ -0,0 +1,121 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.4; + +/** + @title A library for deploying contracts EIP-3171 style. + @author Agustin Aguilar +*/ +library Create3 { + error ErrorCreatingProxy(); + error ErrorCreatingContract(); + error TargetAlreadyExists(); + + /** + @notice The bytecode for a contract that proxies the creation of another contract + @dev If this code is deployed using CREATE2 it can be used to decouple `creationCode` from the child contract address + + 0x67363d3d37363d34f03d5260086018f3: + 0x00 0x67 0x67XXXXXXXXXXXXXXXX PUSH8 bytecode 0x363d3d37363d34f0 + 0x01 0x3d 0x3d RETURNDATASIZE 0 0x363d3d37363d34f0 + 0x02 0x52 0x52 MSTORE + 0x03 0x60 0x6008 PUSH1 08 8 + 0x04 0x60 0x6018 PUSH1 18 24 8 + 0x05 0xf3 0xf3 RETURN + + 0x363d3d37363d34f0: + 0x00 0x36 0x36 CALLDATASIZE cds + 0x01 0x3d 0x3d RETURNDATASIZE 0 cds + 0x02 0x3d 0x3d RETURNDATASIZE 0 0 cds + 0x03 0x37 0x37 CALLDATACOPY + 0x04 0x36 0x36 CALLDATASIZE cds + 0x05 0x3d 0x3d RETURNDATASIZE 0 cds + 0x06 0x34 0x34 CALLVALUE val 0 cds + 0x07 0xf0 0xf0 CREATE addr + */ + + bytes internal constant PROXY_CHILD_BYTECODE = hex"67_36_3d_3d_37_36_3d_34_f0_3d_52_60_08_60_18_f3"; + + // KECCAK256_PROXY_CHILD_BYTECODE = keccak256(PROXY_CHILD_BYTECODE); + bytes32 internal constant KECCAK256_PROXY_CHILD_BYTECODE = 0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f; + + /** + @notice Returns the size of the code on a given address + @param _addr Address that may or may not contain code + @return size of the code on the given `_addr` + */ + function codeSize(address _addr) internal view returns (uint256 size) { + assembly { size := extcodesize(_addr) } + } + + /** + @notice Creates a new contract with given `_creationCode` and `_salt` + @param _salt Salt of the contract creation, resulting address will be derivated from this value only + @param _creationCode Creation code (constructor) of the contract to be deployed, this value doesn't affect the resulting address + @return addr of the deployed contract, reverts on error + */ + function create3(bytes32 _salt, bytes memory _creationCode) internal returns (address addr) { + return create3(_salt, _creationCode, 0); + } + + /** + @notice Creates a new contract with given `_creationCode` and `_salt` + @param _salt Salt of the contract creation, resulting address will be derivated from this value only + @param _creationCode Creation code (constructor) of the contract to be deployed, this value doesn't affect the resulting address + @param _value In WEI of ETH to be forwarded to child contract + @return addr of the deployed contract, reverts on error + */ + function create3(bytes32 _salt, bytes memory _creationCode, uint256 _value) internal returns (address addr) { + // Creation code + bytes memory creationCode = PROXY_CHILD_BYTECODE; + + // Get target final address + addr = addressOf(_salt); + if (codeSize(addr) != 0) revert TargetAlreadyExists(); + + // Create CREATE2 proxy + address proxy; assembly { proxy := create2(0, add(creationCode, 32), mload(creationCode), _salt)} + if (proxy == address(0)) revert ErrorCreatingProxy(); + + // Call proxy with final init code + (bool success,) = proxy.call{ value: _value }(_creationCode); + if (!success || codeSize(addr) == 0) revert ErrorCreatingContract(); + } + + /** + @notice Computes the resulting address of a contract deployed using address(this) and the given `_salt` + @param _salt Salt of the contract creation, resulting address will be derivated from this value only + @return addr of the deployed contract, reverts on error + + @dev The address creation formula is: keccak256(rlp([keccak256(0xff ++ address(this) ++ _salt ++ keccak256(childBytecode))[12:], 0x01])) + */ + function addressOf(bytes32 _salt) internal view returns (address) { + address proxy = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + hex'ff', + address(this), + _salt, + KECCAK256_PROXY_CHILD_BYTECODE + ) + ) + ) + ) + ); + + return address( + uint160( + uint256( + keccak256( + abi.encodePacked( + hex"d6_94", + proxy, + hex"01" + ) + ) + ) + ) + ); + } +} \ No newline at end of file diff --git a/contracts/wake/Create3Deployer.sol b/contracts/wake/Create3Deployer.sol new file mode 100644 index 000000000..741c2a8b2 --- /dev/null +++ b/contracts/wake/Create3Deployer.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "./Create3.sol"; + +contract Create3Deployer { + function deploy(bytes32 _salt, bytes memory _creationCode) external returns (address addr) { + return Create3.create3(_salt, _creationCode); + } + + function deployWithValue(bytes32 _salt, bytes memory _creationCode, uint256 _value) external returns (address addr) { + return Create3.create3(_salt, _creationCode, _value); + } + + function computeAddress(bytes32 _salt) external view returns (address addr) { + return Create3.addressOf(_salt); + } +} \ No newline at end of file diff --git a/contracts/wake/ERC1967Factory.sol b/contracts/wake/ERC1967Factory.sol new file mode 100644 index 000000000..7febad487 --- /dev/null +++ b/contracts/wake/ERC1967Factory.sol @@ -0,0 +1,418 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Factory for deploying and managing ERC1967 proxy contracts. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ERC1967Factory.sol) +/// @author jtriley-eth (https://github.com/jtriley-eth/minimum-viable-proxy) +contract ERC1967Factory { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The caller is not authorized to call the function. + error Unauthorized(); + + /// @dev The proxy deployment failed. + error DeploymentFailed(); + + /// @dev The upgrade failed. + error UpgradeFailed(); + + /// @dev The salt does not start with the caller. + error SaltDoesNotStartWithCaller(); + + /// @dev `bytes4(keccak256(bytes("Unauthorized()")))`. + uint256 internal constant _UNAUTHORIZED_ERROR_SELECTOR = 0x82b42900; + + /// @dev `bytes4(keccak256(bytes("DeploymentFailed()")))`. + uint256 internal constant _DEPLOYMENT_FAILED_ERROR_SELECTOR = 0x30116425; + + /// @dev `bytes4(keccak256(bytes("UpgradeFailed()")))`. + uint256 internal constant _UPGRADE_FAILED_ERROR_SELECTOR = 0x55299b49; + + /// @dev `bytes4(keccak256(bytes("SaltDoesNotStartWithCaller()")))`. + uint256 internal constant _SALT_DOES_NOT_START_WITH_CALLER_ERROR_SELECTOR = 0x2f634836; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* EVENTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The admin of a proxy contract has been changed. + event AdminChanged(address indexed proxy, address indexed admin); + + /// @dev The implementation for a proxy has been upgraded. + event Upgraded(address indexed proxy, address indexed implementation); + + /// @dev A proxy has been deployed. + event Deployed(address indexed proxy, address indexed implementation, address indexed admin); + + /// @dev `keccak256(bytes("AdminChanged(address,address)"))`. + uint256 internal constant _ADMIN_CHANGED_EVENT_SIGNATURE = + 0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f; + + /// @dev `keccak256(bytes("Upgraded(address,address)"))`. + uint256 internal constant _UPGRADED_EVENT_SIGNATURE = + 0x5d611f318680d00598bb735d61bacf0c514c6b50e1e5ad30040a4df2b12791c7; + + /// @dev `keccak256(bytes("Deployed(address,address,address)"))`. + uint256 internal constant _DEPLOYED_EVENT_SIGNATURE = + 0xc95935a66d15e0da5e412aca0ad27ae891d20b2fb91cf3994b6a3bf2b8178082; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* STORAGE */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // The admin slot for a `proxy` is `shl(96, proxy)`. + + /// @dev The ERC-1967 storage slot for the implementation in the proxy. + /// `uint256(keccak256("eip1967.proxy.implementation")) - 1`. + uint256 internal constant _IMPLEMENTATION_SLOT = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ADMIN FUNCTIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the admin of the proxy. + function adminOf(address proxy) public view returns (address admin) { + assembly { + admin := sload(shl(96, proxy)) + } + } + + /// @dev Sets the admin of the proxy. + /// The caller of this function must be the admin of the proxy on this factory. + function changeAdmin(address proxy, address admin) public { + assembly { + // Check if the caller is the admin of the proxy. + if iszero(eq(sload(shl(96, proxy)), caller())) { + mstore(0x00, _UNAUTHORIZED_ERROR_SELECTOR) + revert(0x1c, 0x04) + } + // Store the admin for the proxy. + sstore(shl(96, proxy), admin) + // Emit the {AdminChanged} event. + log3(0, 0, _ADMIN_CHANGED_EVENT_SIGNATURE, proxy, admin) + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* UPGRADE FUNCTIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Upgrades the proxy to point to `implementation`. + /// The caller of this function must be the admin of the proxy on this factory. + function upgrade(address proxy, address implementation) public payable { + upgradeAndCall(proxy, implementation, _emptyData()); + } + + /// @dev Upgrades the proxy to point to `implementation`. + /// Then, calls the proxy with abi encoded `data`. + /// The caller of this function must be the admin of the proxy on this factory. + function upgradeAndCall(address proxy, address implementation, bytes calldata data) + public + payable + { + assembly { + // Check if the caller is the admin of the proxy. + if iszero(eq(sload(shl(96, proxy)), caller())) { + mstore(0x00, _UNAUTHORIZED_ERROR_SELECTOR) + revert(0x1c, 0x04) + } + // Set up the calldata to upgrade the proxy. + let m := mload(0x40) + mstore(m, implementation) + mstore(add(m, 0x20), _IMPLEMENTATION_SLOT) + calldatacopy(add(m, 0x40), data.offset, data.length) + // Try upgrading the proxy and revert upon failure. + if iszero(call(gas(), proxy, callvalue(), m, add(0x40, data.length), 0x00, 0x00)) { + // Revert with the `UpgradeFailed` selector if there is no error returndata. + if iszero(returndatasize()) { + mstore(0x00, _UPGRADE_FAILED_ERROR_SELECTOR) + revert(0x1c, 0x04) + } + // Otherwise, bubble up the returned error. + returndatacopy(0x00, 0x00, returndatasize()) + revert(0x00, returndatasize()) + } + // Emit the {Upgraded} event. + log3(0, 0, _UPGRADED_EVENT_SIGNATURE, proxy, implementation) + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* DEPLOY FUNCTIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Deploys a proxy for `implementation`, with `admin`, + /// and returns its address. + /// The value passed into this function will be forwarded to the proxy. + function deploy(address implementation, address admin) public payable returns (address proxy) { + proxy = deployAndCall(implementation, admin, _emptyData()); + } + + /// @dev Deploys a proxy for `implementation`, with `admin`, + /// and returns its address. + /// The value passed into this function will be forwarded to the proxy. + /// Then, calls the proxy with abi encoded `data`. + function deployAndCall(address implementation, address admin, bytes calldata data) + public + payable + returns (address proxy) + { + proxy = _deploy(implementation, admin, bytes32(0), false, data); + } + + /// @dev Deploys a proxy for `implementation`, with `admin`, `salt`, + /// and returns its deterministic address. + /// The value passed into this function will be forwarded to the proxy. + function deployDeterministic(address implementation, address admin, bytes32 salt) + public + payable + returns (address proxy) + { + proxy = deployDeterministicAndCall(implementation, admin, salt, _emptyData()); + } + + /// @dev Deploys a proxy for `implementation`, with `admin`, `salt`, + /// and returns its deterministic address. + /// The value passed into this function will be forwarded to the proxy. + /// Then, calls the proxy with abi encoded `data`. + function deployDeterministicAndCall( + address implementation, + address admin, + bytes32 salt, + bytes calldata data + ) public payable returns (address proxy) { + assembly { + // If the salt does not start with the zero address or the caller. + if iszero(or(iszero(shr(96, salt)), eq(caller(), shr(96, salt)))) { + mstore(0x00, _SALT_DOES_NOT_START_WITH_CALLER_ERROR_SELECTOR) + revert(0x1c, 0x04) + } + } + proxy = _deploy(implementation, admin, salt, true, data); + } + + /// @dev Deploys the proxy, with optionality to deploy deterministically with a `salt`. + function _deploy( + address implementation, + address admin, + bytes32 salt, + bool useSalt, + bytes calldata data + ) internal returns (address proxy) { + bytes32 m = _initCode(); + assembly { + // Create the proxy. + switch useSalt + case 0 { proxy := create(0, add(m, 0x13), 0x88) } + default { proxy := create2(0, add(m, 0x13), 0x88, salt) } + // Revert if the creation fails. + if iszero(proxy) { + mstore(0x00, _DEPLOYMENT_FAILED_ERROR_SELECTOR) + revert(0x1c, 0x04) + } + + // Set up the calldata to set the implementation of the proxy. + mstore(m, implementation) + mstore(add(m, 0x20), _IMPLEMENTATION_SLOT) + calldatacopy(add(m, 0x40), data.offset, data.length) + // Try setting the implementation on the proxy and revert upon failure. + if iszero(call(gas(), proxy, callvalue(), m, add(0x40, data.length), 0x00, 0x00)) { + // Revert with the `DeploymentFailed` selector if there is no error returndata. + if iszero(returndatasize()) { + mstore(0x00, _DEPLOYMENT_FAILED_ERROR_SELECTOR) + revert(0x1c, 0x04) + } + // Otherwise, bubble up the returned error. + returndatacopy(0x00, 0x00, returndatasize()) + revert(0x00, returndatasize()) + } + + // Store the admin for the proxy. + sstore(shl(96, proxy), admin) + + // Emit the {Deployed} event. + log4(0, 0, _DEPLOYED_EVENT_SIGNATURE, proxy, implementation, admin) + } + } + + /// @dev Returns the address of the proxy deployed with `salt`. + function predictDeterministicAddress(bytes32 salt) public view returns (address predicted) { + bytes32 hash = initCodeHash(); + assembly { + // Compute and store the bytecode hash. + mstore8(0x00, 0xff) // Write the prefix. + mstore(0x35, hash) + mstore(0x01, shl(96, address())) + mstore(0x15, salt) + // Note: `predicted` has dirty upper 96 bits. We won't clean it here + // as it will be automatically cleaned when it is copied into the returndata. + // Please clean as needed if used in other inline assembly blocks. + predicted := keccak256(0x00, 0x55) + // Restore the part of the free memory pointer that has been overwritten. + mstore(0x35, 0) + } + } + + /// @dev Returns the initialization code hash of the proxy. + /// Used for mining vanity addresses with create2crunch. + function initCodeHash() public view returns (bytes32 result) { + bytes32 m = _initCode(); + assembly { + result := keccak256(add(m, 0x13), 0x88) + } + } + + /// @dev Returns a pointer to the initialization code of a proxy created via this factory. + function _initCode() internal view returns (bytes32 m) { + assembly { + /** + * -------------------------------------------------------------------------------------+ + * CREATION (9 bytes) | + * -------------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * -------------------------------------------------------------------------------------| + * 60 runSize | PUSH1 runSize | r | | + * 3d | RETURNDATASIZE | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 3d | RETURNDATASIZE | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * -------------------------------------------------------------------------------------| + * RUNTIME (127 bytes) | + * -------------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * -------------------------------------------------------------------------------------| + * | + * ::: keep some values in stack :::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | | + * 3d | RETURNDATASIZE | 0 0 | | + * | + * ::: check if caller is factory ::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 33 | CALLER | c 0 0 | | + * 73 factory | PUSH20 factory | f c 0 0 | | + * 14 | EQ | isf 0 0 | | + * 60 0x57 | PUSH1 0x57 | dest isf 0 0 | | + * 57 | JUMPI | 0 0 | | + * | + * ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 | | + * 3d | RETURNDATASIZE | 0 cds 0 0 | | + * 3d | RETURNDATASIZE | 0 0 cds 0 0 | | + * 37 | CALLDATACOPY | 0 0 | [0..calldatasize): calldata | + * | + * ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 | [0..calldatasize): calldata | + * 3d | RETURNDATASIZE | 0 cds 0 0 | [0..calldatasize): calldata | + * 7f slot | PUSH32 slot | s 0 cds 0 0 | [0..calldatasize): calldata | + * 54 | SLOAD | i 0 cds 0 0 | [0..calldatasize): calldata | + * 5a | GAS | g i 0 cds 0 0 | [0..calldatasize): calldata | + * f4 | DELEGATECALL | succ | [0..calldatasize): calldata | + * | + * ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds succ | [0..calldatasize): calldata | + * 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..calldatasize): calldata | + * 80 | DUP1 | 0 0 rds succ | [0..calldatasize): calldata | + * 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata | + * | + * ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x52 | PUSH1 0x52 | dest succ | [0..returndatasize): returndata | + * 57 | JUMPI | | [0..returndatasize): returndata | + * | + * ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * fd | REVERT | | [0..returndatasize): returndata | + * | + * ::: delegatecall succeeded, return ::::::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | [0..returndatasize): returndata | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * f3 | RETURN | | [0..returndatasize): returndata | + * | + * ::: set new implementation (caller is factory) ::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | 0 0 | | + * 3d | RETURNDATASIZE | 0 0 0 | | + * 35 | CALLDATALOAD | impl 0 0 | | + * 60 0x20 | PUSH1 0x20 | w impl 0 0 | | + * 35 | CALLDATALOAD | slot impl 0 0 | | + * 55 | SSTORE | 0 0 | | + * | + * ::: no extra calldata, return :::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x40 | PUSH1 0x40 | 2w 0 0 | | + * 80 | DUP1 | 2w 2w 0 0 | | + * 36 | CALLDATASIZE | cds 2w 2w 0 0 | | + * 11 | GT | gt 2w 0 0 | | + * 15 | ISZERO | lte 2w 0 0 | | + * 60 0x52 | PUSH1 0x52 | dest lte 2w 0 0 | | + * 57 | JUMPI | 2w 0 0 | | + * | + * ::: copy extra calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 2w 0 0 | | + * 03 | SUB | t 0 0 | | + * 80 | DUP1 | t t 0 0 | | + * 60 0x40 | PUSH1 0x40 | 2w t t 0 0 | | + * 3d | RETURNDATASIZE | 0 2w t t 0 0 | | + * 37 | CALLDATACOPY | t 0 0 | [0..t): extra calldata | + * | + * ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 t 0 0 | [0..t): extra calldata | + * 3d | RETURNDATASIZE | 0 0 t 0 0 | [0..t): extra calldata | + * 35 | CALLDATALOAD | i 0 t 0 0 | [0..t): extra calldata | + * 5a | GAS | g i 0 t 0 0 | [0..t): extra calldata | + * f4 | DELEGATECALL | succ | [0..t): extra calldata | + * | + * ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds succ | [0..t): extra calldata | + * 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..t): extra calldata | + * 80 | DUP1 | 0 0 rds succ | [0..t): extra calldata | + * 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata | + * | + * ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x52 | PUSH1 0x52 | dest succ | [0..returndatasize): returndata | + * 57 | JUMPI | | [0..returndatasize): returndata | + * | + * ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * fd | REVERT | | [0..returndatasize): returndata | + * -------------------------------------------------------------------------------------+ + */ + m := mload(0x40) + // forgefmt: disable-start + switch shr(112, address()) + case 0 { + // If the factory's address has six or more leading zero bytes. + mstore(add(m, 0x75), 0x604c573d6000fd) // 7 + mstore(add(m, 0x6e), 0x3d3560203555604080361115604c5736038060403d373d3d355af43d6000803e) // 32 + mstore(add(m, 0x4e), 0x3735a920a3ca505d382bbc545af43d6000803e604c573d6000fd5b3d6000f35b) // 32 + mstore(add(m, 0x2e), 0x14605157363d3d37363d7f360894a13ba1a3210667c828492db98dca3e2076cc) // 32 + mstore(add(m, 0x0e), address()) // 14 + mstore(m, 0x60793d8160093d39f33d3d336d) // 9 + 4 + } + default { + mstore(add(m, 0x7b), 0x6052573d6000fd) // 7 + mstore(add(m, 0x74), 0x3d356020355560408036111560525736038060403d373d3d355af43d6000803e) // 32 + mstore(add(m, 0x54), 0x3735a920a3ca505d382bbc545af43d6000803e6052573d6000fd5b3d6000f35b) // 32 + mstore(add(m, 0x34), 0x14605757363d3d37363d7f360894a13ba1a3210667c828492db98dca3e2076cc) // 32 + mstore(add(m, 0x14), address()) // 20 + mstore(m, 0x607f3d8160093d39f33d3d3373) // 9 + 4 + } + // forgefmt: disable-end + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* HELPERS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Helper function to return an empty bytes calldata. + function _emptyData() internal pure returns (bytes calldata data) { + assembly { + data.length := 0 + } + } +} \ No newline at end of file diff --git a/contracts/wake/interfaces/IERC1155.sol b/contracts/wake/interfaces/IERC1155.sol new file mode 100644 index 000000000..f7dd2b410 --- /dev/null +++ b/contracts/wake/interfaces/IERC1155.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2; + +import "./IERC165.sol"; + +/// @title ERC-1155 Multi Token Standard +/// @dev See https://eips.ethereum.org/EIPS/eip-1155 +/// Note: The ERC-165 identifier for this interface is 0xd9b67a26. +interface IERC1155 is IERC165 { + /// @dev + /// - Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard). + /// - The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender). + /// - The `_from` argument MUST be the address of the holder whose balance is decreased. + /// - The `_to` argument MUST be the address of the recipient whose balance is increased. + /// - The `_id` argument MUST be the token type being transferred. + /// - The `_value` argument MUST be the number of tokens the holder balance is decreased by and match what the recipient balance is increased by. + /// - When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address). + /// - When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address). + event TransferSingle( + address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value + ); + + /// @dev + /// - Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard). + /// - The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender). + /// - The `_from` argument MUST be the address of the holder whose balance is decreased. + /// - The `_to` argument MUST be the address of the recipient whose balance is increased. + /// - The `_ids` argument MUST be the list of tokens being transferred. + /// - The `_values` argument MUST be the list of number of tokens (matching the list and order of tokens specified in _ids) the holder balance is decreased by and match what the recipient balance is increased by. + /// - When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address). + /// - When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address). + event TransferBatch( + address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values + ); + + /// @dev MUST emit when approval for a second party/operator address to manage all tokens for an owner address is enabled or disabled (absence of an event assumes disabled). + event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); + + /// @dev MUST emit when the URI is updated for a token ID. URIs are defined in RFC 3986. + /// The URI MUST point to a JSON file that conforms to the "ERC-1155 Metadata URI JSON Schema". + event URI(string _value, uint256 indexed _id); + + /// @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified (with safety call). + /// @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard). + /// - MUST revert if `_to` is the zero address. + /// - MUST revert if balance of holder for token `_id` is lower than the `_value` sent. + /// - MUST revert on any other error. + /// - MUST emit the `TransferSingle` event to reflect the balance change (see "Safe Transfer Rules" section of the standard). + /// - After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard). + /// @param _from Source address + /// @param _to Target address + /// @param _id ID of the token type + /// @param _value Transfer amount + /// @param _data Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to` + function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external; + + /// @notice Transfers `_values` amount(s) of `_ids` from the `_from` address to the `_to` address specified (with safety call). + /// @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard). + /// - MUST revert if `_to` is the zero address. + /// - MUST revert if length of `_ids` is not the same as length of `_values`. + /// - MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `_values` sent to the recipient. + /// - MUST revert on any other error. + /// - MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see "Safe Transfer Rules" section of the standard). + /// - Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc). + /// - After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard). + /// @param _from Source address + /// @param _to Target address + /// @param _ids IDs of each token type (order and length must match _values array) + /// @param _values Transfer amounts per token type (order and length must match _ids array) + /// @param _data Additional data with no specified format, MUST be sent unaltered in call to the `ERC1155TokenReceiver` hook(s) on `_to` + function safeBatchTransferFrom( + address _from, + address _to, + uint256[] calldata _ids, + uint256[] calldata _values, + bytes calldata _data + ) external; + + /// @notice Get the balance of an account's tokens. + /// @param _owner The address of the token holder + /// @param _id ID of the token + /// @return The _owner's balance of the token type requested + function balanceOf(address _owner, uint256 _id) external view returns (uint256); + + /// @notice Get the balance of multiple account/token pairs + /// @param _owners The addresses of the token holders + /// @param _ids ID of the tokens + /// @return The _owner's balance of the token types requested (i.e. balance for each (owner, id) pair) + function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) + external + view + returns (uint256[] memory); + + /// @notice Enable or disable approval for a third party ("operator") to manage all of the caller's tokens. + /// @dev MUST emit the ApprovalForAll event on success. + /// @param _operator Address to add to the set of authorized operators + /// @param _approved True if the operator is approved, false to revoke approval + function setApprovalForAll(address _operator, bool _approved) external; + + /// @notice Queries the approval status of an operator for a given owner. + /// @param _owner The owner of the tokens + /// @param _operator Address of authorized operator + /// @return True if the operator is approved, false if not + function isApprovedForAll(address _owner, address _operator) external view returns (bool); +} diff --git a/contracts/wake/interfaces/IERC165.sol b/contracts/wake/interfaces/IERC165.sol new file mode 100644 index 000000000..9af4bf800 --- /dev/null +++ b/contracts/wake/interfaces/IERC165.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2; + +interface IERC165 { + /// @notice Query if a contract implements an interface + /// @param interfaceID The interface identifier, as specified in ERC-165 + /// @dev Interface identification is specified in ERC-165. This function + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `interfaceID` and + /// `interfaceID` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 interfaceID) external view returns (bool); +} diff --git a/contracts/wake/interfaces/IERC20.sol b/contracts/wake/interfaces/IERC20.sol new file mode 100644 index 000000000..ba40806c3 --- /dev/null +++ b/contracts/wake/interfaces/IERC20.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2; + +/// @dev Interface of the ERC20 standard as defined in the EIP. +/// @dev This includes the optional name, symbol, and decimals metadata. +interface IERC20 { + /// @dev Emitted when `value` tokens are moved from one account (`from`) to another (`to`). + event Transfer(address indexed from, address indexed to, uint256 value); + + /// @dev Emitted when the allowance of a `spender` for an `owner` is set, where `value` + /// is the new allowance. + event Approval(address indexed owner, address indexed spender, uint256 value); + + /// @notice Returns the amount of tokens in existence. + function totalSupply() external view returns (uint256); + + /// @notice Returns the amount of tokens owned by `account`. + function balanceOf(address account) external view returns (uint256); + + /// @notice Moves `amount` tokens from the caller's account to `to`. + function transfer(address to, uint256 amount) external returns (bool); + + /// @notice Returns the remaining number of tokens that `spender` is allowed + /// to spend on behalf of `owner` + function allowance(address owner, address spender) external view returns (uint256); + + /// @notice Sets `amount` as the allowance of `spender` over the caller's tokens. + /// @dev Be aware of front-running risks: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + function approve(address spender, uint256 amount) external returns (bool); + + /// @notice Moves `amount` tokens from `from` to `to` using the allowance mechanism. + /// `amount` is then deducted from the caller's allowance. + function transferFrom(address from, address to, uint256 amount) external returns (bool); + + /// @notice Returns the name of the token. + function name() external view returns (string memory); + + /// @notice Returns the symbol of the token. + function symbol() external view returns (string memory); + + /// @notice Returns the decimals places of the token. + function decimals() external view returns (uint8); +} diff --git a/contracts/wake/interfaces/IERC20Metadata.sol b/contracts/wake/interfaces/IERC20Metadata.sol new file mode 100644 index 000000000..f100217d6 --- /dev/null +++ b/contracts/wake/interfaces/IERC20Metadata.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2; + +import { IERC20 } from "./IERC20.sol"; + +/** + * @dev Interface for the optional metadata functions from the ERC-20 standard. + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} diff --git a/contracts/wake/interfaces/IERC721.sol b/contracts/wake/interfaces/IERC721.sol new file mode 100644 index 000000000..0a16f45cc --- /dev/null +++ b/contracts/wake/interfaces/IERC721.sol @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2; + +import "./IERC165.sol"; + +/// @title ERC-721 Non-Fungible Token Standard +/// @dev See https://eips.ethereum.org/EIPS/eip-721 +/// Note: the ERC-165 identifier for this interface is 0x80ac58cd. +interface IERC721 is IERC165 { + /// @dev This emits when ownership of any NFT changes by any mechanism. + /// This event emits when NFTs are created (`from` == 0) and destroyed + /// (`to` == 0). Exception: during contract creation, any number of NFTs + /// may be created and assigned without emitting Transfer. At the time of + /// any transfer, the approved address for that NFT (if any) is reset to none. + event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); + + /// @dev This emits when the approved address for an NFT is changed or + /// reaffirmed. The zero address indicates there is no approved address. + /// When a Transfer event emits, this also indicates that the approved + /// address for that NFT (if any) is reset to none. + event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); + + /// @dev This emits when an operator is enabled or disabled for an owner. + /// The operator can manage all NFTs of the owner. + event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); + + /// @notice Count all NFTs assigned to an owner + /// @dev NFTs assigned to the zero address are considered invalid, and this + /// function throws for queries about the zero address. + /// @param _owner An address for whom to query the balance + /// @return The number of NFTs owned by `_owner`, possibly zero + function balanceOf(address _owner) external view returns (uint256); + + /// @notice Find the owner of an NFT + /// @dev NFTs assigned to zero address are considered invalid, and queries + /// about them do throw. + /// @param _tokenId The identifier for an NFT + /// @return The address of the owner of the NFT + function ownerOf(uint256 _tokenId) external view returns (address); + + /// @notice Transfers the ownership of an NFT from one address to another address + /// @dev Throws unless `msg.sender` is the current owner, an authorized + /// operator, or the approved address for this NFT. Throws if `_from` is + /// not the current owner. Throws if `_to` is the zero address. Throws if + /// `_tokenId` is not a valid NFT. When transfer is complete, this function + /// checks if `_to` is a smart contract (code size > 0). If so, it calls + /// `onERC721Received` on `_to` and throws if the return value is not + /// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`. + /// @param _from The current owner of the NFT + /// @param _to The new owner + /// @param _tokenId The NFT to transfer + /// @param data Additional data with no specified format, sent in call to `_to` + function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata data) external payable; + + /// @notice Transfers the ownership of an NFT from one address to another address + /// @dev This works identically to the other function with an extra data parameter, + /// except this function just sets data to "". + /// @param _from The current owner of the NFT + /// @param _to The new owner + /// @param _tokenId The NFT to transfer + function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; + + /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE + /// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE + /// THEY MAY BE PERMANENTLY LOST + /// @dev Throws unless `msg.sender` is the current owner, an authorized + /// operator, or the approved address for this NFT. Throws if `_from` is + /// not the current owner. Throws if `_to` is the zero address. Throws if + /// `_tokenId` is not a valid NFT. + /// @param _from The current owner of the NFT + /// @param _to The new owner + /// @param _tokenId The NFT to transfer + function transferFrom(address _from, address _to, uint256 _tokenId) external payable; + + /// @notice Change or reaffirm the approved address for an NFT + /// @dev The zero address indicates there is no approved address. + /// Throws unless `msg.sender` is the current NFT owner, or an authorized + /// operator of the current owner. + /// @param _approved The new approved NFT controller + /// @param _tokenId The NFT to approve + function approve(address _approved, uint256 _tokenId) external payable; + + /// @notice Enable or disable approval for a third party ("operator") to manage + /// all of `msg.sender`'s assets + /// @dev Emits the ApprovalForAll event. The contract MUST allow + /// multiple operators per owner. + /// @param _operator Address to add to the set of authorized operators + /// @param _approved True if the operator is approved, false to revoke approval + function setApprovalForAll(address _operator, bool _approved) external; + + /// @notice Get the approved address for a single NFT + /// @dev Throws if `_tokenId` is not a valid NFT. + /// @param _tokenId The NFT to find the approved address for + /// @return The approved address for this NFT, or the zero address if there is none + function getApproved(uint256 _tokenId) external view returns (address); + + /// @notice Query if an address is an authorized operator for another address + /// @param _owner The address that owns the NFTs + /// @param _operator The address that acts on behalf of the owner + /// @return True if `_operator` is an approved operator for `_owner`, false otherwise + function isApprovedForAll(address _owner, address _operator) external view returns (bool); +} + +/// @dev Note: the ERC-165 identifier for this interface is 0x150b7a02. +interface IERC721TokenReceiver { + /// @notice Handle the receipt of an NFT + /// @dev The ERC721 smart contract calls this function on the recipient + /// after a `transfer`. This function MAY throw to revert and reject the + /// transfer. Return of other than the magic value MUST result in the + /// transaction being reverted. + /// Note: the contract address is always the message sender. + /// @param _operator The address which called `safeTransferFrom` function + /// @param _from The address which previously owned the token + /// @param _tokenId The NFT identifier which is being transferred + /// @param _data Additional data with no specified format + /// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` + /// unless throwing + function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) + external + returns (bytes4); +} + +/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension +/// @dev See https://eips.ethereum.org/EIPS/eip-721 +/// Note: the ERC-165 identifier for this interface is 0x5b5e139f. +interface IERC721Metadata is IERC721 { + /// @notice A descriptive name for a collection of NFTs in this contract + function name() external view returns (string memory _name); + + /// @notice An abbreviated name for NFTs in this contract + function symbol() external view returns (string memory _symbol); + + /// @notice A distinct Uniform Resource Identifier (URI) for a given asset. + /// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC + /// 3986. The URI may point to a JSON file that conforms to the "ERC721 + /// Metadata JSON Schema". + function tokenURI(uint256 _tokenId) external view returns (string memory); +} + +/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension +/// @dev See https://eips.ethereum.org/EIPS/eip-721 +/// Note: the ERC-165 identifier for this interface is 0x780e9d63. +interface IERC721Enumerable is IERC721 { + /// @notice Count NFTs tracked by this contract + /// @return A count of valid NFTs tracked by this contract, where each one of + /// them has an assigned and queryable owner not equal to the zero address + function totalSupply() external view returns (uint256); + + /// @notice Enumerate valid NFTs + /// @dev Throws if `_index` >= `totalSupply()`. + /// @param _index A counter less than `totalSupply()` + /// @return The token identifier for the `_index`th NFT, + /// (sort order not specified) + function tokenByIndex(uint256 _index) external view returns (uint256); + + /// @notice Enumerate NFTs assigned to an owner + /// @dev Throws if `_index` >= `balanceOf(_owner)` or if + /// `_owner` is the zero address, representing invalid NFTs. + /// @param _owner An address where we are interested in NFTs owned by them + /// @param _index A counter less than `balanceOf(_owner)` + /// @return The token identifier for the `_index`th NFT assigned to `_owner`, + /// (sort order not specified) + function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256); +} diff --git a/wake/cli/init.py b/wake/cli/init.py index 8ac22f2b0..9a16ad51e 100644 --- a/wake/cli/init.py +++ b/wake/cli/init.py @@ -120,7 +120,7 @@ def run_init(ctx: Context, force: bool, example: Optional[str]): from ..compiler import SolcOutputSelectionEnum, SolidityCompiler from ..compiler.solc_frontend import SolcOutputErrorSeverityEnum from ..development.pytypes_generator import TypeGenerator - from ..utils.file_utils import copy_dir, is_relative_to + from ..utils.file_utils import copy_dir, is_relative_to, wake_contracts_path if example is None: # create tests directory @@ -173,6 +173,8 @@ def run_init(ctx: Context, force: bool, example: Optional[str]): and file.is_file() ): sol_files.add(file) + for file in wake_contracts_path.rglob("**/*.sol"): + sol_files.add(file) end = time.perf_counter() console.log( f"[green]Found {len(sol_files)} *.sol files in [bold green]{end - start:.2f} s[/bold green][/]" @@ -273,7 +275,7 @@ async def run_init_pytypes( from ..compiler.compiler import CompilationFileSystemEventHandler from ..compiler.solc_frontend import SolcOutputErrorSeverityEnum from ..development.pytypes_generator import TypeGenerator - from ..utils.file_utils import is_relative_to + from ..utils.file_utils import is_relative_to, wake_contracts_path def callback(build: ProjectBuild, build_info: ProjectBuildInfo): start = time.perf_counter() @@ -296,6 +298,8 @@ def callback(build: ProjectBuild, build_info: ProjectBuildInfo): and file.is_file() ): sol_files.add(file) + for file in wake_contracts_path.rglob("**/*.sol"): + sol_files.add(file) end = time.perf_counter() console.log( f"[green]Found {len(sol_files)} *.sol files in [bold green]{end - start:.2f} s[/bold green][/]" diff --git a/wake/compiler/source_unit_name_resolver.py b/wake/compiler/source_unit_name_resolver.py index 93f5fbee3..0cf7d328b 100644 --- a/wake/compiler/source_unit_name_resolver.py +++ b/wake/compiler/source_unit_name_resolver.py @@ -1,8 +1,10 @@ +import itertools from pathlib import Path, PurePath, PurePosixPath from typing import List from wake.config import WakeConfig from wake.config.data_model import SolcRemapping +from wake.utils import wake_contracts_path class SourceUnitNameResolver: @@ -116,6 +118,14 @@ def resolve_cmdline_arg(self, arg: str) -> str: """ path = Path(arg).resolve() pure_path = PurePath(path) - return str( - PurePosixPath(pure_path.relative_to(self.__config.project_root_path)) - ) + for include_path in itertools.chain( + [self.__config.project_root_path], + self.__config.compiler.solc.include_paths, + [wake_contracts_path], + ): + try: + return str(PurePosixPath(pure_path.relative_to(include_path))) + except ValueError: + pass + + raise ValueError(f"File {arg} is not in the project root dir or include paths.")