Skip to content

Commit

Permalink
Merge branch 'main' into onboarding/util-calldata
Browse files Browse the repository at this point in the history
  • Loading branch information
raiseerco authored Oct 1, 2024
2 parents 91f5e90 + a7638ed commit e4b87b3
Show file tree
Hide file tree
Showing 16 changed files with 684 additions and 19 deletions.
2 changes: 1 addition & 1 deletion packages/ethernaut-ai-ui/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ require('ethernaut-ui/src/index')
require('ethernaut-ai/src/index')

extendEnvironment((hre) => {
const config = hre.scopes.ai.tasks.config
const config = hre.scopes.ai.tasks.model
config.paramDefinitions.model.prompt = require('./prompts/model')
})
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const { Terminal } = require('ethernaut-common/src/test/terminal')

describe('config ui', function () {
describe('model ui', function () {
const terminal = new Terminal()

describe('when config is called with no params', function () {
describe('when model is called with no params', function () {
before('call', async function () {
await terminal.run('hardhat ai config', 2000)
await terminal.run('hardhat ai model', 2000)
})

it('displays gpt models', async function () {
Expand Down
31 changes: 31 additions & 0 deletions packages/ethernaut-ai/src/tasks/key.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const types = require('ethernaut-common/src/validation/types')
const output = require('ethernaut-common/src/ui/output')
const storage = require('ethernaut-common/src/io/storage')
const { setEnvVar } = require('ethernaut-common/src/io/env')

require('../scopes/ai')
.task('key', 'Sets the openai api key')
.addParam('apiKey', 'The openai api key to use', undefined, types.string)
.setAction(async ({ apiKey }, hre) => {
try {
const config = storage.readConfig()

let summary = []

if (apiKey) {
const currentKey = process.env.OPENAI_API_KEY
setEnvVar('OPENAI_API_KEY', apiKey)
summary.push(`- API Key set to ${apiKey} (was ${currentKey})`)
}

storage.saveConfig(config)

if (summary.length === 0) {
summary.push('No changes')
}

return output.resultBox(summary.join('\n'))
} catch (err) {
return output.errorBox(err)
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const output = require('ethernaut-common/src/ui/output')
const storage = require('ethernaut-common/src/io/storage')

require('../scopes/ai')
.task('config', 'Configures ai scope parameters')
.task('model', 'Sets the openai model')
.addParam('model', 'The openai model to use', undefined, types.string)
.setAction(async ({ model }, hre) => {
try {
Expand Down
52 changes: 38 additions & 14 deletions packages/ethernaut-common/src/io/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,59 @@ const { getEthernautFolderPath } = require('ethernaut-common/src/io/storage')
const envPath = path.join(getEthernautFolderPath(), '.env')

function refreshEnv() {
// Create the .env file if it doesn't exist
if (!fs.existsSync(envPath)) {
debug.log('No .env file found, creating one...', 'env')
fs.writeFileSync(envPath, '')
}

// Load the .env file
require('dotenv').config({ path: envPath })
}

async function checkEnvVar(varName, message) {
refreshEnv()

// Check if the env var exists at runtime
if (process.env[varName]) {
debug.log(`Environment variable ${varName} found`, 'env')
return
}

debug.log(`Environment variable ${varName} not found - collecting it...`)

if (!fs.existsSync(envPath)) {
debug.log('No .env file found, creating one...', 'env')
fs.writeFileSync(envPath, '')
}
// Collect the env var from the user
const varValue = await prompt({
type: 'input',
message: `Please provide a value for ${varName}${message ? `. ${message}` : ''}`,
})

// Save the env var to the .env file
setEnvVar(varName, varValue)
}

const envConfig = dotenv.parse(fs.readFileSync(envPath))
if (!envConfig[varName]) {
const varValue = await prompt({
type: 'input',
message: `Please provide a value for ${varName}${message ? `. ${message}` : ''}`,
})
debug.log(`Saved environment variable ${varName} in .env file...`)
fs.appendFileSync(envPath, `${varName}=${varValue}`)
require('dotenv').config({ path: envPath })
function loadEnvConfig() {
return dotenv.parse(fs.readFileSync(envPath))
}

function setEnvVar(varName, varValue) {
// Load and set the env var
const envConfig = loadEnvConfig()
envConfig[varName] = varValue

// Write the env var to the .env file
let envFileContent = ''
for (const [key, value] of Object.entries(envConfig)) {
envFileContent += `${key}=${value}\n`
}
fs.writeFileSync(envPath, envFileContent)
debug.log(`Saved environment variable ${varName} in .env file...`)

process.env[varName] = varValue
refreshEnv()
}

module.exports = {
refreshEnv,
checkEnvVar,
setEnvVar,
}
1 change: 1 addition & 0 deletions packages/ethernaut-interact/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ This plugin adds the following tasks:
- send Sends ether to an address
- token Interacts with any ERC20 token
- tx Gives information about a mined transaction
- standards Checks if a contract address meets known token standards

## Environment extensions

Expand Down
116 changes: 116 additions & 0 deletions packages/ethernaut-interact/src/tasks/standards.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
const output = require('ethernaut-common/src/ui/output')
const types = require('ethernaut-common/src/validation/types')
const { getContract } = require('../internal/get-contract')

require('../scopes/interact')
.task('standards', 'Checks if a contract address meets known token standards')
.addPositionalParam(
'address',
'The contract address to check',
undefined,
types.address,
)
.setAction(async ({ address }, hre) => {
try {
const isERC165Supported = await checkERC165Support(address, hre)
const erc20 = await checkERC20(address, hre)
const erc721Support = isERC165Supported
? await checkERC721(address, hre)
: { erc721: false, metadata: false }
const erc1155Support = isERC165Supported
? await checkERC1155(address, hre)
: { erc1155: false, metadata: false }

let str = ''
str += `Address: ${address}\n`
str += 'Token Standards:\n'
str += ` ERC-165 Supported: ${isERC165Supported ? 'Yes' : 'No'}\n`
str += ` ERC-20: ${erc20 ? 'Yes' : 'No'}\n`

// Always show ERC-721 status
str += ` ERC-721: ${erc721Support.erc721 ? 'Yes' : 'No'}\n`
str += ` ERC-721 Metadata: ${erc721Support.metadata ? 'Yes' : 'No'}\n`

// Always show ERC-1155 status
str += ` ERC-1155: ${erc1155Support.erc1155 ? 'Yes' : 'No'}\n`
str += ` ERC-1155 Metadata: ${erc1155Support.metadata ? 'Yes' : 'No'}\n`

return output.resultBox(str)
} catch (err) {
return output.errorBox(err)
}
})

async function checkERC165Support(address, hre) {
try {
const contract = await getContract('erc165', address, hre)
return await contract.supportsInterface('0x01ffc9a7') // ERC-165 interface ID
} catch (err) {
return false
}
}

async function checkERC20(address, hre) {
try {
const contract = await getContract('erc20', address, hre)

// Check the existence of key ERC-20 functions
await contract.totalSupply()
await contract.balanceOf(address)

// Additional checks for ERC-20 functions using their function signatures
const functionsToCheck = [
'transfer',
'approve',
'allowance',
'transferFrom',
]

// Check if these key ERC-20 functions exist in the contract
for (const fn of functionsToCheck) {
if (!contract.interface.getFunction(fn)) {
return false // If any function is missing, it's not ERC-20 compliant
}
}

return true
} catch (err) {
return false
}
}

async function checkERC721(address, hre) {
try {
const contract = await getContract('erc721', address, hre)

// Check if the contract supports the ERC-721 interface ID using ERC-165
const supportsInterface = await contract.supportsInterface('0x80ac58cd') // ERC-721 interface ID
if (!supportsInterface) return false

// Optionally, check if the contract supports ERC-721 Metadata extension
const supportsMetadata = await contract.supportsInterface('0x5b5e139f') // ERC-721 Metadata interface ID

// Return both ERC-721 and metadata support statuses
return { erc721: supportsInterface, metadata: supportsMetadata }
} catch (err) {
return false
}
}

async function checkERC1155(address, hre) {
try {
const contract = await getContract('erc1155', address, hre)

// Check if the contract supports the ERC-1155 interface ID using ERC-165
const supportsInterface = await contract.supportsInterface('0xd9b67a26') // ERC-1155 interface ID
if (!supportsInterface) return false

// Optionally, check if the contract supports ERC-1155 Metadata URI extension
const supportsMetadata = await contract.supportsInterface('0x0e89341c') // ERC-1155 Metadata URI interface ID

// Return both ERC-1155 and metadata support statuses
return { erc1155: supportsInterface, metadata: supportsMetadata }
} catch (err) {
return false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import './ERC165.sol';

/// @title ERC1155 Interface
interface IERC1155 is IERC165 {
event TransferSingle(
address indexed operator,
address indexed from,
address indexed to,
uint256 id,
uint256 value
);

function balanceOf(
address account,
uint256 id
) external view returns (uint256);

function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount
) external;
}

contract ERC1155 is ERC165, IERC1155 {
mapping(uint256 => mapping(address => uint256)) private _balances;

constructor() {
_registerInterface(0xd9b67a26); // ERC-1155 Interface ID
}

function balanceOf(
address account,
uint256 id
) public view override returns (uint256) {
return _balances[id][account];
}

function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount
) external override {
require(to != address(0), 'ERC1155: transfer to the zero address');
require(_balances[id][from] >= amount, 'ERC1155: insufficient balance');

_balances[id][from] -= amount;
_balances[id][to] += amount;

emit TransferSingle(msg.sender, from, to, id, amount);
}

function _mint(address to, uint256 id, uint256 amount) internal {
require(to != address(0), 'ERC1155: mint to the zero address');
_balances[id][to] += amount;

emit TransferSingle(msg.sender, address(0), to, id, amount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/// @title ERC165 Interface
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

contract ERC165 is IERC165 {
mapping(bytes4 => bool) private _supportedInterfaces;

constructor() {
// Register ERC165 itself
_registerInterface(0x01ffc9a7);
}

function supportsInterface(
bytes4 interfaceId
) external view override returns (bool) {
return _supportedInterfaces[interfaceId];
}

function _registerInterface(bytes4 interfaceId) internal {
_supportedInterfaces[interfaceId] = true;
}
}
Loading

0 comments on commit e4b87b3

Please sign in to comment.