Skip to content

Commit

Permalink
Allow tx options override in single transactions (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
vanruch authored Jan 13, 2021
1 parent 3fdec04 commit 22cdc1c
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 16 deletions.
4 changes: 2 additions & 2 deletions packages/example/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { Market, Token, UpgradeabilityProxy } from '../build/artifacts'

deploy({}, (deployer: string) => {
const proxy = createProxy(UpgradeabilityProxy, 'upgradeTo')
const dai = proxy(contract('dai', Token), 'initialize', [100])
const dai = proxy(contract('dai', Token, { gasLimit: 1000000 }), 'initialize', [100])
const btc = proxy(contract('btc', Token), 'initialize', [200])
const market = contract(Market, [dai, btc])
runIf(dai.allowance(deployer, market).equals(0), () => dai.approve(market, 100)).else(() => btc.approve(market, 100))
dai.approve(market, dai.totalSupply().add(42))
dai.approve(market, dai.totalSupply().add(42), { gasLimit: 1000000 })
btc.approve(market, btc.totalSupply())
})
4 changes: 3 additions & 1 deletion packages/mars/src/actions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AbiConstructorEntry, AbiFunctionEntry } from './abi'
import { ArtifactFrom } from './syntax/artifact'
import { BooleanLike, Future } from './values'
import { TransactionOptions } from './execute/sendTransaction'
export type Action =
| DeployAction
| ReadAction
Expand All @@ -15,7 +16,7 @@ export interface DeployAction {
constructor: AbiConstructorEntry
name: string
params: any[]
options: any
options: Partial<TransactionOptions>
resolve: (address: string) => void
}

Expand All @@ -42,6 +43,7 @@ export interface TransactionAction {
address: Future<string>
method: AbiFunctionEntry
params: any[]
options: Partial<TransactionOptions>
resolve: (value: any) => void
}

Expand Down
8 changes: 6 additions & 2 deletions packages/mars/src/execute/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { isBytecodeEqual } from './bytecode'
import { JsonInputs, verify } from '../verification'
import { context } from '../context'

export type TransactionOverrides = Partial<TransactionOptions>

export interface ExecuteOptions extends TransactionOptions {
network: string
deploymentsFile: string
Expand Down Expand Up @@ -79,7 +81,8 @@ export async function getExistingDeployment(
}
}

async function executeDeploy(action: DeployAction, options: ExecuteOptions) {
async function executeDeploy(action: DeployAction, globalOptions: ExecuteOptions) {
const options = { ...globalOptions, ...action.options }
const params = action.params.map((param) => resolveValue(param))
const tx = getDeployTx(action.artifact[AbiSymbol], action.artifact[Bytecode], params)
const existingAddress = await getExistingDeployment(tx, action.name, options)
Expand Down Expand Up @@ -116,7 +119,8 @@ async function executeRead(action: ReadAction, options: ExecuteOptions) {
action.resolve(result)
}

async function executeTransaction(action: TransactionAction, options: ExecuteOptions) {
async function executeTransaction(action: TransactionAction, globalOptions: ExecuteOptions) {
const options = { ...globalOptions, ...action.options }
const params = action.params.map((param) => resolveValue(param))
const { txHash } = await sendTransaction(`${action.name}.${action.method.name}`, options, {
to: resolveValue(action.address),
Expand Down
5 changes: 3 additions & 2 deletions packages/mars/src/execute/sendTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ import { getEthPriceUsd } from './getEthPriceUsd'
export interface TransactionOptions {
wallet: Wallet
gasPrice: BigNumber
gasLimit?: number | BigNumber
noConfirm: boolean
}

export async function sendTransaction(
name: string,
{ wallet, gasPrice, noConfirm }: TransactionOptions,
{ wallet, gasPrice, noConfirm, gasLimit: overwrittenGasLimit }: TransactionOptions,
transaction: providers.TransactionRequest
) {
const gasLimit = await wallet.provider.estimateGas({ ...transaction, from: wallet.address })
const gasLimit = overwrittenGasLimit ?? (await wallet.provider.estimateGas({ ...transaction, from: wallet.address }))
const withGasLimit = { ...transaction, gasLimit, gasPrice }

const price = await getEthPriceUsd()
Expand Down
11 changes: 11 additions & 0 deletions packages/mars/src/generate/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@ function makeArguments(abi: AbiEntry | undefined) {
const args = abi.inputs.map(
(input: AbiParam) => `${getInputName(input)}: ${makeInputType(input.type, input.components)}`
)
if (
'stateMutability' in abi &&
abi.stateMutability !== 'view' &&
abi.stateMutability !== 'pure' &&
abi.type !== 'constructor'
) {
if (args.length === 0) {
return '(options?: Mars.TransactionOverrides)'
}
return `(${args.join(', ')}, options?: Mars.TransactionOverrides)`
}
return `(${args.join(', ')})`
}

Expand Down
2 changes: 1 addition & 1 deletion packages/mars/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export { runGenerator, Result, Transaction } from './generate/generator'
export { deploy } from './deploy'
export { ExecuteOptions } from './execute/execute'
export { ExecuteOptions, TransactionOverrides } from './execute/execute'
export { contract } from './syntax/contract'
export { createProxy } from './syntax/createProxy'
export { createArtifact, ArtifactFrom } from './syntax/artifact'
Expand Down
51 changes: 45 additions & 6 deletions packages/mars/src/syntax/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { AbiSymbol, Address, ArtifactSymbol, Name } from '../symbols'
import { context } from '../context'
import { Future, FutureBoolean, FutureBytes, FutureNumber, resolveBytesLike, resolveNumberLike } from '../values'
import { AbiConstructorEntry } from '../abi'
import { TransactionOverrides } from '../execute/execute'

export type Contract<T> = {
[ArtifactSymbol]: ArtifactFrom<T>
Expand All @@ -23,21 +24,34 @@ export interface WithParams {
}

export function contract<T extends NoParams>(artifact: ArtifactFrom<T>): Contract<T>
export function contract<T extends NoParams>(artifact: ArtifactFrom<T>, options: TransactionOverrides): Contract<T>
export function contract<T extends NoParams>(name: string, artifact: ArtifactFrom<T>): Contract<T>
export function contract<T extends NoParams>(
name: string,
artifact: ArtifactFrom<T>,
options: TransactionOverrides
): Contract<T>
export function contract<T extends WithParams>(artifact: ArtifactFrom<T>, params: ConstructorParams<T>): Contract<T>
export function contract<T extends WithParams>(
artifact: ArtifactFrom<T>,
params: ConstructorParams<T>,
options: TransactionOverrides
): Contract<T>
export function contract<T extends WithParams>(
name: string,
artifact: ArtifactFrom<T>,
params: ConstructorParams<T>
): Contract<T>
export function contract<T extends WithParams>(
name: string,
artifact: ArtifactFrom<T>,
params: ConstructorParams<T>,
options: TransactionOverrides
): Contract<T>
export function contract(...args: any[]): any {
context.ensureEnabled()

const withName = typeof args[0] === 'string'
const artifact: ArtifactFrom<any> = withName ? args[1] : args[0]
const name: string = withName ? args[0] : unCapitalize(artifact[Name])
const params = (withName ? args[2] : args[1]) ?? []
const options = (withName ? args[3] : args[2]) ?? {}
const { name, artifact, params, options } = parseContractArgs(...args)
const constructor = artifact[AbiSymbol].find(({ type }) => type === 'constructor') as AbiConstructorEntry

const [address, resolveAddress] = Future.create<string>()
Expand All @@ -59,6 +73,24 @@ function unCapitalize(value: string) {
return value !== '' ? `${value[0].toLowerCase()}${value.substring(1)}` : ''
}

function parseContractArgs(
...args: any[]
): {
name: string
artifact: ArtifactFrom<any>
params: ConstructorParams<any>
options: TransactionOverrides
} {
const withName = typeof args[0] === 'string'
const artifactIndex = withName ? 1 : 0
const artifact = args[artifactIndex]
const name = withName ? args[0] : unCapitalize(artifact[Name])
const withParams = Array.isArray(args[artifactIndex + 1])
const params = withParams ? args[artifactIndex + 1] : []
const options = (withParams ? args[artifactIndex + 2] : args[artifactIndex + 1]) ?? {}
return { name, artifact, options, params }
}

export function makeContractInstance<T>(name: string, artifact: ArtifactFrom<T>, address: Future<string>): Contract<T> {
const contract: any = {
[ArtifactSymbol]: artifact,
Expand All @@ -71,12 +103,19 @@ export function makeContractInstance<T>(name: string, artifact: ArtifactFrom<T>,
context.ensureEnabled()
const [result, resolveResult] = Future.create()
const isView = ['pure', 'view'].includes(entry.stateMutability)
let options = {}
let params = args
if (!isView && args.length > entry.inputs.length) {
options = args[args.length - 1]
params = params.slice(0, args.length - 1)
}
context.actions.push({
type: isView ? 'READ' : 'TRANSACTION',
name,
address: address,
method: entry,
params: args,
params,
options,
resolve: resolveResult,
})
const type = entry.outputs?.[0]?.type
Expand Down
1 change: 0 additions & 1 deletion packages/mars/src/verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ function findInputs(sourcePath: string) {
for (const file of files) {
const filePath = path.join(dir, file)
if (isDirectory(filePath)) {
console.log(filePath)
stack.push(filePath)
} else if (file.endsWith('.sol')) {
inputFiles.push(filePath)
Expand Down
2 changes: 1 addition & 1 deletion packages/mars/test/fixtures/exampleArtifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const ComplexContract = Mars.createArtifact<{
add(a: Mars.NumberLike): Mars.FutureNumber;
complexMapping(_: Mars.NumberLike, __: Mars.AddressLike): Mars.Future<[Mars.FutureNumber, Mars.FutureNumber]>;
number(): Mars.FutureNumber;
setter(arg1: Mars.StringLike, arg2: Mars.MaybeFuture<Mars.NumberLike[]>): Mars.Transaction;
setter(arg1: Mars.StringLike, arg2: Mars.MaybeFuture<Mars.NumberLike[]>, options?: Mars.TransactionOverrides): Mars.Transaction;
simpleArray(_: Mars.NumberLike): Mars.FutureNumber;
simpleMapping(_: Mars.NumberLike): Mars.Future<string>;
str(): Mars.Future<string>;
Expand Down
38 changes: 38 additions & 0 deletions packages/mars/test/syntax/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ describe('Contract', () => {
expect(getDeployResult().test.simpleContract.address).to.equal(result[Address].resolve())
})

it('deploys contract (no name, no params, gas override)', async () => {
const { result, provider } = await testDeploy(() => contract(SimpleContract, { gasLimit: 2000000 }))
expect(await provider.getCode(result[Address].resolve())).to.equal(
`0x${SimpleContractJSON.evm.deployedBytecode.object}`
)
expect(getDeployResult().test.simpleContract.address).to.equal(result[Address].resolve())
expect((await provider.getTransaction(getDeployResult().test.simpleContract.txHash)).gasLimit).to.equal(2000000)
})

it('deploys contract (with name, no params)', async () => {
const { result, provider } = await testDeploy(() => contract('someName', SimpleContract))
expect(await provider.getCode(result[Address].resolve())).to.equal(
Expand All @@ -26,6 +35,15 @@ describe('Contract', () => {
expect(getDeployResult().test.someName.address).to.equal(result[Address].resolve())
})

it('deploys contract (with name, no params, gas override)', async () => {
const { result, provider } = await testDeploy(() => contract('someName', SimpleContract, { gasLimit: 2000000 }))
expect(await provider.getCode(result[Address].resolve())).to.equal(
`0x${SimpleContractJSON.evm.deployedBytecode.object}`
)
expect(getDeployResult().test.someName.address).to.equal(result[Address].resolve())
expect((await provider.getTransaction(getDeployResult().test.someName.txHash)).gasLimit).to.equal(2000000)
})

it('deploys contract (no name, with params)', async () => {
const { result, provider } = await testDeploy(() => contract(ComplexContract, [10, 'test']))
expect(await provider.getCode(result[Address].resolve())).to.equal(
Expand All @@ -34,6 +52,15 @@ describe('Contract', () => {
expect(getDeployResult().test.complexContract.address).to.equal(result[Address].resolve())
})

it('deploys contract (no name, with params, gas override)', async () => {
const { result, provider } = await testDeploy(() => contract(ComplexContract, [10, 'test'], { gasLimit: 2000000 }))
expect(await provider.getCode(result[Address].resolve())).to.equal(
`0x${ComplexContractJSON.evm.deployedBytecode.object}`
)
expect(getDeployResult().test.complexContract.address).to.equal(result[Address].resolve())
expect((await provider.getTransaction(getDeployResult().test.complexContract.txHash)).gasLimit).to.equal(2000000)
})

it('deploys contract (with name, with params)', async () => {
const { result, provider } = await testDeploy(() => contract('contractName', ComplexContract, [10, 'test']))
expect(await provider.getCode(result[Address].resolve())).to.equal(
Expand All @@ -42,6 +69,17 @@ describe('Contract', () => {
expect(getDeployResult().test.contractName.address).to.equal(result[Address].resolve())
})

it('deploys contract (with name, with params and gas override)', async () => {
const { result, provider } = await testDeploy(() =>
contract('contractName', ComplexContract, [10, 'test'], { gasLimit: 2000000 })
)
expect(await provider.getCode(result[Address].resolve())).to.equal(
`0x${ComplexContractJSON.evm.deployedBytecode.object}`
)
expect(getDeployResult().test.contractName.address).to.equal(result[Address].resolve())
expect((await provider.getTransaction(getDeployResult().test.contractName.txHash)).gasLimit).to.equal(2000000)
})

it('does not deploy same contract twice', async () => {
const { result: firstCall, provider } = await testDeploy(() => contract(SimpleContract))
const { result: secondCall } = await testDeploy(() => contract(SimpleContract), {
Expand Down

0 comments on commit 22cdc1c

Please sign in to comment.