Skip to content

Commit

Permalink
🗳️ Test onchain votes
Browse files Browse the repository at this point in the history
z0r0z committed Jan 2, 2024
1 parent 9df8b63 commit 5f34b4f
Showing 6 changed files with 107 additions and 33 deletions.
27 changes: 14 additions & 13 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
DagonTest:testBurn(address,uint96) (runs: 256, μ: 152926, ~: 152926)
DagonTest:testDeploy() (gas: 2326980)
DagonTest:testFailBurnOverBalance(address,uint96) (runs: 256, μ: 187163, ~: 188563)
DagonTest:testFailBurnOverThreshold(address,uint96) (runs: 256, μ: 218437, ~: 219837)
DagonTest:testFailInvalidThresholdExceedsSupply() (gas: 159028)
DagonTest:testDeploy() (gas: 2328988)
DagonTest:testFailBurnOverBalance(address,uint96) (runs: 256, μ: 187396, ~: 188563)
DagonTest:testFailBurnOverThreshold(address,uint96) (runs: 256, μ: 218670, ~: 219837)
DagonTest:testFailInvalidThresholdExceedsSupply() (gas: 159006)
DagonTest:testFailInvalidThresholdExceedsSupply2() (gas: 164141)
DagonTest:testFailInvalidThresholdNull() (gas: 159054)
DagonTest:testFailIsValidSignature2of3ForInsufficientSignatures() (gas: 201375)
@@ -15,19 +15,20 @@ DagonTest:testFailIsValidSignatureWeightedERC721() (gas: 211786)
DagonTest:testFailSetTokenInvalidStd(address) (runs: 256, μ: 158003, ~: 158003)
DagonTest:testFailTransferFromInactiveAuth(address,address,uint96) (runs: 256, μ: 191883, ~: 192661)
DagonTest:testFailTransferOverBalance(address,address,uint96) (runs: 256, μ: 186506, ~: 187284)
DagonTest:testInstall() (gas: 137381)
DagonTest:testIsValidSignature() (gas: 150791)
DagonTest:testInstall() (gas: 137359)
DagonTest:testIsValidSignature() (gas: 150836)
DagonTest:testIsValidSignature2of3() (gas: 197147)
DagonTest:testIsValidSignature3of3() (gas: 204981)
DagonTest:testIsValidSignatureOnchain() (gas: 197522)
DagonTest:testIsValidSignatureWeighted() (gas: 234638)
DagonTest:testIsValidSignatureWeightedERC1155() (gas: 251048)
DagonTest:testIsValidSignatureWeightedERC20() (gas: 250639)
DagonTest:testIsValidSignatureWeightedERC1155() (gas: 251092)
DagonTest:testIsValidSignatureWeightedERC20() (gas: 250684)
DagonTest:testIsValidSignatureWeightedERC6909() (gas: 251034)
DagonTest:testIsValidSignatureWeightedERC721() (gas: 210519)
DagonTest:testNameAndSymbolAndDecimals(uint256) (runs: 256, μ: 15057, ~: 15057)
DagonTest:testIsValidSignatureWeightedERC721() (gas: 210561)
DagonTest:testNameAndSymbolAndDecimals(uint256) (runs: 256, μ: 15035, ~: 15035)
DagonTest:testSetAuth(address) (runs: 256, μ: 145801, ~: 145801)
DagonTest:testSetThreshold() (gas: 147475)
DagonTest:testSetToken(address) (runs: 256, μ: 148260, ~: 148260)
DagonTest:testSetToken(address) (runs: 256, μ: 148238, ~: 148238)
DagonTest:testSetURI() (gas: 164981)
DagonTest:testTransfer(address,address,uint96) (runs: 256, μ: 175263, ~: 176119)
DagonTest:testTransferWithAuth(address,address,uint96) (runs: 256, μ: 180041, ~: 180761)
DagonTest:testTransfer(address,address,uint96) (runs: 256, μ: 175373, ~: 176540)
DagonTest:testTransferWithAuth(address,address,uint96) (runs: 256, μ: 180039, ~: 180739)
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: ci

on: [push]

jobs:
tests:
name: Forge Testing
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
- name: dependencies
run: forge install
- name: tests
run: forge test

snapshot:
name: Forge Snapshot
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
- name: dependencies
run: forge install
- name: check contract sizes
run: forge build --sizes
- name: check gas snapshots
run: forge snapshot --check
27 changes: 22 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
# Dagon
# Dagon (𒀭)
> minimalist and modular governance abstraction for accounts today through singleton extensions
Dagon is a contract singleton that allows any account to give any token threshold the right to sign for it.
Built with *[Foundry](https://github.com/foundry-rs/forge-std)* and *[Solady](https://github.com/vectorized/solady)*.

It is thus proposed as a gas-efficient (and *AA-forward*) abstraction layer to blockchain-based governance. Chiefly, by just validating contract signatures and not dealing with execution or more opinionated proposal logic, Dagon can complement more communities today, as well as serve as a source of record for the greater DAO ecosystem, which are all free to implement their own custom hooks and checks to Dagon validation. In V0, the Dagon pattern can be used for offchain polling for any token, including `ERC-20`, `ERC-721`, `ERC-1155`, `ERC-6909`, and includes a native token mint and burn function, `DAGON`, but can also validate onchain operations that submit ownership to Dagon validation using `ERC-173`.
## Premise

For example, <Insert> DAOs might use Dagon in small ways to start, such as to validate their membership signatures in the typical snapshot proposal and for simple dapp display purposes, or if Dagon is further registered as the owner of their group smart account, Dagon can work as a proposal engine, validating executions for smart accounts and timelocks. In this mode, Dagon supports both token-weighted and m/n signature schemes. Collection of Dagon signatures is gasless, and can be posted in a single transaction and block fee using `isValidSignature()` in an ERC-4337 userOp flow.
Dagon is a contract singleton that allows any account to give any token a threshold right to sign for it. It thus supports existing token communities and DAO deployments right out-of-the-gate. Dagon is optimized especially for most off-chain voting methods, such as multisig and weighted snapshot proposals, as well, initially offers a platform-agnostic upgrade path into smart account-based governance abstraction.

Overall, Dagon is designed with `ERC-4337` and account abstraction in mind to validate group userOps and custody, but works well with accounts that at least support both the `ERC-1271` (Contract Signatures) and `ERC-173` (Ownership) interfaces. Even so, an EOA can nonetheless mint a Dagon personal token and authorize a threshold to return an `isValidSignature()` sign-off for off-chain or legal purposes. There is likely much to explore.
Chiefly, by just validating contract signature process and not dealing with execution or more opinionated proposal logic, Dagon can complement more organizations today, as well as serve as a source of record for the greater DAO ecosystem, which are all free to implement their own custom hooks and checks to Dagon validation. In V0, which is focused as a voting engine, the Dagon pattern can be used for offchain polling for any token, including `ERC-20`, `ERC-721`, `ERC-1155`, `ERC-6909`, and includes a native token mint and burn function to allow tokens to upgrade (or new tokens to be issued) under `DAGON` (itself `ERC-6909`), but can also validate onchain user operations (userOps) that submit ownership to Dagon validation using the `ERC-173` `transferOwnership` flow.

For example, <Insert> DAO might start to use Dagon in small ways as an extension to its ordinary operating system, such as to prove the results of group polls and for simple dapp display purposes, but if Dagon is also registered as the owner of a group smart account, Dagon can then work as the DAO's proposal engine, validating userOps and letting them be posted onchain. In this mode, Dagon supports both token-weighted and m/n signature schemes. Collection of Dagon signatures is gasless, and can be posted in a single transaction and block fee using `isValidSignature()` in the typical `ERC-4337` userOp flow.

Overall, Dagon is designed with `ERC-4337` and account abstraction in mind, but works well with accounts that at least support both the `ERC-1271` (Contract Signatures) and `ERC-173` (Ownership) standard interfaces. (Even so, an EOA can nonetheless mint a Dagon personal token and authorize a threshold to sign-off for off-chain or legal purposes.) There is likely much to explore.

## Getting Started

Run: `curl -L https://foundry.paradigm.xyz | bash && source ~/.bashrc && foundryup`

Build the foundry project with `forge build`. Run contract tests with `forge test`. Measure gas fees with `forge snapshot`. Format code with `forge fmt`.

## Disclaimer

*These smart contracts and testing suite are being provided as is. No guarantee, representation or warranty is being made, express or implied, as to the safety or correctness of anything provided herein or through related user interfaces. This repository and related code have not been audited and as such there can be no assurance anything will work as intended, and users may experience delays, failures, errors, omissions, loss of transmitted information or loss of funds. The creators are not liable for any of the foregoing. Users should proceed with caution and use at their own risk.*

## License

See [LICENSE](./LICENSE) for more details.
2 changes: 1 addition & 1 deletion lib/accounts
13 changes: 7 additions & 6 deletions src/Dagon.sol
Original file line number Diff line number Diff line change
@@ -69,7 +69,7 @@ contract Dagon is ERC6909 {

/// @dev The token interface standards enum.
enum TokenStandard {
OWNER,
DAGON,
ERC20,
ERC721,
ERC1155,
@@ -148,7 +148,7 @@ contract Dagon is ERC6909 {
) {
pos += 85;
prev = owner;
tally += set.std == TokenStandard.OWNER
tally += set.std == TokenStandard.DAGON
? balanceOf(owner, uint256(uint160(msg.sender)))
: set.std == TokenStandard.ERC20 || set.std == TokenStandard.ERC721
? set.tkn.balanceOf(owner)
@@ -202,6 +202,7 @@ contract Dagon is ERC6909 {
virtual
returns (uint256)
{
bytes32 hash = SignatureCheckerLib.toEthSignedMessageHash(userOpHash);
Settings memory set = _settings[account];
unchecked {
uint256 pos;
@@ -212,19 +213,19 @@ contract Dagon is ERC6909 {
if (
SignatureCheckerLib.isValidSignatureNow(
owner = address(bytes20(signature[pos:pos + 20])),
SignatureCheckerLib.toEthSignedMessageHash(userOpHash),
hash,
signature[pos + 20:pos + 85]
) && voted[owner][userOpHash] == 0 // Check double voting.
) {
pos += 85;
tally += voted[owner][userOpHash] = set.std == TokenStandard.OWNER
tally += voted[owner][userOpHash] = set.std == TokenStandard.DAGON
? balanceOf(owner, uint256(uint160(account)))
: set.std == TokenStandard.ERC20 || set.std == TokenStandard.ERC721
? set.tkn.balanceOf(owner)
: set.tkn.balanceOf(owner, uint256(uint160(account)));
}
}
return votingTally[userOpHash] += tally;
return votingTally[hash] += tally;
}
}

@@ -313,7 +314,7 @@ contract Dagon is ERC6909 {
if (
threshold
> (
set.std == TokenStandard.OWNER
set.std == TokenStandard.DAGON
? totalSupply(uint256(uint160(msg.sender)))
: set.std == TokenStandard.ERC20 || set.std == TokenStandard.ERC721
? set.tkn.totalSupply()
35 changes: 27 additions & 8 deletions test/Dagon.t.sol
Original file line number Diff line number Diff line change
@@ -152,7 +152,7 @@ contract DagonTest is Test {

Dagon.Settings memory setting;
setting.tkn = ITokenOwner(address(0));
setting.std = Dagon.TokenStandard.OWNER;
setting.std = Dagon.TokenStandard.DAGON;
setting.threshold = 1;

Dagon.Metadata memory meta;
@@ -230,7 +230,7 @@ contract DagonTest is Test {
}

function testSetToken(ITokenOwner tkn) public {
Dagon.TokenStandard std = Dagon.TokenStandard.OWNER;
Dagon.TokenStandard std = Dagon.TokenStandard.DAGON;
testInstall();
vm.prank(address(account));
owners.setToken(tkn, std);
@@ -260,6 +260,7 @@ contract DagonTest is Test {

function testTransfer(address from, address to, uint96 amount) public {
vm.assume(from != alice && to != alice);
vm.assume(to != address(0) && to != address(0xff));
vm.assume(amount < type(uint96).max);
testInstall();
vm.prank(address(account));
@@ -355,6 +356,24 @@ contract DagonTest is Test {
assertEq(validationData, 0x00);
}

function testIsValidSignatureOnchain() public {
testInstall();
bytes32 userOpHash = keccak256("OWN");
NaniAccount.UserOperation memory userOp;
userOp.signature = "";
require(userOp.signature.length == 0, "INVALID_LEN");
userOp.sender = address(account);

bytes memory signature =
abi.encodePacked(alice, _sign(alicePk, _toEthSignedMessageHash(userOpHash)));

owners.vote(address(account), userOpHash, signature);

vm.prank(_ENTRY_POINT);
uint256 validationData = account.validateUserOp(userOp, userOpHash, 0);
assertEq(validationData, 0x00);
}

// In 2-of-3, 3 signed.
function testIsValidSignature3of3() public payable {
Dagon.Ownership[] memory _owners = new Dagon.Ownership[](3);
@@ -372,7 +391,7 @@ contract DagonTest is Test {

Dagon.Settings memory setting;
setting.tkn = ITokenOwner(address(0));
setting.std = Dagon.TokenStandard.OWNER;
setting.std = Dagon.TokenStandard.DAGON;
setting.threshold = 1;

Dagon.Metadata memory meta;
@@ -430,7 +449,7 @@ contract DagonTest is Test {

Dagon.Settings memory setting;
setting.tkn = ITokenOwner(address(0));
setting.std = Dagon.TokenStandard.OWNER;
setting.std = Dagon.TokenStandard.DAGON;
setting.threshold = 1;

Dagon.Metadata memory meta;
@@ -486,7 +505,7 @@ contract DagonTest is Test {

Dagon.Settings memory setting;
setting.tkn = ITokenOwner(address(0));
setting.std = Dagon.TokenStandard.OWNER;
setting.std = Dagon.TokenStandard.DAGON;
setting.threshold = 2;

Dagon.Metadata memory meta;
@@ -540,7 +559,7 @@ contract DagonTest is Test {

Dagon.Settings memory setting;
setting.tkn = ITokenOwner(address(0));
setting.std = Dagon.TokenStandard.OWNER;
setting.std = Dagon.TokenStandard.DAGON;
setting.threshold = 40;

Dagon.Metadata memory meta;
@@ -601,7 +620,7 @@ contract DagonTest is Test {

Dagon.Settings memory setting;
setting.tkn = ITokenOwner(address(0));
setting.std = Dagon.TokenStandard.OWNER;
setting.std = Dagon.TokenStandard.DAGON;
setting.threshold = 40;

Dagon.Metadata memory meta;
@@ -1103,7 +1122,7 @@ contract DagonTest is Test {

Dagon.Settings memory setting;
setting.tkn = ITokenOwner(address(0));
setting.std = Dagon.TokenStandard.OWNER;
setting.std = Dagon.TokenStandard.DAGON;
setting.threshold = 40;

Dagon.Metadata memory meta;

0 comments on commit 5f34b4f

Please sign in to comment.