forked from kamescg/delegatable-sol
-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes #29 Adds Support for [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) for allowing contract accounts to give and receive delegations. Required one additional bit to the `SignedDelegation` and `SignedInvocation` types: `signerIsContract`. If this bit is `true`, then the first 20 bytes of the `signature` field is treated as the address of a contract to treat as the intended signer, and that contract is sent the remaining `signature: bytes` along with the delegation type hash to determine for itself whether this proof should be treated as valid authorization. I formerly was somewhat against using EIP-1271 in this way, because I had seen some contract accounts merely allow assigning a single signer as their EIP1271 recovery strategy, which completely undermines the entire point of having a contract account. My position on this has evolved a bit, to believe that correct usage is possible, and so we shouldn't let the possibility of flawed contract accounts prevent good ones from participating in this. For example, good usage might look like a multisig might have a custom datastructure for representing which accounts' signatures are included, and including all the signatures combined in the `signature` payload. Larger controller sets like a token-weighted DAO might involve too many signatures to submit as a delegation/invocation as `calldata`, but I'll leave that research as to do.
- Loading branch information
Showing
6 changed files
with
233 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
import { ethers } from "hardhat"; | ||
import { Contract, ContractFactory, utils, Wallet } from "ethers"; | ||
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; | ||
// @ts-ignore | ||
import { generateUtil } from "eth-delegatable-utils"; | ||
import { getPrivateKeys } from "../utils/getPrivateKeys"; | ||
import { expect } from "chai"; | ||
import { Provider } from "@ethersproject/providers"; | ||
import { generateDelegation } from "./utils"; | ||
const { getSigners } = ethers; | ||
|
||
describe("Delegatable", () => { | ||
const CONTACT_NAME = "Delegatable"; | ||
let CONTRACT_INFO: any; | ||
let delegatableUtils: any; | ||
let signer0: SignerWithAddress; | ||
let wallet0: Wallet; | ||
let wallet1: Wallet; | ||
let pk0: string; | ||
let pk1: string; | ||
|
||
let AllowedMethodsEnforcer: Contract; | ||
let AllowedMethodsEnforcerFactory: ContractFactory; | ||
let Delegatable: Contract; | ||
let DelegatableFactory: ContractFactory; | ||
let ContractAccount: Contract; | ||
let ContractAccountFactory: ContractFactory; | ||
|
||
before(async () => { | ||
[signer0] = await getSigners(); | ||
[wallet0, wallet1] = getPrivateKeys( | ||
signer0.provider as unknown as Provider | ||
); | ||
DelegatableFactory = await ethers.getContractFactory("MockDelegatable"); | ||
ContractAccountFactory = await ethers.getContractFactory("EIP1271"); | ||
AllowedMethodsEnforcerFactory = await ethers.getContractFactory( | ||
"AllowedMethodsEnforcer" | ||
); | ||
pk0 = wallet0._signingKey().privateKey; | ||
pk1 = wallet1._signingKey().privateKey; | ||
}); | ||
|
||
beforeEach(async () => { | ||
Delegatable = await DelegatableFactory.connect(wallet0).deploy( | ||
CONTACT_NAME | ||
); | ||
AllowedMethodsEnforcer = await AllowedMethodsEnforcerFactory.connect( | ||
wallet0 | ||
).deploy(); | ||
ContractAccount = await ContractAccountFactory.connect(wallet1).deploy(); | ||
|
||
CONTRACT_INFO = { | ||
chainId: Delegatable.deployTransaction.chainId, | ||
verifyingContract: Delegatable.address, | ||
name: CONTACT_NAME, | ||
}; | ||
delegatableUtils = generateUtil(CONTRACT_INFO); | ||
}); | ||
|
||
describe("contract accounts", () => { | ||
it("should be able to delegate its own powers", async () => { | ||
await Delegatable.transferOwnership(ContractAccount.address); | ||
expect(await Delegatable.owner()).to.equal(ContractAccount.address); | ||
expect(await Delegatable.owner()).to.equal(ContractAccount.address); | ||
|
||
const _delegation: SignedDelegation = generateDelegation( | ||
CONTACT_NAME, | ||
Delegatable, | ||
pk1, | ||
wallet0.address | ||
); | ||
|
||
_delegation.signerIsContract = true; | ||
_delegation.signature = `${ | ||
ContractAccount.address | ||
}${_delegation.signature.substring(2)}`; | ||
|
||
const INVOCATION_MESSAGE = { | ||
replayProtection: { | ||
nonce: "0x01", | ||
queue: "0x00", | ||
}, | ||
batch: [ | ||
{ | ||
authority: [_delegation], | ||
transaction: { | ||
to: Delegatable.address, | ||
gasLimit: "21000000000000", | ||
data: ( | ||
await Delegatable.populateTransaction.setPurpose("To delegate!") | ||
).data, | ||
}, | ||
}, | ||
], | ||
}; | ||
const invocation = delegatableUtils.signInvocations( | ||
INVOCATION_MESSAGE, | ||
pk0 | ||
); | ||
await Delegatable.invoke([ | ||
{ | ||
signature: invocation.signature, | ||
invocations: invocation.invocations, | ||
}, | ||
]); | ||
expect(await Delegatable.purpose()).to.eq("To delegate!"); | ||
}); | ||
|
||
it("should be able to use powers they have been delegated by an EOA", async () => { | ||
const _delegation: SignedDelegation = generateDelegation( | ||
CONTACT_NAME, | ||
Delegatable, | ||
pk0, | ||
ContractAccount.address | ||
); | ||
|
||
const INVOCATION_MESSAGE = { | ||
replayProtection: { | ||
nonce: "0x01", | ||
queue: "0x00", | ||
}, | ||
batch: [ | ||
{ | ||
authority: [_delegation], | ||
transaction: { | ||
to: Delegatable.address, | ||
gasLimit: "21000000000000", | ||
data: ( | ||
await Delegatable.populateTransaction.setPurpose("To delegate!") | ||
).data, | ||
}, | ||
}, | ||
], | ||
}; | ||
const invocation = delegatableUtils.signInvocations( | ||
INVOCATION_MESSAGE, | ||
pk1 | ||
); | ||
|
||
invocation.signerIsContract = true; | ||
invocation.signature = `${ | ||
ContractAccount.address | ||
}${invocation.signature.substring(2)}`; | ||
await Delegatable.invoke([invocation]); | ||
expect(await Delegatable.purpose()).to.eq("To delegate!"); | ||
}); | ||
}); | ||
}); |